diff options
91 files changed, 2961 insertions, 3263 deletions
diff --git a/appveyor.yml b/appveyor.yml index 2a4a15d40..6b5d23b03 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,7 +8,7 @@ cache:    - C:\ProgramData\chocolatey\bin -> appveyor.yml    - C:\ProgramData\chocolatey\lib -> appveyor.yml -os: Visual Studio 2015 +os: Visual Studio 2017  platform:    - x64 @@ -20,11 +20,9 @@ install:    - git submodule update --init --recursive  before_build: -  # Xamarin log spam workaround -  - del "C:\Program Files (x86)\MSBuild\14.0\Microsoft.Common.targets\ImportAfter\Xamarin.Common.targets"    - mkdir build    - cd build -  - cmake -G "Visual Studio 14 2015 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 .. +  - cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 ..    - cd ..  build: diff --git a/externals/catch b/externals/catch -Subproject c984fc3ecde60b59efa2203e82261acac8ac850 +Subproject 3dcc9233515ea69e45a11be369f867a944e72e7 diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index 84f9c03a7..9c2e6ed88 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp @@ -2,6 +2,7 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <array>  #include <memory>  #include <string>  #include "audio_core/audio_core.h" @@ -10,8 +11,8 @@  #include "audio_core/null_sink.h"  #include "audio_core/sink.h"  #include "audio_core/sink_details.h" +#include "common/common_types.h"  #include "core/core_timing.h" -#include "core/hle/kernel/vm_manager.h"  #include "core/hle/service/dsp_dsp.h"  namespace AudioCore { @@ -39,20 +40,8 @@ void Init() {      CoreTiming::ScheduleEvent(audio_frame_ticks, tick_event);  } -void AddAddressSpace(Kernel::VMManager& address_space) { -    auto r0_vma = address_space -                      .MapBackingMemory(DSP::HLE::region0_base, -                                        reinterpret_cast<u8*>(&DSP::HLE::g_regions[0]), -                                        sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO) -                      .MoveFrom(); -    address_space.Reprotect(r0_vma, Kernel::VMAPermission::ReadWrite); - -    auto r1_vma = address_space -                      .MapBackingMemory(DSP::HLE::region1_base, -                                        reinterpret_cast<u8*>(&DSP::HLE::g_regions[1]), -                                        sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO) -                      .MoveFrom(); -    address_space.Reprotect(r1_vma, Kernel::VMAPermission::ReadWrite); +std::array<u8, Memory::DSP_RAM_SIZE>& GetDspMemory() { +    return DSP::HLE::g_dsp_memory.raw_memory;  }  void SelectSink(std::string sink_id) { diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h index 0edf6dd15..ab323ce1f 100644 --- a/src/audio_core/audio_core.h +++ b/src/audio_core/audio_core.h @@ -4,11 +4,10 @@  #pragma once +#include <array>  #include <string> - -namespace Kernel { -class VMManager; -} +#include "common/common_types.h" +#include "core/memory.h"  namespace AudioCore { @@ -17,8 +16,8 @@ constexpr int native_sample_rate = 32728; ///< 32kHz  /// Initialise Audio Core  void Init(); -/// Add DSP address spaces to a Process. -void AddAddressSpace(Kernel::VMManager& vm_manager); +/// Returns a reference to the array backing DSP memory +std::array<u8, Memory::DSP_RAM_SIZE>& GetDspMemory();  /// Select the sink to use based on sink id.  void SelectSink(std::string sink_id); @@ -29,4 +28,4 @@ void EnableStretching(bool enable);  /// Shutdown Audio Core  void Shutdown(); -} // namespace +} // namespace AudioCore diff --git a/src/audio_core/hle/dsp.cpp b/src/audio_core/hle/dsp.cpp index 31421fdc6..260b182ed 100644 --- a/src/audio_core/hle/dsp.cpp +++ b/src/audio_core/hle/dsp.cpp @@ -16,31 +16,33 @@ namespace HLE {  // Region management -std::array<SharedMemory, 2> g_regions; +DspMemory g_dsp_memory;  static size_t CurrentRegionIndex() {      // The region with the higher frame counter is chosen unless there is wraparound.      // This function only returns a 0 or 1. +    u16 frame_counter_0 = g_dsp_memory.region_0.frame_counter; +    u16 frame_counter_1 = g_dsp_memory.region_1.frame_counter; -    if (g_regions[0].frame_counter == 0xFFFFu && g_regions[1].frame_counter != 0xFFFEu) { +    if (frame_counter_0 == 0xFFFFu && frame_counter_1 != 0xFFFEu) {          // Wraparound has occurred.          return 1;      } -    if (g_regions[1].frame_counter == 0xFFFFu && g_regions[0].frame_counter != 0xFFFEu) { +    if (frame_counter_1 == 0xFFFFu && frame_counter_0 != 0xFFFEu) {          // Wraparound has occurred.          return 0;      } -    return (g_regions[0].frame_counter > g_regions[1].frame_counter) ? 0 : 1; +    return (frame_counter_0 > frame_counter_1) ? 0 : 1;  }  static SharedMemory& ReadRegion() { -    return g_regions[CurrentRegionIndex()]; +    return CurrentRegionIndex() == 0 ? g_dsp_memory.region_0 : g_dsp_memory.region_1;  }  static SharedMemory& WriteRegion() { -    return g_regions[1 - CurrentRegionIndex()]; +    return CurrentRegionIndex() != 0 ? g_dsp_memory.region_0 : g_dsp_memory.region_1;  }  // Audio processing and mixing diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/dsp.h index 0a0f60ac1..94ce48863 100644 --- a/src/audio_core/hle/dsp.h +++ b/src/audio_core/hle/dsp.h @@ -31,8 +31,8 @@ namespace HLE {  // double-buffer. The frame counter is located as the very last u16 of each region and is  // incremented each audio tick. -constexpr VAddr region0_base = 0x1FF50000; -constexpr VAddr region1_base = 0x1FF70000; +constexpr u32 region0_offset = 0x50000; +constexpr u32 region1_offset = 0x70000;  /**   * The DSP is native 16-bit. The DSP also appears to be big-endian. When reading 32-bit numbers from @@ -512,7 +512,22 @@ struct SharedMemory {  };  ASSERT_DSP_STRUCT(SharedMemory, 0x8000); -extern std::array<SharedMemory, 2> g_regions; +union DspMemory { +    std::array<u8, 0x80000> raw_memory; +    struct { +        u8 unused_0[0x50000]; +        SharedMemory region_0; +        u8 unused_1[0x18000]; +        SharedMemory region_1; +        u8 unused_2[0x8000]; +    }; +}; +static_assert(offsetof(DspMemory, region_0) == region0_offset, +              "DSP region 0 is at the wrong offset"); +static_assert(offsetof(DspMemory, region_1) == region1_offset, +              "DSP region 1 is at the wrong offset"); + +extern DspMemory g_dsp_memory;  // Structures must have an offset that is a multiple of two.  static_assert(offsetof(SharedMemory, frame_counter) % 2 == 0, diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 084372df4..d8a8fe44f 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -19,7 +19,13 @@ const char* sdl2_config_file = R"(  #      - "joystick": the index of the joystick to bind  #      - "button"(optional): the index of the button to bind  #      - "hat"(optional): the index of the hat to bind as direction buttons +#      - "axis"(optional): the index of the axis to bind  #      - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right" +#      - "threshould"(only used for axis): a float value in (-1.0, 1.0) which the button is +#          triggered if the axis value crosses +#      - "direction"(only used for axis): "+" means the button is triggered when the axis value +#          is greater than the threshold; "-" means the button is triggered when the axis value +#          is smaller than the threshold  button_a=  button_b=  button_x= diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 3e6106f0a..4e837668e 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -11,8 +11,6 @@ set(SRCS              configuration/configure_graphics.cpp              configuration/configure_input.cpp              configuration/configure_system.cpp -            debugger/callstack.cpp -            debugger/disassembler.cpp              debugger/graphics/graphics.cpp              debugger/graphics/graphics_breakpoint_observer.cpp              debugger/graphics/graphics_breakpoints.cpp @@ -43,8 +41,6 @@ set(HEADERS              configuration/configure_graphics.h              configuration/configure_input.h              configuration/configure_system.h -            debugger/callstack.h -            debugger/disassembler.h              debugger/graphics/graphics.h              debugger/graphics/graphics_breakpoint_observer.h              debugger/graphics/graphics_breakpoints.h @@ -74,8 +70,6 @@ set(UIS              configuration/configure_graphics.ui              configuration/configure_input.ui              configuration/configure_system.ui -            debugger/callstack.ui -            debugger/disassembler.ui              debugger/registers.ui              hotkeys.ui              main.ui diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 0b9b73f9e..2b99447ec 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -177,6 +177,7 @@ void Config::ReadValues() {      UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool();      UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool(); +    UISettings::values.show_filter_bar = qt_config->value("showFilterBar", true).toBool();      UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool();      UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool();      UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); @@ -295,6 +296,7 @@ void Config::SaveValues() {      qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode);      qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); +    qt_config->setValue("showFilterBar", UISettings::values.show_filter_bar);      qt_config->setValue("showStatusBar", UISettings::values.show_status_bar);      qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing);      qt_config->setValue("firstStart", UISettings::values.first_start); diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index daac9b63a..116a6330f 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -102,14 +102,8 @@ ConfigureInput::ConfigureInput(QWidget* parent)      this->loadConfiguration(); -    // TODO(wwylele): enable these when the input emulation for them is implemented -    ui->buttonZL->setEnabled(false); -    ui->buttonZR->setEnabled(false); +    // TODO(wwylele): enable this when we actually emulate it      ui->buttonHome->setEnabled(false); -    ui->buttonCStickUp->setEnabled(false); -    ui->buttonCStickDown->setEnabled(false); -    ui->buttonCStickLeft->setEnabled(false); -    ui->buttonCStickRight->setEnabled(false);  }  void ConfigureInput::applyConfiguration() { diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index a3a9015a4..9b1e6711d 100644 --- a/src/citra_qt/configuration/configure_system.cpp +++ b/src/citra_qt/configuration/configure_system.cpp @@ -2,6 +2,7 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <QMessageBox>  #include "citra_qt/configuration/configure_system.h"  #include "citra_qt/ui_settings.h"  #include "core/core.h" @@ -15,8 +16,11 @@ static const std::array<int, 12> days_in_month = {{  ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {      ui->setupUi(this); -    connect(ui->combo_birthmonth, SIGNAL(currentIndexChanged(int)), -            SLOT(updateBirthdayComboBox(int))); +    connect(ui->combo_birthmonth, +            static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, +            &ConfigureSystem::updateBirthdayComboBox); +    connect(ui->button_regenerate_console_id, &QPushButton::clicked, this, +            &ConfigureSystem::refreshConsoleID);      this->setConfiguration();  } @@ -71,6 +75,10 @@ void ConfigureSystem::ReadSystemSettings() {      // set sound output mode      sound_index = Service::CFG::GetSoundOutputMode();      ui->combo_sound->setCurrentIndex(sound_index); + +    // set the console id +    u64 console_id = Service::CFG::GetConsoleUniqueId(); +    ui->label_console_id->setText("Console ID: 0x" + QString::number(console_id, 16).toUpper());  }  void ConfigureSystem::applyConfiguration() { @@ -140,3 +148,21 @@ void ConfigureSystem::updateBirthdayComboBox(int birthmonth_index) {      // restore the day selection      ui->combo_birthday->setCurrentIndex(birthday_index);  } + +void ConfigureSystem::refreshConsoleID() { +    QMessageBox::StandardButton reply; +    QString warning_text = tr("This will replace your current virtual 3DS with a new one. " +                              "Your current virtual 3DS will not be recoverable. " +                              "This might have unexpected effects in games. This might fail, " +                              "if you use an outdated config savegame. Continue?"); +    reply = QMessageBox::critical(this, tr("Warning"), warning_text, +                                  QMessageBox::No | QMessageBox::Yes); +    if (reply == QMessageBox::No) +        return; +    u32 random_number; +    u64 console_id; +    Service::CFG::GenerateConsoleUniqueId(random_number, console_id); +    Service::CFG::SetConsoleUniqueId(random_number, console_id); +    Service::CFG::UpdateConfigNANDSavegame(); +    ui->label_console_id->setText("Console ID: 0x" + QString::number(console_id, 16).toUpper()); +} diff --git a/src/citra_qt/configuration/configure_system.h b/src/citra_qt/configuration/configure_system.h index db0ead13c..f13de17d4 100644 --- a/src/citra_qt/configuration/configure_system.h +++ b/src/citra_qt/configuration/configure_system.h @@ -23,6 +23,7 @@ public:  public slots:      void updateBirthdayComboBox(int birthmonth_index); +    void refreshConsoleID();  private:      void ReadSystemSettings(); diff --git a/src/citra_qt/configuration/configure_system.ui b/src/citra_qt/configuration/configure_system.ui index cc54fa37f..8caf49623 100644 --- a/src/citra_qt/configuration/configure_system.ui +++ b/src/citra_qt/configuration/configure_system.ui @@ -220,6 +220,29 @@            </item>           </widget>          </item> +        <item row="4" column="0"> +         <widget class="QLabel" name="label_console_id"> +          <property name="text"> +           <string>Console ID:</string> +          </property> +         </widget> +        </item> +        <item row="4" column="1"> +         <widget class="QPushButton" name="button_regenerate_console_id"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +          <property name="layoutDirection"> +           <enum>Qt::RightToLeft</enum> +          </property> +          <property name="text"> +           <string>Regenerate</string> +          </property> +         </widget> +        </item>         </layout>        </widget>       </item> diff --git a/src/citra_qt/debugger/callstack.cpp b/src/citra_qt/debugger/callstack.cpp deleted file mode 100644 index 08d2e7a22..000000000 --- a/src/citra_qt/debugger/callstack.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <QStandardItemModel> -#include "citra_qt/debugger/callstack.h" -#include "common/common_types.h" -#include "common/symbols.h" -#include "core/arm/arm_interface.h" -#include "core/arm/disassembler/arm_disasm.h" -#include "core/core.h" -#include "core/memory.h" - -CallstackWidget::CallstackWidget(QWidget* parent) : QDockWidget(parent) { -    ui.setupUi(this); - -    callstack_model = new QStandardItemModel(this); -    callstack_model->setColumnCount(4); -    callstack_model->setHeaderData(0, Qt::Horizontal, "Stack Pointer"); -    callstack_model->setHeaderData(2, Qt::Horizontal, "Return Address"); -    callstack_model->setHeaderData(1, Qt::Horizontal, "Call Address"); -    callstack_model->setHeaderData(3, Qt::Horizontal, "Function"); -    ui.treeView->setModel(callstack_model); -} - -void CallstackWidget::OnDebugModeEntered() { -    // Stack pointer -    const u32 sp = Core::CPU().GetReg(13); - -    Clear(); - -    int counter = 0; -    for (u32 addr = 0x10000000; addr >= sp; addr -= 4) { -        if (!Memory::IsValidVirtualAddress(addr)) -            break; - -        const u32 ret_addr = Memory::Read32(addr); -        const u32 call_addr = ret_addr - 4; // get call address??? - -        if (!Memory::IsValidVirtualAddress(call_addr)) -            break; - -        /* TODO (mattvail) clean me, move to debugger interface */ -        u32 insn = Memory::Read32(call_addr); -        if (ARM_Disasm::Decode(insn) == OP_BL) { -            std::string name; -            // ripped from disasm -            u32 i_offset = insn & 0xffffff; -            // Sign-extend the 24-bit offset -            if ((i_offset >> 23) & 1) -                i_offset |= 0xff000000; - -            // Pre-compute the left-shift and the prefetch offset -            i_offset <<= 2; -            i_offset += 8; -            const u32 func_addr = call_addr + i_offset; - -            callstack_model->setItem( -                counter, 0, new QStandardItem(QString("0x%1").arg(addr, 8, 16, QLatin1Char('0')))); -            callstack_model->setItem(counter, 1, new QStandardItem(QString("0x%1").arg( -                                                     ret_addr, 8, 16, QLatin1Char('0')))); -            callstack_model->setItem(counter, 2, new QStandardItem(QString("0x%1").arg( -                                                     call_addr, 8, 16, QLatin1Char('0')))); - -            name = Symbols::HasSymbol(func_addr) ? Symbols::GetSymbol(func_addr).name : "unknown"; -            callstack_model->setItem( -                counter, 3, new QStandardItem( -                                QString("%1_%2") -                                    .arg(QString::fromStdString(name)) -                                    .arg(QString("0x%1").arg(func_addr, 8, 16, QLatin1Char('0'))))); - -            counter++; -        } -    } -} - -void CallstackWidget::OnDebugModeLeft() {} - -void CallstackWidget::Clear() { -    for (int row = 0; row < callstack_model->rowCount(); row++) { -        for (int column = 0; column < callstack_model->columnCount(); column++) { -            callstack_model->setItem(row, column, new QStandardItem()); -        } -    } -} diff --git a/src/citra_qt/debugger/callstack.h b/src/citra_qt/debugger/callstack.h deleted file mode 100644 index f04ab9c7e..000000000 --- a/src/citra_qt/debugger/callstack.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <QDockWidget> -#include "ui_callstack.h" - -class QStandardItemModel; - -class CallstackWidget : public QDockWidget { -    Q_OBJECT - -public: -    explicit CallstackWidget(QWidget* parent = nullptr); - -public slots: -    void OnDebugModeEntered(); -    void OnDebugModeLeft(); - -private: -    Ui::CallStack ui; -    QStandardItemModel* callstack_model; - -    /// Clears the callstack widget while keeping the column widths the same -    void Clear(); -}; diff --git a/src/citra_qt/debugger/callstack.ui b/src/citra_qt/debugger/callstack.ui deleted file mode 100644 index 248ea3dd7..000000000 --- a/src/citra_qt/debugger/callstack.ui +++ /dev/null @@ -1,39 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>CallStack</class> - <widget class="QDockWidget" name="CallStack"> -  <property name="geometry"> -   <rect> -    <x>0</x> -    <y>0</y> -    <width>400</width> -    <height>300</height> -   </rect> -  </property> -  <property name="windowTitle"> -   <string>Call Stack</string> -  </property> -  <widget class="QWidget" name="dockWidgetContents"> -   <layout class="QVBoxLayout" name="verticalLayout"> -    <item> -     <widget class="QTreeView" name="treeView"> -      <property name="editTriggers"> -       <set>QAbstractItemView::NoEditTriggers</set> -      </property> -      <property name="alternatingRowColors"> -       <bool>true</bool> -      </property> -      <property name="rootIsDecorated"> -       <bool>false</bool> -      </property> -      <property name="itemsExpandable"> -       <bool>false</bool> -      </property> -     </widget> -    </item> -   </layout> -  </widget> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/citra_qt/debugger/disassembler.cpp b/src/citra_qt/debugger/disassembler.cpp deleted file mode 100644 index e9c8ad858..000000000 --- a/src/citra_qt/debugger/disassembler.cpp +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <QShortcut> -#include "citra_qt/bootmanager.h" -#include "citra_qt/debugger/disassembler.h" -#include "citra_qt/hotkeys.h" -#include "citra_qt/util/util.h" -#include "common/break_points.h" -#include "common/symbols.h" -#include "core/arm/arm_interface.h" -#include "core/arm/disassembler/arm_disasm.h" -#include "core/core.h" -#include "core/memory.h" - -DisassemblerModel::DisassemblerModel(QObject* parent) -    : QAbstractListModel(parent), base_address(0), code_size(0), program_counter(0), -      selection(QModelIndex()) {} - -int DisassemblerModel::columnCount(const QModelIndex& parent) const { -    return 3; -} - -int DisassemblerModel::rowCount(const QModelIndex& parent) const { -    return code_size; -} - -QVariant DisassemblerModel::data(const QModelIndex& index, int role) const { -    switch (role) { -    case Qt::DisplayRole: { -        u32 address = base_address + index.row() * 4; -        u32 instr = Memory::Read32(address); -        std::string disassembly = ARM_Disasm::Disassemble(address, instr); - -        if (index.column() == 0) { -            return QString("0x%1").arg((uint)(address), 8, 16, QLatin1Char('0')); -        } else if (index.column() == 1) { -            return QString::fromStdString(disassembly); -        } else if (index.column() == 2) { -            if (Symbols::HasSymbol(address)) { -                TSymbol symbol = Symbols::GetSymbol(address); -                return QString("%1 - Size:%2") -                    .arg(QString::fromStdString(symbol.name)) -                    .arg(symbol.size / 4); // divide by 4 to get instruction count -            } else if (ARM_Disasm::Decode(instr) == OP_BL) { -                u32 offset = instr & 0xFFFFFF; - -                // Sign-extend the 24-bit offset -                if ((offset >> 23) & 1) -                    offset |= 0xFF000000; - -                // Pre-compute the left-shift and the prefetch offset -                offset <<= 2; -                offset += 8; - -                TSymbol symbol = Symbols::GetSymbol(address + offset); -                return QString("    --> %1").arg(QString::fromStdString(symbol.name)); -            } -        } - -        break; -    } - -    case Qt::BackgroundRole: { -        unsigned int address = base_address + 4 * index.row(); - -        if (breakpoints.IsAddressBreakPoint(address)) -            return QBrush(QColor(0xFF, 0xC0, 0xC0)); -        else if (address == program_counter) -            return QBrush(QColor(0xC0, 0xC0, 0xFF)); - -        break; -    } - -    case Qt::FontRole: { -        if (index.column() == 0 || index.column() == 1) { // 2 is the symbols column -            return GetMonospaceFont(); -        } -        break; -    } - -    default: -        break; -    } - -    return QVariant(); -} - -QModelIndex DisassemblerModel::IndexFromAbsoluteAddress(unsigned int address) const { -    return index((address - base_address) / 4, 0); -} - -const BreakPoints& DisassemblerModel::GetBreakPoints() const { -    return breakpoints; -} - -void DisassemblerModel::ParseFromAddress(unsigned int address) { - -    // NOTE: A too large value causes lagging when scrolling the disassembly -    const unsigned int chunk_size = 1000 * 500; - -    // If we haven't loaded anything yet, initialize base address to the parameter address -    if (code_size == 0) -        base_address = address; - -    // If the new area is already loaded, just continue -    if (base_address + code_size > address + chunk_size && base_address <= address) -        return; - -    // Insert rows before currently loaded data -    if (base_address > address) { -        unsigned int num_rows = (address - base_address) / 4; - -        beginInsertRows(QModelIndex(), 0, num_rows); -        code_size += num_rows; -        base_address = address; - -        endInsertRows(); -    } - -    // Insert rows after currently loaded data -    if (base_address + code_size < address + chunk_size) { -        unsigned int num_rows = (base_address + chunk_size - code_size - address) / 4; - -        beginInsertRows(QModelIndex(), 0, num_rows); -        code_size += num_rows; -        endInsertRows(); -    } - -    SetNextInstruction(address); -} - -void DisassemblerModel::OnSelectionChanged(const QModelIndex& new_selection) { -    selection = new_selection; -} - -void DisassemblerModel::OnSetOrUnsetBreakpoint() { -    if (!selection.isValid()) -        return; - -    unsigned int address = base_address + selection.row() * 4; - -    if (breakpoints.IsAddressBreakPoint(address)) { -        breakpoints.Remove(address); -    } else { -        breakpoints.Add(address); -    } - -    emit dataChanged(selection, selection); -} - -void DisassemblerModel::SetNextInstruction(unsigned int address) { -    QModelIndex cur_index = IndexFromAbsoluteAddress(program_counter); -    QModelIndex prev_index = IndexFromAbsoluteAddress(address); - -    program_counter = address; - -    emit dataChanged(cur_index, cur_index); -    emit dataChanged(prev_index, prev_index); -} - -DisassemblerWidget::DisassemblerWidget(QWidget* parent, EmuThread* emu_thread) -    : QDockWidget(parent), base_addr(0), emu_thread(emu_thread) { - -    disasm_ui.setupUi(this); - -    RegisterHotkey("Disassembler", "Start/Stop", QKeySequence(Qt::Key_F5), Qt::ApplicationShortcut); -    RegisterHotkey("Disassembler", "Step", QKeySequence(Qt::Key_F10), Qt::ApplicationShortcut); -    RegisterHotkey("Disassembler", "Step into", QKeySequence(Qt::Key_F11), Qt::ApplicationShortcut); -    RegisterHotkey("Disassembler", "Set Breakpoint", QKeySequence(Qt::Key_F9), -                   Qt::ApplicationShortcut); - -    connect(disasm_ui.button_step, SIGNAL(clicked()), this, SLOT(OnStep())); -    connect(disasm_ui.button_pause, SIGNAL(clicked()), this, SLOT(OnPause())); -    connect(disasm_ui.button_continue, SIGNAL(clicked()), this, SLOT(OnContinue())); - -    connect(GetHotkey("Disassembler", "Start/Stop", this), SIGNAL(activated()), this, -            SLOT(OnToggleStartStop())); -    connect(GetHotkey("Disassembler", "Step", this), SIGNAL(activated()), this, SLOT(OnStep())); -    connect(GetHotkey("Disassembler", "Step into", this), SIGNAL(activated()), this, -            SLOT(OnStepInto())); - -    setEnabled(false); -} - -void DisassemblerWidget::Init() { -    model->ParseFromAddress(Core::CPU().GetPC()); - -    disasm_ui.treeView->resizeColumnToContents(0); -    disasm_ui.treeView->resizeColumnToContents(1); -    disasm_ui.treeView->resizeColumnToContents(2); - -    QModelIndex model_index = model->IndexFromAbsoluteAddress(Core::CPU().GetPC()); -    disasm_ui.treeView->scrollTo(model_index); -    disasm_ui.treeView->selectionModel()->setCurrentIndex( -        model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); -} - -void DisassemblerWidget::OnContinue() { -    emu_thread->SetRunning(true); -} - -void DisassemblerWidget::OnStep() { -    OnStepInto(); // change later -} - -void DisassemblerWidget::OnStepInto() { -    emu_thread->SetRunning(false); -    emu_thread->ExecStep(); -} - -void DisassemblerWidget::OnPause() { -    emu_thread->SetRunning(false); - -    // TODO: By now, the CPU might not have actually stopped... -    if (Core::System::GetInstance().IsPoweredOn()) { -        model->SetNextInstruction(Core::CPU().GetPC()); -    } -} - -void DisassemblerWidget::OnToggleStartStop() { -    emu_thread->SetRunning(!emu_thread->IsRunning()); -} - -void DisassemblerWidget::OnDebugModeEntered() { -    u32 next_instr = Core::CPU().GetPC(); - -    if (model->GetBreakPoints().IsAddressBreakPoint(next_instr)) -        emu_thread->SetRunning(false); - -    model->SetNextInstruction(next_instr); - -    QModelIndex model_index = model->IndexFromAbsoluteAddress(next_instr); -    disasm_ui.treeView->scrollTo(model_index); -    disasm_ui.treeView->selectionModel()->setCurrentIndex( -        model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); -} - -void DisassemblerWidget::OnDebugModeLeft() {} - -int DisassemblerWidget::SelectedRow() { -    QModelIndex index = disasm_ui.treeView->selectionModel()->currentIndex(); -    if (!index.isValid()) -        return -1; - -    return disasm_ui.treeView->selectionModel()->currentIndex().row(); -} - -void DisassemblerWidget::OnEmulationStarting(EmuThread* emu_thread) { -    this->emu_thread = emu_thread; - -    model = new DisassemblerModel(this); -    disasm_ui.treeView->setModel(model); - -    connect(disasm_ui.treeView->selectionModel(), -            SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), model, -            SLOT(OnSelectionChanged(const QModelIndex&))); -    connect(disasm_ui.button_breakpoint, SIGNAL(clicked()), model, SLOT(OnSetOrUnsetBreakpoint())); -    connect(GetHotkey("Disassembler", "Set Breakpoint", this), SIGNAL(activated()), model, -            SLOT(OnSetOrUnsetBreakpoint())); - -    Init(); -    setEnabled(true); -} - -void DisassemblerWidget::OnEmulationStopping() { -    disasm_ui.treeView->setModel(nullptr); -    delete model; -    emu_thread = nullptr; -    setEnabled(false); -} diff --git a/src/citra_qt/debugger/disassembler.h b/src/citra_qt/debugger/disassembler.h deleted file mode 100644 index a6e59515c..000000000 --- a/src/citra_qt/debugger/disassembler.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <QAbstractListModel> -#include <QDockWidget> -#include "common/break_points.h" -#include "common/common_types.h" -#include "ui_disassembler.h" - -class QAction; -class EmuThread; - -class DisassemblerModel : public QAbstractListModel { -    Q_OBJECT - -public: -    explicit DisassemblerModel(QObject* parent); - -    int columnCount(const QModelIndex& parent = QModelIndex()) const override; -    int rowCount(const QModelIndex& parent = QModelIndex()) const override; -    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - -    QModelIndex IndexFromAbsoluteAddress(unsigned int address) const; -    const BreakPoints& GetBreakPoints() const; - -public slots: -    void ParseFromAddress(unsigned int address); -    void OnSelectionChanged(const QModelIndex&); -    void OnSetOrUnsetBreakpoint(); -    void SetNextInstruction(unsigned int address); - -private: -    unsigned int base_address; -    unsigned int code_size; -    unsigned int program_counter; - -    QModelIndex selection; -    BreakPoints breakpoints; -}; - -class DisassemblerWidget : public QDockWidget { -    Q_OBJECT - -public: -    DisassemblerWidget(QWidget* parent, EmuThread* emu_thread); - -    void Init(); - -public slots: -    void OnContinue(); -    void OnStep(); -    void OnStepInto(); -    void OnPause(); -    void OnToggleStartStop(); - -    void OnDebugModeEntered(); -    void OnDebugModeLeft(); - -    void OnEmulationStarting(EmuThread* emu_thread); -    void OnEmulationStopping(); - -private: -    // returns -1 if no row is selected -    int SelectedRow(); - -    Ui::DockWidget disasm_ui; - -    DisassemblerModel* model; - -    u32 base_addr; - -    EmuThread* emu_thread; -}; diff --git a/src/citra_qt/debugger/disassembler.ui b/src/citra_qt/debugger/disassembler.ui deleted file mode 100644 index 5ca6dc5d2..000000000 --- a/src/citra_qt/debugger/disassembler.ui +++ /dev/null @@ -1,81 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>DockWidget</class> - <widget class="QDockWidget" name="DockWidget"> -  <property name="geometry"> -   <rect> -    <x>0</x> -    <y>0</y> -    <width>430</width> -    <height>401</height> -   </rect> -  </property> -  <property name="windowTitle"> -   <string>Disassembly</string> -  </property> -  <widget class="QWidget" name="dockWidgetContents"> -   <layout class="QVBoxLayout" name="verticalLayout"> -    <item> -     <layout class="QHBoxLayout" name="horizontalLayout"> -      <item> -       <widget class="QPushButton" name="button_step"> -        <property name="text"> -         <string>Step</string> -        </property> -       </widget> -      </item> -      <item> -       <widget class="QPushButton" name="button_pause"> -        <property name="text"> -         <string>Pause</string> -        </property> -       </widget> -      </item> -      <item> -       <widget class="QPushButton" name="button_continue"> -        <property name="text"> -         <string>Continue</string> -        </property> -       </widget> -      </item> -      <item> -       <widget class="QPushButton" name="pushButton"> -        <property name="text"> -         <string>Step Into</string> -        </property> -       </widget> -      </item> -      <item> -       <widget class="QPushButton" name="button_breakpoint"> -        <property name="text"> -         <string>Set Breakpoint</string> -        </property> -       </widget> -      </item> -     </layout> -    </item> -    <item> -     <widget class="QTreeView" name="treeView"> -      <property name="alternatingRowColors"> -       <bool>true</bool> -      </property> -      <property name="indentation"> -       <number>20</number> -      </property> -      <property name="rootIsDecorated"> -       <bool>false</bool> -      </property> -      <property name="uniformRowHeights"> -       <bool>true</bool> -      </property> -      <attribute name="headerVisible"> -       <bool>false</bool> -      </attribute> -     </widget> -    </item> -   </layout> -  </widget> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index a9ec9e830..a8e3541cd 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -2,11 +2,12 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <QApplication>  #include <QFileInfo>  #include <QHeaderView> +#include <QKeyEvent>  #include <QMenu>  #include <QThreadPool> -#include <QVBoxLayout>  #include "common/common_paths.h"  #include "common/logging/log.h"  #include "common/string_util.h" @@ -15,10 +16,192 @@  #include "game_list_p.h"  #include "ui_settings.h" -GameList::GameList(QWidget* parent) : QWidget{parent} { -    QVBoxLayout* layout = new QVBoxLayout; +GameList::SearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) { +    this->gamelist = gamelist; +    edit_filter_text_old = ""; +} + +// EventFilter in order to process systemkeys while editing the searchfield +bool GameList::SearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) { +    // If it isn't a KeyRelease event then continue with standard event processing +    if (event->type() != QEvent::KeyRelease) +        return QObject::eventFilter(obj, event); + +    QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); +    int rowCount = gamelist->tree_view->model()->rowCount(); +    QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower(); + +    // If the searchfield's text hasn't changed special function keys get checked +    // If no function key changes the searchfield's text the filter doesn't need to get reloaded +    if (edit_filter_text == edit_filter_text_old) { +        switch (keyEvent->key()) { +        // Escape: Resets the searchfield +        case Qt::Key_Escape: { +            if (edit_filter_text_old.isEmpty()) { +                return QObject::eventFilter(obj, event); +            } else { +                gamelist->search_field->edit_filter->clear(); +                edit_filter_text = ""; +            } +            break; +        } +        // Return and Enter +        // If the enter key gets pressed first checks how many and which entry is visible +        // If there is only one result launch this game +        case Qt::Key_Return: +        case Qt::Key_Enter: { +            QStandardItemModel* item_model = new QStandardItemModel(gamelist->tree_view); +            QModelIndex root_index = item_model->invisibleRootItem()->index(); +            QStandardItem* child_file; +            QString file_path; +            int resultCount = 0; +            for (int i = 0; i < rowCount; ++i) { +                if (!gamelist->tree_view->isRowHidden(i, root_index)) { +                    ++resultCount; +                    child_file = gamelist->item_model->item(i, 0); +                    file_path = child_file->data(GameListItemPath::FullPathRole).toString(); +                } +            } +            if (resultCount == 1) { +                // To avoid loading error dialog loops while confirming them using enter +                // Also users usually want to run a diffrent game after closing one +                gamelist->search_field->edit_filter->setText(""); +                edit_filter_text = ""; +                emit gamelist->GameChosen(file_path); +            } else { +                return QObject::eventFilter(obj, event); +            } +            break; +        } +        default: +            return QObject::eventFilter(obj, event); +        } +    } +    edit_filter_text_old = edit_filter_text; +    return QObject::eventFilter(obj, event); +} + +void GameList::SearchField::setFilterResult(int visible, int total) { +    QString result_of_text = tr("of"); +    QString result_text; +    if (total == 1) { +        result_text = tr("result"); +    } else { +        result_text = tr("results"); +    } +    label_filter_result->setText( +        QString("%1 %2 %3 %4").arg(visible).arg(result_of_text).arg(total).arg(result_text)); +} + +void GameList::SearchField::clear() { +    edit_filter->setText(""); +} + +void GameList::SearchField::setFocus() { +    if (edit_filter->isVisible()) { +        edit_filter->setFocus(); +    } +} + +GameList::SearchField::SearchField(GameList* parent) : QWidget{parent} { +    KeyReleaseEater* keyReleaseEater = new KeyReleaseEater(parent); +    layout_filter = new QHBoxLayout; +    layout_filter->setMargin(8); +    label_filter = new QLabel; +    label_filter->setText(tr("Filter:")); +    edit_filter = new QLineEdit; +    edit_filter->setText(""); +    edit_filter->setPlaceholderText(tr("Enter pattern to filter")); +    edit_filter->installEventFilter(keyReleaseEater); +    edit_filter->setClearButtonEnabled(true); +    connect(edit_filter, SIGNAL(textChanged(const QString&)), parent, +            SLOT(onTextChanged(const QString&))); +    label_filter_result = new QLabel; +    button_filter_close = new QToolButton(this); +    button_filter_close->setText("X"); +    button_filter_close->setCursor(Qt::ArrowCursor); +    button_filter_close->setStyleSheet("QToolButton{ border: none; padding: 0px; color: " +                                       "#000000; font-weight: bold; background: #F0F0F0; }" +                                       "QToolButton:hover{ border: none; padding: 0px; color: " +                                       "#EEEEEE; font-weight: bold; background: #E81123}"); +    connect(button_filter_close, SIGNAL(clicked()), parent, SLOT(onFilterCloseClicked())); +    layout_filter->setSpacing(10); +    layout_filter->addWidget(label_filter); +    layout_filter->addWidget(edit_filter); +    layout_filter->addWidget(label_filter_result); +    layout_filter->addWidget(button_filter_close); +    setLayout(layout_filter); +} + +/** + * Checks if all words separated by spaces are contained in another string + * This offers a word order insensitive search function + * + * @param String that gets checked if it contains all words of the userinput string + * @param String containing all words getting checked + * @return true if the haystack contains all words of userinput + */ +bool GameList::containsAllWords(QString haystack, QString userinput) { +    QStringList userinput_split = userinput.split(" ", QString::SplitBehavior::SkipEmptyParts); +    return std::all_of(userinput_split.begin(), userinput_split.end(), +                       [haystack](QString s) { return haystack.contains(s); }); +} + +// Event in order to filter the gamelist after editing the searchfield +void GameList::onTextChanged(const QString& newText) { +    int rowCount = tree_view->model()->rowCount(); +    QString edit_filter_text = newText.toLower(); + +    QModelIndex root_index = item_model->invisibleRootItem()->index(); + +    // If the searchfield is empty every item is visible +    // Otherwise the filter gets applied +    if (edit_filter_text.isEmpty()) { +        for (int i = 0; i < rowCount; ++i) { +            tree_view->setRowHidden(i, root_index, false); +        } +        search_field->setFilterResult(rowCount, rowCount); +    } else { +        QStandardItem* child_file; +        QString file_path, file_name, file_title, file_programmid; +        int result_count = 0; +        for (int i = 0; i < rowCount; ++i) { +            child_file = item_model->item(i, 0); +            file_path = child_file->data(GameListItemPath::FullPathRole).toString().toLower(); +            file_name = file_path.mid(file_path.lastIndexOf("/") + 1); +            file_title = child_file->data(GameListItemPath::TitleRole).toString().toLower(); +            file_programmid = +                child_file->data(GameListItemPath::ProgramIdRole).toString().toLower(); + +            // Only items which filename in combination with its title contains all words +            // that are in the searchfiel will be visible in the gamelist +            // The search is case insensitive because of toLower() +            // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent +            // multiple conversions of edit_filter_text for each game in the gamelist +            if (containsAllWords(file_name.append(" ").append(file_title), edit_filter_text) || +                (file_programmid.count() == 16 && edit_filter_text.contains(file_programmid))) { +                tree_view->setRowHidden(i, root_index, false); +                ++result_count; +            } else { +                tree_view->setRowHidden(i, root_index, true); +            } +            search_field->setFilterResult(result_count, rowCount); +        } +    } +} + +void GameList::onFilterCloseClicked() { +    main_window->filterBarSetChecked(false); +} +GameList::GameList(GMainWindow* parent) : QWidget{parent} { +    watcher = new QFileSystemWatcher(this); +    connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); + +    this->main_window = parent; +    layout = new QVBoxLayout;      tree_view = new QTreeView; +    search_field = new SearchField(this);      item_model = new QStandardItemModel(tree_view);      tree_view->setModel(item_model); @@ -39,14 +222,15 @@ GameList::GameList(QWidget* parent) : QWidget{parent} {      connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);      connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); -    connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);      // We must register all custom types with the Qt Automoc system so that we are able to use it      // with signals/slots. In this case, QList falls under the umbrells of custom types.      qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");      layout->setContentsMargins(0, 0, 0, 0); +    layout->setSpacing(0);      layout->addWidget(tree_view); +    layout->addWidget(search_field);      setLayout(layout);  } @@ -54,6 +238,20 @@ GameList::~GameList() {      emit ShouldCancelWorker();  } +void GameList::setFilterFocus() { +    if (tree_view->model()->rowCount() > 0) { +        search_field->setFocus(); +    } +} + +void GameList::setFilterVisible(bool visibility) { +    search_field->setVisible(visibility); +} + +void GameList::clearFilter() { +    search_field->clear(); +} +  void GameList::AddEntry(const QList<QStandardItem*>& entry_items) {      item_model->invisibleRootItem()->appendRow(entry_items);  } @@ -69,11 +267,33 @@ void GameList::ValidateEntry(const QModelIndex& item) {      std::string std_file_path(file_path.toStdString());      if (!FileUtil::Exists(std_file_path) || FileUtil::IsDirectory(std_file_path))          return; +    // Users usually want to run a diffrent game after closing one +    search_field->clear();      emit GameChosen(file_path);  } -void GameList::DonePopulating() { +void GameList::DonePopulating(QStringList watch_list) { +    // Clear out the old directories to watch for changes and add the new ones +    auto watch_dirs = watcher->directories(); +    if (!watch_dirs.isEmpty()) { +        watcher->removePaths(watch_dirs); +    } +    // Workaround: Add the watch paths in chunks to allow the gui to refresh +    // This prevents the UI from stalling when a large number of watch paths are added +    // Also artificially caps the watcher to a certain number of directories +    constexpr int LIMIT_WATCH_DIRECTORIES = 5000; +    constexpr int SLICE_SIZE = 25; +    int len = std::min(watch_list.length(), LIMIT_WATCH_DIRECTORIES); +    for (int i = 0; i < len; i += SLICE_SIZE) { +        watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE)); +        QCoreApplication::processEvents(); +    }      tree_view->setEnabled(true); +    int rowCount = tree_view->model()->rowCount(); +    search_field->setFilterResult(rowCount, rowCount); +    if (rowCount > 0) { +        search_field->setFocus(); +    }  }  void GameList::PopupContextMenu(const QPoint& menu_location) { @@ -97,6 +317,7 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {      if (!FileUtil::Exists(dir_path.toStdString()) ||          !FileUtil::IsDirectory(dir_path.toStdString())) {          LOG_ERROR(Frontend, "Could not find game list folder at %s", dir_path.toLocal8Bit().data()); +        search_field->setFilterResult(0, 0);          return;      } @@ -106,11 +327,6 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {      emit ShouldCancelWorker(); -    auto watch_dirs = watcher.directories(); -    if (!watch_dirs.isEmpty()) { -        watcher.removePaths(watch_dirs); -    } -    UpdateWatcherList(dir_path.toStdString(), deep_scan ? 256 : 0);      GameListWorker* worker = new GameListWorker(dir_path, deep_scan);      connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); @@ -151,42 +367,11 @@ static bool HasSupportedFileExtension(const std::string& file_name) {  void GameList::RefreshGameDirectory() {      if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {          LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); +        search_field->clear();          PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);      }  } -/** - * Adds the game list folder to the QFileSystemWatcher to check for updates. - * - * The file watcher will fire off an update to the game list when a change is detected in the game - * list folder. - * - * Notice: This method is run on the UI thread because QFileSystemWatcher is not thread safe and - * this function is fast enough to not stall the UI thread. If performance is an issue, it should - * be moved to another thread and properly locked to prevent concurrency issues. - * - * @param dir folder to check for changes in - * @param recursion 0 if recursion is disabled. Any positive number passed to this will add each - *        directory recursively to the watcher and will update the file list if any of the folders - *        change. The number determines how deep the recursion should traverse. - */ -void GameList::UpdateWatcherList(const std::string& dir, unsigned int recursion) { -    const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, -                                            const std::string& virtual_name) -> bool { -        std::string physical_name = directory + DIR_SEP + virtual_name; - -        if (FileUtil::IsDirectory(physical_name)) { -            UpdateWatcherList(physical_name, recursion - 1); -        } -        return true; -    }; - -    watcher.addPath(QString::fromStdString(dir)); -    if (recursion > 0) { -        FileUtil::ForeachDirectoryEntry(nullptr, dir, callback); -    } -} -  void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {      const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory,                                              const std::string& virtual_name) -> bool { @@ -195,7 +380,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign          if (stop_processing)              return false; // Breaks the callback loop. -        if (!FileUtil::IsDirectory(physical_name) && HasSupportedFileExtension(physical_name)) { +        bool is_dir = FileUtil::IsDirectory(physical_name); +        if (!is_dir && HasSupportedFileExtension(physical_name)) {              std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);              if (!loader)                  return true; @@ -212,7 +398,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign                      QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),                  new GameListItemSize(FileUtil::GetSize(physical_name)),              }); -        } else if (recursion > 0) { +        } else if (is_dir && recursion > 0) { +            watch_list.append(QString::fromStdString(physical_name));              AddFstEntriesToGameList(physical_name, recursion - 1);          } @@ -224,8 +411,9 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign  void GameListWorker::run() {      stop_processing = false; +    watch_list.append(dir_path);      AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); -    emit Finished(); +    emit Finished(watch_list);  }  void GameListWorker::Cancel() { diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index b141fa3a5..4823a1296 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -5,13 +5,19 @@  #pragma once  #include <QFileSystemWatcher> +#include <QHBoxLayout> +#include <QLabel> +#include <QLineEdit>  #include <QModelIndex>  #include <QSettings>  #include <QStandardItem>  #include <QStandardItemModel>  #include <QString> +#include <QToolButton>  #include <QTreeView> +#include <QVBoxLayout>  #include <QWidget> +#include "main.h"  class GameListWorker; @@ -26,9 +32,40 @@ public:          COLUMN_COUNT, // Number of columns      }; -    explicit GameList(QWidget* parent = nullptr); +    class SearchField : public QWidget { +    public: +        void setFilterResult(int visible, int total); +        void clear(); +        void setFocus(); +        explicit SearchField(GameList* parent = nullptr); + +    private: +        class KeyReleaseEater : public QObject { +        public: +            explicit KeyReleaseEater(GameList* gamelist); + +        private: +            GameList* gamelist = nullptr; +            QString edit_filter_text_old; + +        protected: +            bool eventFilter(QObject* obj, QEvent* event); +        }; +        QHBoxLayout* layout_filter = nullptr; +        QTreeView* tree_view = nullptr; +        QLabel* label_filter = nullptr; +        QLineEdit* edit_filter = nullptr; +        QLabel* label_filter_result = nullptr; +        QToolButton* button_filter_close = nullptr; +    }; + +    explicit GameList(GMainWindow* parent = nullptr);      ~GameList() override; +    void clearFilter(); +    void setFilterFocus(); +    void setFilterVisible(bool visibility); +      void PopulateAsync(const QString& dir_path, bool deep_scan);      void SaveInterfaceLayout(); @@ -41,17 +78,24 @@ signals:      void ShouldCancelWorker();      void OpenSaveFolderRequested(u64 program_id); +private slots: +    void onTextChanged(const QString& newText); +    void onFilterCloseClicked(); +  private:      void AddEntry(const QList<QStandardItem*>& entry_items);      void ValidateEntry(const QModelIndex& item); -    void DonePopulating(); +    void DonePopulating(QStringList watch_list);      void PopupContextMenu(const QPoint& menu_location); -    void UpdateWatcherList(const std::string& path, unsigned int recursion);      void RefreshGameDirectory(); +    bool containsAllWords(QString haystack, QString userinput); +    SearchField* search_field; +    GMainWindow* main_window = nullptr; +    QVBoxLayout* layout = nullptr;      QTreeView* tree_view = nullptr;      QStandardItemModel* item_model = nullptr;      GameListWorker* current_worker = nullptr; -    QFileSystemWatcher watcher; +    QFileSystemWatcher* watcher = nullptr;  }; diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index 3c11b6dd1..d1118ff7f 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -170,9 +170,15 @@ signals:       * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry.       */      void EntryReady(QList<QStandardItem*> entry_items); -    void Finished(); + +    /** +     * After the worker has traversed the game directory looking for entries, this signal is emmited +     * with a list of folders that should be watched for changes as well. +     */ +    void Finished(QStringList watch_list);  private: +    QStringList watch_list;      QString dir_path;      bool deep_scan;      std::atomic_bool stop_processing; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index b17ed6968..d7fad555f 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -16,8 +16,6 @@  #include "citra_qt/bootmanager.h"  #include "citra_qt/configuration/config.h"  #include "citra_qt/configuration/configure_dialog.h" -#include "citra_qt/debugger/callstack.h" -#include "citra_qt/debugger/disassembler.h"  #include "citra_qt/debugger/graphics/graphics.h"  #include "citra_qt/debugger/graphics/graphics_breakpoints.h"  #include "citra_qt/debugger/graphics/graphics_cmdlists.h" @@ -40,7 +38,6 @@  #include "common/scm_rev.h"  #include "common/scope_exit.h"  #include "common/string_util.h" -#include "core/arm/disassembler/load_symbol_map.h"  #include "core/core.h"  #include "core/file_sys/archive_source_sd_savedata.h"  #include "core/gdbstub/gdbstub.h" @@ -93,7 +90,7 @@ void GMainWindow::InitializeWidgets() {      render_window = new GRenderWindow(this, emu_thread.get());      render_window->hide(); -    game_list = new GameList(); +    game_list = new GameList(this);      ui.horizontalLayout->addWidget(game_list);      // Create status bar @@ -130,15 +127,6 @@ void GMainWindow::InitializeDebugWidgets() {      debug_menu->addAction(microProfileDialog->toggleViewAction());  #endif -    disasmWidget = new DisassemblerWidget(this, emu_thread.get()); -    addDockWidget(Qt::BottomDockWidgetArea, disasmWidget); -    disasmWidget->hide(); -    debug_menu->addAction(disasmWidget->toggleViewAction()); -    connect(this, &GMainWindow::EmulationStarting, disasmWidget, -            &DisassemblerWidget::OnEmulationStarting); -    connect(this, &GMainWindow::EmulationStopping, disasmWidget, -            &DisassemblerWidget::OnEmulationStopping); -      registersWidget = new RegistersWidget(this);      addDockWidget(Qt::RightDockWidgetArea, registersWidget);      registersWidget->hide(); @@ -148,11 +136,6 @@ void GMainWindow::InitializeDebugWidgets() {      connect(this, &GMainWindow::EmulationStopping, registersWidget,              &RegistersWidget::OnEmulationStopping); -    callstackWidget = new CallstackWidget(this); -    addDockWidget(Qt::RightDockWidgetArea, callstackWidget); -    callstackWidget->hide(); -    debug_menu->addAction(callstackWidget->toggleViewAction()); -      graphicsWidget = new GPUCommandStreamWidget(this);      addDockWidget(Qt::RightDockWidgetArea, graphicsWidget);      graphicsWidget->hide(); @@ -247,6 +230,9 @@ void GMainWindow::RestoreUIState() {      ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar);      OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked()); +    ui.action_Show_Filter_Bar->setChecked(UISettings::values.show_filter_bar); +    game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked()); +      ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar);      statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked());  } @@ -266,8 +252,6 @@ void GMainWindow::ConnectWidgetEvents() {  void GMainWindow::ConnectMenuEvents() {      // File      connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile); -    connect(ui.action_Load_Symbol_Map, &QAction::triggered, this, -            &GMainWindow::OnMenuLoadSymbolMap);      connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,              &GMainWindow::OnMenuSelectGameListRoot);      connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close); @@ -283,6 +267,8 @@ void GMainWindow::ConnectMenuEvents() {              &GMainWindow::ToggleWindowMode);      connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this,              &GMainWindow::OnDisplayTitleBars); +    ui.action_Show_Filter_Bar->setShortcut(tr("CTRL+F")); +    connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar);      connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);  } @@ -386,26 +372,17 @@ void GMainWindow::BootGame(const QString& filename) {      connect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame()));      // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views      // before the CPU continues -    connect(emu_thread.get(), SIGNAL(DebugModeEntered()), disasmWidget, SLOT(OnDebugModeEntered()), -            Qt::BlockingQueuedConnection);      connect(emu_thread.get(), SIGNAL(DebugModeEntered()), registersWidget,              SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); -    connect(emu_thread.get(), SIGNAL(DebugModeEntered()), callstackWidget, -            SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection);      connect(emu_thread.get(), SIGNAL(DebugModeEntered()), waitTreeWidget,              SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); -    connect(emu_thread.get(), SIGNAL(DebugModeLeft()), disasmWidget, SLOT(OnDebugModeLeft()), -            Qt::BlockingQueuedConnection);      connect(emu_thread.get(), SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft()),              Qt::BlockingQueuedConnection); -    connect(emu_thread.get(), SIGNAL(DebugModeLeft()), callstackWidget, SLOT(OnDebugModeLeft()), -            Qt::BlockingQueuedConnection);      connect(emu_thread.get(), SIGNAL(DebugModeLeft()), waitTreeWidget, SLOT(OnDebugModeLeft()),              Qt::BlockingQueuedConnection);      // Update the GUI      registersWidget->OnDebugModeEntered(); -    callstackWidget->OnDebugModeEntered();      if (ui.action_Single_Window_Mode->isChecked()) {          game_list->hide();      } @@ -444,6 +421,7 @@ void GMainWindow::ShutdownGame() {      ui.action_Stop->setEnabled(false);      render_window->hide();      game_list->show(); +    game_list->setFilterFocus();      // Disable status bar updates      status_bar_update_timer.stop(); @@ -525,16 +503,6 @@ void GMainWindow::OnMenuLoadFile() {      }  } -void GMainWindow::OnMenuLoadSymbolMap() { -    QString filename = QFileDialog::getOpenFileName( -        this, tr("Load Symbol Map"), UISettings::values.symbols_path, tr("Symbol Map (*.*)")); -    if (!filename.isEmpty()) { -        UISettings::values.symbols_path = QFileInfo(filename).path(); - -        LoadSymbolMap(filename.toStdString()); -    } -} -  void GMainWindow::OnMenuSelectGameListRoot() {      QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));      if (!dir_path.isEmpty()) { @@ -617,6 +585,15 @@ void GMainWindow::OnConfigure() {      }  } +void GMainWindow::OnToggleFilterBar() { +    game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked()); +    if (ui.action_Show_Filter_Bar->isChecked()) { +        game_list->setFilterFocus(); +    } else { +        game_list->clearFilter(); +    } +} +  void GMainWindow::OnSwapScreens() {      Settings::values.swap_screen = !Settings::values.swap_screen;      Settings::Apply(); @@ -671,6 +648,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) {  #endif      UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked();      UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); +    UISettings::values.show_filter_bar = ui.action_Show_Filter_Bar->isChecked();      UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked();      UISettings::values.first_start = false; @@ -720,6 +698,11 @@ bool GMainWindow::ConfirmChangeGame() {      return answer != QMessageBox::No;  } +void GMainWindow::filterBarSetChecked(bool state) { +    ui.action_Show_Filter_Bar->setChecked(state); +    emit(OnToggleFilterBar()); +} +  #ifdef main  #undef main  #endif diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index ec841eaa5..cb2e87cbd 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -7,11 +7,10 @@  #include <memory>  #include <QMainWindow> +#include <QTimer>  #include "ui_main.h" -class CallstackWidget;  class Config; -class DisassemblerWidget;  class EmuThread;  class GameList;  class GImageInfo; @@ -41,6 +40,7 @@ class GMainWindow : public QMainWindow {      };  public: +    void filterBarSetChecked(bool state);      GMainWindow();      ~GMainWindow(); @@ -116,12 +116,12 @@ private slots:      void OnGameListLoadFile(QString game_path);      void OnGameListOpenSaveFolder(u64 program_id);      void OnMenuLoadFile(); -    void OnMenuLoadSymbolMap();      /// Called whenever a user selects the "File->Select Game List Root" menu item      void OnMenuSelectGameListRoot();      void OnMenuRecentFile();      void OnSwapScreens();      void OnConfigure(); +    void OnToggleFilterBar();      void OnDisplayTitleBars(bool);      void ToggleWindowMode();      void OnCreateGraphicsSurfaceViewer(); @@ -149,9 +149,7 @@ private:      // Debugger panes      ProfilerWidget* profilerWidget;      MicroProfileDialog* microProfileDialog; -    DisassemblerWidget* disasmWidget;      RegistersWidget* registersWidget; -    CallstackWidget* callstackWidget;      GPUCommandStreamWidget* graphicsWidget;      GPUCommandListWidget* graphicsCommandsWidget;      GraphicsBreakPointsWidget* graphicsBreakpointsWidget; diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index 47dbb6ef7..b13d578f5 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -58,7 +58,6 @@       </property>      </widget>      <addaction name="action_Load_File"/> -    <addaction name="action_Load_Symbol_Map"/>      <addaction name="separator"/>      <addaction name="action_Select_Game_List_Root"/>      <addaction name="menu_recent_files"/> @@ -88,6 +87,7 @@      </widget>      <addaction name="action_Single_Window_Mode"/>      <addaction name="action_Display_Dock_Widget_Headers"/> +    <addaction name="action_Show_Filter_Bar"/>      <addaction name="action_Show_Status_Bar"/>      <addaction name="menu_View_Debugging"/>     </widget> @@ -167,6 +167,14 @@      <string>Display Dock Widget Headers</string>     </property>    </action> +  <action name="action_Show_Filter_Bar"> +   <property name="checkable"> +    <bool>true</bool> +   </property> +   <property name="text"> +    <string>Show Filter Bar</string> +   </property> +  </action>    <action name="action_Show_Status_Bar">     <property name="checkable">      <bool>true</bool> diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h index 6408ece2b..bc37f81c5 100644 --- a/src/citra_qt/ui_settings.h +++ b/src/citra_qt/ui_settings.h @@ -27,6 +27,7 @@ struct Values {      bool single_window_mode;      bool display_titlebar; +    bool show_filter_bar;      bool show_status_bar;      bool confirm_before_closing; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 13277a5c2..4b30185f1 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -38,7 +38,6 @@ set(SRCS              param_package.cpp              scm_rev.cpp              string_util.cpp -            symbols.cpp              thread.cpp              timer.cpp              ) @@ -74,7 +73,6 @@ set(HEADERS              scope_exit.h              string_util.h              swap.h -            symbols.h              synchronized_wrapper.h              thread.h              thread_queue_list.h diff --git a/src/common/symbols.cpp b/src/common/symbols.cpp deleted file mode 100644 index c4d16af85..000000000 --- a/src/common/symbols.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/symbols.h" - -TSymbolsMap g_symbols; - -namespace Symbols { -bool HasSymbol(u32 address) { -    return g_symbols.find(address) != g_symbols.end(); -} - -void Add(u32 address, const std::string& name, u32 size, u32 type) { -    if (!HasSymbol(address)) { -        TSymbol symbol; -        symbol.address = address; -        symbol.name = name; -        symbol.size = size; -        symbol.type = type; - -        g_symbols.emplace(address, symbol); -    } -} - -TSymbol GetSymbol(u32 address) { -    const auto iter = g_symbols.find(address); - -    if (iter != g_symbols.end()) -        return iter->second; - -    return {}; -} - -const std::string GetName(u32 address) { -    return GetSymbol(address).name; -} - -void Remove(u32 address) { -    g_symbols.erase(address); -} - -void Clear() { -    g_symbols.clear(); -} -} diff --git a/src/common/symbols.h b/src/common/symbols.h deleted file mode 100644 index f5a48e05a..000000000 --- a/src/common/symbols.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <map> -#include <string> -#include <utility> -#include "common/common_types.h" - -struct TSymbol { -    u32 address = 0; -    std::string name; -    u32 size = 0; -    u32 type = 0; -}; - -typedef std::map<u32, TSymbol> TSymbolsMap; -typedef std::pair<u32, TSymbol> TSymbolsPair; - -namespace Symbols { -bool HasSymbol(u32 address); - -void Add(u32 address, const std::string& name, u32 size, u32 type); -TSymbol GetSymbol(u32 address); -const std::string GetName(u32 address); -void Remove(u32 address); -void Clear(); -} diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index b161c05ba..c733e5d21 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,6 +1,4 @@  set(SRCS -            arm/disassembler/arm_disasm.cpp -            arm/disassembler/load_symbol_map.cpp              arm/dynarmic/arm_dynarmic.cpp              arm/dynarmic/arm_dynarmic_cp15.cpp              arm/dyncom/arm_dyncom.cpp @@ -109,6 +107,7 @@ set(SRCS              hle/service/hid/hid_spvr.cpp              hle/service/hid/hid_user.cpp              hle/service/http_c.cpp +            hle/service/ir/extra_hid.cpp              hle/service/ir/ir.cpp              hle/service/ir/ir_rst.cpp              hle/service/ir/ir_u.cpp @@ -140,6 +139,7 @@ set(SRCS              hle/service/nwm/nwm_soc.cpp              hle/service/nwm/nwm_tst.cpp              hle/service/nwm/nwm_uds.cpp +            hle/service/nwm/uds_beacon.cpp              hle/service/pm_app.cpp              hle/service/ptm/ptm.cpp              hle/service/ptm/ptm_gets.cpp @@ -178,8 +178,6 @@ set(SRCS  set(HEADERS              arm/arm_interface.h -            arm/disassembler/arm_disasm.h -            arm/disassembler/load_symbol_map.h              arm/dynarmic/arm_dynarmic.h              arm/dynarmic/arm_dynarmic_cp15.h              arm/dyncom/arm_dyncom.h @@ -298,6 +296,7 @@ set(HEADERS              hle/service/hid/hid_spvr.h              hle/service/hid/hid_user.h              hle/service/http_c.h +            hle/service/ir/extra_hid.h              hle/service/ir/ir.h              hle/service/ir/ir_rst.h              hle/service/ir/ir_u.h @@ -329,6 +328,7 @@ set(HEADERS              hle/service/nwm/nwm_soc.h              hle/service/nwm/nwm_tst.h              hle/service/nwm/nwm_uds.h +            hle/service/nwm/uds_beacon.h              hle/service/pm_app.h              hle/service/ptm/ptm.h              hle/service/ptm/ptm_gets.h diff --git a/src/core/arm/disassembler/arm_disasm.cpp b/src/core/arm/disassembler/arm_disasm.cpp deleted file mode 100644 index 05d6ed1fb..000000000 --- a/src/core/arm/disassembler/arm_disasm.cpp +++ /dev/null @@ -1,1344 +0,0 @@ -// Copyright 2006 The Android Open Source Project - -#include <string> -#include <unordered_set> -#include "common/common_types.h" -#include "common/string_util.h" -#include "core/arm/disassembler/arm_disasm.h" -#include "core/arm/skyeye_common/armsupp.h" - -static const char* cond_names[] = {"eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc", -                                   "hi", "ls", "ge", "lt", "gt", "le", "",   "RESERVED"}; - -static const char* opcode_names[] = { -    "invalid",   "undefined", "adc",    "add",     "and",    "b",      "bl",     "bic", -    "bkpt",      "blx",       "bx",     "cdp",     "clrex",  "clz",    "cmn",    "cmp", -    "eor",       "ldc",       "ldm",    "ldr",     "ldrb",   "ldrbt",  "ldrex",  "ldrexb", -    "ldrexd",    "ldrexh",    "ldrh",   "ldrsb",   "ldrsh",  "ldrt",   "mcr",    "mla", -    "mov",       "mrc",       "mrs",    "msr",     "mul",    "mvn",    "nop",    "orr", -    "pkh",       "pld",       "qadd16", "qadd8",   "qasx",   "qsax",   "qsub16", "qsub8", -    "rev",       "rev16",     "revsh",  "rsb",     "rsc",    "sadd16", "sadd8",  "sasx", -    "sbc",       "sel",       "sev",    "shadd16", "shadd8", "shasx",  "shsax",  "shsub16", -    "shsub8",    "smlad",     "smlal",  "smlald",  "smlsd",  "smlsld", "smmla",  "smmls", -    "smmul",     "smuad",     "smull",  "smusd",   "ssat",   "ssat16", "ssax",   "ssub16", -    "ssub8",     "stc",       "stm",    "str",     "strb",   "strbt",  "strex",  "strexb", -    "strexd",    "strexh",    "strh",   "strt",    "sub",    "swi",    "swp",    "swpb", -    "sxtab",     "sxtab16",   "sxtah",  "sxtb",    "sxtb16", "sxth",   "teq",    "tst", -    "uadd16",    "uadd8",     "uasx",   "uhadd16", "uhadd8", "uhasx",  "uhsax",  "uhsub16", -    "uhsub8",    "umlal",     "umull",  "uqadd16", "uqadd8", "uqasx",  "uqsax",  "uqsub16", -    "uqsub8",    "usad8",     "usada8", "usat",    "usat16", "usax",   "usub16", "usub8", -    "uxtab",     "uxtab16",   "uxtah",  "uxtb",    "uxtb16", "uxth",   "wfe",    "wfi", -    "yield", - -    "undefined", "adc",       "add",    "and",     "asr",    "b",      "bic",    "bkpt", -    "bl",        "blx",       "bx",     "cmn",     "cmp",    "eor",    "ldmia",  "ldr", -    "ldrb",      "ldrh",      "ldrsb",  "ldrsh",   "lsl",    "lsr",    "mov",    "mul", -    "mvn",       "neg",       "orr",    "pop",     "push",   "ror",    "sbc",    "stmia", -    "str",       "strb",      "strh",   "sub",     "swi",    "tst", - -    nullptr}; - -// Indexed by the shift type (bits 6-5) -static const char* shift_names[] = {"LSL", "LSR", "ASR", "ROR"}; - -static const char* cond_to_str(u32 cond) { -    return cond_names[cond]; -} - -std::string ARM_Disasm::Disassemble(u32 addr, u32 insn) { -    Opcode opcode = Decode(insn); -    switch (opcode) { -    case OP_INVALID: -        return "Invalid"; -    case OP_UNDEFINED: -        return "Undefined"; -    case OP_ADC: -    case OP_ADD: -    case OP_AND: -    case OP_BIC: -    case OP_CMN: -    case OP_CMP: -    case OP_EOR: -    case OP_MOV: -    case OP_MVN: -    case OP_ORR: -    case OP_RSB: -    case OP_RSC: -    case OP_SBC: -    case OP_SUB: -    case OP_TEQ: -    case OP_TST: -        return DisassembleALU(opcode, insn); -    case OP_B: -    case OP_BL: -        return DisassembleBranch(addr, opcode, insn); -    case OP_BKPT: -        return DisassembleBKPT(insn); -    case OP_BLX: -        // not supported yet -        break; -    case OP_BX: -        return DisassembleBX(insn); -    case OP_CDP: -        return "cdp"; -    case OP_CLREX: -        return "clrex"; -    case OP_CLZ: -        return DisassembleCLZ(insn); -    case OP_LDC: -        return "ldc"; -    case OP_LDM: -    case OP_STM: -        return DisassembleMemblock(opcode, insn); -    case OP_LDR: -    case OP_LDRB: -    case OP_LDRBT: -    case OP_LDRT: -    case OP_STR: -    case OP_STRB: -    case OP_STRBT: -    case OP_STRT: -        return DisassembleMem(insn); -    case OP_LDREX: -    case OP_LDREXB: -    case OP_LDREXD: -    case OP_LDREXH: -    case OP_STREX: -    case OP_STREXB: -    case OP_STREXD: -    case OP_STREXH: -        return DisassembleREX(opcode, insn); -    case OP_LDRH: -    case OP_LDRSB: -    case OP_LDRSH: -    case OP_STRH: -        return DisassembleMemHalf(insn); -    case OP_MCR: -    case OP_MRC: -        return DisassembleMCR(opcode, insn); -    case OP_MLA: -        return DisassembleMLA(opcode, insn); -    case OP_MRS: -        return DisassembleMRS(insn); -    case OP_MSR: -        return DisassembleMSR(insn); -    case OP_MUL: -        return DisassembleMUL(opcode, insn); -    case OP_NOP: -    case OP_SEV: -    case OP_WFE: -    case OP_WFI: -    case OP_YIELD: -        return DisassembleNoOperands(opcode, insn); -    case OP_PKH: -        return DisassemblePKH(insn); -    case OP_PLD: -        return DisassemblePLD(insn); -    case OP_QADD16: -    case OP_QADD8: -    case OP_QASX: -    case OP_QSAX: -    case OP_QSUB16: -    case OP_QSUB8: -    case OP_SADD16: -    case OP_SADD8: -    case OP_SASX: -    case OP_SHADD16: -    case OP_SHADD8: -    case OP_SHASX: -    case OP_SHSAX: -    case OP_SHSUB16: -    case OP_SHSUB8: -    case OP_SSAX: -    case OP_SSUB16: -    case OP_SSUB8: -    case OP_UADD16: -    case OP_UADD8: -    case OP_UASX: -    case OP_UHADD16: -    case OP_UHADD8: -    case OP_UHASX: -    case OP_UHSAX: -    case OP_UHSUB16: -    case OP_UHSUB8: -    case OP_UQADD16: -    case OP_UQADD8: -    case OP_UQASX: -    case OP_UQSAX: -    case OP_UQSUB16: -    case OP_UQSUB8: -    case OP_USAX: -    case OP_USUB16: -    case OP_USUB8: -        return DisassembleParallelAddSub(opcode, insn); -    case OP_REV: -    case OP_REV16: -    case OP_REVSH: -        return DisassembleREV(opcode, insn); -    case OP_SEL: -        return DisassembleSEL(insn); -    case OP_SMLAD: -    case OP_SMLALD: -    case OP_SMLSD: -    case OP_SMLSLD: -    case OP_SMMLA: -    case OP_SMMLS: -    case OP_SMMUL: -    case OP_SMUAD: -    case OP_SMUSD: -    case OP_USAD8: -    case OP_USADA8: -        return DisassembleMediaMulDiv(opcode, insn); -    case OP_SSAT: -    case OP_SSAT16: -    case OP_USAT: -    case OP_USAT16: -        return DisassembleSAT(opcode, insn); -    case OP_STC: -        return "stc"; -    case OP_SWI: -        return DisassembleSWI(insn); -    case OP_SWP: -    case OP_SWPB: -        return DisassembleSWP(opcode, insn); -    case OP_SXTAB: -    case OP_SXTAB16: -    case OP_SXTAH: -    case OP_SXTB: -    case OP_SXTB16: -    case OP_SXTH: -    case OP_UXTAB: -    case OP_UXTAB16: -    case OP_UXTAH: -    case OP_UXTB: -    case OP_UXTB16: -    case OP_UXTH: -        return DisassembleXT(opcode, insn); -    case OP_UMLAL: -    case OP_UMULL: -    case OP_SMLAL: -    case OP_SMULL: -        return DisassembleUMLAL(opcode, insn); -    default: -        return "Error"; -    } -    return nullptr; -} - -std::string ARM_Disasm::DisassembleALU(Opcode opcode, u32 insn) { -    static const u8 kNoOperand1 = 1; -    static const u8 kNoDest = 2; -    static const u8 kNoSbit = 4; - -    std::string rn_str; -    std::string rd_str; - -    u8 flags = 0; -    u8 cond = (insn >> 28) & 0xf; -    u8 is_immed = (insn >> 25) & 0x1; -    u8 bit_s = (insn >> 20) & 1; -    u8 rn = (insn >> 16) & 0xf; -    u8 rd = (insn >> 12) & 0xf; -    u8 immed = insn & 0xff; - -    const char* opname = opcode_names[opcode]; -    switch (opcode) { -    case OP_CMN: -    case OP_CMP: -    case OP_TEQ: -    case OP_TST: -        flags = kNoDest | kNoSbit; -        break; -    case OP_MOV: -    case OP_MVN: -        flags = kNoOperand1; -        break; -    default: -        break; -    } - -    // The "mov" instruction ignores the first operand (rn). -    rn_str[0] = 0; -    if ((flags & kNoOperand1) == 0) { -        rn_str = Common::StringFromFormat("r%d, ", rn); -    } - -    // The following instructions do not write the result register (rd): -    // tst, teq, cmp, cmn. -    rd_str[0] = 0; -    if ((flags & kNoDest) == 0) { -        rd_str = Common::StringFromFormat("r%d, ", rd); -    } - -    const char* sbit_str = ""; -    if (bit_s && !(flags & kNoSbit)) -        sbit_str = "s"; - -    if (is_immed) { -        return Common::StringFromFormat("%s%s%s\t%s%s#%u  ; 0x%x", opname, cond_to_str(cond), -                                        sbit_str, rd_str.c_str(), rn_str.c_str(), immed, immed); -    } - -    u8 shift_is_reg = (insn >> 4) & 1; -    u8 rotate = (insn >> 8) & 0xf; -    u8 rm = insn & 0xf; -    u8 shift_type = (insn >> 5) & 0x3; -    u8 rs = (insn >> 8) & 0xf; -    u8 shift_amount = (insn >> 7) & 0x1f; -    u32 rotated_val = immed; -    u8 rotate2 = rotate << 1; -    rotated_val = (rotated_val >> rotate2) | (rotated_val << (32 - rotate2)); - -    if (!shift_is_reg && shift_type == 0 && shift_amount == 0) { -        return Common::StringFromFormat("%s%s%s\t%s%sr%d", opname, cond_to_str(cond), sbit_str, -                                        rd_str.c_str(), rn_str.c_str(), rm); -    } - -    const char* shift_name = shift_names[shift_type]; -    if (shift_is_reg) { -        return Common::StringFromFormat("%s%s%s\t%s%sr%d, %s r%d", opname, cond_to_str(cond), -                                        sbit_str, rd_str.c_str(), rn_str.c_str(), rm, shift_name, -                                        rs); -    } -    if (shift_amount == 0) { -        if (shift_type == 3) { -            return Common::StringFromFormat("%s%s%s\t%s%sr%d, RRX", opname, cond_to_str(cond), -                                            sbit_str, rd_str.c_str(), rn_str.c_str(), rm); -        } -        shift_amount = 32; -    } -    return Common::StringFromFormat("%s%s%s\t%s%sr%d, %s #%u", opname, cond_to_str(cond), sbit_str, -                                    rd_str.c_str(), rn_str.c_str(), rm, shift_name, shift_amount); -} - -std::string ARM_Disasm::DisassembleBranch(u32 addr, Opcode opcode, u32 insn) { -    u8 cond = (insn >> 28) & 0xf; -    u32 offset = insn & 0xffffff; -    // Sign-extend the 24-bit offset -    if ((offset >> 23) & 1) -        offset |= 0xff000000; - -    // Pre-compute the left-shift and the prefetch offset -    offset <<= 2; -    offset += 8; -    addr += offset; -    const char* opname = opcode_names[opcode]; -    return Common::StringFromFormat("%s%s\t0x%x", opname, cond_to_str(cond), addr); -} - -std::string ARM_Disasm::DisassembleBX(u32 insn) { -    u8 cond = (insn >> 28) & 0xf; -    u8 rn = insn & 0xf; -    return Common::StringFromFormat("bx%s\tr%d", cond_to_str(cond), rn); -} - -std::string ARM_Disasm::DisassembleBKPT(u32 insn) { -    u8 cond = (insn >> 28) & 0xf; -    u32 immed = (((insn >> 8) & 0xfff) << 4) | (insn & 0xf); -    return Common::StringFromFormat("bkpt%s\t#%d", cond_to_str(cond), immed); -} - -std::string ARM_Disasm::DisassembleCLZ(u32 insn) { -    u8 cond = (insn >> 28) & 0xf; -    u8 rd = (insn >> 12) & 0xf; -    u8 rm = insn & 0xf; -    return Common::StringFromFormat("clz%s\tr%d, r%d", cond_to_str(cond), rd, rm); -} - -std::string ARM_Disasm::DisassembleMediaMulDiv(Opcode opcode, u32 insn) { -    u32 cond = BITS(insn, 28, 31); -    u32 rd = BITS(insn, 16, 19); -    u32 ra = BITS(insn, 12, 15); -    u32 rm = BITS(insn, 8, 11); -    u32 m = BIT(insn, 5); -    u32 rn = BITS(insn, 0, 3); - -    std::string cross = ""; -    if (m) { -        if (opcode == OP_SMMLA || opcode == OP_SMMUL || opcode == OP_SMMLS) -            cross = "r"; -        else -            cross = "x"; -    } - -    std::string ext_reg = ""; -    std::unordered_set<Opcode, std::hash<int>> with_ext_reg = {OP_SMLAD, OP_SMLSD, OP_SMMLA, -                                                               OP_SMMLS, OP_USADA8}; -    if (with_ext_reg.find(opcode) != with_ext_reg.end()) -        ext_reg = Common::StringFromFormat(", r%u", ra); - -    std::string rd_low = ""; -    if (opcode == OP_SMLALD || opcode == OP_SMLSLD) -        rd_low = Common::StringFromFormat("r%u, ", ra); - -    return Common::StringFromFormat("%s%s%s\t%sr%u, r%u, r%u%s", opcode_names[opcode], -                                    cross.c_str(), cond_to_str(cond), rd_low.c_str(), rd, rn, rm, -                                    ext_reg.c_str()); -} - -std::string ARM_Disasm::DisassembleMemblock(Opcode opcode, u32 insn) { -    std::string tmp_list; - -    u8 cond = (insn >> 28) & 0xf; -    u8 write_back = (insn >> 21) & 0x1; -    u8 bit_s = (insn >> 22) & 0x1; -    u8 is_up = (insn >> 23) & 0x1; -    u8 is_pre = (insn >> 24) & 0x1; -    u8 rn = (insn >> 16) & 0xf; -    u16 reg_list = insn & 0xffff; - -    const char* opname = opcode_names[opcode]; - -    const char* bang = ""; -    if (write_back) -        bang = "!"; - -    const char* carret = ""; -    if (bit_s) -        carret = "^"; - -    const char* comma = ""; -    tmp_list[0] = 0; -    for (int ii = 0; ii < 16; ++ii) { -        if (reg_list & (1 << ii)) { -            tmp_list += Common::StringFromFormat("%sr%d", comma, ii); -            comma = ","; -        } -    } - -    const char* addr_mode = ""; -    if (is_pre) { -        if (is_up) { -            addr_mode = "ib"; -        } else { -            addr_mode = "db"; -        } -    } else { -        if (is_up) { -            addr_mode = "ia"; -        } else { -            addr_mode = "da"; -        } -    } - -    return Common::StringFromFormat("%s%s%s\tr%d%s, {%s}%s", opname, cond_to_str(cond), addr_mode, -                                    rn, bang, tmp_list.c_str(), carret); -} - -std::string ARM_Disasm::DisassembleMem(u32 insn) { -    u8 cond = (insn >> 28) & 0xf; -    u8 is_reg = (insn >> 25) & 0x1; -    u8 is_load = (insn >> 20) & 0x1; -    u8 write_back = (insn >> 21) & 0x1; -    u8 is_byte = (insn >> 22) & 0x1; -    u8 is_up = (insn >> 23) & 0x1; -    u8 is_pre = (insn >> 24) & 0x1; -    u8 rn = (insn >> 16) & 0xf; -    u8 rd = (insn >> 12) & 0xf; -    u16 offset = insn & 0xfff; - -    const char* opname = "ldr"; -    if (!is_load) -        opname = "str"; - -    const char* bang = ""; -    if (write_back) -        bang = "!"; - -    const char* minus = ""; -    if (is_up == 0) -        minus = "-"; - -    const char* byte = ""; -    if (is_byte) -        byte = "b"; - -    if (is_reg == 0) { -        if (is_pre) { -            if (offset == 0) { -                return Common::StringFromFormat("%s%s%s\tr%d, [r%d]", opname, cond_to_str(cond), -                                                byte, rd, rn); -            } else { -                return Common::StringFromFormat("%s%s%s\tr%d, [r%d, #%s%u]%s", opname, -                                                cond_to_str(cond), byte, rd, rn, minus, offset, -                                                bang); -            } -        } else { -            const char* transfer = ""; -            if (write_back) -                transfer = "t"; - -            return Common::StringFromFormat("%s%s%s%s\tr%d, [r%d], #%s%u", opname, -                                            cond_to_str(cond), byte, transfer, rd, rn, minus, -                                            offset); -        } -    } - -    u8 rm = insn & 0xf; -    u8 shift_type = (insn >> 5) & 0x3; -    u8 shift_amount = (insn >> 7) & 0x1f; - -    const char* shift_name = shift_names[shift_type]; - -    if (is_pre) { -        if (shift_amount == 0) { -            if (shift_type == 0) { -                return Common::StringFromFormat("%s%s%s\tr%d, [r%d, %sr%d]%s", opname, -                                                cond_to_str(cond), byte, rd, rn, minus, rm, bang); -            } -            if (shift_type == 3) { -                return Common::StringFromFormat("%s%s%s\tr%d, [r%d, %sr%d, RRX]%s", opname, -                                                cond_to_str(cond), byte, rd, rn, minus, rm, bang); -            } -            shift_amount = 32; -        } -        return Common::StringFromFormat("%s%s%s\tr%d, [r%d, %sr%d, %s #%u]%s", opname, -                                        cond_to_str(cond), byte, rd, rn, minus, rm, shift_name, -                                        shift_amount, bang); -    } - -    const char* transfer = ""; -    if (write_back) -        transfer = "t"; - -    if (shift_amount == 0) { -        if (shift_type == 0) { -            return Common::StringFromFormat("%s%s%s%s\tr%d, [r%d], %sr%d", opname, -                                            cond_to_str(cond), byte, transfer, rd, rn, minus, rm); -        } -        if (shift_type == 3) { -            return Common::StringFromFormat("%s%s%s%s\tr%d, [r%d], %sr%d, RRX", opname, -                                            cond_to_str(cond), byte, transfer, rd, rn, minus, rm); -        } -        shift_amount = 32; -    } - -    return Common::StringFromFormat("%s%s%s%s\tr%d, [r%d], %sr%d, %s #%u", opname, -                                    cond_to_str(cond), byte, transfer, rd, rn, minus, rm, -                                    shift_name, shift_amount); -} - -std::string ARM_Disasm::DisassembleMemHalf(u32 insn) { -    u8 cond = (insn >> 28) & 0xf; -    u8 is_load = (insn >> 20) & 0x1; -    u8 write_back = (insn >> 21) & 0x1; -    u8 is_immed = (insn >> 22) & 0x1; -    u8 is_up = (insn >> 23) & 0x1; -    u8 is_pre = (insn >> 24) & 0x1; -    u8 rn = (insn >> 16) & 0xf; -    u8 rd = (insn >> 12) & 0xf; -    u8 bits_65 = (insn >> 5) & 0x3; -    u8 rm = insn & 0xf; -    u8 offset = (((insn >> 8) & 0xf) << 4) | (insn & 0xf); - -    const char* opname = "ldr"; -    if (is_load == 0) -        opname = "str"; - -    const char* width = ""; -    if (bits_65 == 1) -        width = "h"; -    else if (bits_65 == 2) -        width = "sb"; -    else -        width = "sh"; - -    const char* bang = ""; -    if (write_back) -        bang = "!"; -    const char* minus = ""; -    if (is_up == 0) -        minus = "-"; - -    if (is_immed) { -        if (is_pre) { -            if (offset == 0) { -                return Common::StringFromFormat("%s%s%s\tr%d, [r%d]", opname, cond_to_str(cond), -                                                width, rd, rn); -            } else { -                return Common::StringFromFormat("%s%s%s\tr%d, [r%d, #%s%u]%s", opname, -                                                cond_to_str(cond), width, rd, rn, minus, offset, -                                                bang); -            } -        } else { -            return Common::StringFromFormat("%s%s%s\tr%d, [r%d], #%s%u", opname, cond_to_str(cond), -                                            width, rd, rn, minus, offset); -        } -    } - -    if (is_pre) { -        return Common::StringFromFormat("%s%s%s\tr%d, [r%d, %sr%d]%s", opname, cond_to_str(cond), -                                        width, rd, rn, minus, rm, bang); -    } else { -        return Common::StringFromFormat("%s%s%s\tr%d, [r%d], %sr%d", opname, cond_to_str(cond), -                                        width, rd, rn, minus, rm); -    } -} - -std::string ARM_Disasm::DisassembleMCR(Opcode opcode, u32 insn) { -    u8 cond = (insn >> 28) & 0xf; -    u8 crn = (insn >> 16) & 0xf; -    u8 crd = (insn >> 12) & 0xf; -    u8 cpnum = (insn >> 8) & 0xf; -    u8 opcode2 = (insn >> 5) & 0x7; -    u8 crm = insn & 0xf; - -    const char* opname = opcode_names[opcode]; -    return Common::StringFromFormat("%s%s\t%d, 0, r%d, cr%d, cr%d, {%d}", opname, cond_to_str(cond), -                                    cpnum, crd, crn, crm, opcode2); -} - -std::string ARM_Disasm::DisassembleMLA(Opcode opcode, u32 insn) { -    u8 cond = (insn >> 28) & 0xf; -    u8 rd = (insn >> 16) & 0xf; -    u8 rn = (insn >> 12) & 0xf; -    u8 rs = (insn >> 8) & 0xf; -    u8 rm = insn & 0xf; -    u8 bit_s = (insn >> 20) & 1; - -    const char* opname = opcode_names[opcode]; -    return Common::StringFromFormat("%s%s%s\tr%d, r%d, r%d, r%d", opname, cond_to_str(cond), -                                    bit_s ? "s" : "", rd, rm, rs, rn); -} - -std::string ARM_Disasm::DisassembleUMLAL(Opcode opcode, u32 insn) { -    u8 cond = (insn >> 28) & 0xf; -    u8 rdhi = (insn >> 16) & 0xf; -    u8 rdlo = (insn >> 12) & 0xf; -    u8 rs = (insn >> 8) & 0xf; -    u8 rm = insn & 0xf; -    u8 bit_s = (insn >> 20) & 1; - -    const char* opname = opcode_names[opcode]; -    return Common::StringFromFormat("%s%s%s\tr%d, r%d, r%d, r%d", opname, cond_to_str(cond), -                                    bit_s ? "s" : "", rdlo, rdhi, rm, rs); -} - -std::string ARM_Disasm::DisassembleMUL(Opcode opcode, u32 insn) { -    u8 cond = (insn >> 28) & 0xf; -    u8 rd = (insn >> 16) & 0xf; -    u8 rs = (insn >> 8) & 0xf; -    u8 rm = insn & 0xf; -    u8 bit_s = (insn >> 20) & 1; - -    const char* opname = opcode_names[opcode]; -    return Common::StringFromFormat("%s%s%s\tr%d, r%d, r%d", opname, cond_to_str(cond), -                                    bit_s ? "s" : "", rd, rm, rs); -} - -std::string ARM_Disasm::DisassembleMRS(u32 insn) { -    u8 cond = (insn >> 28) & 0xf; -    u8 rd = (insn >> 12) & 0xf; -    u8 ps = (insn >> 22) & 1; - -    return Common::StringFromFormat("mrs%s\tr%d, %s", cond_to_str(cond), rd, ps ? "spsr" : "cpsr"); -} - -std::string ARM_Disasm::DisassembleMSR(u32 insn) { -    char flags[8]; -    int flag_index = 0; -    u8 cond = (insn >> 28) & 0xf; -    u8 is_immed = (insn >> 25) & 0x1; -    u8 pd = (insn >> 22) & 1; -    u8 mask = (insn >> 16) & 0xf; - -    if (mask & 1) -        flags[flag_index++] = 'c'; -    if (mask & 2) -        flags[flag_index++] = 'x'; -    if (mask & 4) -        flags[flag_index++] = 's'; -    if (mask & 8) -        flags[flag_index++] = 'f'; -    flags[flag_index] = 0; - -    if (is_immed) { -        u32 immed = insn & 0xff; -        u8 rotate = (insn >> 8) & 0xf; -        u8 rotate2 = rotate << 1; -        u32 rotated_val = (immed >> rotate2) | (immed << (32 - rotate2)); -        return Common::StringFromFormat("msr%s\t%s_%s, #0x%x", cond_to_str(cond), -                                        pd ? "spsr" : "cpsr", flags, rotated_val); -    } - -    u8 rm = insn & 0xf; - -    return Common::StringFromFormat("msr%s\t%s_%s, r%d", cond_to_str(cond), pd ? "spsr" : "cpsr", -                                    flags, rm); -} - -std::string ARM_Disasm::DisassembleNoOperands(Opcode opcode, u32 insn) { -    u32 cond = BITS(insn, 28, 31); -    return Common::StringFromFormat("%s%s", opcode_names[opcode], cond_to_str(cond)); -} - -std::string ARM_Disasm::DisassembleParallelAddSub(Opcode opcode, u32 insn) { -    u32 cond = BITS(insn, 28, 31); -    u32 rn = BITS(insn, 16, 19); -    u32 rd = BITS(insn, 12, 15); -    u32 rm = BITS(insn, 0, 3); - -    return Common::StringFromFormat("%s%s\tr%u, r%u, r%u", opcode_names[opcode], cond_to_str(cond), -                                    rd, rn, rm); -} - -std::string ARM_Disasm::DisassemblePKH(u32 insn) { -    u32 cond = BITS(insn, 28, 31); -    u32 rn = BITS(insn, 16, 19); -    u32 rd = BITS(insn, 12, 15); -    u32 imm5 = BITS(insn, 7, 11); -    u32 tb = BIT(insn, 6); -    u32 rm = BITS(insn, 0, 3); - -    std::string suffix = tb ? "tb" : "bt"; -    std::string shift = ""; - -    if (tb && imm5 == 0) -        imm5 = 32; - -    if (imm5 > 0) { -        shift = tb ? ", ASR" : ", LSL"; -        shift += " #" + std::to_string(imm5); -    } - -    return Common::StringFromFormat("pkh%s%s\tr%u, r%u, r%u%s", suffix.c_str(), cond_to_str(cond), -                                    rd, rn, rm, shift.c_str()); -} - -std::string ARM_Disasm::DisassemblePLD(u32 insn) { -    u8 is_reg = (insn >> 25) & 0x1; -    u8 is_up = (insn >> 23) & 0x1; -    u8 rn = (insn >> 16) & 0xf; - -    const char* minus = ""; -    if (is_up == 0) -        minus = "-"; - -    if (is_reg) { -        u8 rm = insn & 0xf; -        return Common::StringFromFormat("pld\t[r%d, %sr%d]", rn, minus, rm); -    } - -    u16 offset = insn & 0xfff; -    if (offset == 0) { -        return Common::StringFromFormat("pld\t[r%d]", rn); -    } else { -        return Common::StringFromFormat("pld\t[r%d, #%s%u]", rn, minus, offset); -    } -} - -std::string ARM_Disasm::DisassembleREV(Opcode opcode, u32 insn) { -    u32 cond = BITS(insn, 28, 31); -    u32 rd = BITS(insn, 12, 15); -    u32 rm = BITS(insn, 0, 3); - -    return Common::StringFromFormat("%s%s\tr%u, r%u", opcode_names[opcode], cond_to_str(cond), rd, -                                    rm); -} - -std::string ARM_Disasm::DisassembleREX(Opcode opcode, u32 insn) { -    u32 rn = BITS(insn, 16, 19); -    u32 rd = BITS(insn, 12, 15); -    u32 rt = BITS(insn, 0, 3); -    u32 cond = BITS(insn, 28, 31); - -    switch (opcode) { -    case OP_STREX: -    case OP_STREXB: -    case OP_STREXH: -        return Common::StringFromFormat("%s%s\tr%d, r%d, [r%d]", opcode_names[opcode], -                                        cond_to_str(cond), rd, rt, rn); -    case OP_STREXD: -        return Common::StringFromFormat("%s%s\tr%d, r%d, r%d, [r%d]", opcode_names[opcode], -                                        cond_to_str(cond), rd, rt, rt + 1, rn); - -    // for LDREX instructions, rd corresponds to Rt from reference manual -    case OP_LDREX: -    case OP_LDREXB: -    case OP_LDREXH: -        return Common::StringFromFormat("%s%s\tr%d, [r%d]", opcode_names[opcode], cond_to_str(cond), -                                        rd, rn); -    case OP_LDREXD: -        return Common::StringFromFormat("%s%s\tr%d, r%d, [r%d]", opcode_names[opcode], -                                        cond_to_str(cond), rd, rd + 1, rn); -    default: -        return opcode_names[OP_UNDEFINED]; -    } -} - -std::string ARM_Disasm::DisassembleSAT(Opcode opcode, u32 insn) { -    u32 cond = BITS(insn, 28, 31); -    u32 sat_imm = BITS(insn, 16, 20); -    u32 rd = BITS(insn, 12, 15); -    u32 imm5 = BITS(insn, 7, 11); -    u32 sh = BIT(insn, 6); -    u32 rn = BITS(insn, 0, 3); - -    std::string shift_part = ""; -    bool opcode_has_shift = (opcode == OP_SSAT) || (opcode == OP_USAT); -    if (opcode_has_shift && !(sh == 0 && imm5 == 0)) { -        if (sh == 0) -            shift_part += ", LSL #"; -        else -            shift_part += ", ASR #"; - -        if (imm5 == 0) -            imm5 = 32; -        shift_part += std::to_string(imm5); -    } - -    if (opcode == OP_SSAT || opcode == OP_SSAT16) -        sat_imm++; - -    return Common::StringFromFormat("%s%s\tr%u, #%u, r%u%s", opcode_names[opcode], -                                    cond_to_str(cond), rd, sat_imm, rn, shift_part.c_str()); -} - -std::string ARM_Disasm::DisassembleSEL(u32 insn) { -    u32 cond = BITS(insn, 28, 31); -    u32 rn = BITS(insn, 16, 19); -    u32 rd = BITS(insn, 12, 15); -    u32 rm = BITS(insn, 0, 3); - -    return Common::StringFromFormat("%s%s\tr%u, r%u, r%u", opcode_names[OP_SEL], cond_to_str(cond), -                                    rd, rn, rm); -} - -std::string ARM_Disasm::DisassembleSWI(u32 insn) { -    u8 cond = (insn >> 28) & 0xf; -    u32 sysnum = insn & 0x00ffffff; - -    return Common::StringFromFormat("swi%s 0x%x", cond_to_str(cond), sysnum); -} - -std::string ARM_Disasm::DisassembleSWP(Opcode opcode, u32 insn) { -    u8 cond = (insn >> 28) & 0xf; -    u8 rn = (insn >> 16) & 0xf; -    u8 rd = (insn >> 12) & 0xf; -    u8 rm = insn & 0xf; - -    const char* opname = opcode_names[opcode]; -    return Common::StringFromFormat("%s%s\tr%d, r%d, [r%d]", opname, cond_to_str(cond), rd, rm, rn); -} - -std::string ARM_Disasm::DisassembleXT(Opcode opcode, u32 insn) { -    u32 cond = BITS(insn, 28, 31); -    u32 rn = BITS(insn, 16, 19); -    u32 rd = BITS(insn, 12, 15); -    u32 rotate = BITS(insn, 10, 11); -    u32 rm = BITS(insn, 0, 3); - -    std::string rn_part = ""; -    static std::unordered_set<Opcode, std::hash<int>> extend_with_add = { -        OP_SXTAB, OP_SXTAB16, OP_SXTAH, OP_UXTAB, OP_UXTAB16, OP_UXTAH}; -    if (extend_with_add.find(opcode) != extend_with_add.end()) -        rn_part = ", r" + std::to_string(rn); - -    std::string rotate_part = ""; -    if (rotate != 0) -        rotate_part = ", ROR #" + std::to_string(rotate << 3); - -    return Common::StringFromFormat("%s%s\tr%u%s, r%u%s", opcode_names[opcode], cond_to_str(cond), -                                    rd, rn_part.c_str(), rm, rotate_part.c_str()); -} - -Opcode ARM_Disasm::Decode(u32 insn) { -    u32 bits27_26 = (insn >> 26) & 0x3; -    switch (bits27_26) { -    case 0x0: -        return Decode00(insn); -    case 0x1: -        return Decode01(insn); -    case 0x2: -        return Decode10(insn); -    case 0x3: -        return Decode11(insn); -    } -    return OP_INVALID; -} - -Opcode ARM_Disasm::Decode00(u32 insn) { -    u8 bit25 = (insn >> 25) & 0x1; -    u8 bit4 = (insn >> 4) & 0x1; -    if (bit25 == 0 && bit4 == 1) { -        if ((insn & 0x0ffffff0) == 0x012fff10) { -            // Bx instruction -            return OP_BX; -        } -        if ((insn & 0x0ff000f0) == 0x01600010) { -            // Clz instruction -            return OP_CLZ; -        } -        if ((insn & 0xfff000f0) == 0xe1200070) { -            // Bkpt instruction -            return OP_BKPT; -        } -        u32 bits7_4 = (insn >> 4) & 0xf; -        if (bits7_4 == 0x9) { -            u32 bit24 = BIT(insn, 24); -            if (bit24) { -                return DecodeSyncPrimitive(insn); -            } -            // One of the multiply instructions -            return DecodeMUL(insn); -        } - -        u8 bit7 = (insn >> 7) & 0x1; -        if (bit7 == 1) { -            // One of the load/store halfword/byte instructions -            return DecodeLDRH(insn); -        } -    } - -    u32 op1 = BITS(insn, 20, 24); -    if (bit25 && (op1 == 0x12 || op1 == 0x16)) { -        // One of the MSR (immediate) and hints instructions -        return DecodeMSRImmAndHints(insn); -    } - -    // One of the data processing instructions -    return DecodeALU(insn); -} - -Opcode ARM_Disasm::Decode01(u32 insn) { -    u8 is_reg = (insn >> 25) & 0x1; -    u8 bit4 = (insn >> 4) & 0x1; -    if (is_reg == 1 && bit4 == 1) -        return DecodeMedia(insn); -    u8 is_load = (insn >> 20) & 0x1; -    u8 is_byte = (insn >> 22) & 0x1; -    if ((insn & 0xfd70f000) == 0xf550f000) { -        // Pre-load -        return OP_PLD; -    } -    if (insn == 0xf57ff01f) { -        // Clear-Exclusive -        return OP_CLREX; -    } -    if (is_load) { -        if (is_byte) { -            // Load byte -            return OP_LDRB; -        } -        // Load word -        return OP_LDR; -    } -    if (is_byte) { -        // Store byte -        return OP_STRB; -    } -    // Store word -    return OP_STR; -} - -Opcode ARM_Disasm::Decode10(u32 insn) { -    u8 bit25 = (insn >> 25) & 0x1; -    if (bit25 == 0) { -        // LDM/STM -        u8 is_load = (insn >> 20) & 0x1; -        if (is_load) -            return OP_LDM; -        return OP_STM; -    } - -    // Branch with link -    if ((insn >> 24) & 1) -        return OP_BL; - -    return OP_B; -} - -Opcode ARM_Disasm::Decode11(u32 insn) { -    u8 bit25 = (insn >> 25) & 0x1; -    if (bit25 == 0) { -        // LDC, SDC -        u8 is_load = (insn >> 20) & 0x1; -        if (is_load) { -            // LDC -            return OP_LDC; -        } -        // STC -        return OP_STC; -    } - -    u8 bit24 = (insn >> 24) & 0x1; -    if (bit24 == 0x1) { -        // SWI -        return OP_SWI; -    } - -    u8 bit4 = (insn >> 4) & 0x1; -    u8 cpnum = (insn >> 8) & 0xf; - -    if (cpnum == 15) { -        // Special case for coprocessor 15 -        u8 opcode = (insn >> 21) & 0x7; -        if (bit4 == 0 || opcode != 0) { -            // This is an unexpected bit pattern.  Create an undefined -            // instruction in case this is ever executed. -            return OP_UNDEFINED; -        } - -        // MRC, MCR -        u8 is_mrc = (insn >> 20) & 0x1; -        if (is_mrc) -            return OP_MRC; -        return OP_MCR; -    } - -    if (bit4 == 0) { -        // CDP -        return OP_CDP; -    } -    // MRC, MCR -    u8 is_mrc = (insn >> 20) & 0x1; -    if (is_mrc) -        return OP_MRC; -    return OP_MCR; -} - -Opcode ARM_Disasm::DecodeSyncPrimitive(u32 insn) { -    u32 op = BITS(insn, 20, 23); -    u32 bit22 = BIT(insn, 22); -    switch (op) { -    case 0x0: -        if (bit22) -            return OP_SWPB; -        return OP_SWP; -    case 0x8: -        return OP_STREX; -    case 0x9: -        return OP_LDREX; -    case 0xA: -        return OP_STREXD; -    case 0xB: -        return OP_LDREXD; -    case 0xC: -        return OP_STREXB; -    case 0xD: -        return OP_LDREXB; -    case 0xE: -        return OP_STREXH; -    case 0xF: -        return OP_LDREXH; -    default: -        return OP_UNDEFINED; -    } -} - -Opcode ARM_Disasm::DecodeParallelAddSub(u32 insn) { -    u32 op1 = BITS(insn, 20, 21); -    u32 op2 = BITS(insn, 5, 7); -    u32 is_unsigned = BIT(insn, 22); - -    if (op1 == 0x0 || op2 == 0x5 || op2 == 0x6) -        return OP_UNDEFINED; - -    // change op1 range from [1, 3] to range [0, 2] -    op1--; - -    // change op2 range from [0, 4] U {7} to range [0, 5] -    if (op2 == 0x7) -        op2 = 0x5; - -    static std::vector<Opcode> opcodes = { -        // op1 = 0 -        OP_SADD16, OP_UADD16, OP_SASX, OP_UASX, OP_SSAX, OP_USAX, OP_SSUB16, OP_USUB16, OP_SADD8, -        OP_UADD8, OP_SSUB8, OP_USUB8, -        // op1 = 1 -        OP_QADD16, OP_UQADD16, OP_QASX, OP_UQASX, OP_QSAX, OP_UQSAX, OP_QSUB16, OP_UQSUB16, -        OP_QADD8, OP_UQADD8, OP_QSUB8, OP_UQSUB8, -        // op1 = 2 -        OP_SHADD16, OP_UHADD16, OP_SHASX, OP_UHASX, OP_SHSAX, OP_UHSAX, OP_SHSUB16, OP_UHSUB16, -        OP_SHADD8, OP_UHADD8, OP_SHSUB8, OP_UHSUB8}; - -    u32 opcode_index = op1 * 12 + op2 * 2 + is_unsigned; -    return opcodes[opcode_index]; -} - -Opcode ARM_Disasm::DecodePackingSaturationReversal(u32 insn) { -    u32 op1 = BITS(insn, 20, 22); -    u32 a = BITS(insn, 16, 19); -    u32 op2 = BITS(insn, 5, 7); - -    switch (op1) { -    case 0x0: -        if (BIT(op2, 0) == 0) -            return OP_PKH; -        if (op2 == 0x3 && a != 0xf) -            return OP_SXTAB16; -        if (op2 == 0x3 && a == 0xf) -            return OP_SXTB16; -        if (op2 == 0x5) -            return OP_SEL; -        break; -    case 0x2: -        if (BIT(op2, 0) == 0) -            return OP_SSAT; -        if (op2 == 0x1) -            return OP_SSAT16; -        if (op2 == 0x3 && a != 0xf) -            return OP_SXTAB; -        if (op2 == 0x3 && a == 0xf) -            return OP_SXTB; -        break; -    case 0x3: -        if (op2 == 0x1) -            return OP_REV; -        if (BIT(op2, 0) == 0) -            return OP_SSAT; -        if (op2 == 0x3 && a != 0xf) -            return OP_SXTAH; -        if (op2 == 0x3 && a == 0xf) -            return OP_SXTH; -        if (op2 == 0x5) -            return OP_REV16; -        break; -    case 0x4: -        if (op2 == 0x3 && a != 0xf) -            return OP_UXTAB16; -        if (op2 == 0x3 && a == 0xf) -            return OP_UXTB16; -        break; -    case 0x6: -        if (BIT(op2, 0) == 0) -            return OP_USAT; -        if (op2 == 0x1) -            return OP_USAT16; -        if (op2 == 0x3 && a != 0xf) -            return OP_UXTAB; -        if (op2 == 0x3 && a == 0xf) -            return OP_UXTB; -        break; -    case 0x7: -        if (BIT(op2, 0) == 0) -            return OP_USAT; -        if (op2 == 0x3 && a != 0xf) -            return OP_UXTAH; -        if (op2 == 0x3 && a == 0xf) -            return OP_UXTH; -        if (op2 == 0x5) -            return OP_REVSH; -        break; -    default: -        break; -    } - -    return OP_UNDEFINED; -} - -Opcode ARM_Disasm::DecodeMUL(u32 insn) { -    u8 bit24 = (insn >> 24) & 0x1; -    if (bit24 != 0) { -        // This is an unexpected bit pattern.  Create an undefined -        // instruction in case this is ever executed. -        return OP_UNDEFINED; -    } -    u8 bit23 = (insn >> 23) & 0x1; -    u8 bit22_U = (insn >> 22) & 0x1; -    u8 bit21_A = (insn >> 21) & 0x1; -    if (bit23 == 0) { -        // 32-bit multiply -        if (bit22_U != 0) { -            // This is an unexpected bit pattern.  Create an undefined -            // instruction in case this is ever executed. -            return OP_UNDEFINED; -        } -        if (bit21_A == 0) -            return OP_MUL; -        return OP_MLA; -    } -    // 64-bit multiply -    if (bit22_U == 0) { -        // Unsigned multiply long -        if (bit21_A == 0) -            return OP_UMULL; -        return OP_UMLAL; -    } -    // Signed multiply long -    if (bit21_A == 0) -        return OP_SMULL; -    return OP_SMLAL; -} - -Opcode ARM_Disasm::DecodeMSRImmAndHints(u32 insn) { -    u32 op = BIT(insn, 22); -    u32 op1 = BITS(insn, 16, 19); -    u32 op2 = BITS(insn, 0, 7); - -    if (op == 0 && op1 == 0) { -        switch (op2) { -        case 0x0: -            return OP_NOP; -        case 0x1: -            return OP_YIELD; -        case 0x2: -            return OP_WFE; -        case 0x3: -            return OP_WFI; -        case 0x4: -            return OP_SEV; -        default: -            return OP_UNDEFINED; -        } -    } - -    return OP_MSR; -} - -Opcode ARM_Disasm::DecodeMediaMulDiv(u32 insn) { -    u32 op1 = BITS(insn, 20, 22); -    u32 op2_h = BITS(insn, 6, 7); -    u32 a = BITS(insn, 12, 15); - -    switch (op1) { -    case 0x0: -        if (op2_h == 0x0) { -            if (a != 0xf) -                return OP_SMLAD; -            else -                return OP_SMUAD; -        } else if (op2_h == 0x1) { -            if (a != 0xf) -                return OP_SMLSD; -            else -                return OP_SMUSD; -        } -        break; -    case 0x4: -        if (op2_h == 0x0) -            return OP_SMLALD; -        else if (op2_h == 0x1) -            return OP_SMLSLD; -        break; -    case 0x5: -        if (op2_h == 0x0) { -            if (a != 0xf) -                return OP_SMMLA; -            else -                return OP_SMMUL; -        } else if (op2_h == 0x3) { -            return OP_SMMLS; -        } -        break; -    default: -        break; -    } - -    return OP_UNDEFINED; -} - -Opcode ARM_Disasm::DecodeMedia(u32 insn) { -    u32 op1 = BITS(insn, 20, 24); -    u32 rd = BITS(insn, 12, 15); -    u32 op2 = BITS(insn, 5, 7); - -    switch (BITS(op1, 3, 4)) { -    case 0x0: -        // unsigned and signed parallel addition and subtraction -        return DecodeParallelAddSub(insn); -    case 0x1: -        // Packing, unpacking, saturation, and reversal -        return DecodePackingSaturationReversal(insn); -    case 0x2: -        // Signed multiply, signed and unsigned divide -        return DecodeMediaMulDiv(insn); -    case 0x3: -        if (op2 == 0 && rd == 0xf) -            return OP_USAD8; -        if (op2 == 0 && rd != 0xf) -            return OP_USADA8; -        break; -    default: -        break; -    } - -    return OP_UNDEFINED; -} - -Opcode ARM_Disasm::DecodeLDRH(u32 insn) { -    u8 is_load = (insn >> 20) & 0x1; -    u8 bits_65 = (insn >> 5) & 0x3; -    if (is_load) { -        if (bits_65 == 0x1) { -            // Load unsigned halfword -            return OP_LDRH; -        } else if (bits_65 == 0x2) { -            // Load signed byte -            return OP_LDRSB; -        } -        // Signed halfword -        if (bits_65 != 0x3) { -            // This is an unexpected bit pattern.  Create an undefined -            // instruction in case this is ever executed. -            return OP_UNDEFINED; -        } -        // Load signed halfword -        return OP_LDRSH; -    } -    // Store halfword -    if (bits_65 != 0x1) { -        // This is an unexpected bit pattern.  Create an undefined -        // instruction in case this is ever executed. -        return OP_UNDEFINED; -    } -    // Store halfword -    return OP_STRH; -} - -Opcode ARM_Disasm::DecodeALU(u32 insn) { -    u8 is_immed = (insn >> 25) & 0x1; -    u8 opcode = (insn >> 21) & 0xf; -    u8 bit_s = (insn >> 20) & 1; -    u8 shift_is_reg = (insn >> 4) & 1; -    u8 bit7 = (insn >> 7) & 1; -    if (!is_immed && shift_is_reg && (bit7 != 0)) { -        // This is an unexpected bit pattern.  Create an undefined -        // instruction in case this is ever executed. -        return OP_UNDEFINED; -    } -    switch (opcode) { -    case 0x0: -        return OP_AND; -    case 0x1: -        return OP_EOR; -    case 0x2: -        return OP_SUB; -    case 0x3: -        return OP_RSB; -    case 0x4: -        return OP_ADD; -    case 0x5: -        return OP_ADC; -    case 0x6: -        return OP_SBC; -    case 0x7: -        return OP_RSC; -    case 0x8: -        if (bit_s) -            return OP_TST; -        return OP_MRS; -    case 0x9: -        if (bit_s) -            return OP_TEQ; -        return OP_MSR; -    case 0xa: -        if (bit_s) -            return OP_CMP; -        return OP_MRS; -    case 0xb: -        if (bit_s) -            return OP_CMN; -        return OP_MSR; -    case 0xc: -        return OP_ORR; -    case 0xd: -        return OP_MOV; -    case 0xe: -        return OP_BIC; -    case 0xf: -        return OP_MVN; -    } -    // Unreachable -    return OP_INVALID; -} diff --git a/src/core/arm/disassembler/arm_disasm.h b/src/core/arm/disassembler/arm_disasm.h deleted file mode 100644 index 300e228ed..000000000 --- a/src/core/arm/disassembler/arm_disasm.h +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2006 The Android Open Source Project - -#pragma once - -#include <string> -#include "common/common_types.h" - -// Note: this list of opcodes must match the list used to initialize -// the opflags[] array in opcode.cpp. -enum Opcode { -    OP_INVALID, -    OP_UNDEFINED, -    OP_ADC, -    OP_ADD, -    OP_AND, -    OP_B, -    OP_BL, -    OP_BIC, -    OP_BKPT, -    OP_BLX, -    OP_BX, -    OP_CDP, -    OP_CLREX, -    OP_CLZ, -    OP_CMN, -    OP_CMP, -    OP_EOR, -    OP_LDC, -    OP_LDM, -    OP_LDR, -    OP_LDRB, -    OP_LDRBT, -    OP_LDREX, -    OP_LDREXB, -    OP_LDREXD, -    OP_LDREXH, -    OP_LDRH, -    OP_LDRSB, -    OP_LDRSH, -    OP_LDRT, -    OP_MCR, -    OP_MLA, -    OP_MOV, -    OP_MRC, -    OP_MRS, -    OP_MSR, -    OP_MUL, -    OP_MVN, -    OP_NOP, -    OP_ORR, -    OP_PKH, -    OP_PLD, -    OP_QADD16, -    OP_QADD8, -    OP_QASX, -    OP_QSAX, -    OP_QSUB16, -    OP_QSUB8, -    OP_REV, -    OP_REV16, -    OP_REVSH, -    OP_RSB, -    OP_RSC, -    OP_SADD16, -    OP_SADD8, -    OP_SASX, -    OP_SBC, -    OP_SEL, -    OP_SEV, -    OP_SHADD16, -    OP_SHADD8, -    OP_SHASX, -    OP_SHSAX, -    OP_SHSUB16, -    OP_SHSUB8, -    OP_SMLAD, -    OP_SMLAL, -    OP_SMLALD, -    OP_SMLSD, -    OP_SMLSLD, -    OP_SMMLA, -    OP_SMMLS, -    OP_SMMUL, -    OP_SMUAD, -    OP_SMULL, -    OP_SMUSD, -    OP_SSAT, -    OP_SSAT16, -    OP_SSAX, -    OP_SSUB16, -    OP_SSUB8, -    OP_STC, -    OP_STM, -    OP_STR, -    OP_STRB, -    OP_STRBT, -    OP_STREX, -    OP_STREXB, -    OP_STREXD, -    OP_STREXH, -    OP_STRH, -    OP_STRT, -    OP_SUB, -    OP_SWI, -    OP_SWP, -    OP_SWPB, -    OP_SXTAB, -    OP_SXTAB16, -    OP_SXTAH, -    OP_SXTB, -    OP_SXTB16, -    OP_SXTH, -    OP_TEQ, -    OP_TST, -    OP_UADD16, -    OP_UADD8, -    OP_UASX, -    OP_UHADD16, -    OP_UHADD8, -    OP_UHASX, -    OP_UHSAX, -    OP_UHSUB16, -    OP_UHSUB8, -    OP_UMLAL, -    OP_UMULL, -    OP_UQADD16, -    OP_UQADD8, -    OP_UQASX, -    OP_UQSAX, -    OP_UQSUB16, -    OP_UQSUB8, -    OP_USAD8, -    OP_USADA8, -    OP_USAT, -    OP_USAT16, -    OP_USAX, -    OP_USUB16, -    OP_USUB8, -    OP_UXTAB, -    OP_UXTAB16, -    OP_UXTAH, -    OP_UXTB, -    OP_UXTB16, -    OP_UXTH, -    OP_WFE, -    OP_WFI, -    OP_YIELD, - -    // Define thumb opcodes -    OP_THUMB_UNDEFINED, -    OP_THUMB_ADC, -    OP_THUMB_ADD, -    OP_THUMB_AND, -    OP_THUMB_ASR, -    OP_THUMB_B, -    OP_THUMB_BIC, -    OP_THUMB_BKPT, -    OP_THUMB_BL, -    OP_THUMB_BLX, -    OP_THUMB_BX, -    OP_THUMB_CMN, -    OP_THUMB_CMP, -    OP_THUMB_EOR, -    OP_THUMB_LDMIA, -    OP_THUMB_LDR, -    OP_THUMB_LDRB, -    OP_THUMB_LDRH, -    OP_THUMB_LDRSB, -    OP_THUMB_LDRSH, -    OP_THUMB_LSL, -    OP_THUMB_LSR, -    OP_THUMB_MOV, -    OP_THUMB_MUL, -    OP_THUMB_MVN, -    OP_THUMB_NEG, -    OP_THUMB_ORR, -    OP_THUMB_POP, -    OP_THUMB_PUSH, -    OP_THUMB_ROR, -    OP_THUMB_SBC, -    OP_THUMB_STMIA, -    OP_THUMB_STR, -    OP_THUMB_STRB, -    OP_THUMB_STRH, -    OP_THUMB_SUB, -    OP_THUMB_SWI, -    OP_THUMB_TST, - -    OP_END // must be last -}; - -class ARM_Disasm { -public: -    static std::string Disassemble(u32 addr, u32 insn); -    static Opcode Decode(u32 insn); - -private: -    static Opcode Decode00(u32 insn); -    static Opcode Decode01(u32 insn); -    static Opcode Decode10(u32 insn); -    static Opcode Decode11(u32 insn); -    static Opcode DecodeSyncPrimitive(u32 insn); -    static Opcode DecodeParallelAddSub(u32 insn); -    static Opcode DecodePackingSaturationReversal(u32 insn); -    static Opcode DecodeMUL(u32 insn); -    static Opcode DecodeMSRImmAndHints(u32 insn); -    static Opcode DecodeMediaMulDiv(u32 insn); -    static Opcode DecodeMedia(u32 insn); -    static Opcode DecodeLDRH(u32 insn); -    static Opcode DecodeALU(u32 insn); - -    static std::string DisassembleALU(Opcode opcode, u32 insn); -    static std::string DisassembleBranch(u32 addr, Opcode opcode, u32 insn); -    static std::string DisassembleBX(u32 insn); -    static std::string DisassembleBKPT(u32 insn); -    static std::string DisassembleCLZ(u32 insn); -    static std::string DisassembleMediaMulDiv(Opcode opcode, u32 insn); -    static std::string DisassembleMemblock(Opcode opcode, u32 insn); -    static std::string DisassembleMem(u32 insn); -    static std::string DisassembleMemHalf(u32 insn); -    static std::string DisassembleMCR(Opcode opcode, u32 insn); -    static std::string DisassembleMLA(Opcode opcode, u32 insn); -    static std::string DisassembleUMLAL(Opcode opcode, u32 insn); -    static std::string DisassembleMUL(Opcode opcode, u32 insn); -    static std::string DisassembleMRS(u32 insn); -    static std::string DisassembleMSR(u32 insn); -    static std::string DisassembleNoOperands(Opcode opcode, u32 insn); -    static std::string DisassembleParallelAddSub(Opcode opcode, u32 insn); -    static std::string DisassemblePKH(u32 insn); -    static std::string DisassemblePLD(u32 insn); -    static std::string DisassembleREV(Opcode opcode, u32 insn); -    static std::string DisassembleREX(Opcode opcode, u32 insn); -    static std::string DisassembleSAT(Opcode opcode, u32 insn); -    static std::string DisassembleSEL(u32 insn); -    static std::string DisassembleSWI(u32 insn); -    static std::string DisassembleSWP(Opcode opcode, u32 insn); -    static std::string DisassembleXT(Opcode opcode, u32 insn); -}; diff --git a/src/core/arm/disassembler/load_symbol_map.cpp b/src/core/arm/disassembler/load_symbol_map.cpp deleted file mode 100644 index 6863c103a..000000000 --- a/src/core/arm/disassembler/load_symbol_map.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <sstream> -#include <string> -#include <vector> -#include "common/file_util.h" -#include "common/symbols.h" -#include "core/arm/disassembler/load_symbol_map.h" - -/* - * Loads a symbol map file for use with the disassembler - * @param filename String filename path of symbol map file - */ -void LoadSymbolMap(std::string filename) { -    std::ifstream infile(filename); - -    std::string address_str, function_name, line; -    u32 size; - -    while (std::getline(infile, line)) { -        std::istringstream iss(line); -        if (!(iss >> address_str >> size >> function_name)) { -            break; // Error parsing -        } -        u32 address = std::stoul(address_str, nullptr, 16); - -        Symbols::Add(address, function_name, size, 2); -    } -} diff --git a/src/core/arm/disassembler/load_symbol_map.h b/src/core/arm/disassembler/load_symbol_map.h deleted file mode 100644 index d28c551c3..000000000 --- a/src/core/arm/disassembler/load_symbol_map.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <string> - -/* - * Loads a symbol map file for use with the disassembler - * @param filename String filename path of symbol map file - */ -void LoadSymbolMap(std::string filename); diff --git a/src/core/arm/dyncom/arm_dyncom_dec.cpp b/src/core/arm/dyncom/arm_dyncom_dec.cpp index 64dcaae08..dcfcd6561 100644 --- a/src/core/arm/dyncom/arm_dyncom_dec.cpp +++ b/src/core/arm/dyncom/arm_dyncom_dec.cpp @@ -415,7 +415,7 @@ const InstructionSetEncodingItem arm_exclusion_code[] = {  };  // clang-format on -ARMDecodeStatus DecodeARMInstruction(u32 instr, s32* idx) { +ARMDecodeStatus DecodeARMInstruction(u32 instr, int* idx) {      int n = 0;      int base = 0;      int instr_slots = sizeof(arm_instruction) / sizeof(InstructionSetEncodingItem); diff --git a/src/core/arm/dyncom/arm_dyncom_dec.h b/src/core/arm/dyncom/arm_dyncom_dec.h index 2fb7ac37c..1dcf7ecd1 100644 --- a/src/core/arm/dyncom/arm_dyncom_dec.h +++ b/src/core/arm/dyncom/arm_dyncom_dec.h @@ -8,7 +8,7 @@  enum class ARMDecodeStatus { SUCCESS, FAILURE }; -ARMDecodeStatus DecodeARMInstruction(u32 instr, s32* idx); +ARMDecodeStatus DecodeARMInstruction(u32 instr, int* idx);  struct InstructionSetEncodingItem {      const char* name; diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index 273bc8167..f4fbb8d04 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -5,11 +5,11 @@  #define CITRA_IGNORE_EXIT(x)  #include <algorithm> +#include <cinttypes>  #include <cstdio>  #include "common/common_types.h"  #include "common/logging/log.h"  #include "common/microprofile.h" -#include "core/arm/disassembler/arm_disasm.h"  #include "core/arm/dyncom/arm_dyncom_dec.h"  #include "core/arm/dyncom/arm_dyncom_interpreter.h"  #include "core/arm/dyncom/arm_dyncom_run.h" @@ -808,8 +808,8 @@ MICROPROFILE_DEFINE(DynCom_Decode, "DynCom", "Decode", MP_RGB(255, 64, 64));  static unsigned int InterpreterTranslateInstruction(const ARMul_State* cpu, const u32 phys_addr,                                                      ARM_INST_PTR& inst_base) { -    unsigned int inst_size = 4; -    unsigned int inst = Memory::Read32(phys_addr & 0xFFFFFFFC); +    u32 inst_size = 4; +    u32 inst = Memory::Read32(phys_addr & 0xFFFFFFFC);      // If we are in Thumb mode, we'll translate one Thumb instruction to the corresponding ARM      // instruction @@ -827,11 +827,10 @@ static unsigned int InterpreterTranslateInstruction(const ARMul_State* cpu, cons      int idx;      if (DecodeARMInstruction(inst, &idx) == ARMDecodeStatus::FAILURE) { -        std::string disasm = ARM_Disasm::Disassemble(phys_addr, inst); -        LOG_ERROR(Core_ARM11, "Decode failure.\tPC : [0x%x]\tInstruction : %s [%x]", phys_addr, -                  disasm.c_str(), inst); -        LOG_ERROR(Core_ARM11, "cpsr=0x%x, cpu->TFlag=%d, r15=0x%x", cpu->Cpsr, cpu->TFlag, -                  cpu->Reg[15]); +        LOG_ERROR(Core_ARM11, "Decode failure.\tPC: [0x%08" PRIX32 "]\tInstruction: %08" PRIX32, +                  phys_addr, inst); +        LOG_ERROR(Core_ARM11, "cpsr=0x%" PRIX32 ", cpu->TFlag=%d, r15=0x%08" PRIX32, cpu->Cpsr, +                  cpu->TFlag, cpu->Reg[15]);          CITRA_IGNORE_EXIT(-1);      }      inst_base = arm_instruction_trans[idx](inst, idx); diff --git a/src/core/arm/skyeye_common/vfp/vfp_helper.h b/src/core/arm/skyeye_common/vfp/vfp_helper.h index 5e14345ce..1eba71b48 100644 --- a/src/core/arm/skyeye_common/vfp/vfp_helper.h +++ b/src/core/arm/skyeye_common/vfp/vfp_helper.h @@ -291,7 +291,7 @@ inline s32 vfp_single_pack(const vfp_single* s) {      return (s32)val;  } -u32 vfp_single_normaliseround(ARMul_State* state, int sd, vfp_single* vs, u32 fpscr, +u32 vfp_single_normaliseround(ARMul_State* state, int sd, vfp_single* vs, u32 fpscr, u32 exceptions,                                const char* func);  // Double-precision @@ -429,5 +429,5 @@ inline u32 fls(u32 x) {  u32 vfp_double_multiply(vfp_double* vdd, vfp_double* vdn, vfp_double* vdm, u32 fpscr);  u32 vfp_double_add(vfp_double* vdd, vfp_double* vdn, vfp_double* vdm, u32 fpscr); -u32 vfp_double_normaliseround(ARMul_State* state, int dd, vfp_double* vd, u32 fpscr, +u32 vfp_double_normaliseround(ARMul_State* state, int dd, vfp_double* vd, u32 fpscr, u32 exceptions,                                const char* func); diff --git a/src/core/arm/skyeye_common/vfp/vfpdouble.cpp b/src/core/arm/skyeye_common/vfp/vfpdouble.cpp index 2886f351f..7b035f56a 100644 --- a/src/core/arm/skyeye_common/vfp/vfpdouble.cpp +++ b/src/core/arm/skyeye_common/vfp/vfpdouble.cpp @@ -82,11 +82,10 @@ static void vfp_double_normalise_denormal(struct vfp_double* vd) {  }  u32 vfp_double_normaliseround(ARMul_State* state, int dd, struct vfp_double* vd, u32 fpscr, -                              const char* func) { +                              u32 exceptions, const char* func) {      u64 significand, incr;      int exponent, shift, underflow;      u32 rmode; -    u32 exceptions = 0;      vfp_double_dump("pack: in", vd); @@ -360,7 +359,8 @@ static u32 vfp_double_fsqrt(ARMul_State* state, int dd, int unused, int dm, u32      }      vdd.significand = vfp_shiftright64jamming(vdd.significand, 1); -    exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fsqrt"); +    exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, 0, "fsqrt"); +      return exceptions;  } @@ -492,8 +492,7 @@ static u32 vfp_double_fcvts(ARMul_State* state, int sd, int unused, int dm, u32      else          vsd.exponent = vdm.exponent - (1023 - 127); -    exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fcvts"); -    return exceptions; +    return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fcvts");  pack_nan:      vfp_put_float(state, vfp_single_pack(&vsd), sd); @@ -502,7 +501,6 @@ pack_nan:  static u32 vfp_double_fuito(ARMul_State* state, int dd, int unused, int dm, u32 fpscr) {      struct vfp_double vdm; -    u32 exceptions = 0;      u32 m = vfp_get_float(state, dm);      LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); @@ -510,13 +508,11 @@ static u32 vfp_double_fuito(ARMul_State* state, int dd, int unused, int dm, u32      vdm.exponent = 1023 + 63 - 1;      vdm.significand = (u64)m; -    exceptions |= vfp_double_normaliseround(state, dd, &vdm, fpscr, "fuito"); -    return exceptions; +    return vfp_double_normaliseround(state, dd, &vdm, fpscr, 0, "fuito");  }  static u32 vfp_double_fsito(ARMul_State* state, int dd, int unused, int dm, u32 fpscr) {      struct vfp_double vdm; -    u32 exceptions = 0;      u32 m = vfp_get_float(state, dm);      LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); @@ -524,8 +520,7 @@ static u32 vfp_double_fsito(ARMul_State* state, int dd, int unused, int dm, u32      vdm.exponent = 1023 + 63 - 1;      vdm.significand = vdm.sign ? (~m + 1) : m; -    exceptions |= vfp_double_normaliseround(state, dd, &vdm, fpscr, "fsito"); -    return exceptions; +    return vfp_double_normaliseround(state, dd, &vdm, fpscr, 0, "fsito");  }  static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32 fpscr) { @@ -912,8 +907,7 @@ static u32 vfp_double_multiply_accumulate(ARMul_State* state, int dd, int dn, in      exceptions |= vfp_double_add(&vdd, &vdn, &vdp, fpscr); -    exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, func); -    return exceptions; +    return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, func);  }  /* @@ -970,9 +964,7 @@ static u32 vfp_double_fmul(ARMul_State* state, int dd, int dn, int dm, u32 fpscr          vfp_double_normalise_denormal(&vdm);      exceptions |= vfp_double_multiply(&vdd, &vdn, &vdm, fpscr); - -    exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fmul"); -    return exceptions; +    return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fmul");  }  /* @@ -994,8 +986,7 @@ static u32 vfp_double_fnmul(ARMul_State* state, int dd, int dn, int dm, u32 fpsc      exceptions |= vfp_double_multiply(&vdd, &vdn, &vdm, fpscr);      vdd.sign = vfp_sign_negate(vdd.sign); -    exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fnmul"); -    return exceptions; +    return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fnmul");  }  /* @@ -1016,8 +1007,7 @@ static u32 vfp_double_fadd(ARMul_State* state, int dd, int dn, int dm, u32 fpscr      exceptions |= vfp_double_add(&vdd, &vdn, &vdm, fpscr); -    exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fadd"); -    return exceptions; +    return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fadd");  }  /* @@ -1043,8 +1033,7 @@ static u32 vfp_double_fsub(ARMul_State* state, int dd, int dn, int dm, u32 fpscr      exceptions |= vfp_double_add(&vdd, &vdn, &vdm, fpscr); -    exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fsub"); -    return exceptions; +    return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fsub");  }  /* @@ -1126,9 +1115,7 @@ static u32 vfp_double_fdiv(ARMul_State* state, int dd, int dn, int dm, u32 fpscr          }          vdd.significand |= (reml != 0);      } - -    exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fdiv"); -    return exceptions; +    return vfp_double_normaliseround(state, dd, &vdd, fpscr, 0, "fdiv");  vdn_nan:      exceptions |= vfp_propagate_nan(&vdd, &vdn, &vdm, fpscr); @@ -1154,8 +1141,7 @@ infinity:  invalid:      vfp_put_double(state, vfp_double_pack(&vfp_double_default_qnan), dd); -    exceptions |= FPSCR_IOC; -    return exceptions; +    return FPSCR_IOC;  }  static struct op fops[] = { @@ -1230,7 +1216,7 @@ u32 vfp_double_cpdo(ARMul_State* state, u32 inst, u32 fpscr) {          except = fop->fn(state, dest, dn, dm, fpscr);          LOG_TRACE(Core_ARM11, "VFP: itr%d: exceptions=%08x", vecitr >> FPSCR_LENGTH_BIT, except); -        exceptions |= except; +        exceptions |= except & ~VFP_NAN_FLAG;          /*           * CHECK: It appears to be undefined whether we stop when diff --git a/src/core/arm/skyeye_common/vfp/vfpsingle.cpp b/src/core/arm/skyeye_common/vfp/vfpsingle.cpp index 1590d89a4..6b4cb8efa 100644 --- a/src/core/arm/skyeye_common/vfp/vfpsingle.cpp +++ b/src/core/arm/skyeye_common/vfp/vfpsingle.cpp @@ -83,10 +83,9 @@ static void vfp_single_normalise_denormal(struct vfp_single* vs) {  }  u32 vfp_single_normaliseround(ARMul_State* state, int sd, struct vfp_single* vs, u32 fpscr, -                              const char* func) { +                              u32 exceptions, const char* func) {      u32 significand, incr, rmode;      int exponent, shift, underflow; -    u32 exceptions = 0;      vfp_single_dump("pack: in", vs); @@ -394,7 +393,8 @@ static u32 vfp_single_fsqrt(ARMul_State* state, int sd, int unused, s32 m, u32 f      }      vsd.significand = vfp_shiftright32jamming(vsd.significand, 1); -    exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fsqrt"); +    exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, 0, "fsqrt"); +      return exceptions;  } @@ -515,8 +515,7 @@ static u32 vfp_single_fcvtd(ARMul_State* state, int dd, int unused, s32 m, u32 f      else          vdd.exponent = vsm.exponent + (1023 - 127); -    exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fcvtd"); -    return exceptions; +    return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fcvtd");  pack_nan:      vfp_put_double(state, vfp_double_pack(&vdd), dd); @@ -525,26 +524,22 @@ pack_nan:  static u32 vfp_single_fuito(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr) {      struct vfp_single vs; -    u32 exceptions = 0;      vs.sign = 0;      vs.exponent = 127 + 31 - 1;      vs.significand = (u32)m; -    exceptions |= vfp_single_normaliseround(state, sd, &vs, fpscr, "fuito"); -    return exceptions; +    return vfp_single_normaliseround(state, sd, &vs, fpscr, 0, "fuito");  }  static u32 vfp_single_fsito(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr) {      struct vfp_single vs; -    u32 exceptions = 0;      vs.sign = (m & 0x80000000) >> 16;      vs.exponent = 127 + 31 - 1;      vs.significand = vs.sign ? -m : m; -    exceptions |= vfp_single_normaliseround(state, sd, &vs, fpscr, "fsito"); -    return exceptions; +    return vfp_single_normaliseround(state, sd, &vs, fpscr, 0, "fsito");  }  static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr) { @@ -936,8 +931,7 @@ static u32 vfp_single_multiply_accumulate(ARMul_State* state, int sd, int sn, s3      exceptions |= vfp_single_add(&vsd, &vsn, &vsp, fpscr); -    exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, func); -    return exceptions; +    return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, func);  }  /* @@ -948,10 +942,8 @@ static u32 vfp_single_multiply_accumulate(ARMul_State* state, int sd, int sn, s3   * sd = sd + (sn * sm)   */  static u32 vfp_single_fmac(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr) { -    u32 exceptions = 0;      LOG_TRACE(Core_ARM11, "s%u = %08x", sn, sd); -    exceptions |= vfp_single_multiply_accumulate(state, sd, sn, m, fpscr, 0, "fmac"); -    return exceptions; +    return vfp_single_multiply_accumulate(state, sd, sn, m, fpscr, 0, "fmac");  }  /* @@ -999,9 +991,7 @@ static u32 vfp_single_fmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)          vfp_single_normalise_denormal(&vsm);      exceptions |= vfp_single_multiply(&vsd, &vsn, &vsm, fpscr); - -    exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fmul"); -    return exceptions; +    return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fmul");  }  /* @@ -1024,9 +1014,7 @@ static u32 vfp_single_fnmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr      exceptions |= vfp_single_multiply(&vsd, &vsn, &vsm, fpscr);      vsd.sign = vfp_sign_negate(vsd.sign); - -    exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fnmul"); -    return exceptions; +    return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fnmul");  }  /* @@ -1052,8 +1040,7 @@ static u32 vfp_single_fadd(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)      exceptions |= vfp_single_add(&vsd, &vsn, &vsm, fpscr); -    exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fadd"); -    return exceptions; +    return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fadd");  }  /* @@ -1062,12 +1049,22 @@ static u32 vfp_single_fadd(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)  static u32 vfp_single_fsub(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr) {      LOG_TRACE(Core_ARM11, "s%u = %08x", sn, sd);      /* -     * Subtraction is addition with one sign inverted. +     * Subtraction is addition with one sign inverted. Unpack the second operand to perform FTZ if +     * necessary, we can't let fadd do this because a denormal in m might get flushed to +0 in FTZ +     * mode, and the resulting sign of 0 OP +0 differs between fadd and fsub. We do not need to do +     * this for n because +0 OP 0 is always +0 for both fadd and fsub.       */ +    struct vfp_single vsm; +    u32 exceptions = vfp_single_unpack(&vsm, m, fpscr); +    if (exceptions & FPSCR_IDC) { +        // The value was flushed to zero, re-pack it. +        m = vfp_single_pack(&vsm); +    } +      if (m != 0x7FC00000) // Only negate if m isn't NaN.          m = vfp_single_packed_negate(m); -    return vfp_single_fadd(state, sd, sn, m, fpscr); +    return vfp_single_fadd(state, sd, sn, m, fpscr) | exceptions;  }  /* @@ -1148,8 +1145,7 @@ static u32 vfp_single_fdiv(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)      if ((vsd.significand & 0x3f) == 0)          vsd.significand |= ((u64)vsm.significand * vsd.significand != (u64)vsn.significand << 32); -    exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fdiv"); -    return exceptions; +    return vfp_single_normaliseround(state, sd, &vsd, fpscr, 0, "fdiv");  vsn_nan:      exceptions |= vfp_propagate_nan(&vsd, &vsn, &vsm, fpscr); @@ -1175,8 +1171,7 @@ infinity:  invalid:      vfp_put_float(state, vfp_single_pack(&vfp_single_default_qnan), sd); -    exceptions |= FPSCR_IOC; -    return exceptions; +    return FPSCR_IOC;  }  static struct op fops[] = { @@ -1246,7 +1241,7 @@ u32 vfp_single_cpdo(ARMul_State* state, u32 inst, u32 fpscr) {          except = fop->fn(state, dest, sn, m, fpscr);          LOG_TRACE(Core_ARM11, "itr%d: exceptions=%08x", vecitr >> FPSCR_LENGTH_BIT, except); -        exceptions |= except; +        exceptions |= except & ~VFP_NAN_FLAG;          /*           * CHECK: It appears to be undefined whether we stop when diff --git a/src/core/core.cpp b/src/core/core.cpp index 140ff6451..881f1e93c 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -13,11 +13,11 @@  #include "core/core_timing.h"  #include "core/gdbstub/gdbstub.h"  #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/memory.h"  #include "core/hle/kernel/thread.h"  #include "core/hle/service/service.h"  #include "core/hw/hw.h"  #include "core/loader/loader.h" +#include "core/memory_setup.h"  #include "core/settings.h"  #include "video_core/video_core.h" @@ -123,7 +123,8 @@ void System::Reschedule() {  }  System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { -    Memory::Init(); +    Memory::InitMemoryMap(); +    LOG_DEBUG(HW_Memory, "initialized OK");      if (Settings::values.use_cpu_jit) {          cpu_core = std::make_unique<ARM_Dynarmic>(USER32MODE); diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp index 33c165197..8250a90b5 100644 --- a/src/core/hle/kernel/memory.cpp +++ b/src/core/hle/kernel/memory.cpp @@ -2,11 +2,13 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <cinttypes>  #include <map>  #include <memory>  #include <utility>  #include <vector>  #include "audio_core/audio_core.h" +#include "common/assert.h"  #include "common/common_types.h"  #include "common/logging/log.h"  #include "core/hle/config_mem.h" @@ -92,52 +94,96 @@ MemoryRegionInfo* GetMemoryRegion(MemoryRegion region) {          UNREACHABLE();      }  } -} - -namespace Memory { -namespace { +std::array<u8, Memory::VRAM_SIZE> vram; +std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram; + +void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping) { +    using namespace Memory; + +    struct MemoryArea { +        VAddr vaddr_base; +        PAddr paddr_base; +        u32 size; +    }; + +    // The order of entries in this array is important. The VRAM and IO VAddr ranges overlap, and +    // VRAM must be tried first. +    static constexpr MemoryArea memory_areas[] = { +        {VRAM_VADDR, VRAM_PADDR, VRAM_SIZE}, +        {IO_AREA_VADDR, IO_AREA_PADDR, IO_AREA_SIZE}, +        {DSP_RAM_VADDR, DSP_RAM_PADDR, DSP_RAM_SIZE}, +        {N3DS_EXTRA_RAM_VADDR, N3DS_EXTRA_RAM_PADDR, N3DS_EXTRA_RAM_SIZE - 0x20000}, +    }; + +    VAddr mapping_limit = mapping.address + mapping.size; +    if (mapping_limit < mapping.address) { +        LOG_CRITICAL(Loader, "Mapping size overflowed: address=0x%08" PRIX32 " size=0x%" PRIX32, +                     mapping.address, mapping.size); +        return; +    } -struct MemoryArea { -    u32 base; -    u32 size; -    const char* name; -}; +    auto area = +        std::find_if(std::begin(memory_areas), std::end(memory_areas), [&](const auto& area) { +            return mapping.address >= area.vaddr_base && +                   mapping_limit <= area.vaddr_base + area.size; +        }); +    if (area == std::end(memory_areas)) { +        LOG_ERROR(Loader, "Unhandled special mapping: address=0x%08" PRIX32 " size=0x%" PRIX32 +                          " read_only=%d unk_flag=%d", +                  mapping.address, mapping.size, mapping.read_only, mapping.unk_flag); +        return; +    } -// We don't declare the IO regions in here since its handled by other means. -static MemoryArea memory_areas[] = { -    {VRAM_VADDR, VRAM_SIZE, "VRAM"}, // Video memory (VRAM) -}; -} +    u32 offset_into_region = mapping.address - area->vaddr_base; +    if (area->paddr_base == IO_AREA_PADDR) { +        LOG_ERROR(Loader, "MMIO mappings are not supported yet. phys_addr=0x%08" PRIX32, +                  area->paddr_base + offset_into_region); +        return; +    } -void Init() { -    InitMemoryMap(); -    LOG_DEBUG(HW_Memory, "initialized OK"); -} +    // TODO(yuriks): Use GetPhysicalPointer when that becomes independent of the virtual +    // mappings. +    u8* target_pointer = nullptr; +    switch (area->paddr_base) { +    case VRAM_PADDR: +        target_pointer = vram.data(); +        break; +    case DSP_RAM_PADDR: +        target_pointer = AudioCore::GetDspMemory().data(); +        break; +    case N3DS_EXTRA_RAM_PADDR: +        target_pointer = n3ds_extra_ram.data(); +        break; +    default: +        UNREACHABLE(); +    } -void InitLegacyAddressSpace(Kernel::VMManager& address_space) { -    using namespace Kernel; +    // TODO(yuriks): This flag seems to have some other effect, but it's unknown what +    MemoryState memory_state = mapping.unk_flag ? MemoryState::Static : MemoryState::IO; -    for (MemoryArea& area : memory_areas) { -        auto block = std::make_shared<std::vector<u8>>(area.size); -        address_space -            .MapMemoryBlock(area.base, std::move(block), 0, area.size, MemoryState::Private) -            .Unwrap(); -    } +    auto vma = address_space +                   .MapBackingMemory(mapping.address, target_pointer + offset_into_region, +                                     mapping.size, memory_state) +                   .MoveFrom(); +    address_space.Reprotect(vma, +                            mapping.read_only ? VMAPermission::Read : VMAPermission::ReadWrite); +} +void MapSharedPages(VMManager& address_space) {      auto cfg_mem_vma = address_space -                           .MapBackingMemory(CONFIG_MEMORY_VADDR, (u8*)&ConfigMem::config_mem, -                                             CONFIG_MEMORY_SIZE, MemoryState::Shared) +                           .MapBackingMemory(Memory::CONFIG_MEMORY_VADDR, +                                             reinterpret_cast<u8*>(&ConfigMem::config_mem), +                                             Memory::CONFIG_MEMORY_SIZE, MemoryState::Shared)                             .MoveFrom();      address_space.Reprotect(cfg_mem_vma, VMAPermission::Read);      auto shared_page_vma = address_space -                               .MapBackingMemory(SHARED_PAGE_VADDR, (u8*)&SharedPage::shared_page, -                                                 SHARED_PAGE_SIZE, MemoryState::Shared) +                               .MapBackingMemory(Memory::SHARED_PAGE_VADDR, +                                                 reinterpret_cast<u8*>(&SharedPage::shared_page), +                                                 Memory::SHARED_PAGE_SIZE, MemoryState::Shared)                                 .MoveFrom();      address_space.Reprotect(shared_page_vma, VMAPermission::Read); - -    AudioCore::AddAddressSpace(address_space);  } -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/memory.h b/src/core/hle/kernel/memory.h index 4e1856a41..08c1a9989 100644 --- a/src/core/hle/kernel/memory.h +++ b/src/core/hle/kernel/memory.h @@ -23,11 +23,7 @@ struct MemoryRegionInfo {  void MemoryInit(u32 mem_type);  void MemoryShutdown();  MemoryRegionInfo* GetMemoryRegion(MemoryRegion region); -} -namespace Memory { - -void Init(); -void InitLegacyAddressSpace(Kernel::VMManager& address_space); - -} // namespace +void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping); +void MapSharedPages(VMManager& address_space); +} // namespace Kernel diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index ba80fe7f8..32cb25fb7 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -35,7 +35,6 @@ SharedPtr<Process> Process::Create(SharedPtr<CodeSet> code_set) {      process->codeset = std::move(code_set);      process->flags.raw = 0;      process->flags.memory_region.Assign(MemoryRegion::APPLICATION); -    Memory::InitLegacyAddressSpace(process->vm_manager);      return process;  } @@ -78,8 +77,15 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) {              AddressMapping mapping;              mapping.address = descriptor << 12; -            mapping.size = (end_desc << 12) - mapping.address; -            mapping.writable = (descriptor & (1 << 20)) != 0; +            VAddr end_address = end_desc << 12; + +            if (mapping.address < end_address) { +                mapping.size = end_address - mapping.address; +            } else { +                mapping.size = 0; +            } + +            mapping.read_only = (descriptor & (1 << 20)) != 0;              mapping.unk_flag = (end_desc & (1 << 20)) != 0;              address_mappings.push_back(mapping); @@ -88,8 +94,10 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) {              AddressMapping mapping;              mapping.address = descriptor << 12;              mapping.size = Memory::PAGE_SIZE; -            mapping.writable = true; // TODO: Not sure if correct +            mapping.read_only = false;              mapping.unk_flag = false; + +            address_mappings.push_back(mapping);          } else if ((type & 0xFE0) == 0xFC0) { // 0x01FF              // Kernel version              kernel_version = descriptor & 0xFFFF; @@ -131,6 +139,12 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {      misc_memory_used += stack_size;      memory_region->used += stack_size; +    // Map special address mappings +    MapSharedPages(vm_manager); +    for (const auto& mapping : address_mappings) { +        HandleSpecialMapping(vm_manager, mapping); +    } +      vm_manager.LogLayout(Log::Level::Debug);      Kernel::SetupMainThread(codeset->entrypoint, main_thread_priority);  } @@ -138,6 +152,7 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {  VAddr Process::GetLinearHeapAreaAddress() const {      return kernel_version < 0x22C ? Memory::LINEAR_HEAP_VADDR : Memory::NEW_LINEAR_HEAP_VADDR;  } +  VAddr Process::GetLinearHeapBase() const {      return GetLinearHeapAreaAddress() + memory_region->base;  } diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index b566950b0..b52211d2a 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -20,7 +20,7 @@ struct AddressMapping {      // Address and size must be page-aligned      VAddr address;      u32 size; -    bool writable; +    bool read_only;      bool unk_flag;  }; diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 4ddb1bc90..8c8c1ec77 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -4,6 +4,7 @@  #include <algorithm>  #include <array> +#include <cryptopp/osrng.h>  #include <cryptopp/sha.h>  #include "common/file_util.h"  #include "common/logging/log.h" @@ -50,6 +51,7 @@ enum ConfigBlockID {      SoundOutputModeBlockID = 0x00070001,      ConsoleUniqueID1BlockID = 0x00090000,      ConsoleUniqueID2BlockID = 0x00090001, +    ConsoleUniqueID3BlockID = 0x00090002,      UsernameBlockID = 0x000A0000,      BirthdayBlockID = 0x000A0001,      LanguageBlockID = 0x000A0002, @@ -86,7 +88,6 @@ struct ConsoleCountryInfo {  static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exactly 4 bytes");  } -static const u64 CONSOLE_UNIQUE_ID = 0xDEADC0DE;  static const ConsoleModelInfo CONSOLE_MODEL = {NINTENDO_3DS_XL, {0, 0, 0}};  static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN;  static const UsernameBlock CONSOLE_USERNAME_BLOCK = {u"CITRA", 0, 0}; @@ -438,13 +439,22 @@ ResultCode FormatConfig() {      if (!res.IsSuccess())          return res; -    res = CreateConfigInfoBlk(ConsoleUniqueID1BlockID, sizeof(CONSOLE_UNIQUE_ID), 0xE, -                              &CONSOLE_UNIQUE_ID); +    u32 random_number; +    u64 console_id; +    GenerateConsoleUniqueId(random_number, console_id); + +    u64_le console_id_le = console_id; +    res = CreateConfigInfoBlk(ConsoleUniqueID1BlockID, sizeof(console_id_le), 0xE, &console_id_le);      if (!res.IsSuccess())          return res; -    res = CreateConfigInfoBlk(ConsoleUniqueID2BlockID, sizeof(CONSOLE_UNIQUE_ID), 0xE, -                              &CONSOLE_UNIQUE_ID); +    res = CreateConfigInfoBlk(ConsoleUniqueID2BlockID, sizeof(console_id_le), 0xE, &console_id_le); +    if (!res.IsSuccess()) +        return res; + +    u32_le random_number_le = random_number; +    res = CreateConfigInfoBlk(ConsoleUniqueID3BlockID, sizeof(random_number_le), 0xE, +                              &random_number_le);      if (!res.IsSuccess())          return res; @@ -663,5 +673,40 @@ SoundOutputMode GetSoundOutputMode() {      return static_cast<SoundOutputMode>(block);  } +void GenerateConsoleUniqueId(u32& random_number, u64& console_id) { +    CryptoPP::AutoSeededRandomPool rng; +    random_number = rng.GenerateWord32(0, 0xFFFF); +    u64_le local_friend_code_seed; +    rng.GenerateBlock(reinterpret_cast<byte*>(&local_friend_code_seed), +                      sizeof(local_friend_code_seed)); +    console_id = (local_friend_code_seed & 0x3FFFFFFFF) | (static_cast<u64>(random_number) << 48); +} + +ResultCode SetConsoleUniqueId(u32 random_number, u64 console_id) { +    u64_le console_id_le = console_id; +    ResultCode res = +        SetConfigInfoBlock(ConsoleUniqueID1BlockID, sizeof(console_id_le), 0xE, &console_id_le); +    if (!res.IsSuccess()) +        return res; + +    res = SetConfigInfoBlock(ConsoleUniqueID2BlockID, sizeof(console_id_le), 0xE, &console_id_le); +    if (!res.IsSuccess()) +        return res; + +    u32_le random_number_le = random_number; +    res = SetConfigInfoBlock(ConsoleUniqueID3BlockID, sizeof(random_number_le), 0xE, +                             &random_number_le); +    if (!res.IsSuccess()) +        return res; + +    return RESULT_SUCCESS; +} + +u64 GetConsoleUniqueId() { +    u64_le console_id_le; +    GetConfigInfoBlock(ConsoleUniqueID2BlockID, sizeof(console_id_le), 0xE, &console_id_le); +    return console_id_le; +} +  } // namespace CFG  } // namespace Service diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index 618c9647e..1659ebf32 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -342,5 +342,26 @@ void SetSoundOutputMode(SoundOutputMode mode);   */  SoundOutputMode GetSoundOutputMode(); +/** + * Generates a new random console unique id. + * @param random_number a random generated 16bit number stored at 0x90002, used for generating the + * console_id + * @param console_id the randomly created console id + */ +void GenerateConsoleUniqueId(u32& random_number, u64& console_id); + +/** + * Sets the random_number and the  console unique id in the config savegame. + * @param random_number the random_number to set + * @param console_id the console id to set + */ +ResultCode SetConsoleUniqueId(u32 random_number, u64 console_id); + +/** + * Gets the console unique id from config savegame. + * @returns the console unique id + */ +u64 GetConsoleUniqueId(); +  } // namespace CFG  } // namespace Service diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index b19e831fe..64d01cdd7 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -53,30 +53,29 @@ static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::      buttons;  static std::unique_ptr<Input::AnalogDevice> circle_pad; -static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { +DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {      // 30 degree and 60 degree are angular thresholds for directions      constexpr float TAN30 = 0.577350269f;      constexpr float TAN60 = 1 / TAN30;      // a circle pad radius greater than 40 will trigger circle pad direction      constexpr int CIRCLE_PAD_THRESHOLD_SQUARE = 40 * 40; -    PadState state; -    state.hex = 0; +    DirectionState state{false, false, false, false};      if (circle_pad_x * circle_pad_x + circle_pad_y * circle_pad_y > CIRCLE_PAD_THRESHOLD_SQUARE) {          float t = std::abs(static_cast<float>(circle_pad_y) / circle_pad_x);          if (circle_pad_x != 0 && t < TAN60) {              if (circle_pad_x > 0) -                state.circle_right.Assign(1); +                state.right = true;              else -                state.circle_left.Assign(1); +                state.left = true;          }          if (circle_pad_x == 0 || t > TAN30) {              if (circle_pad_y > 0) -                state.circle_up.Assign(1); +                state.up = true;              else -                state.circle_down.Assign(1); +                state.down = true;          }      } @@ -125,7 +124,11 @@ static void UpdatePadCallback(u64 userdata, int cycles_late) {      constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position      s16 circle_pad_x = static_cast<s16>(circle_pad_x_f * MAX_CIRCLEPAD_POS);      s16 circle_pad_y = static_cast<s16>(circle_pad_y_f * MAX_CIRCLEPAD_POS); -    state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex; +    const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y); +    state.circle_up.Assign(direction.up); +    state.circle_down.Assign(direction.down); +    state.circle_left.Assign(direction.left); +    state.circle_right.Assign(direction.right);      mem->pad.current_state.hex = state.hex;      mem->pad.index = next_pad_index; diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index b505cdcd5..1ef972e70 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -176,6 +176,16 @@ ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A);  #undef ASSERT_REG_POSITION  #endif // !defined(_MSC_VER) +struct DirectionState { +    bool up; +    bool down; +    bool left; +    bool right; +}; + +/// Translates analog stick axes to directions. This is exposed for ir_rst module to use. +DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y); +  /**   * HID::GetIPCHandles service function   *  Inputs: diff --git a/src/core/hle/service/ir/extra_hid.cpp b/src/core/hle/service/ir/extra_hid.cpp new file mode 100644 index 000000000..e7acc17a5 --- /dev/null +++ b/src/core/hle/service/ir/extra_hid.cpp @@ -0,0 +1,231 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/alignment.h" +#include "common/bit_field.h" +#include "common/string_util.h" +#include "core/core_timing.h" +#include "core/hle/service/ir/extra_hid.h" +#include "core/settings.h" + +namespace Service { +namespace IR { + +enum class RequestID : u8 { +    /** +     * ConfigureHIDPolling request +     * Starts HID input polling, or changes the polling interval if it is already started. +     *  Inputs: +     *     byte 0: request ID +     *     byte 1: polling interval in ms +     *     byte 2: unknown +     */ +    ConfigureHIDPolling = 1, + +    /** +     * ReadCalibrationData request +     * Reads the calibration data stored in circle pad pro. +     *  Inputs: +     *     byte 0: request ID +     *     byte 1: expected response time in ms? +     *     byte 2-3: data offset (aligned to 0x10) +     *     byte 4-5: data size (aligned to 0x10) +     */ +    ReadCalibrationData = 2, + +    // TODO(wwylele): there are three more request types (id = 3, 4 and 5) +}; + +enum class ResponseID : u8 { + +    /** +     * PollHID response +     * Sends current HID status +     *  Output: +     *     byte 0: response ID +     *     byte 1-3: Right circle pad position. This three bytes are two little-endian 12-bit +     *         fields. The first one is for x-axis and the second one is for y-axis. +     *     byte 4: bit[0:4] battery level; bit[5] ZL button; bit[6] ZR button; bit[7] R button +     *         Note that for the three button fields, the bit is set when the button is NOT pressed. +     *     byte 5: unknown +     */ +    PollHID = 0x10, + +    /** +     * ReadCalibrationData response +     * Sends the calibration data reads from circle pad pro. +     *  Output: +     *     byte 0: resonse ID +     *     byte 1-2: data offset (aligned to 0x10) +     *     byte 3-4: data size (aligned to 0x10) +     *     byte 5-...: calibration data +     */ +    ReadCalibrationData = 0x11, +}; + +ExtraHID::ExtraHID(SendFunc send_func) : IRDevice(send_func) { +    LoadInputDevices(); + +    // The data below was retrieved from a New 3DS +    // TODO(wwylele): this data is probably writable (via request 3?) and thus should be saved to +    // and loaded from somewhere. +    calibration_data = std::array<u8, 0x40>{{ +        // 0x00 +        0x00, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, +        // 0x08 +        0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0xF5, +        // 0x10 +        0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, +        // 0x18 +        0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65, +        // 0x20 +        0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, +        // 0x28 +        0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65, +        // 0x30 +        0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, +        // 0x38 +        0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65, +    }}; + +    hid_polling_callback_id = +        CoreTiming::RegisterEvent("ExtraHID::SendHIDStatus", [this](u64, int cycles_late) { +            SendHIDStatus(); +            CoreTiming::ScheduleEvent(msToCycles(hid_period) - cycles_late, +                                      hid_polling_callback_id); +        }); +} + +ExtraHID::~ExtraHID() { +    OnDisconnect(); +} + +void ExtraHID::OnConnect() {} + +void ExtraHID::OnDisconnect() { +    CoreTiming::UnscheduleEvent(hid_polling_callback_id, 0); +} + +void ExtraHID::HandleConfigureHIDPollingRequest(const std::vector<u8>& request) { +    if (request.size() != 3) { +        LOG_ERROR(Service_IR, "Wrong request size (%zu): %s", request.size(), +                  Common::ArrayToString(request.data(), request.size()).c_str()); +        return; +    } + +    // Change HID input polling interval +    CoreTiming::UnscheduleEvent(hid_polling_callback_id, 0); +    hid_period = request[1]; +    CoreTiming::ScheduleEvent(msToCycles(hid_period), hid_polling_callback_id); +} + +void ExtraHID::HandleReadCalibrationDataRequest(const std::vector<u8>& request_buf) { +    struct ReadCalibrationDataRequest { +        RequestID request_id; +        u8 expected_response_time; +        u16_le offset; +        u16_le size; +    }; +    static_assert(sizeof(ReadCalibrationDataRequest) == 6, +                  "ReadCalibrationDataRequest has wrong size"); + +    if (request_buf.size() != sizeof(ReadCalibrationDataRequest)) { +        LOG_ERROR(Service_IR, "Wrong request size (%zu): %s", request_buf.size(), +                  Common::ArrayToString(request_buf.data(), request_buf.size()).c_str()); +        return; +    } + +    ReadCalibrationDataRequest request; +    std::memcpy(&request, request_buf.data(), sizeof(request)); + +    const u16 offset = Common::AlignDown(request.offset, 16); +    const u16 size = Common::AlignDown(request.size, 16); + +    if (offset + size > calibration_data.size()) { +        LOG_ERROR(Service_IR, "Read beyond the end of calibration data! (offset=%u, size=%u)", +                  offset, size); +        return; +    } + +    std::vector<u8> response(5); +    response[0] = static_cast<u8>(ResponseID::ReadCalibrationData); +    std::memcpy(&response[1], &request.offset, sizeof(request.offset)); +    std::memcpy(&response[3], &request.size, sizeof(request.size)); +    response.insert(response.end(), calibration_data.begin() + offset, +                    calibration_data.begin() + offset + size); +    Send(response); +} + +void ExtraHID::OnReceive(const std::vector<u8>& data) { +    switch (static_cast<RequestID>(data[0])) { +    case RequestID::ConfigureHIDPolling: +        HandleConfigureHIDPollingRequest(data); +        break; +    case RequestID::ReadCalibrationData: +        HandleReadCalibrationDataRequest(data); +        break; +    default: +        LOG_ERROR(Service_IR, "Unknown request: %s", +                  Common::ArrayToString(data.data(), data.size()).c_str()); +        break; +    } +} + +void ExtraHID::SendHIDStatus() { +    if (is_device_reload_pending.exchange(false)) +        LoadInputDevices(); + +    struct { +        union { +            BitField<0, 8, u32_le> header; +            BitField<8, 12, u32_le> c_stick_x; +            BitField<20, 12, u32_le> c_stick_y; +        } c_stick; +        union { +            BitField<0, 5, u8> battery_level; +            BitField<5, 1, u8> zl_not_held; +            BitField<6, 1, u8> zr_not_held; +            BitField<7, 1, u8> r_not_held; +        } buttons; +        u8 unknown; +    } response; +    static_assert(sizeof(response) == 6, "HID status response has wrong size!"); + +    constexpr int C_STICK_CENTER = 0x800; +    // TODO(wwylele): this value is not accurately measured. We currently assume that the axis can +    // take values in the whole range of a 12-bit integer. +    constexpr int C_STICK_RADIUS = 0x7FF; + +    float x, y; +    std::tie(x, y) = c_stick->GetStatus(); + +    response.c_stick.header.Assign(static_cast<u8>(ResponseID::PollHID)); +    response.c_stick.c_stick_x.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * x)); +    response.c_stick.c_stick_y.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * y)); +    response.buttons.battery_level.Assign(0x1F); +    response.buttons.zl_not_held.Assign(!zl->GetStatus()); +    response.buttons.zr_not_held.Assign(!zr->GetStatus()); +    response.buttons.r_not_held.Assign(1); +    response.unknown = 0; + +    std::vector<u8> response_buffer(sizeof(response)); +    memcpy(response_buffer.data(), &response, sizeof(response)); +    Send(response_buffer); +} + +void ExtraHID::RequestInputDevicesReload() { +    is_device_reload_pending.store(true); +} + +void ExtraHID::LoadInputDevices() { +    zl = Input::CreateDevice<Input::ButtonDevice>( +        Settings::values.buttons[Settings::NativeButton::ZL]); +    zr = Input::CreateDevice<Input::ButtonDevice>( +        Settings::values.buttons[Settings::NativeButton::ZR]); +    c_stick = Input::CreateDevice<Input::AnalogDevice>( +        Settings::values.analogs[Settings::NativeAnalog::CStick]); +} + +} // namespace IR +} // namespace Service diff --git a/src/core/hle/service/ir/extra_hid.h b/src/core/hle/service/ir/extra_hid.h new file mode 100644 index 000000000..a2459a73a --- /dev/null +++ b/src/core/hle/service/ir/extra_hid.h @@ -0,0 +1,48 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <atomic> +#include "core/frontend/input.h" +#include "core/hle/service/ir/ir_user.h" + +namespace Service { +namespace IR { + +/** + * An IRDevice emulating Circle Pad Pro or New 3DS additional HID hardware. + * This device sends periodic udates at a rate configured by the 3DS, and sends calibration data if + * requested. + */ +class ExtraHID final : public IRDevice { +public: +    explicit ExtraHID(SendFunc send_func); +    ~ExtraHID(); + +    void OnConnect() override; +    void OnDisconnect() override; +    void OnReceive(const std::vector<u8>& data) override; + +    /// Requests input devices reload from current settings. Called when the input settings change. +    void RequestInputDevicesReload(); + +private: +    void SendHIDStatus(); +    void HandleConfigureHIDPollingRequest(const std::vector<u8>& request); +    void HandleReadCalibrationDataRequest(const std::vector<u8>& request); +    void LoadInputDevices(); + +    u8 hid_period; +    int hid_polling_callback_id; +    std::array<u8, 0x40> calibration_data; +    std::unique_ptr<Input::ButtonDevice> zl; +    std::unique_ptr<Input::ButtonDevice> zr; +    std::unique_ptr<Input::AnalogDevice> c_stick; +    std::atomic<bool> is_device_reload_pending; +}; + +} // namespace IR +} // namespace Service diff --git a/src/core/hle/service/ir/ir.cpp b/src/core/hle/service/ir/ir.cpp index 7ac34a990..f06dd552f 100644 --- a/src/core/hle/service/ir/ir.cpp +++ b/src/core/hle/service/ir/ir.cpp @@ -25,6 +25,11 @@ void Shutdown() {      ShutdownRST();  } +void ReloadInputDevices() { +    ReloadInputDevicesUser(); +    ReloadInputDevicesRST(); +} +  } // namespace IR  } // namespace Service diff --git a/src/core/hle/service/ir/ir.h b/src/core/hle/service/ir/ir.h index c741498e2..6be3e950c 100644 --- a/src/core/hle/service/ir/ir.h +++ b/src/core/hle/service/ir/ir.h @@ -16,5 +16,8 @@ void Init();  /// Shutdown IR service  void Shutdown(); +/// Reload input devices. Used when input configuration changed +void ReloadInputDevices(); +  } // namespace IR  } // namespace Service diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp index 3f1275c53..53807cd91 100644 --- a/src/core/hle/service/ir/ir_rst.cpp +++ b/src/core/hle/service/ir/ir_rst.cpp @@ -2,16 +2,135 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <atomic> +#include "common/bit_field.h" +#include "core/core_timing.h" +#include "core/frontend/input.h"  #include "core/hle/kernel/event.h"  #include "core/hle/kernel/shared_memory.h" +#include "core/hle/service/hid/hid.h"  #include "core/hle/service/ir/ir.h"  #include "core/hle/service/ir/ir_rst.h" +#include "core/settings.h"  namespace Service {  namespace IR { -static Kernel::SharedPtr<Kernel::Event> handle_event; +union PadState { +    u32_le hex; + +    BitField<14, 1, u32_le> zl; +    BitField<15, 1, u32_le> zr; + +    BitField<24, 1, u32_le> c_stick_right; +    BitField<25, 1, u32_le> c_stick_left; +    BitField<26, 1, u32_le> c_stick_up; +    BitField<27, 1, u32_le> c_stick_down; +}; + +struct PadDataEntry { +    PadState current_state; +    PadState delta_additions; +    PadState delta_removals; + +    s16_le c_stick_x; +    s16_le c_stick_y; +}; + +struct SharedMem { +    u64_le index_reset_ticks;          ///< CPU tick count for when HID module updated entry index 0 +    u64_le index_reset_ticks_previous; ///< Previous `index_reset_ticks` +    u32_le index; +    INSERT_PADDING_WORDS(1); +    std::array<PadDataEntry, 8> entries; ///< Last 8 pad entries +}; + +static_assert(sizeof(SharedMem) == 0x98, "SharedMem has wrong size!"); + +static Kernel::SharedPtr<Kernel::Event> update_event;  static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; +static u32 next_pad_index; +static int update_callback_id; +static std::unique_ptr<Input::ButtonDevice> zl_button; +static std::unique_ptr<Input::ButtonDevice> zr_button; +static std::unique_ptr<Input::AnalogDevice> c_stick; +static std::atomic<bool> is_device_reload_pending; +static bool raw_c_stick; +static int update_period; + +static void LoadInputDevices() { +    zl_button = Input::CreateDevice<Input::ButtonDevice>( +        Settings::values.buttons[Settings::NativeButton::ZL]); +    zr_button = Input::CreateDevice<Input::ButtonDevice>( +        Settings::values.buttons[Settings::NativeButton::ZR]); +    c_stick = Input::CreateDevice<Input::AnalogDevice>( +        Settings::values.analogs[Settings::NativeAnalog::CStick]); +} + +static void UnloadInputDevices() { +    zl_button = nullptr; +    zr_button = nullptr; +    c_stick = nullptr; +} + +static void UpdateCallback(u64 userdata, int cycles_late) { +    SharedMem* mem = reinterpret_cast<SharedMem*>(shared_memory->GetPointer()); + +    if (is_device_reload_pending.exchange(false)) +        LoadInputDevices(); + +    PadState state; +    state.zl.Assign(zl_button->GetStatus()); +    state.zr.Assign(zr_button->GetStatus()); + +    // Get current c-stick position and update c-stick direction +    float c_stick_x_f, c_stick_y_f; +    std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); +    constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius +    const s16 c_stick_x = static_cast<s16>(c_stick_x_f * MAX_CSTICK_RADIUS); +    const s16 c_stick_y = static_cast<s16>(c_stick_y_f * MAX_CSTICK_RADIUS); + +    if (!raw_c_stick) { +        const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y); +        state.c_stick_up.Assign(direction.up); +        state.c_stick_down.Assign(direction.down); +        state.c_stick_left.Assign(direction.left); +        state.c_stick_right.Assign(direction.right); +    } + +    // TODO (wwylele): implement raw C-stick data for raw_c_stick = true + +    const u32 last_entry_index = mem->index; +    mem->index = next_pad_index; +    next_pad_index = (next_pad_index + 1) % mem->entries.size(); + +    // Get the previous Pad state +    PadState old_state{mem->entries[last_entry_index].current_state}; + +    // Compute bitmask with 1s for bits different from the old state +    PadState changed = {state.hex ^ old_state.hex}; + +    // Get the current Pad entry +    PadDataEntry& pad_entry = mem->entries[mem->index]; + +    // Update entry properties +    pad_entry.current_state.hex = state.hex; +    pad_entry.delta_additions.hex = changed.hex & state.hex; +    pad_entry.delta_removals.hex = changed.hex & old_state.hex; +    pad_entry.c_stick_x = c_stick_x; +    pad_entry.c_stick_y = c_stick_y; + +    // If we just updated index 0, provide a new timestamp +    if (mem->index == 0) { +        mem->index_reset_ticks_previous = mem->index_reset_ticks; +        mem->index_reset_ticks = CoreTiming::GetTicks(); +    } + +    update_event->Signal(); + +    // Reschedule recurrent event +    CoreTiming::ScheduleEvent(msToCycles(update_period) - cycles_late, update_callback_id); +}  /**   * IR::GetHandles service function @@ -22,18 +141,52 @@ static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory;   *      4 : Event handle   */  static void GetHandles(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x01, 0, 0); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 3); +    rb.Push(RESULT_SUCCESS); +    rb.PushMoveHandles(Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(), +                       Kernel::g_handle_table.Create(Service::IR::update_event).MoveFrom()); +} + +/** + * IR::Initialize service function + *  Inputs: + *      1 : pad state update period in ms + *      2 : bool output raw c-stick data + */ +static void Initialize(Interface* self) { +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x02, 2, 0); +    update_period = static_cast<int>(rp.Pop<u32>()); +    raw_c_stick = rp.Pop<bool>(); -    cmd_buff[1] = RESULT_SUCCESS.raw; -    cmd_buff[2] = 0x4000000; -    cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(); -    cmd_buff[4] = Kernel::g_handle_table.Create(Service::IR::handle_event).MoveFrom(); +    if (raw_c_stick) +        LOG_ERROR(Service_IR, "raw C-stick data is not implemented!"); + +    next_pad_index = 0; +    is_device_reload_pending.store(true); +    CoreTiming::ScheduleEvent(msToCycles(update_period), update_callback_id); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    rb.Push(RESULT_SUCCESS); + +    LOG_DEBUG(Service_IR, "called. update_period=%d, raw_c_stick=%d", update_period, raw_c_stick); +} + +static void Shutdown(Interface* self) { +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 1, 0); + +    CoreTiming::UnscheduleEvent(update_callback_id, 0); +    UnloadInputDevices(); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    rb.Push(RESULT_SUCCESS); +    LOG_DEBUG(Service_IR, "called");  }  const Interface::FunctionInfo FunctionTable[] = {      {0x00010000, GetHandles, "GetHandles"}, -    {0x00020080, nullptr, "Initialize"}, -    {0x00030000, nullptr, "Shutdown"}, +    {0x00020080, Initialize, "Initialize"}, +    {0x00030000, Shutdown, "Shutdown"},      {0x00090000, nullptr, "WriteToTwoFields"},  }; @@ -43,17 +196,24 @@ IR_RST_Interface::IR_RST_Interface() {  void InitRST() {      using namespace Kernel; - +    // Note: these two kernel objects are even available before Initialize service function is +    // called.      shared_memory = -        SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, -                             MemoryPermission::ReadWrite, 0, MemoryRegion::BASE, "IR:SharedMemory"); +        SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read, +                             0, MemoryRegion::BASE, "IRRST:SharedMemory"); +    update_event = Event::Create(ResetType::OneShot, "IRRST:UpdateEvent"); -    handle_event = Event::Create(ResetType::OneShot, "IR:HandleEvent"); +    update_callback_id = CoreTiming::RegisterEvent("IRRST:UpdateCallBack", UpdateCallback);  }  void ShutdownRST() {      shared_memory = nullptr; -    handle_event = nullptr; +    update_event = nullptr; +    UnloadInputDevices(); +} + +void ReloadInputDevicesRST() { +    is_device_reload_pending.store(true);  }  } // namespace IR diff --git a/src/core/hle/service/ir/ir_rst.h b/src/core/hle/service/ir/ir_rst.h index 75b732627..d932bb7e5 100644 --- a/src/core/hle/service/ir/ir_rst.h +++ b/src/core/hle/service/ir/ir_rst.h @@ -21,5 +21,8 @@ public:  void InitRST();  void ShutdownRST(); +/// Reload input devices. Used when input configuration changed +void ReloadInputDevicesRST(); +  } // namespace IR  } // namespace Service diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index b326d7fc7..226af0083 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -2,110 +2,481 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <memory> +#include <boost/crc.hpp> +#include <boost/optional.hpp> +#include "common/string_util.h" +#include "common/swap.h"  #include "core/hle/kernel/event.h"  #include "core/hle/kernel/shared_memory.h" +#include "core/hle/service/ir/extra_hid.h"  #include "core/hle/service/ir/ir.h"  #include "core/hle/service/ir/ir_user.h"  namespace Service {  namespace IR { -static Kernel::SharedPtr<Kernel::Event> conn_status_event; -static Kernel::SharedPtr<Kernel::SharedMemory> transfer_shared_memory; +// This is a header that will present in the ir:USER shared memory if it is initialized with +// InitializeIrNopShared service function. Otherwise the shared memory doesn't have this header if +// it is initialized with InitializeIrNop service function. +struct SharedMemoryHeader { +    u32_le latest_receive_error_result; +    u32_le latest_send_error_result; +    // TODO(wwylele): for these fields below, make them enum when the meaning of values is known. +    u8 connection_status; +    u8 trying_to_connect_status; +    u8 connection_role; +    u8 machine_id; +    u8 connected; +    u8 network_id; +    u8 initialized; +    u8 unknown; + +    // This is not the end of the shared memory. It is followed by a receive buffer and a send +    // buffer. We handle receive buffer in the BufferManager class. For the send buffer, because +    // games usually don't access it, we don't emulate it. +}; +static_assert(sizeof(SharedMemoryHeader) == 16, "SharedMemoryHeader has wrong size!"); + +/** + * A manager of the send/receive buffers in the shared memory. Currently it is only used for the + * receive buffer. + * + * A buffer consists of three parts: + *     - BufferInfo: stores available count of packets, and their position in the PacketInfo + *         circular queue. + *     - PacketInfo circular queue: stores the position of each avaiable packets in the Packet data + *         buffer. Each entry is a pair of {offset, size}. + *     - Packet data circular buffer: stores the actual data of packets. + * + * IR packets can be put into and get from the buffer. + * + * When a new packet is put into the buffer, its data is put into the data circular buffer, + * following the end of previous packet data. A new entry is also added to the PacketInfo circular + * queue pointing to the added packet data. Then BufferInfo is updated. + * + * Packets can be released from the other end of the buffer. When releasing a packet, the front + * entry in thePacketInfo circular queue is removed, and as a result the corresponding memory in the + * data circular buffer is also released. BufferInfo is updated as well. + * + * The client application usually has a similar manager constructed over the same shared memory + * region, performing the same put/get/release operation. This way the client and the service + * communicate via a pair of manager of the same buffer. + * + * TODO(wwylele): implement Get function, which is used by ReceiveIrnop service function. + */ +class BufferManager { +public: +    BufferManager(Kernel::SharedPtr<Kernel::SharedMemory> shared_memory_, u32 info_offset_, +                  u32 buffer_offset_, u32 max_packet_count_, u32 buffer_size) +        : shared_memory(shared_memory_), info_offset(info_offset_), buffer_offset(buffer_offset_), +          max_packet_count(max_packet_count_), +          max_data_size(buffer_size - sizeof(PacketInfo) * max_packet_count_) { +        UpdateBufferInfo(); +    } + +    /** +     * Puts a packet to the head of the buffer. +     * @params packet The data of the packet to put. +     * @returns whether the operation is successful. +     */ +    bool Put(const std::vector<u8>& packet) { +        if (info.packet_count == max_packet_count) +            return false; + +        u32 write_offset; + +        // finds free space offset in data buffer +        if (info.packet_count == 0) { +            write_offset = 0; +            if (packet.size() > max_data_size) +                return false; +        } else { +            const u32 last_index = (info.end_index + max_packet_count - 1) % max_packet_count; +            const PacketInfo first = GetPacketInfo(info.begin_index); +            const PacketInfo last = GetPacketInfo(last_index); +            write_offset = (last.offset + last.size) % max_data_size; +            const u32 free_space = (first.offset + max_data_size - write_offset) % max_data_size; +            if (packet.size() > free_space) +                return false; +        } + +        // writes packet info +        PacketInfo packet_info{write_offset, static_cast<u32>(packet.size())}; +        SetPacketInfo(info.end_index, packet_info); + +        // writes packet data +        for (size_t i = 0; i < packet.size(); ++i) { +            *GetDataBufferPointer((write_offset + i) % max_data_size) = packet[i]; +        } + +        // updates buffer info +        info.end_index++; +        info.end_index %= max_packet_count; +        info.packet_count++; +        UpdateBufferInfo(); +        return true; +    } + +    /** +     * Release packets from the tail of the buffer +     * @params count Numbers of packets to release. +     * @returns whether the operation is successful. +     */ +    bool Release(u32 count) { +        if (info.packet_count < count) +            return false; + +        info.packet_count -= count; +        info.begin_index += count; +        info.begin_index %= max_packet_count; +        UpdateBufferInfo(); +        return true; +    } + +private: +    struct BufferInfo { +        u32_le begin_index; +        u32_le end_index; +        u32_le packet_count; +        u32_le unknown; +    }; +    static_assert(sizeof(BufferInfo) == 16, "BufferInfo has wrong size!"); + +    struct PacketInfo { +        u32_le offset; +        u32_le size; +    }; +    static_assert(sizeof(PacketInfo) == 8, "PacketInfo has wrong size!"); + +    u8* GetPacketInfoPointer(u32 index) { +        return shared_memory->GetPointer(buffer_offset + sizeof(PacketInfo) * index); +    } + +    void SetPacketInfo(u32 index, const PacketInfo& packet_info) { +        memcpy(GetPacketInfoPointer(index), &packet_info, sizeof(PacketInfo)); +    } + +    PacketInfo GetPacketInfo(u32 index) { +        PacketInfo packet_info; +        memcpy(&packet_info, GetPacketInfoPointer(index), sizeof(PacketInfo)); +        return packet_info; +    } + +    u8* GetDataBufferPointer(u32 offset) { +        return shared_memory->GetPointer(buffer_offset + sizeof(PacketInfo) * max_packet_count + +                                         offset); +    } + +    void UpdateBufferInfo() { +        if (info_offset) { +            memcpy(shared_memory->GetPointer(info_offset), &info, sizeof(info)); +        } +    } + +    BufferInfo info{0, 0, 0, 0}; +    Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; +    u32 info_offset; +    u32 buffer_offset; +    u32 max_packet_count; +    u32 max_data_size; +}; + +static Kernel::SharedPtr<Kernel::Event> conn_status_event, send_event, receive_event; +static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; +static std::unique_ptr<ExtraHID> extra_hid; +static IRDevice* connected_device; +static boost::optional<BufferManager> receive_buffer; + +/// Wraps the payload into packet and puts it to the receive buffer +static void PutToReceive(const std::vector<u8>& payload) { +    LOG_TRACE(Service_IR, "called, data=%s", +              Common::ArrayToString(payload.data(), payload.size()).c_str()); +    size_t size = payload.size(); + +    std::vector<u8> packet; + +    // Builds packet header. For the format info: +    // https://www.3dbrew.org/wiki/IRUSER_Shared_Memory#Packet_structure + +    // fixed value +    packet.push_back(0xA5); +    // destination network ID +    u8 network_id = *(shared_memory->GetPointer(offsetof(SharedMemoryHeader, network_id))); +    packet.push_back(network_id); + +    // puts the size info. +    // The highest bit of the first byte is unknown, which is set to zero here. The second highest +    // bit is a flag that determines whether the size info is in extended form. If the packet size +    // can be represent within 6 bits, the short form (1 byte) of size info is chosen, the size is +    // put to the lower bits of this byte, and the flag is clear. If the packet size cannot be +    // represent within 6 bits, the extended form (2 bytes) is chosen, the lower 8 bits of the size +    // is put to the second byte, the higher bits of the size is put to the lower bits of the first +    // byte, and the flag is set. Note that the packet size must be within 14 bits due to this +    // format restriction, or it will overlap with the flag bit. +    if (size < 0x40) { +        packet.push_back(static_cast<u8>(size)); +    } else if (size < 0x4000) { +        packet.push_back(static_cast<u8>(size >> 8) | 0x40); +        packet.push_back(static_cast<u8>(size)); +    } else { +        ASSERT(false); +    } + +    // puts the payload +    packet.insert(packet.end(), payload.begin(), payload.end()); + +    // calculates CRC and puts to the end +    packet.push_back(boost::crc<8, 0x07, 0, 0, false, false>(packet.data(), packet.size())); + +    if (receive_buffer->Put(packet)) { +        receive_event->Signal(); +    } else { +        LOG_ERROR(Service_IR, "receive buffer is full!"); +    } +}  /**   * IR::InitializeIrNopShared service function + * Initializes ir:USER service with a user provided shared memory. The shared memory is configured + * to shared mode (with SharedMemoryHeader at the beginning of the shared memory).   *  Inputs: - *      1 : Size of transfer buffer + *      1 : Size of shared memory   *      2 : Recv buffer size - *      3 : unknown + *      3 : Recv buffer packet count   *      4 : Send buffer size - *      5 : unknown + *      5 : Send buffer packet count   *      6 : BaudRate (u8) - *      7 : 0 - *      8 : Handle of transfer shared memory + *      7 : 0 (Handle descriptor) + *      8 : Handle of shared memory   *  Outputs:   *      1 : Result of function, 0 on success, otherwise error code   */  static void InitializeIrNopShared(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x18, 6, 2); +    const u32 shared_buff_size = rp.Pop<u32>(); +    const u32 recv_buff_size = rp.Pop<u32>(); +    const u32 recv_buff_packet_count = rp.Pop<u32>(); +    const u32 send_buff_size = rp.Pop<u32>(); +    const u32 send_buff_packet_count = rp.Pop<u32>(); +    const u8 baud_rate = rp.Pop<u8>(); +    const Kernel::Handle handle = rp.PopHandle(); -    u32 transfer_buff_size = cmd_buff[1]; -    u32 recv_buff_size = cmd_buff[2]; -    u32 unk1 = cmd_buff[3]; -    u32 send_buff_size = cmd_buff[4]; -    u32 unk2 = cmd_buff[5]; -    u8 baud_rate = cmd_buff[6] & 0xFF; -    Kernel::Handle handle = cmd_buff[8]; +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); -    if (Kernel::g_handle_table.IsValid(handle)) { -        transfer_shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle); -        transfer_shared_memory->name = "IR:TransferSharedMemory"; +    shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle); +    if (!shared_memory) { +        LOG_CRITICAL(Service_IR, "invalid shared memory handle 0x%08X", handle); +        rb.Push(ResultCode(ErrorDescription::InvalidHandle, ErrorModule::OS, +                           ErrorSummary::WrongArgument, ErrorLevel::Permanent)); +        return;      } +    shared_memory->name = "IR_USER: shared memory"; -    cmd_buff[1] = RESULT_SUCCESS.raw; +    receive_buffer = +        BufferManager(shared_memory, 0x10, 0x20, recv_buff_packet_count, recv_buff_size); +    SharedMemoryHeader shared_memory_init{}; +    shared_memory_init.initialized = 1; +    std::memcpy(shared_memory->GetPointer(), &shared_memory_init, sizeof(SharedMemoryHeader)); -    LOG_WARNING(Service_IR, "(STUBBED) called, transfer_buff_size=%d, recv_buff_size=%d, " -                            "unk1=%d, send_buff_size=%d, unk2=%d, baud_rate=%u, handle=0x%08X", -                transfer_buff_size, recv_buff_size, unk1, send_buff_size, unk2, baud_rate, handle); +    rb.Push(RESULT_SUCCESS); + +    LOG_INFO(Service_IR, "called, shared_buff_size=%u, recv_buff_size=%u, " +                         "recv_buff_packet_count=%u, send_buff_size=%u, " +                         "send_buff_packet_count=%u, baud_rate=%u, handle=0x%08X", +             shared_buff_size, recv_buff_size, recv_buff_packet_count, send_buff_size, +             send_buff_packet_count, baud_rate, handle);  }  /**   * IR::RequireConnection service function + * Searches for an IR device and connects to it. After connecting to the device, applications can + * use SendIrNop function, ReceiveIrNop function (or read from the buffer directly) to communicate + * with the device.   *  Inputs: - *      1 : unknown (u8), looks like always 1 + *      1 : device ID? always 1 for circle pad pro   *  Outputs:   *      1 : Result of function, 0 on success, otherwise error code   */  static void RequireConnection(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x06, 1, 0); +    const u8 device_id = rp.Pop<u8>(); + +    u8* shared_memory_ptr = shared_memory->GetPointer(); +    if (device_id == 1) { +        // These values are observed on a New 3DS. The meaning of them is unclear. +        // TODO (wwylele): should assign network_id a (random?) number +        shared_memory_ptr[offsetof(SharedMemoryHeader, connection_status)] = 2; +        shared_memory_ptr[offsetof(SharedMemoryHeader, connection_role)] = 2; +        shared_memory_ptr[offsetof(SharedMemoryHeader, connected)] = 1; + +        connected_device = extra_hid.get(); +        connected_device->OnConnect(); +        conn_status_event->Signal(); +    } else { +        LOG_WARNING(Service_IR, "unknown device id %u. Won't connect.", device_id); +        shared_memory_ptr[offsetof(SharedMemoryHeader, connection_status)] = 1; +        shared_memory_ptr[offsetof(SharedMemoryHeader, trying_to_connect_status)] = 2; +    } + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    rb.Push(RESULT_SUCCESS); + +    LOG_INFO(Service_IR, "called, device_id = %u", device_id); +} + +/** + * IR::GetReceiveEvent service function + * Gets an event that is signaled when a packet is received from the IR device. + *  Outputs: + *      1 : Result of function, 0 on success, otherwise error code + *      2 : 0 (Handle descriptor) + *      3 : Receive event handle + */ +void GetReceiveEvent(Interface* self) { +    IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0A, 1, 2); -    conn_status_event->Signal(); +    rb.Push(RESULT_SUCCESS); +    rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::receive_event).MoveFrom()); + +    LOG_INFO(Service_IR, "called"); +} + +/** + * IR::GetSendEvent service function + * Gets an event that is signaled when the sending of a packet is complete + *  Outputs: + *      1 : Result of function, 0 on success, otherwise error code + *      2 : 0 (Handle descriptor) + *      3 : Send event handle + */ +void GetSendEvent(Interface* self) { +    IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0B, 1, 2); -    cmd_buff[1] = RESULT_SUCCESS.raw; +    rb.Push(RESULT_SUCCESS); +    rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::send_event).MoveFrom()); -    LOG_WARNING(Service_IR, "(STUBBED) called"); +    LOG_INFO(Service_IR, "called");  }  /**   * IR::Disconnect service function + * Disconnects from the current connected IR device.   *  Outputs:   *      1 : Result of function, 0 on success, otherwise error code   */  static void Disconnect(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    if (connected_device) { +        connected_device->OnDisconnect(); +        connected_device = nullptr; +        conn_status_event->Signal(); +    } + +    u8* shared_memory_ptr = shared_memory->GetPointer(); +    shared_memory_ptr[offsetof(SharedMemoryHeader, connection_status)] = 0; +    shared_memory_ptr[offsetof(SharedMemoryHeader, connected)] = 0; -    cmd_buff[1] = RESULT_SUCCESS.raw; +    IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x09, 1, 0); +    rb.Push(RESULT_SUCCESS); -    LOG_WARNING(Service_IR, "(STUBBED) called"); +    LOG_INFO(Service_IR, "called");  }  /**   * IR::GetConnectionStatusEvent service function + * Gets an event that is signaled when the connection status is changed   *  Outputs:   *      1 : Result of function, 0 on success, otherwise error code - *      2 : Connection Status Event handle + *      2 : 0 (Handle descriptor) + *      3 : Connection Status Event handle   */  static void GetConnectionStatusEvent(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0C, 1, 2); -    cmd_buff[1] = RESULT_SUCCESS.raw; -    cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom(); +    rb.Push(RESULT_SUCCESS); +    rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom()); -    LOG_WARNING(Service_IR, "(STUBBED) called"); +    LOG_INFO(Service_IR, "called");  }  /**   * IR::FinalizeIrNop service function + * Finalize ir:USER service.   *  Outputs:   *      1 : Result of function, 0 on success, otherwise error code   */  static void FinalizeIrNop(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    if (connected_device) { +        connected_device->OnDisconnect(); +        connected_device = nullptr; +    } + +    shared_memory = nullptr; +    receive_buffer = boost::none; + +    IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x02, 1, 0); +    rb.Push(RESULT_SUCCESS); + +    LOG_INFO(Service_IR, "called"); +} + +/** + * IR::SendIrNop service function + * Sends a packet to the connected IR device + *  Inpus: + *      1 : Size of data to send + *      2 : 2 + (size << 14) (Static buffer descriptor) + *      3 : Data buffer address + *  Outputs: + *      1 : Result of function, 0 on success, otherwise error code + */ +static void SendIrNop(Interface* self) { +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0D, 1, 2); +    const u32 size = rp.Pop<u32>(); +    const VAddr address = rp.PopStaticBuffer(); + +    std::vector<u8> buffer(size); +    Memory::ReadBlock(address, buffer.data(), size); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    if (connected_device) { +        connected_device->OnReceive(buffer); +        send_event->Signal(); +        rb.Push(RESULT_SUCCESS); +    } else { +        LOG_ERROR(Service_IR, "not connected"); +        rb.Push(ResultCode(static_cast<ErrorDescription>(13), ErrorModule::IR, +                           ErrorSummary::InvalidState, ErrorLevel::Status)); +    } + +    LOG_TRACE(Service_IR, "called, data=%s", Common::ArrayToString(buffer.data(), size).c_str()); +} + +/** + * IR::ReleaseReceivedData function + * Release a specified amount of packet from the receive buffer. This is called after the + * application reads received packet from the buffer directly, to release the buffer space for + * future packets. + *  Inpus: + *      1 : Number of packets to release + *  Outputs: + *      1 : Result of function, 0 on success, otherwise error code + */ +static void ReleaseReceivedData(Interface* self) { +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x19, 1, 0); +    u32 count = rp.Pop<u32>(); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); -    cmd_buff[1] = RESULT_SUCCESS.raw; +    if (receive_buffer->Release(count)) { +        rb.Push(RESULT_SUCCESS); +    } else { +        LOG_ERROR(Service_IR, "failed to release %u packets", count); +        rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::IR, ErrorSummary::NotFound, +                           ErrorLevel::Status)); +    } -    LOG_WARNING(Service_IR, "(STUBBED) called"); +    LOG_TRACE(Service_IR, "called, count=%u", count);  }  const Interface::FunctionInfo FunctionTable[] = { @@ -118,10 +489,10 @@ const Interface::FunctionInfo FunctionTable[] = {      {0x000702C0, nullptr, "AutoConnection"},      {0x00080000, nullptr, "AnyConnection"},      {0x00090000, Disconnect, "Disconnect"}, -    {0x000A0000, nullptr, "GetReceiveEvent"}, -    {0x000B0000, nullptr, "GetSendEvent"}, +    {0x000A0000, GetReceiveEvent, "GetReceiveEvent"}, +    {0x000B0000, GetSendEvent, "GetSendEvent"},      {0x000C0000, GetConnectionStatusEvent, "GetConnectionStatusEvent"}, -    {0x000D0042, nullptr, "SendIrNop"}, +    {0x000D0042, SendIrNop, "SendIrNop"},      {0x000E0042, nullptr, "SendIrNopLarge"},      {0x000F0040, nullptr, "ReceiveIrnop"},      {0x00100042, nullptr, "ReceiveIrnopLarge"}, @@ -133,7 +504,7 @@ const Interface::FunctionInfo FunctionTable[] = {      {0x00160000, nullptr, "GetSendSizeFreeAndUsed"},      {0x00170000, nullptr, "GetConnectionRole"},      {0x00180182, InitializeIrNopShared, "InitializeIrNopShared"}, -    {0x00190040, nullptr, "ReleaseReceivedData"}, +    {0x00190040, ReleaseReceivedData, "ReleaseReceivedData"},      {0x001A0040, nullptr, "SetOwnMachineId"},  }; @@ -144,13 +515,43 @@ IR_User_Interface::IR_User_Interface() {  void InitUser() {      using namespace Kernel; -    transfer_shared_memory = nullptr; +    shared_memory = nullptr; +      conn_status_event = Event::Create(ResetType::OneShot, "IR:ConnectionStatusEvent"); +    send_event = Event::Create(ResetType::OneShot, "IR:SendEvent"); +    receive_event = Event::Create(ResetType::OneShot, "IR:ReceiveEvent"); + +    receive_buffer = boost::none; + +    extra_hid = std::make_unique<ExtraHID>(PutToReceive); + +    connected_device = nullptr;  }  void ShutdownUser() { -    transfer_shared_memory = nullptr; +    if (connected_device) { +        connected_device->OnDisconnect(); +        connected_device = nullptr; +    } + +    extra_hid = nullptr; +    receive_buffer = boost::none; +    shared_memory = nullptr;      conn_status_event = nullptr; +    send_event = nullptr; +    receive_event = nullptr; +} + +void ReloadInputDevicesUser() { +    if (extra_hid) +        extra_hid->RequestInputDevicesReload(); +} + +IRDevice::IRDevice(SendFunc send_func_) : send_func(send_func_) {} +IRDevice::~IRDevice() = default; + +void IRDevice::Send(const std::vector<u8>& data) { +    send_func(data);  }  } // namespace IR diff --git a/src/core/hle/service/ir/ir_user.h b/src/core/hle/service/ir/ir_user.h index 3849bd923..930650406 100644 --- a/src/core/hle/service/ir/ir_user.h +++ b/src/core/hle/service/ir/ir_user.h @@ -4,11 +4,41 @@  #pragma once +#include <functional>  #include "core/hle/service/service.h"  namespace Service {  namespace IR { +/// An interface representing a device that can communicate with 3DS via ir:USER service +class IRDevice { +public: +    /** +     * A function object that implements the method to send data to the 3DS, which takes a vector of +     * data to send. +     */ +    using SendFunc = std::function<void(const std::vector<u8>& data)>; + +    explicit IRDevice(SendFunc send_func); +    virtual ~IRDevice(); + +    /// Called when connected with 3DS +    virtual void OnConnect() = 0; + +    /// Called when disconnected from 3DS +    virtual void OnDisconnect() = 0; + +    /// Called when data is received from the 3DS. This is invoked by the ir:USER send function. +    virtual void OnReceive(const std::vector<u8>& data) = 0; + +protected: +    /// Sends data to the 3DS. The actual sending method is specified in the constructor +    void Send(const std::vector<u8>& data); + +private: +    const SendFunc send_func; +}; +  class IR_User_Interface : public Service::Interface {  public:      IR_User_Interface(); @@ -21,5 +51,8 @@ public:  void InitUser();  void ShutdownUser(); +/// Reload input devices. Used when input configuration changed +void ReloadInputDevicesUser(); +  } // namespace IR  } // namespace Service diff --git a/src/core/hle/service/ldr_ro/ldr_ro.cpp b/src/core/hle/service/ldr_ro/ldr_ro.cpp index 7af76676b..d1e6d869f 100644 --- a/src/core/hle/service/ldr_ro/ldr_ro.cpp +++ b/src/core/hle/service/ldr_ro/ldr_ro.cpp @@ -40,9 +40,6 @@ static const ResultCode ERROR_INVALID_MEMORY_STATE = // 0xD8A12C08  static const ResultCode ERROR_NOT_LOADED = // 0xD8A12C0D      ResultCode(static_cast<ErrorDescription>(13), ErrorModule::RO, ErrorSummary::InvalidState,                 ErrorLevel::Permanent); -static const ResultCode ERROR_INVALID_DESCRIPTOR = // 0xD9001830 -    ResultCode(ErrorDescription::OS_InvalidBufferDescriptor, ErrorModule::OS, -               ErrorSummary::WrongArgument, ErrorLevel::Permanent);  static MemorySynchronizer memory_synchronizer; @@ -71,66 +68,61 @@ static bool VerifyBufferState(VAddr buffer_ptr, u32 size) {   *      1 : Result of function, 0 on success, otherwise error code   */  static void Initialize(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); -    VAddr crs_buffer_ptr = cmd_buff[1]; -    u32 crs_size = cmd_buff[2]; -    VAddr crs_address = cmd_buff[3]; -    u32 descriptor = cmd_buff[4]; -    u32 process = cmd_buff[5]; - -    LOG_DEBUG(Service_LDR, "called, crs_buffer_ptr=0x%08X, crs_address=0x%08X, crs_size=0x%X, " -                           "descriptor=0x%08X, process=0x%08X", -              crs_buffer_ptr, crs_address, crs_size, descriptor, process); - -    if (descriptor != 0) { -        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); -        cmd_buff[0] = IPC::MakeHeader(0, 1, 0); -        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; -        return; -    } +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x01, 3, 2); +    VAddr crs_buffer_ptr = rp.Pop<u32>(); +    u32 crs_size = rp.Pop<u32>(); +    VAddr crs_address = rp.Pop<u32>(); +    // TODO (wwylele): RO service checks the descriptor here and return error 0xD9001830 for +    // incorrect descriptor. This error return should be probably built in IPC::RequestParser. +    // All other service functions below have the same issue. +    Kernel::Handle process = rp.PopHandle(); + +    LOG_DEBUG(Service_LDR, +              "called, crs_buffer_ptr=0x%08X, crs_address=0x%08X, crs_size=0x%X, process=0x%08X", +              crs_buffer_ptr, crs_address, crs_size, process); -    cmd_buff[0] = IPC::MakeHeader(1, 1, 0); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (loaded_crs != 0) {          LOG_ERROR(Service_LDR, "Already initialized"); -        cmd_buff[1] = ERROR_ALREADY_INITIALIZED.raw; +        rb.Push(ERROR_ALREADY_INITIALIZED);          return;      }      if (crs_size < CRO_HEADER_SIZE) {          LOG_ERROR(Service_LDR, "CRS is too small"); -        cmd_buff[1] = ERROR_BUFFER_TOO_SMALL.raw; +        rb.Push(ERROR_BUFFER_TOO_SMALL);          return;      }      if (crs_buffer_ptr & Memory::PAGE_MASK) {          LOG_ERROR(Service_LDR, "CRS original address is not aligned"); -        cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; +        rb.Push(ERROR_MISALIGNED_ADDRESS);          return;      }      if (crs_address & Memory::PAGE_MASK) {          LOG_ERROR(Service_LDR, "CRS mapping address is not aligned"); -        cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; +        rb.Push(ERROR_MISALIGNED_ADDRESS);          return;      }      if (crs_size & Memory::PAGE_MASK) {          LOG_ERROR(Service_LDR, "CRS size is not aligned"); -        cmd_buff[1] = ERROR_MISALIGNED_SIZE.raw; +        rb.Push(ERROR_MISALIGNED_SIZE);          return;      }      if (!VerifyBufferState(crs_buffer_ptr, crs_size)) {          LOG_ERROR(Service_LDR, "CRS original buffer is in invalid state"); -        cmd_buff[1] = ERROR_INVALID_MEMORY_STATE.raw; +        rb.Push(ERROR_INVALID_MEMORY_STATE);          return;      }      if (crs_address < Memory::PROCESS_IMAGE_VADDR ||          crs_address + crs_size > Memory::PROCESS_IMAGE_VADDR_END) {          LOG_ERROR(Service_LDR, "CRS mapping address is not in the process image region"); -        cmd_buff[1] = ERROR_ILLEGAL_ADDRESS.raw; +        rb.Push(ERROR_ILLEGAL_ADDRESS);          return;      } @@ -145,7 +137,7 @@ static void Initialize(Interface* self) {                       .Code();          if (result.IsError()) {              LOG_ERROR(Service_LDR, "Error mapping memory block %08X", result.raw); -            cmd_buff[1] = result.raw; +            rb.Push(result);              return;          } @@ -153,7 +145,7 @@ static void Initialize(Interface* self) {                                                                        Kernel::VMAPermission::Read);          if (result.IsError()) {              LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw); -            cmd_buff[1] = result.raw; +            rb.Push(result);              return;          } @@ -172,7 +164,7 @@ static void Initialize(Interface* self) {      result = crs.Rebase(0, crs_size, 0, 0, 0, 0, true);      if (result.IsError()) {          LOG_ERROR(Service_LDR, "Error rebasing CRS 0x%08X", result.raw); -        cmd_buff[1] = result.raw; +        rb.Push(result);          return;      } @@ -180,7 +172,7 @@ static void Initialize(Interface* self) {      loaded_crs = crs_address; -    cmd_buff[1] = RESULT_SUCCESS.raw; +    rb.Push(RESULT_SUCCESS);  }  /** @@ -196,25 +188,17 @@ static void Initialize(Interface* self) {   *      1 : Result of function, 0 on success, otherwise error code   */  static void LoadCRR(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); -    u32 crr_buffer_ptr = cmd_buff[1]; -    u32 crr_size = cmd_buff[2]; -    u32 descriptor = cmd_buff[3]; -    u32 process = cmd_buff[4]; - -    if (descriptor != 0) { -        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); -        cmd_buff[0] = IPC::MakeHeader(0, 1, 0); -        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; -        return; -    } +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x02, 2, 2); +    VAddr crr_buffer_ptr = rp.Pop<u32>(); +    u32 crr_size = rp.Pop<u32>(); +    Kernel::Handle process = rp.PopHandle(); -    cmd_buff[0] = IPC::MakeHeader(2, 1, 0); -    cmd_buff[1] = RESULT_SUCCESS.raw; // No error +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    rb.Push(RESULT_SUCCESS); -    LOG_WARNING(Service_LDR, "(STUBBED) called, crr_buffer_ptr=0x%08X, crr_size=0x%08X, " -                             "descriptor=0x%08X, process=0x%08X", -                crr_buffer_ptr, crr_size, descriptor, process); +    LOG_WARNING(Service_LDR, +                "(STUBBED) called, crr_buffer_ptr=0x%08X, crr_size=0x%08X, process=0x%08X", +                crr_buffer_ptr, crr_size, process);  }  /** @@ -229,24 +213,15 @@ static void LoadCRR(Interface* self) {   *      1 : Result of function, 0 on success, otherwise error code   */  static void UnloadCRR(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); -    u32 crr_buffer_ptr = cmd_buff[1]; -    u32 descriptor = cmd_buff[2]; -    u32 process = cmd_buff[3]; - -    if (descriptor != 0) { -        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); -        cmd_buff[0] = IPC::MakeHeader(0, 1, 0); -        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; -        return; -    } +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 1, 2); +    u32 crr_buffer_ptr = rp.Pop<u32>(); +    Kernel::Handle process = rp.PopHandle(); -    cmd_buff[0] = IPC::MakeHeader(3, 1, 0); -    cmd_buff[1] = RESULT_SUCCESS.raw; // No error +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    rb.Push(RESULT_SUCCESS); -    LOG_WARNING(Service_LDR, -                "(STUBBED) called, crr_buffer_ptr=0x%08X, descriptor=0x%08X, process=0x%08X", -                crr_buffer_ptr, descriptor, process); +    LOG_WARNING(Service_LDR, "(STUBBED) called, crr_buffer_ptr=0x%08X, process=0x%08X", +                crr_buffer_ptr, process);  }  /** @@ -276,87 +251,85 @@ static void UnloadCRR(Interface* self) {   *      There is a dispatcher template below.   */  static void LoadCRO(Interface* self, bool link_on_load_bug_fix) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); -    VAddr cro_buffer_ptr = cmd_buff[1]; -    VAddr cro_address = cmd_buff[2]; -    u32 cro_size = cmd_buff[3]; -    VAddr data_segment_address = cmd_buff[4]; -    u32 zero = cmd_buff[5]; -    u32 data_segment_size = cmd_buff[6]; -    u32 bss_segment_address = cmd_buff[7]; -    u32 bss_segment_size = cmd_buff[8]; -    bool auto_link = (cmd_buff[9] & 0xFF) != 0; -    u32 fix_level = cmd_buff[10]; -    VAddr crr_address = cmd_buff[11]; -    u32 descriptor = cmd_buff[12]; -    u32 process = cmd_buff[13]; - -    LOG_DEBUG(Service_LDR, -              "called (%s), cro_buffer_ptr=0x%08X, cro_address=0x%08X, cro_size=0x%X, " -              "data_segment_address=0x%08X, zero=%d, data_segment_size=0x%X, " -              "bss_segment_address=0x%08X, bss_segment_size=0x%X, " -              "auto_link=%s, fix_level=%d, crr_address=0x%08X, descriptor=0x%08X, process=0x%08X", +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), link_on_load_bug_fix ? 0x09 : 0x04, 11, 2); +    VAddr cro_buffer_ptr = rp.Pop<u32>(); +    VAddr cro_address = rp.Pop<u32>(); +    u32 cro_size = rp.Pop<u32>(); +    VAddr data_segment_address = rp.Pop<u32>(); +    u32 zero = rp.Pop<u32>(); +    u32 data_segment_size = rp.Pop<u32>(); +    u32 bss_segment_address = rp.Pop<u32>(); +    u32 bss_segment_size = rp.Pop<u32>(); +    bool auto_link = rp.Pop<bool>(); +    u32 fix_level = rp.Pop<u32>(); +    VAddr crr_address = rp.Pop<u32>(); +    Kernel::Handle process = rp.PopHandle(); + +    LOG_DEBUG(Service_LDR, "called (%s), cro_buffer_ptr=0x%08X, cro_address=0x%08X, cro_size=0x%X, " +                           "data_segment_address=0x%08X, zero=%d, data_segment_size=0x%X, " +                           "bss_segment_address=0x%08X, bss_segment_size=0x%X, auto_link=%s, " +                           "fix_level=%d, crr_address=0x%08X, process=0x%08X",                link_on_load_bug_fix ? "new" : "old", cro_buffer_ptr, cro_address, cro_size,                data_segment_address, zero, data_segment_size, bss_segment_address, bss_segment_size, -              auto_link ? "true" : "false", fix_level, crr_address, descriptor, process); - -    if (descriptor != 0) { -        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); -        cmd_buff[0] = IPC::MakeHeader(0, 1, 0); -        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; -        return; -    } +              auto_link ? "true" : "false", fix_level, crr_address, process); -    cmd_buff[0] = IPC::MakeHeader(link_on_load_bug_fix ? 9 : 4, 2, 0); +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);      if (loaded_crs == 0) {          LOG_ERROR(Service_LDR, "Not initialized"); -        cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; +        rb.Push(ERROR_NOT_INITIALIZED); +        rb.Push<u32>(0);          return;      }      if (cro_size < CRO_HEADER_SIZE) {          LOG_ERROR(Service_LDR, "CRO too small"); -        cmd_buff[1] = ERROR_BUFFER_TOO_SMALL.raw; +        rb.Push(ERROR_BUFFER_TOO_SMALL); +        rb.Push<u32>(0);          return;      }      if (cro_buffer_ptr & Memory::PAGE_MASK) {          LOG_ERROR(Service_LDR, "CRO original address is not aligned"); -        cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; +        rb.Push(ERROR_MISALIGNED_ADDRESS); +        rb.Push<u32>(0);          return;      }      if (cro_address & Memory::PAGE_MASK) {          LOG_ERROR(Service_LDR, "CRO mapping address is not aligned"); -        cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; +        rb.Push(ERROR_MISALIGNED_ADDRESS); +        rb.Push<u32>(0);          return;      }      if (cro_size & Memory::PAGE_MASK) {          LOG_ERROR(Service_LDR, "CRO size is not aligned"); -        cmd_buff[1] = ERROR_MISALIGNED_SIZE.raw; +        rb.Push(ERROR_MISALIGNED_SIZE); +        rb.Push<u32>(0);          return;      }      if (!VerifyBufferState(cro_buffer_ptr, cro_size)) {          LOG_ERROR(Service_LDR, "CRO original buffer is in invalid state"); -        cmd_buff[1] = ERROR_INVALID_MEMORY_STATE.raw; +        rb.Push(ERROR_INVALID_MEMORY_STATE); +        rb.Push<u32>(0);          return;      }      if (cro_address < Memory::PROCESS_IMAGE_VADDR ||          cro_address + cro_size > Memory::PROCESS_IMAGE_VADDR_END) {          LOG_ERROR(Service_LDR, "CRO mapping address is not in the process image region"); -        cmd_buff[1] = ERROR_ILLEGAL_ADDRESS.raw; +        rb.Push(ERROR_ILLEGAL_ADDRESS); +        rb.Push<u32>(0);          return;      }      if (zero) {          LOG_ERROR(Service_LDR, "Zero is not zero %d", zero); -        cmd_buff[1] = ResultCode(static_cast<ErrorDescription>(29), ErrorModule::RO, -                                 ErrorSummary::Internal, ErrorLevel::Usage) -                          .raw; +        rb.Push(ResultCode(static_cast<ErrorDescription>(29), ErrorModule::RO, +                           ErrorSummary::Internal, ErrorLevel::Usage)); +        rb.Push<u32>(0);          return;      } @@ -371,7 +344,8 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) {                       .Code();          if (result.IsError()) {              LOG_ERROR(Service_LDR, "Error mapping memory block %08X", result.raw); -            cmd_buff[1] = result.raw; +            rb.Push(result); +            rb.Push<u32>(0);              return;          } @@ -380,7 +354,8 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) {          if (result.IsError()) {              LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw);              Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); -            cmd_buff[1] = result.raw; +            rb.Push(result); +            rb.Push<u32>(0);              return;          } @@ -400,7 +375,8 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) {      if (result.IsError()) {          LOG_ERROR(Service_LDR, "Error verifying CRO in CRR %08X", result.raw);          Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); -        cmd_buff[1] = result.raw; +        rb.Push(result); +        rb.Push<u32>(0);          return;      } @@ -409,7 +385,8 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) {      if (result.IsError()) {          LOG_ERROR(Service_LDR, "Error rebasing CRO %08X", result.raw);          Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); -        cmd_buff[1] = result.raw; +        rb.Push(result); +        rb.Push<u32>(0);          return;      } @@ -417,7 +394,8 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) {      if (result.IsError()) {          LOG_ERROR(Service_LDR, "Error linking CRO %08X", result.raw);          Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); -        cmd_buff[1] = result.raw; +        rb.Push(result); +        rb.Push<u32>(0);          return;      } @@ -435,7 +413,8 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) {              if (result.IsError()) {                  LOG_ERROR(Service_LDR, "Error unmapping memory block %08X", result.raw);                  Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); -                cmd_buff[1] = result.raw; +                rb.Push(result); +                rb.Push<u32>(0);                  return;              }          } @@ -453,7 +432,8 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) {          if (result.IsError()) {              LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw);              Kernel::g_current_process->vm_manager.UnmapRange(cro_address, fix_size); -            cmd_buff[1] = result.raw; +            rb.Push(result); +            rb.Push<u32>(0);              return;          }      } @@ -463,8 +443,7 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) {      LOG_INFO(Service_LDR, "CRO \"%s\" loaded at 0x%08X, fixed_end=0x%08X", cro.ModuleName().data(),               cro_address, cro_address + fix_size); -    cmd_buff[1] = RESULT_SUCCESS.raw; -    cmd_buff[2] = fix_size; +    rb.Push(RESULT_SUCCESS, fix_size);  }  template <bool link_on_load_bug_fix> @@ -486,43 +465,35 @@ static void LoadCRO(Interface* self) {   *      1 : Result of function, 0 on success, otherwise error code   */  static void UnloadCRO(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); -    VAddr cro_address = cmd_buff[1]; -    u32 zero = cmd_buff[2]; -    VAddr cro_buffer_ptr = cmd_buff[3]; -    u32 descriptor = cmd_buff[4]; -    u32 process = cmd_buff[5]; - -    LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, zero=%d, cro_buffer_ptr=0x%08X, " -                           "descriptor=0x%08X, process=0x%08X", -              cro_address, zero, cro_buffer_ptr, descriptor, process); - -    if (descriptor != 0) { -        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); -        cmd_buff[0] = IPC::MakeHeader(0, 1, 0); -        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; -        return; -    } +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x05, 3, 2); +    VAddr cro_address = rp.Pop<u32>(); +    u32 zero = rp.Pop<u32>(); +    VAddr cro_buffer_ptr = rp.Pop<u32>(); +    Kernel::Handle process = rp.PopHandle(); + +    LOG_DEBUG(Service_LDR, +              "called, cro_address=0x%08X, zero=%d, cro_buffer_ptr=0x%08X, process=0x%08X", +              cro_address, zero, cro_buffer_ptr, process);      CROHelper cro(cro_address); -    cmd_buff[0] = IPC::MakeHeader(5, 1, 0); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (loaded_crs == 0) {          LOG_ERROR(Service_LDR, "Not initialized"); -        cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; +        rb.Push(ERROR_NOT_INITIALIZED);          return;      }      if (cro_address & Memory::PAGE_MASK) {          LOG_ERROR(Service_LDR, "CRO address is not aligned"); -        cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; +        rb.Push(ERROR_MISALIGNED_ADDRESS);          return;      }      if (!cro.IsLoaded()) {          LOG_ERROR(Service_LDR, "Invalid or not loaded CRO"); -        cmd_buff[1] = ERROR_NOT_LOADED.raw; +        rb.Push(ERROR_NOT_LOADED);          return;      } @@ -535,7 +506,7 @@ static void UnloadCRO(Interface* self) {      ResultCode result = cro.Unlink(loaded_crs);      if (result.IsError()) {          LOG_ERROR(Service_LDR, "Error unlinking CRO %08X", result.raw); -        cmd_buff[1] = result.raw; +        rb.Push(result);          return;      } @@ -545,7 +516,7 @@ static void UnloadCRO(Interface* self) {          result = cro.ClearRelocations();          if (result.IsError()) {              LOG_ERROR(Service_LDR, "Error clearing relocations %08X", result.raw); -            cmd_buff[1] = result.raw; +            rb.Push(result);              return;          }      } @@ -565,7 +536,7 @@ static void UnloadCRO(Interface* self) {      Core::CPU().ClearInstructionCache(); -    cmd_buff[1] = result.raw; +    rb.Push(result);  }  /** @@ -580,40 +551,31 @@ static void UnloadCRO(Interface* self) {   *      1 : Result of function, 0 on success, otherwise error code   */  static void LinkCRO(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); -    VAddr cro_address = cmd_buff[1]; -    u32 descriptor = cmd_buff[2]; -    u32 process = cmd_buff[3]; - -    LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, descriptor=0x%08X, process=0x%08X", -              cro_address, descriptor, process); - -    if (descriptor != 0) { -        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); -        cmd_buff[0] = IPC::MakeHeader(0, 1, 0); -        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; -        return; -    } +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x06, 1, 2); +    VAddr cro_address = rp.Pop<u32>(); +    Kernel::Handle process = rp.PopHandle(); + +    LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, process=0x%08X", cro_address, process);      CROHelper cro(cro_address); -    cmd_buff[0] = IPC::MakeHeader(6, 1, 0); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (loaded_crs == 0) {          LOG_ERROR(Service_LDR, "Not initialized"); -        cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; +        rb.Push(ERROR_NOT_INITIALIZED);          return;      }      if (cro_address & Memory::PAGE_MASK) {          LOG_ERROR(Service_LDR, "CRO address is not aligned"); -        cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; +        rb.Push(ERROR_MISALIGNED_ADDRESS);          return;      }      if (!cro.IsLoaded()) {          LOG_ERROR(Service_LDR, "Invalid or not loaded CRO"); -        cmd_buff[1] = ERROR_NOT_LOADED.raw; +        rb.Push(ERROR_NOT_LOADED);          return;      } @@ -627,7 +589,7 @@ static void LinkCRO(Interface* self) {      memory_synchronizer.SynchronizeOriginalMemory();      Core::CPU().ClearInstructionCache(); -    cmd_buff[1] = result.raw; +    rb.Push(result);  }  /** @@ -642,40 +604,31 @@ static void LinkCRO(Interface* self) {   *      1 : Result of function, 0 on success, otherwise error code   */  static void UnlinkCRO(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); -    VAddr cro_address = cmd_buff[1]; -    u32 descriptor = cmd_buff[2]; -    u32 process = cmd_buff[3]; - -    LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, descriptor=0x%08X, process=0x%08X", -              cro_address, descriptor, process); - -    if (descriptor != 0) { -        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); -        cmd_buff[0] = IPC::MakeHeader(0, 1, 0); -        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; -        return; -    } +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x07, 1, 2); +    VAddr cro_address = rp.Pop<u32>(); +    Kernel::Handle process = rp.PopHandle(); + +    LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, process=0x%08X", cro_address, process);      CROHelper cro(cro_address); -    cmd_buff[0] = IPC::MakeHeader(7, 1, 0); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (loaded_crs == 0) {          LOG_ERROR(Service_LDR, "Not initialized"); -        cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; +        rb.Push(ERROR_NOT_INITIALIZED);          return;      }      if (cro_address & Memory::PAGE_MASK) {          LOG_ERROR(Service_LDR, "CRO address is not aligned"); -        cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; +        rb.Push(ERROR_MISALIGNED_ADDRESS);          return;      }      if (!cro.IsLoaded()) {          LOG_ERROR(Service_LDR, "Invalid or not loaded CRO"); -        cmd_buff[1] = ERROR_NOT_LOADED.raw; +        rb.Push(ERROR_NOT_LOADED);          return;      } @@ -689,7 +642,7 @@ static void UnlinkCRO(Interface* self) {      memory_synchronizer.SynchronizeOriginalMemory();      Core::CPU().ClearInstructionCache(); -    cmd_buff[1] = result.raw; +    rb.Push(result);  }  /** @@ -704,29 +657,21 @@ static void UnlinkCRO(Interface* self) {   *      1 : Result of function, 0 on success, otherwise error code   */  static void Shutdown(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); -    VAddr crs_buffer_ptr = cmd_buff[1]; -    u32 descriptor = cmd_buff[2]; -    u32 process = cmd_buff[3]; - -    LOG_DEBUG(Service_LDR, "called, crs_buffer_ptr=0x%08X, descriptor=0x%08X, process=0x%08X", -              crs_buffer_ptr, descriptor, process); - -    if (descriptor != 0) { -        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); -        cmd_buff[0] = IPC::MakeHeader(0, 1, 0); -        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; -        return; -    } +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x08, 1, 2); +    VAddr crs_buffer_ptr = rp.Pop<u32>(); +    Kernel::Handle process = rp.PopHandle(); + +    LOG_DEBUG(Service_LDR, "called, crs_buffer_ptr=0x%08X, process=0x%08X", crs_buffer_ptr, +              process); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (loaded_crs == 0) {          LOG_ERROR(Service_LDR, "Not initialized"); -        cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; +        rb.Push(ERROR_NOT_INITIALIZED);          return;      } -    cmd_buff[0] = IPC::MakeHeader(8, 1, 0); -      CROHelper crs(loaded_crs);      crs.Unrebase(true); @@ -744,7 +689,7 @@ static void Shutdown(Interface* self) {      }      loaded_crs = 0; -    cmd_buff[1] = result.raw; +    rb.Push(result);  }  const Interface::FunctionInfo FunctionTable[] = { diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index ef6c5ebe3..581816e81 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -2,6 +2,7 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <array>  #include <cstring>  #include <unordered_map>  #include <vector> @@ -12,6 +13,7 @@  #include "core/hle/kernel/shared_memory.h"  #include "core/hle/result.h"  #include "core/hle/service/nwm/nwm_uds.h" +#include "core/hle/service/nwm/uds_beacon.h"  #include "core/memory.h"  namespace Service { @@ -27,10 +29,12 @@ static Kernel::SharedPtr<Kernel::SharedMemory> recv_buffer_memory;  // Connection status of this 3DS.  static ConnectionStatus connection_status{}; -// Node information about the current 3DS. -// TODO(Subv): Keep an array of all nodes connected to the network, -// that data has to be retransmitted in every beacon frame. -static NodeInfo node_info; +/* Node information about the current network. + * The amount of elements in this vector is always the maximum number + * of nodes specified in the network configuration. + * The first node is always the host, so this always contains at least 1 entry. + */ +static NodeList node_info(1);  // Mapping of bind node ids to their respective events.  static std::unordered_map<u32, Kernel::SharedPtr<Kernel::Event>> bind_node_events; @@ -82,29 +86,70 @@ static void Shutdown(Interface* self) {   *      1 : Result of function, 0 on success, otherwise error code   */  static void RecvBeaconBroadcastData(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); -    u32 out_buffer_size = cmd_buff[1]; -    u32 unk1 = cmd_buff[2]; -    u32 unk2 = cmd_buff[3]; -    u32 mac_address = cmd_buff[4]; +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0F, 16, 4); -    u32 unk3 = cmd_buff[6]; +    u32 out_buffer_size = rp.Pop<u32>(); +    u32 unk1 = rp.Pop<u32>(); +    u32 unk2 = rp.Pop<u32>(); -    u32 wlan_comm_id = cmd_buff[15]; -    u32 ctr_gen_id = cmd_buff[16]; -    u32 value = cmd_buff[17]; -    u32 input_handle = cmd_buff[18]; -    u32 new_buffer_size = cmd_buff[19]; -    u32 out_buffer_ptr = cmd_buff[20]; +    MacAddress mac_address; +    rp.PopRaw(mac_address); -    cmd_buff[1] = RESULT_SUCCESS.raw; +    rp.Skip(9, false); -    LOG_WARNING(Service_NWM, -                "(STUBBED) called out_buffer_size=0x%08X, unk1=0x%08X, unk2=0x%08X," -                "mac_address=0x%08X, unk3=0x%08X, wlan_comm_id=0x%08X, ctr_gen_id=0x%08X," -                "value=%u, input_handle=0x%08X, new_buffer_size=0x%08X, out_buffer_ptr=0x%08X", -                out_buffer_size, unk1, unk2, mac_address, unk3, wlan_comm_id, ctr_gen_id, value, -                input_handle, new_buffer_size, out_buffer_ptr); +    u32 wlan_comm_id = rp.Pop<u32>(); +    u32 id = rp.Pop<u32>(); +    Kernel::Handle input_handle = rp.PopHandle(); + +    size_t desc_size; +    const VAddr out_buffer_ptr = rp.PopMappedBuffer(&desc_size); +    ASSERT(desc_size == out_buffer_size); + +    VAddr current_buffer_pos = out_buffer_ptr; +    u32 total_size = sizeof(BeaconDataReplyHeader); + +    // Retrieve all beacon frames that were received from the desired mac address. +    std::deque<WifiPacket> beacons = +        GetReceivedPackets(WifiPacket::PacketType::Beacon, mac_address); + +    BeaconDataReplyHeader data_reply_header{}; +    data_reply_header.total_entries = beacons.size(); +    data_reply_header.max_output_size = out_buffer_size; + +    Memory::WriteBlock(current_buffer_pos, &data_reply_header, sizeof(BeaconDataReplyHeader)); +    current_buffer_pos += sizeof(BeaconDataReplyHeader); + +    // Write each of the received beacons into the buffer +    for (const auto& beacon : beacons) { +        BeaconEntryHeader entry{}; +        // TODO(Subv): Figure out what this size is used for. +        entry.unk_size = sizeof(BeaconEntryHeader) + beacon.data.size(); +        entry.total_size = sizeof(BeaconEntryHeader) + beacon.data.size(); +        entry.wifi_channel = beacon.channel; +        entry.header_size = sizeof(BeaconEntryHeader); +        entry.mac_address = beacon.transmitter_address; + +        ASSERT(current_buffer_pos < out_buffer_ptr + out_buffer_size); + +        Memory::WriteBlock(current_buffer_pos, &entry, sizeof(BeaconEntryHeader)); +        current_buffer_pos += sizeof(BeaconEntryHeader); + +        Memory::WriteBlock(current_buffer_pos, beacon.data.data(), beacon.data.size()); +        current_buffer_pos += beacon.data.size(); + +        total_size += sizeof(BeaconEntryHeader) + beacon.data.size(); +    } + +    // Update the total size in the structure and write it to the buffer again. +    data_reply_header.total_size = total_size; +    Memory::WriteBlock(out_buffer_ptr, &data_reply_header, sizeof(BeaconDataReplyHeader)); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    rb.Push(RESULT_SUCCESS); + +    LOG_DEBUG(Service_NWM, "called out_buffer_size=0x%08X, wlan_comm_id=0x%08X, id=0x%08X," +                           "input_handle=0x%08X, out_buffer_ptr=0x%08X, unk1=0x%08X, unk2=0x%08X", +              out_buffer_size, wlan_comm_id, id, input_handle, out_buffer_ptr, unk1, unk2);  }  /** @@ -127,10 +172,10 @@ static void InitializeWithVersion(Interface* self) {      u32 sharedmem_size = rp.Pop<u32>();      // Update the node information with the data the game gave us. -    rp.PopRaw(node_info); +    rp.PopRaw(node_info[0]); + +    u16 version = rp.Pop<u16>(); -    u16 version; -    rp.PopRaw(version);      Kernel::Handle sharedmem_handle = rp.PopHandle();      recv_buffer_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(sharedmem_handle); @@ -191,10 +236,8 @@ static void Bind(Interface* self) {      u32 bind_node_id = rp.Pop<u32>();      u32 recv_buffer_size = rp.Pop<u32>(); -    u8 data_channel; -    rp.PopRaw(data_channel); -    u16 network_node_id; -    rp.PopRaw(network_node_id); +    u8 data_channel = rp.Pop<u8>(); +    u16 network_node_id = rp.Pop<u16>();      // TODO(Subv): Store the data channel and verify it when receiving data frames. @@ -251,13 +294,25 @@ static void BeginHostingNetwork(Interface* self) {      ASSERT_MSG(network_info.max_nodes > 1, "Trying to host a network of only one member.");      connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsHost); + +    // Ensure the application data size is less than the maximum value. +    ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, "Data size is too big."); + +    // Set up basic information for this network. +    network_info.oui_value = NintendoOUI; +    network_info.oui_type = static_cast<u8>(NintendoTagId::NetworkInfo); +      connection_status.max_nodes = network_info.max_nodes; +    // Resize the nodes list to hold max_nodes. +    node_info.resize(network_info.max_nodes); +      // There's currently only one node in the network (the host).      connection_status.total_nodes = 1; +    network_info.total_nodes = 1;      // The host is always the first node      connection_status.network_node_id = 1; -    node_info.network_node_id = 1; +    node_info[0].network_node_id = 1;      // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken.      connection_status.node_bitmask |= 1; @@ -325,7 +380,7 @@ static void GetChannel(Interface* self) {      u8 channel = is_connected ? network_channel : 0;      rb.Push(RESULT_SUCCESS); -    rb.PushRaw(channel); +    rb.Push(channel);      LOG_DEBUG(Service_NWM, "called");  } @@ -373,7 +428,8 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) {      if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost))          return; -    // TODO(Subv): Actually generate the beacon and send it. +    // TODO(Subv): Actually send the beacon. +    std::vector<u8> frame = GenerateBeaconFrame(network_info, node_info);      // Start broadcasting the network, send a beacon frame every 102.4ms.      CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - cycles_late, diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h index 65349f9fd..29b146569 100644 --- a/src/core/hle/service/nwm/nwm_uds.h +++ b/src/core/hle/service/nwm/nwm_uds.h @@ -6,6 +6,7 @@  #include <array>  #include <cstddef> +#include <vector>  #include "common/common_types.h"  #include "common/swap.h"  #include "core/hle/service/service.h" @@ -33,6 +34,8 @@ struct NodeInfo {  static_assert(sizeof(NodeInfo) == 40, "NodeInfo has incorrect size."); +using NodeList = std::vector<NodeInfo>; +  enum class NetworkStatus {      NotConnected = 3,      ConnectedAsHost = 6, @@ -75,6 +78,8 @@ struct NetworkInfo {      std::array<u8, ApplicationDataSize> application_data;  }; +static_assert(offsetof(NetworkInfo, oui_value) == 0xC, "oui_value is at the wrong offset."); +static_assert(offsetof(NetworkInfo, wlan_comm_id) == 0x10, "wlancommid is at the wrong offset.");  static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size.");  class NWM_UDS final : public Interface { diff --git a/src/core/hle/service/nwm/uds_beacon.cpp b/src/core/hle/service/nwm/uds_beacon.cpp new file mode 100644 index 000000000..c6e5bc5f1 --- /dev/null +++ b/src/core/hle/service/nwm/uds_beacon.cpp @@ -0,0 +1,333 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// 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> + +namespace Service { +namespace NWM { + +// 802.11 broadcast MAC address +constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +constexpr u64 DefaultNetworkUptime = 900000000; // 15 minutes in microseconds. + +// Note: These values were taken from a packet capture of an o3DS XL +// broadcasting a Super Smash Bros. 4 lobby. +constexpr u16 DefaultExtraCapabilities = 0x0431; + +// Size of the SSID broadcast by an UDS beacon frame. +constexpr u8 UDSBeaconSSIDSize = 8; + +// The maximum size of the data stored in the EncryptedData0 tag (24). +constexpr u32 EncryptedDataSizeCutoff = 0xFA; + +/** + * NWM Beacon data encryption key, taken from the NWM module code. + * We stub this with an all-zeros key as that is enough for Citra's purpose. + * The real key can be used here to generate beacons that will be accepted by + * a real 3ds. + */ +constexpr std::array<u8, CryptoPP::AES::BLOCKSIZE> nwm_beacon_key = {}; + +/** + * Generates a buffer with the fixed parameters of an 802.11 Beacon frame + * using dummy values. + * @returns A buffer with the fixed parameters of the beacon frame. + */ +std::vector<u8> GenerateFixedParameters() { +    std::vector<u8> buffer(sizeof(BeaconFrameHeader)); + +    BeaconFrameHeader header{}; +    // Use a fixed default time for now. +    // TODO(Subv): Perhaps use the difference between now and the time the network was started? +    header.timestamp = DefaultNetworkUptime; +    header.beacon_interval = DefaultBeaconInterval; +    header.capabilities = DefaultExtraCapabilities; + +    std::memcpy(buffer.data(), &header, sizeof(header)); + +    return buffer; +} + +/** + * Generates an SSID tag of an 802.11 Beacon frame with an 8-byte all-zero SSID value. + * @returns A buffer with the SSID tag. + */ +std::vector<u8> GenerateSSIDTag() { +    std::vector<u8> buffer(sizeof(TagHeader) + UDSBeaconSSIDSize); + +    TagHeader tag_header{}; +    tag_header.tag_id = static_cast<u8>(TagId::SSID); +    tag_header.length = UDSBeaconSSIDSize; + +    std::memcpy(buffer.data(), &tag_header, sizeof(TagHeader)); + +    // The rest of the buffer is already filled with zeros. + +    return buffer; +} + +/** + * Generates a buffer with the basic tagged parameters of an 802.11 Beacon frame + * such as SSID, Rate Information, Country Information, etc. + * @returns A buffer with the tagged parameters of the beacon frame. + */ +std::vector<u8> GenerateBasicTaggedParameters() { +    // Append the SSID tag +    std::vector<u8> buffer = GenerateSSIDTag(); + +    // TODO(Subv): Add the SupportedRates tag. +    // TODO(Subv): Add the DSParameterSet tag. +    // TODO(Subv): Add the TrafficIndicationMap tag. +    // TODO(Subv): Add the CountryInformation tag. +    // TODO(Subv): Add the ERPInformation tag. + +    return buffer; +} + +/** + * Generates a buffer with the Dummy Nintendo tag. + * It is currently unknown what this tag does. + * TODO(Subv): Figure out if this is needed and what it does. + * @returns A buffer with the Nintendo tagged parameters of the beacon frame. + */ +std::vector<u8> GenerateNintendoDummyTag() { +    // Note: These values were taken from a packet capture of an o3DS XL +    // broadcasting a Super Smash Bros. 4 lobby. +    constexpr std::array<u8, 3> dummy_data = {0x0A, 0x00, 0x00}; + +    DummyTag tag{}; +    tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); +    tag.header.length = sizeof(DummyTag) - sizeof(TagHeader); +    tag.oui_type = static_cast<u8>(NintendoTagId::Dummy); +    tag.oui = NintendoOUI; +    tag.data = dummy_data; + +    std::vector<u8> buffer(sizeof(DummyTag)); +    std::memcpy(buffer.data(), &tag, sizeof(DummyTag)); + +    return buffer; +} + +/** + * Generates a buffer with the Network Info Nintendo tag. + * This tag contains the network information of the network that is being broadcast. + * It also contains the application data provided by the application that opened the network. + * @returns A buffer with the Nintendo network info parameter of the beacon frame. + */ +std::vector<u8> GenerateNintendoNetworkInfoTag(const NetworkInfo& network_info) { +    NetworkInfoTag tag{}; +    tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); +    tag.header.length = +        sizeof(NetworkInfoTag) - sizeof(TagHeader) + network_info.application_data_size; +    tag.appdata_size = network_info.application_data_size; +    // Set the hash to zero initially, it will be updated once we calculate it. +    tag.sha_hash = {}; + +    // Ensure the network structure has the correct OUI and OUI type. +    ASSERT(network_info.oui_type == static_cast<u8>(NintendoTagId::NetworkInfo)); +    ASSERT(network_info.oui_value == NintendoOUI); + +    // Ensure the application data size is less than the maximum value. +    ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, "Data size is too big."); + +    // This tag contains the network info structure starting at the OUI. +    std::memcpy(tag.network_info.data(), &network_info.oui_value, tag.network_info.size()); + +    // Copy the tag and the data so we can calculate the SHA1 over it. +    std::vector<u8> buffer(sizeof(tag) + network_info.application_data_size); +    std::memcpy(buffer.data(), &tag, sizeof(tag)); +    std::memcpy(buffer.data() + sizeof(tag), network_info.application_data.data(), +                network_info.application_data_size); + +    // Calculate the SHA1 of the contents of the tag. +    std::array<u8, CryptoPP::SHA1::DIGESTSIZE> hash; +    CryptoPP::SHA1().CalculateDigest(hash.data(), +                                     buffer.data() + offsetof(NetworkInfoTag, network_info), +                                     buffer.size() - sizeof(TagHeader)); + +    // Copy it directly into the buffer, overwriting the zeros that we had previously placed there. +    std::memcpy(buffer.data() + offsetof(NetworkInfoTag, sha_hash), hash.data(), hash.size()); + +    return buffer; +} + +/* + * Calculates the CTR used for the AES-CTR encryption of the data stored in the + * EncryptedDataTags. + * @returns The CTR used for beacon crypto. + */ +std::array<u8, CryptoPP::AES::BLOCKSIZE> GetBeaconCryptoCTR(const NetworkInfo& network_info) { +    BeaconDataCryptoCTR data{}; + +    data.host_mac = network_info.host_mac_address; +    data.wlan_comm_id = network_info.wlan_comm_id; +    data.id = network_info.id; +    data.network_id = network_info.network_id; + +    std::array<u8, CryptoPP::AES::BLOCKSIZE> hash; +    std::memcpy(hash.data(), &data, sizeof(data)); + +    return hash; +} + +/* + * Serializes the node information into the format needed for network transfer, + * and then encrypts it with the NWM key. + * @returns The serialized and encrypted node information. + */ +std::vector<u8> GeneratedEncryptedData(const NetworkInfo& network_info, const NodeList& nodes) { +    std::vector<u8> buffer(sizeof(BeaconData)); + +    BeaconData data{}; +    std::memcpy(buffer.data(), &data, sizeof(BeaconData)); + +    for (const NodeInfo& node : nodes) { +        // Serialize each node and convert the data from +        // host byte-order to Big Endian. +        BeaconNodeInfo info{}; +        info.friend_code_seed = node.friend_code_seed; +        info.network_node_id = node.network_node_id; +        for (int i = 0; i < info.username.size(); ++i) +            info.username[i] = node.username[i]; + +        buffer.insert(buffer.end(), reinterpret_cast<u8*>(&info), +                      reinterpret_cast<u8*>(&info) + sizeof(info)); +    } + +    // Calculate the MD5 hash of the data in the buffer, not including the hash field. +    std::array<u8, CryptoPP::MD5::DIGESTSIZE> hash; +    CryptoPP::MD5().CalculateDigest(hash.data(), buffer.data() + offsetof(BeaconData, bitmask), +                                    buffer.size() - sizeof(data.md5_hash)); + +    // Copy the hash into the buffer. +    std::memcpy(buffer.data(), hash.data(), hash.size()); + +    // Encrypt the data using AES-CTR and the NWM beacon key. +    using CryptoPP::AES; +    std::array<u8, AES::BLOCKSIZE> counter = GetBeaconCryptoCTR(network_info); +    CryptoPP::CTR_Mode<AES>::Encryption aes; +    aes.SetKeyWithIV(nwm_beacon_key.data(), AES::BLOCKSIZE, counter.data()); +    aes.ProcessData(buffer.data(), buffer.data(), buffer.size()); + +    return buffer; +} + +void DecryptBeaconData(const NetworkInfo& network_info, std::vector<u8>& buffer) { +    // Decrypt the data using AES-CTR and the NWM beacon key. +    using CryptoPP::AES; +    std::array<u8, AES::BLOCKSIZE> counter = GetBeaconCryptoCTR(network_info); +    CryptoPP::CTR_Mode<AES>::Decryption aes; +    aes.SetKeyWithIV(nwm_beacon_key.data(), AES::BLOCKSIZE, counter.data()); +    aes.ProcessData(buffer.data(), buffer.data(), buffer.size()); +} + +/** + * Generates a buffer with the Network Info Nintendo tag. + * This tag contains the first portion of the encrypted payload in the 802.11 beacon frame. + * The encrypted payload contains information about the nodes currently connected to the network. + * @returns A buffer with the first Nintendo encrypted data parameters of the beacon frame. + */ +std::vector<u8> GenerateNintendoFirstEncryptedDataTag(const NetworkInfo& network_info, +                                                      const NodeList& nodes) { +    const size_t payload_size = +        std::min<size_t>(EncryptedDataSizeCutoff, nodes.size() * sizeof(NodeInfo)); + +    EncryptedDataTag tag{}; +    tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); +    tag.header.length = sizeof(tag) - sizeof(TagHeader) + payload_size; +    tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData0); +    tag.oui = NintendoOUI; + +    std::vector<u8> buffer(sizeof(tag) + payload_size); +    std::memcpy(buffer.data(), &tag, sizeof(tag)); + +    std::vector<u8> encrypted_data = GeneratedEncryptedData(network_info, nodes); +    std::memcpy(buffer.data() + sizeof(tag), encrypted_data.data(), payload_size); + +    return buffer; +} + +/** + * Generates a buffer with the Network Info Nintendo tag. + * This tag contains the second portion of the encrypted payload in the 802.11 beacon frame. + * The encrypted payload contains information about the nodes currently connected to the network. + * This tag is only present if the payload size is greater than EncryptedDataSizeCutoff (0xFA) + * bytes. + * @returns A buffer with the second Nintendo encrypted data parameters of the beacon frame. + */ +std::vector<u8> GenerateNintendoSecondEncryptedDataTag(const NetworkInfo& network_info, +                                                       const NodeList& nodes) { +    // This tag is only present if the payload is larger than EncryptedDataSizeCutoff (0xFA). +    if (nodes.size() * sizeof(NodeInfo) <= EncryptedDataSizeCutoff) +        return {}; + +    const size_t payload_size = nodes.size() * sizeof(NodeInfo) - EncryptedDataSizeCutoff; + +    const size_t tag_length = sizeof(EncryptedDataTag) - sizeof(TagHeader) + payload_size; + +    // TODO(Subv): What does the 3DS do when a game has too much data to fit into the tag? +    ASSERT_MSG(tag_length <= 255, "Data is too big."); + +    EncryptedDataTag tag{}; +    tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); +    tag.header.length = tag_length; +    tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData1); +    tag.oui = NintendoOUI; + +    std::vector<u8> buffer(sizeof(tag) + payload_size); +    std::memcpy(buffer.data(), &tag, sizeof(tag)); + +    std::vector<u8> encrypted_data = GeneratedEncryptedData(network_info, nodes); +    std::memcpy(buffer.data() + sizeof(tag), encrypted_data.data() + EncryptedDataSizeCutoff, +                payload_size); + +    return buffer; +} + +/** + * Generates a buffer with the Nintendo tagged parameters of an 802.11 Beacon frame + * for UDS communication. + * @returns A buffer with the Nintendo tagged parameters of the beacon frame. + */ +std::vector<u8> GenerateNintendoTaggedParameters(const NetworkInfo& network_info, +                                                 const NodeList& nodes) { +    ASSERT_MSG(network_info.max_nodes == nodes.size(), "Inconsistent network state."); + +    std::vector<u8> buffer = GenerateNintendoDummyTag(); +    std::vector<u8> network_info_tag = GenerateNintendoNetworkInfoTag(network_info); +    std::vector<u8> first_data_tag = GenerateNintendoFirstEncryptedDataTag(network_info, nodes); +    std::vector<u8> second_data_tag = GenerateNintendoSecondEncryptedDataTag(network_info, nodes); + +    buffer.insert(buffer.end(), network_info_tag.begin(), network_info_tag.end()); +    buffer.insert(buffer.end(), first_data_tag.begin(), first_data_tag.end()); +    buffer.insert(buffer.end(), second_data_tag.begin(), second_data_tag.end()); + +    return buffer; +} + +std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes) { +    std::vector<u8> buffer = GenerateFixedParameters(); +    std::vector<u8> basic_tags = GenerateBasicTaggedParameters(); +    std::vector<u8> nintendo_tags = GenerateNintendoTaggedParameters(network_info, nodes); + +    buffer.insert(buffer.end(), basic_tags.begin(), basic_tags.end()); +    buffer.insert(buffer.end(), nintendo_tags.begin(), nintendo_tags.end()); + +    return buffer; +} + +std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender) { +    return {}; +} +} // namespace NWM +} // namespace Service diff --git a/src/core/hle/service/nwm/uds_beacon.h b/src/core/hle/service/nwm/uds_beacon.h new file mode 100644 index 000000000..6df4c4f47 --- /dev/null +++ b/src/core/hle/service/nwm/uds_beacon.h @@ -0,0 +1,173 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <deque> +#include <vector> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/service/service.h" + +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 +enum class TagId : u8 { +    SSID = 0, +    SupportedRates = 1, +    DSParameterSet = 2, +    TrafficIndicationMap = 5, +    CountryInformation = 7, +    ERPInformation = 42, +    VendorSpecific = 221 +}; + +/** + * Internal vendor-specific tag ids as stored inside + * VendorSpecific blocks in the Beacon frames. + */ +enum class NintendoTagId : u8 { +    Dummy = 20, +    NetworkInfo = 21, +    EncryptedData0 = 24, +    EncryptedData1 = 25, +}; + +struct BeaconEntryHeader { +    u32_le total_size; +    INSERT_PADDING_BYTES(1); +    u8 wifi_channel; +    INSERT_PADDING_BYTES(2); +    MacAddress mac_address; +    INSERT_PADDING_BYTES(6); +    u32_le unk_size; +    u32_le header_size; +}; + +static_assert(sizeof(BeaconEntryHeader) == 0x1C, "BeaconEntryHeader has incorrect size."); + +struct BeaconDataReplyHeader { +    u32_le max_output_size; +    u32_le total_size; +    u32_le total_entries; +}; + +static_assert(sizeof(BeaconDataReplyHeader) == 12, "BeaconDataReplyHeader has incorrect size."); + +#pragma pack(push, 1) +struct BeaconFrameHeader { +    // Number of microseconds the AP has been active. +    u64_le timestamp; +    // Interval between beacon transmissions, expressed in TU. +    u16_le beacon_interval; +    // Indicates the presence of optional capabilities. +    u16_le capabilities; +}; +#pragma pack(pop) + +static_assert(sizeof(BeaconFrameHeader) == 12, "BeaconFrameHeader has incorrect size."); + +struct TagHeader { +    u8 tag_id; +    u8 length; +}; + +static_assert(sizeof(TagHeader) == 2, "TagHeader has incorrect size."); + +struct DummyTag { +    TagHeader header; +    std::array<u8, 3> oui; +    u8 oui_type; +    std::array<u8, 3> data; +}; + +static_assert(sizeof(DummyTag) == 9, "DummyTag has incorrect size."); + +struct NetworkInfoTag { +    TagHeader header; +    std::array<u8, 0x1F> network_info; +    std::array<u8, 0x14> sha_hash; +    u8 appdata_size; +}; + +static_assert(sizeof(NetworkInfoTag) == 54, "NetworkInfoTag has incorrect size."); + +struct EncryptedDataTag { +    TagHeader header; +    std::array<u8, 3> oui; +    u8 oui_type; +}; + +static_assert(sizeof(EncryptedDataTag) == 6, "EncryptedDataTag has incorrect size."); + +#pragma pack(push, 1) +// The raw bytes of this structure are the CTR used in the encryption (AES-CTR) +// of the beacon data stored in the EncryptedDataTags. +struct BeaconDataCryptoCTR { +    MacAddress host_mac; +    u32_le wlan_comm_id; +    u8 id; +    INSERT_PADDING_BYTES(1); +    u32_le network_id; +}; + +static_assert(sizeof(BeaconDataCryptoCTR) == 0x10, "BeaconDataCryptoCTR has incorrect size."); + +struct BeaconNodeInfo { +    u64_be friend_code_seed; +    std::array<u16_be, 10> username; +    u16_be network_node_id; +}; + +static_assert(sizeof(BeaconNodeInfo) == 0x1E, "BeaconNodeInfo has incorrect size."); + +struct BeaconData { +    std::array<u8, 0x10> md5_hash; +    u16_be bitmask; +}; +#pragma pack(pop) + +static_assert(sizeof(BeaconData) == 0x12, "BeaconData has incorrect size."); + +/// Information about a received WiFi packet. +/// Acts as our own 802.11 header. +struct WifiPacket { +    enum class PacketType { Beacon, Data }; + +    PacketType type; ///< The type of 802.11 frame, Beacon / Data. + +    /// Raw 802.11 frame data, starting at the management frame header for management frames. +    std::vector<u8> data; +    MacAddress transmitter_address; ///< Mac address of the transmitter. +    MacAddress destination_address; ///< Mac address of the receiver. +    u8 channel;                     ///< WiFi channel where this frame was transmitted. +}; + +/** + * Decrypts the beacon data buffer for the network described by `network_info`. + */ +void DecryptBeaconData(const NetworkInfo& network_info, std::vector<u8>& buffer); + +/** + * Generates an 802.11 beacon frame starting at the management frame header. + * This frame contains information about the network and its connected clients. + * @returns The generated frame. + */ +std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes); + +/** + * Returns a list of received 802.11 frames from the specified sender + * matching the type since the last call. + */ +std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender); +} // namespace NWM +} // namespace Service diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index e373ed47a..319e8c946 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -27,67 +27,72 @@ static bool shell_open;  static bool battery_is_charging; -void GetAdapterState(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +static bool pedometer_is_counting; -    // TODO(purpasmart96): This function is only a stub, -    // it returns a valid result without implementing full functionality. +void GetAdapterState(Service::Interface* self) { +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x5, 0, 0); -    cmd_buff[1] = RESULT_SUCCESS.raw; -    cmd_buff[2] = battery_is_charging ? 1 : 0; +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); +    rb.Push(RESULT_SUCCESS); +    rb.Push(battery_is_charging);      LOG_WARNING(Service_PTM, "(STUBBED) called");  }  void GetShellState(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x6, 0, 0); -    cmd_buff[1] = RESULT_SUCCESS.raw; -    cmd_buff[2] = shell_open ? 1 : 0; +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); +    rb.Push(RESULT_SUCCESS); +    rb.Push(shell_open);  }  void GetBatteryLevel(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x7, 0, 0); -    // TODO(purpasmart96): This function is only a stub, -    // it returns a valid result without implementing full functionality. - -    cmd_buff[1] = RESULT_SUCCESS.raw; -    cmd_buff[2] = -        static_cast<u32>(ChargeLevels::CompletelyFull); // Set to a completely full battery +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); +    rb.Push(RESULT_SUCCESS); +    rb.Push(static_cast<u32>(ChargeLevels::CompletelyFull)); // Set to a completely full battery      LOG_WARNING(Service_PTM, "(STUBBED) called");  }  void GetBatteryChargeState(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x8, 0, 0); -    // TODO(purpasmart96): This function is only a stub, -    // it returns a valid result without implementing full functionality. +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); +    rb.Push(RESULT_SUCCESS); +    rb.Push(battery_is_charging); -    cmd_buff[1] = RESULT_SUCCESS.raw; -    cmd_buff[2] = battery_is_charging ? 1 : 0; +    LOG_WARNING(Service_PTM, "(STUBBED) called"); +} + +void GetPedometerState(Service::Interface* self) { +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x9, 0, 0); + +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); +    rb.Push(RESULT_SUCCESS); +    rb.Push(pedometer_is_counting);      LOG_WARNING(Service_PTM, "(STUBBED) called");  }  void GetTotalStepCount(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xC, 0, 0); -    // TODO: This function is only a stub, -    // it returns 0 as the total step count - -    cmd_buff[1] = RESULT_SUCCESS.raw; -    cmd_buff[2] = 0; +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); +    rb.Push(RESULT_SUCCESS); +    rb.Push<u32>(0);      LOG_WARNING(Service_PTM, "(STUBBED) called");  }  void GetSoftwareClosedFlag(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x80F, 0, 0); -    cmd_buff[1] = RESULT_SUCCESS.raw; -    cmd_buff[2] = 0; +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); +    rb.Push(RESULT_SUCCESS); +    rb.Push(false);      LOG_WARNING(Service_PTM, "(STUBBED) called");  } @@ -121,6 +126,7 @@ void Init() {      shell_open = true;      battery_is_charging = true; +    pedometer_is_counting = false;      // Open the SharedExtSaveData archive 0xF000000B and create the gamecoin.dat file if it doesn't      // exist diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h index 683fb445b..e17e59835 100644 --- a/src/core/hle/service/ptm/ptm.h +++ b/src/core/hle/service/ptm/ptm.h @@ -75,6 +75,14 @@ void GetBatteryLevel(Interface* self);  void GetBatteryChargeState(Interface* self);  /** + * PTM::GetPedometerState service function + *  Outputs: + *      1 : Result of function, 0 on success, otherwise error code + *      2 : Output of function, 0 = not counting steps, 1 = counting steps. + */ +void GetPedometerState(Interface* self); + +/**   * PTM::GetTotalStepCount service function   *  Outputs:   *      1 : Result of function, 0 on success, otherwise error code diff --git a/src/core/hle/service/ptm/ptm_u.cpp b/src/core/hle/service/ptm/ptm_u.cpp index e0b65ba89..696a58a36 100644 --- a/src/core/hle/service/ptm/ptm_u.cpp +++ b/src/core/hle/service/ptm/ptm_u.cpp @@ -17,7 +17,7 @@ const Interface::FunctionInfo FunctionTable[] = {      {0x00060000, GetShellState, "GetShellState"},      {0x00070000, GetBatteryLevel, "GetBatteryLevel"},      {0x00080000, GetBatteryChargeState, "GetBatteryChargeState"}, -    {0x00090000, nullptr, "GetPedometerState"}, +    {0x00090000, GetPedometerState, "GetPedometerState"},      {0x000A0042, nullptr, "GetStepHistoryEntry"},      {0x000B00C2, nullptr, "GetStepHistory"},      {0x000C0000, GetTotalStepCount, "GetTotalStepCount"}, diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 2db823c61..8538cfc9d 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -2,12 +2,12 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <cinttypes>  #include <map>  #include "common/logging/log.h"  #include "common/microprofile.h"  #include "common/scope_exit.h"  #include "common/string_util.h" -#include "common/symbols.h"  #include "core/arm/arm_interface.h"  #include "core/core_timing.h"  #include "core/hle/function_wrappers.h" @@ -524,13 +524,7 @@ static ResultCode CreateThread(Kernel::Handle* out_handle, s32 priority, u32 ent                                 u32 stack_top, s32 processor_id) {      using Kernel::Thread; -    std::string name; -    if (Symbols::HasSymbol(entry_point)) { -        TSymbol symbol = Symbols::GetSymbol(entry_point); -        name = symbol.name; -    } else { -        name = Common::StringFromFormat("unknown-%08x", entry_point); -    } +    std::string name = Common::StringFromFormat("unknown-%08" PRIX32, entry_point);      if (priority > THREADPRIO_LOWEST) {          return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS, diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 8eb5200ab..cfcde9167 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -8,7 +8,6 @@  #include "common/common_types.h"  #include "common/file_util.h"  #include "common/logging/log.h" -#include "common/symbols.h"  #include "core/hle/kernel/process.h"  #include "core/hle/kernel/resource_limit.h"  #include "core/loader/elf.h" @@ -210,7 +209,6 @@ public:          return (u32)(header->e_flags);      }      SharedPtr<CodeSet> LoadInto(u32 vaddr); -    bool LoadSymbols();      int GetNumSegments() const {          return (int)(header->e_phnum); @@ -258,8 +256,6 @@ ElfReader::ElfReader(void* ptr) {      sections = (Elf32_Shdr*)(base + header->e_shoff);      entryPoint = header->e_entry; - -    LoadSymbols();  }  const char* ElfReader::GetSectionName(int section) const { @@ -362,34 +358,6 @@ SectionID ElfReader::GetSectionByName(const char* name, int firstSection) const      return -1;  } -bool ElfReader::LoadSymbols() { -    bool hasSymbols = false; -    SectionID sec = GetSectionByName(".symtab"); -    if (sec != -1) { -        int stringSection = sections[sec].sh_link; -        const char* stringBase = reinterpret_cast<const char*>(GetSectionDataPtr(stringSection)); - -        // We have a symbol table! -        const Elf32_Sym* symtab = reinterpret_cast<const Elf32_Sym*>(GetSectionDataPtr(sec)); -        unsigned int numSymbols = sections[sec].sh_size / sizeof(Elf32_Sym); -        for (unsigned sym = 0; sym < numSymbols; sym++) { -            int size = symtab[sym].st_size; -            if (size == 0) -                continue; - -            int type = symtab[sym].st_info & 0xF; - -            const char* name = stringBase + symtab[sym].st_name; - -            Symbols::Add(symtab[sym].st_value, name, size, type); - -            hasSymbols = true; -        } -    } - -    return hasSymbols; -} -  ////////////////////////////////////////////////////////////////////////////////////////////////////  // Loader namespace diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 65e4bba85..b8438e490 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -672,12 +672,14 @@ PAddr VirtualToPhysicalAddress(const VAddr addr) {          return addr - VRAM_VADDR + VRAM_PADDR;      } else if (addr >= LINEAR_HEAP_VADDR && addr < LINEAR_HEAP_VADDR_END) {          return addr - LINEAR_HEAP_VADDR + FCRAM_PADDR; +    } else if (addr >= NEW_LINEAR_HEAP_VADDR && addr < NEW_LINEAR_HEAP_VADDR_END) { +        return addr - NEW_LINEAR_HEAP_VADDR + FCRAM_PADDR;      } else if (addr >= DSP_RAM_VADDR && addr < DSP_RAM_VADDR_END) {          return addr - DSP_RAM_VADDR + DSP_RAM_PADDR;      } else if (addr >= IO_AREA_VADDR && addr < IO_AREA_VADDR_END) {          return addr - IO_AREA_VADDR + IO_AREA_PADDR; -    } else if (addr >= NEW_LINEAR_HEAP_VADDR && addr < NEW_LINEAR_HEAP_VADDR_END) { -        return addr - NEW_LINEAR_HEAP_VADDR + FCRAM_PADDR; +    } else if (addr >= N3DS_EXTRA_RAM_VADDR && addr < N3DS_EXTRA_RAM_VADDR_END) { +        return addr - N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_PADDR;      }      LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%08X", addr); @@ -696,6 +698,8 @@ VAddr PhysicalToVirtualAddress(const PAddr addr) {          return addr - DSP_RAM_PADDR + DSP_RAM_VADDR;      } else if (addr >= IO_AREA_PADDR && addr < IO_AREA_PADDR_END) {          return addr - IO_AREA_PADDR + IO_AREA_VADDR; +    } else if (addr >= N3DS_EXTRA_RAM_PADDR && addr < N3DS_EXTRA_RAM_PADDR_END) { +        return addr - N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_VADDR;      }      LOG_ERROR(HW_Memory, "Unknown physical address @ 0x%08X", addr); diff --git a/src/core/memory.h b/src/core/memory.h index 903b58a22..802aa465e 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -37,6 +37,12 @@ enum : PAddr {      VRAM_SIZE = 0x00600000, ///< VRAM size (6MB)      VRAM_PADDR_END = VRAM_PADDR + VRAM_SIZE, +    /// New 3DS additional memory. Supposedly faster than regular FCRAM. Part of it can be used by +    /// applications and system modules if mapped via the ExHeader. +    N3DS_EXTRA_RAM_PADDR = 0x1F000000, +    N3DS_EXTRA_RAM_SIZE = 0x00400000, ///< New 3DS additional memory size (4MB) +    N3DS_EXTRA_RAM_PADDR_END = N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_SIZE, +      /// DSP memory      DSP_RAM_PADDR = 0x1FF00000,      DSP_RAM_SIZE = 0x00080000, ///< DSP memory size (512KB) @@ -81,6 +87,10 @@ enum : VAddr {      LINEAR_HEAP_SIZE = 0x08000000,      LINEAR_HEAP_VADDR_END = LINEAR_HEAP_VADDR + LINEAR_HEAP_SIZE, +    /// Maps 1:1 to New 3DS additional memory +    N3DS_EXTRA_RAM_VADDR = 0x1E800000, +    N3DS_EXTRA_RAM_VADDR_END = N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_SIZE, +      /// Maps 1:1 to the IO register area.      IO_AREA_VADDR = 0x1EC00000,      IO_AREA_VADDR_END = IO_AREA_VADDR + IO_AREA_SIZE, diff --git a/src/core/settings.cpp b/src/core/settings.cpp index a598f9f2f..d2e7c6b97 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -5,6 +5,7 @@  #include "audio_core/audio_core.h"  #include "core/gdbstub/gdbstub.h"  #include "core/hle/service/hid/hid.h" +#include "core/hle/service/ir/ir.h"  #include "settings.h"  #include "video_core/video_core.h" @@ -32,6 +33,7 @@ void Apply() {      AudioCore::EnableStretching(values.enable_audio_stretching);      Service::HID::ReloadInputDevices(); +    Service::IR::ReloadInputDevices();  }  } // namespace diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp index ae0206909..756ee58b7 100644 --- a/src/input_common/sdl/sdl.cpp +++ b/src/input_common/sdl/sdl.cpp @@ -8,6 +8,7 @@  #include <tuple>  #include <unordered_map>  #include <SDL.h> +#include "common/logging/log.h"  #include "common/math_util.h"  #include "input_common/sdl/sdl.h" @@ -40,12 +41,16 @@ public:          return SDL_JoystickGetButton(joystick.get(), button) == 1;      } -    std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const { +    float GetAxis(int axis) const {          if (!joystick)              return {};          SDL_JoystickUpdate(); -        float x = SDL_JoystickGetAxis(joystick.get(), axis_x) / 32767.0f; -        float y = SDL_JoystickGetAxis(joystick.get(), axis_y) / 32767.0f; +        return SDL_JoystickGetAxis(joystick.get(), axis) / 32767.0f; +    } + +    std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const { +        float x = GetAxis(axis_x); +        float y = GetAxis(axis_y);          y = -y; // 3DS uses an y-axis inverse from SDL          // Make sure the coordinates are in the unit circle, @@ -97,6 +102,27 @@ private:      Uint8 direction;  }; +class SDLAxisButton final : public Input::ButtonDevice { +public: +    explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_, +                           bool trigger_if_greater_) +        : joystick(joystick_), axis(axis_), threshold(threshold_), +          trigger_if_greater(trigger_if_greater_) {} + +    bool GetStatus() const override { +        float axis_value = joystick->GetAxis(axis); +        if (trigger_if_greater) +            return axis_value > threshold; +        return axis_value < threshold; +    } + +private: +    std::shared_ptr<SDLJoystick> joystick; +    int axis; +    float threshold; +    bool trigger_if_greater; +}; +  class SDLAnalog final : public Input::AnalogDevice {  public:      SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_) @@ -130,8 +156,14 @@ public:       *     - "joystick": the index of the joystick to bind       *     - "button"(optional): the index of the button to bind       *     - "hat"(optional): the index of the hat to bind as direction buttons +     *     - "axis"(optional): the index of the axis to bind       *     - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", -     *                                     "down", "left" or "right" +     *         "down", "left" or "right" +     *     - "threshould"(only used for axis): a float value in (-1.0, 1.0) which the button is +     *         triggered if the axis value crosses +     *     - "direction"(only used for axis): "+" means the button is triggered when the axis value +     *         is greater than the threshold; "-" means the button is triggered when the axis value +     *         is smaller than the threshold       */      std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {          const int joystick_index = params.Get("joystick", 0); @@ -155,6 +187,23 @@ public:                                                          direction);          } +        if (params.Has("axis")) { +            const int axis = params.Get("axis", 0); +            const float threshold = params.Get("threshold", 0.5f); +            const std::string direction_name = params.Get("direction", ""); +            bool trigger_if_greater; +            if (direction_name == "+") { +                trigger_if_greater = true; +            } else if (direction_name == "-") { +                trigger_if_greater = false; +            } else { +                trigger_if_greater = true; +                LOG_ERROR(Input, "Unknown direction %s", direction_name.c_str()); +            } +            return std::make_unique<SDLAxisButton>(GetJoystick(joystick_index), axis, threshold, +                                                   trigger_if_greater); +        } +          const int button = params.Get("button", 0);          return std::make_unique<SDLButton>(GetJoystick(joystick_index), button);      } diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 2e32ff905..8d3f76bde 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -32,12 +32,13 @@ namespace Pica {  namespace CommandProcessor { -static int float_regs_counter = 0; +static int vs_float_regs_counter = 0; +static u32 vs_uniform_write_buffer[4]; -static u32 uniform_write_buffer[4]; +static int gs_float_regs_counter = 0; +static u32 gs_uniform_write_buffer[4];  static int default_attr_counter = 0; -  static u32 default_attr_write_buffer[3];  // Expand a 4-bit mask to 4-byte mask, e.g. 0b0101 -> 0x00FF00FF @@ -48,6 +49,97 @@ static const u32 expand_bits_to_bytes[] = {  MICROPROFILE_DEFINE(GPU_Drawing, "GPU", "Drawing", MP_RGB(50, 50, 240)); +static const char* GetShaderSetupTypeName(Shader::ShaderSetup& setup) { +    if (&setup == &g_state.vs) { +        return "vertex shader"; +    } +    if (&setup == &g_state.gs) { +        return "geometry shader"; +    } +    return "unknown shader"; +} + +static void WriteUniformBoolReg(Shader::ShaderSetup& setup, u32 value) { +    for (unsigned i = 0; i < setup.uniforms.b.size(); ++i) +        setup.uniforms.b[i] = (value & (1 << i)) != 0; +} + +static void WriteUniformIntReg(Shader::ShaderSetup& setup, unsigned index, +                               const Math::Vec4<u8>& values) { +    ASSERT(index < setup.uniforms.i.size()); +    setup.uniforms.i[index] = values; +    LOG_TRACE(HW_GPU, "Set %s integer uniform %d to %02x %02x %02x %02x", +              GetShaderSetupTypeName(setup), index, values.x, values.y, values.z, values.w); +} + +static void WriteUniformFloatReg(ShaderRegs& config, Shader::ShaderSetup& setup, +                                 int& float_regs_counter, u32 uniform_write_buffer[4], u32 value) { +    auto& uniform_setup = config.uniform_setup; + +    // TODO: Does actual hardware indeed keep an intermediate buffer or does +    //       it directly write the values? +    uniform_write_buffer[float_regs_counter++] = value; + +    // Uniforms are written in a packed format such that four float24 values are encoded in +    // three 32-bit numbers. We write to internal memory once a full such vector is +    // written. +    if ((float_regs_counter >= 4 && uniform_setup.IsFloat32()) || +        (float_regs_counter >= 3 && !uniform_setup.IsFloat32())) { +        float_regs_counter = 0; + +        auto& uniform = setup.uniforms.f[uniform_setup.index]; + +        if (uniform_setup.index >= 96) { +            LOG_ERROR(HW_GPU, "Invalid %s float uniform index %d", GetShaderSetupTypeName(setup), +                      (int)uniform_setup.index); +        } else { + +            // NOTE: The destination component order indeed is "backwards" +            if (uniform_setup.IsFloat32()) { +                for (auto i : {0, 1, 2, 3}) +                    uniform[3 - i] = float24::FromFloat32(*(float*)(&uniform_write_buffer[i])); +            } else { +                // TODO: Untested +                uniform.w = float24::FromRaw(uniform_write_buffer[0] >> 8); +                uniform.z = float24::FromRaw(((uniform_write_buffer[0] & 0xFF) << 16) | +                                             ((uniform_write_buffer[1] >> 16) & 0xFFFF)); +                uniform.y = float24::FromRaw(((uniform_write_buffer[1] & 0xFFFF) << 8) | +                                             ((uniform_write_buffer[2] >> 24) & 0xFF)); +                uniform.x = float24::FromRaw(uniform_write_buffer[2] & 0xFFFFFF); +            } + +            LOG_TRACE(HW_GPU, "Set %s float uniform %x to (%f %f %f %f)", +                      GetShaderSetupTypeName(setup), (int)uniform_setup.index, +                      uniform.x.ToFloat32(), uniform.y.ToFloat32(), uniform.z.ToFloat32(), +                      uniform.w.ToFloat32()); + +            // TODO: Verify that this actually modifies the register! +            uniform_setup.index.Assign(uniform_setup.index + 1); +        } +    } +} + +static void WriteProgramCode(ShaderRegs& config, Shader::ShaderSetup& setup, +                             unsigned max_program_code_length, u32 value) { +    if (config.program.offset >= max_program_code_length) { +        LOG_ERROR(HW_GPU, "Invalid %s program offset %d", GetShaderSetupTypeName(setup), +                  (int)config.program.offset); +    } else { +        setup.program_code[config.program.offset] = value; +        config.program.offset++; +    } +} + +static void WriteSwizzlePatterns(ShaderRegs& config, Shader::ShaderSetup& setup, u32 value) { +    if (config.swizzle_patterns.offset >= setup.swizzle_data.size()) { +        LOG_ERROR(HW_GPU, "Invalid %s swizzle pattern offset %d", GetShaderSetupTypeName(setup), +                  (int)config.swizzle_patterns.offset); +    } else { +        setup.swizzle_data[config.swizzle_patterns.offset] = value; +        config.swizzle_patterns.offset++; +    } +} +  static void WritePicaReg(u32 id, u32 value, u32 mask) {      auto& regs = g_state.regs; @@ -330,21 +422,70 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {          break;      } -    case PICA_REG_INDEX(vs.bool_uniforms): -        for (unsigned i = 0; i < 16; ++i) -            g_state.vs.uniforms.b[i] = (regs.vs.bool_uniforms.Value() & (1 << i)) != 0; +    case PICA_REG_INDEX(gs.bool_uniforms): +        WriteUniformBoolReg(g_state.gs, g_state.regs.gs.bool_uniforms.Value()); +        break; +    case PICA_REG_INDEX_WORKAROUND(gs.int_uniforms[0], 0x281): +    case PICA_REG_INDEX_WORKAROUND(gs.int_uniforms[1], 0x282): +    case PICA_REG_INDEX_WORKAROUND(gs.int_uniforms[2], 0x283): +    case PICA_REG_INDEX_WORKAROUND(gs.int_uniforms[3], 0x284): { +        unsigned index = (id - PICA_REG_INDEX_WORKAROUND(gs.int_uniforms[0], 0x281)); +        auto values = regs.gs.int_uniforms[index]; +        WriteUniformIntReg(g_state.gs, index, +                           Math::Vec4<u8>(values.x, values.y, values.z, values.w)); +        break; +    } + +    case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[0], 0x291): +    case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[1], 0x292): +    case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[2], 0x293): +    case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[3], 0x294): +    case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[4], 0x295): +    case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[5], 0x296): +    case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[6], 0x297): +    case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[7], 0x298): { +        WriteUniformFloatReg(g_state.regs.gs, g_state.gs, gs_float_regs_counter, +                             gs_uniform_write_buffer, value); +        break; +    } + +    case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[0], 0x29c): +    case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[1], 0x29d): +    case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[2], 0x29e): +    case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[3], 0x29f): +    case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[4], 0x2a0): +    case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[5], 0x2a1): +    case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[6], 0x2a2): +    case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[7], 0x2a3): { +        WriteProgramCode(g_state.regs.gs, g_state.gs, 4096, value); +        break; +    } + +    case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[0], 0x2a6): +    case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[1], 0x2a7): +    case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[2], 0x2a8): +    case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[3], 0x2a9): +    case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[4], 0x2aa): +    case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[5], 0x2ab): +    case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[6], 0x2ac): +    case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[7], 0x2ad): { +        WriteSwizzlePatterns(g_state.regs.gs, g_state.gs, value); +        break; +    } + +    case PICA_REG_INDEX(vs.bool_uniforms): +        WriteUniformBoolReg(g_state.vs, g_state.regs.vs.bool_uniforms.Value());          break;      case PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[0], 0x2b1):      case PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[1], 0x2b2):      case PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[2], 0x2b3):      case PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[3], 0x2b4): { -        int index = (id - PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[0], 0x2b1)); +        unsigned index = (id - PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[0], 0x2b1));          auto values = regs.vs.int_uniforms[index]; -        g_state.vs.uniforms.i[index] = Math::Vec4<u8>(values.x, values.y, values.z, values.w); -        LOG_TRACE(HW_GPU, "Set integer uniform %d to %02x %02x %02x %02x", index, values.x.Value(), -                  values.y.Value(), values.z.Value(), values.w.Value()); +        WriteUniformIntReg(g_state.vs, index, +                           Math::Vec4<u8>(values.x, values.y, values.z, values.w));          break;      } @@ -356,51 +497,11 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {      case PICA_REG_INDEX_WORKAROUND(vs.uniform_setup.set_value[5], 0x2c6):      case PICA_REG_INDEX_WORKAROUND(vs.uniform_setup.set_value[6], 0x2c7):      case PICA_REG_INDEX_WORKAROUND(vs.uniform_setup.set_value[7], 0x2c8): { -        auto& uniform_setup = regs.vs.uniform_setup; - -        // TODO: Does actual hardware indeed keep an intermediate buffer or does -        //       it directly write the values? -        uniform_write_buffer[float_regs_counter++] = value; - -        // Uniforms are written in a packed format such that four float24 values are encoded in -        // three 32-bit numbers. We write to internal memory once a full such vector is -        // written. -        if ((float_regs_counter >= 4 && uniform_setup.IsFloat32()) || -            (float_regs_counter >= 3 && !uniform_setup.IsFloat32())) { -            float_regs_counter = 0; - -            auto& uniform = g_state.vs.uniforms.f[uniform_setup.index]; - -            if (uniform_setup.index > 95) { -                LOG_ERROR(HW_GPU, "Invalid VS uniform index %d", (int)uniform_setup.index); -                break; -            } - -            // NOTE: The destination component order indeed is "backwards" -            if (uniform_setup.IsFloat32()) { -                for (auto i : {0, 1, 2, 3}) -                    uniform[3 - i] = float24::FromFloat32(*(float*)(&uniform_write_buffer[i])); -            } else { -                // TODO: Untested -                uniform.w = float24::FromRaw(uniform_write_buffer[0] >> 8); -                uniform.z = float24::FromRaw(((uniform_write_buffer[0] & 0xFF) << 16) | -                                             ((uniform_write_buffer[1] >> 16) & 0xFFFF)); -                uniform.y = float24::FromRaw(((uniform_write_buffer[1] & 0xFFFF) << 8) | -                                             ((uniform_write_buffer[2] >> 24) & 0xFF)); -                uniform.x = float24::FromRaw(uniform_write_buffer[2] & 0xFFFFFF); -            } - -            LOG_TRACE(HW_GPU, "Set uniform %x to (%f %f %f %f)", (int)uniform_setup.index, -                      uniform.x.ToFloat32(), uniform.y.ToFloat32(), uniform.z.ToFloat32(), -                      uniform.w.ToFloat32()); - -            // TODO: Verify that this actually modifies the register! -            uniform_setup.index.Assign(uniform_setup.index + 1); -        } +        WriteUniformFloatReg(g_state.regs.vs, g_state.vs, vs_float_regs_counter, +                             vs_uniform_write_buffer, value);          break;      } -    // Load shader program code      case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[0], 0x2cc):      case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[1], 0x2cd):      case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[2], 0x2ce): @@ -409,12 +510,10 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {      case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[5], 0x2d1):      case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[6], 0x2d2):      case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[7], 0x2d3): { -        g_state.vs.program_code[regs.vs.program.offset] = value; -        regs.vs.program.offset++; +        WriteProgramCode(g_state.regs.vs, g_state.vs, 512, value);          break;      } -    // Load swizzle pattern data      case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[0], 0x2d6):      case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[1], 0x2d7):      case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[2], 0x2d8): @@ -423,8 +522,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {      case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[5], 0x2db):      case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[6], 0x2dc):      case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[7], 0x2dd): { -        g_state.vs.swizzle_data[regs.vs.swizzle_patterns.offset] = value; -        regs.vs.swizzle_patterns.offset++; +        WriteSwizzlePatterns(g_state.regs.vs, g_state.vs, value);          break;      } diff --git a/src/video_core/regs.h b/src/video_core/regs.h index 86826088b..1776dad89 100644 --- a/src/video_core/regs.h +++ b/src/video_core/regs.h @@ -93,7 +93,7 @@ ASSERT_REG_POSITION(rasterizer.viewport_corner, 0x68);  ASSERT_REG_POSITION(rasterizer.depthmap_enable, 0x6D);  ASSERT_REG_POSITION(texturing, 0x80); -ASSERT_REG_POSITION(texturing.texture0_enable, 0x80); +ASSERT_REG_POSITION(texturing.main_config, 0x80);  ASSERT_REG_POSITION(texturing.texture0, 0x81);  ASSERT_REG_POSITION(texturing.texture0_format, 0x8e);  ASSERT_REG_POSITION(texturing.fragment_lighting_enable, 0x8f); diff --git a/src/video_core/regs_framebuffer.h b/src/video_core/regs_framebuffer.h index 9ddc79243..a50bd4111 100644 --- a/src/video_core/regs_framebuffer.h +++ b/src/video_core/regs_framebuffer.h @@ -211,13 +211,14 @@ struct FramebufferRegs {              BitField<0, 2, u32> allow_depth_stencil_write; // 0 = disable, else enable          }; -        DepthFormat depth_format; // TODO: Should be a BitField! +        BitField<0, 2, DepthFormat> depth_format; +          BitField<16, 3, ColorFormat> color_format;          INSERT_PADDING_WORDS(0x4); -        u32 depth_buffer_address; -        u32 color_buffer_address; +        BitField<0, 28, u32> depth_buffer_address; +        BitField<0, 28, u32> color_buffer_address;          union {              // Apparently, the framebuffer width is stored as expected, diff --git a/src/video_core/regs_pipeline.h b/src/video_core/regs_pipeline.h index 0a4ec6e1e..31c747d77 100644 --- a/src/video_core/regs_pipeline.h +++ b/src/video_core/regs_pipeline.h @@ -22,10 +22,10 @@ struct PipelineRegs {      };      struct { -        BitField<0, 29, u32> base_address; +        BitField<1, 28, u32> base_address;          PAddr GetPhysicalBaseAddress() const { -            return base_address * 8; +            return base_address * 16;          }          // Descriptor for internal vertex attributes @@ -99,7 +99,7 @@ struct PipelineRegs {          // This e.g. allows to load different attributes from different memory locations          struct {              // Source attribute data offset from the base address -            u32 data_offset; +            BitField<0, 28, u32> data_offset;              union {                  BitField<0, 4, u32> comp0; @@ -180,6 +180,8 @@ struct PipelineRegs {          //     kicked off.          //  2) Games can configure these registers to provide a command list subroutine mechanism. +        // TODO: verify the bit length of these two fields +        // According to 3dbrew, the bit length of them are 21 and 29, respectively          BitField<0, 20, u32> size[2]; ///< Size (in bytes / 8) of each channel's command buffer          BitField<0, 28, u32> addr[2]; ///< Physical address / 8 of each channel's command buffer          u32 trigger[2]; ///< Triggers execution of the channel's command buffer when written to diff --git a/src/video_core/regs_rasterizer.h b/src/video_core/regs_rasterizer.h index a471a3b38..2874fd127 100644 --- a/src/video_core/regs_rasterizer.h +++ b/src/video_core/regs_rasterizer.h @@ -92,13 +92,13 @@ struct RasterizerRegs {          BitField<0, 2, ScissorMode> mode;          union { -            BitField<0, 16, u32> x1; -            BitField<16, 16, u32> y1; +            BitField<0, 10, u32> x1; +            BitField<16, 10, u32> y1;          };          union { -            BitField<0, 16, u32> x2; -            BitField<16, 16, u32> y2; +            BitField<0, 10, u32> x2; +            BitField<16, 10, u32> y2;          };      } scissor_test; diff --git a/src/video_core/regs_texturing.h b/src/video_core/regs_texturing.h index be8bc6826..3318812da 100644 --- a/src/video_core/regs_texturing.h +++ b/src/video_core/regs_texturing.h @@ -29,6 +29,11 @@ struct TexturingRegs {              ClampToBorder = 1,              Repeat = 2,              MirroredRepeat = 3, +            // Mode 4-7 produces some weird result and may be just invalid: +            // 4: Positive coord: clamp to edge; negative coord: repeat +            // 5: Positive coord: clamp to border; negative coord: repeat +            // 6: Repeat +            // 7: Repeat          };          enum TextureFilter : u32 { @@ -45,22 +50,22 @@ struct TexturingRegs {          } border_color;          union { -            BitField<0, 16, u32> height; -            BitField<16, 16, u32> width; +            BitField<0, 11, u32> height; +            BitField<16, 11, u32> width;          };          union {              BitField<1, 1, TextureFilter> mag_filter;              BitField<2, 1, TextureFilter> min_filter; -            BitField<8, 2, WrapMode> wrap_t; -            BitField<12, 2, WrapMode> wrap_s; -            BitField<28, 2, TextureType> -                type; ///< @note Only valid for texture 0 according to 3DBrew. +            BitField<8, 3, WrapMode> wrap_t; +            BitField<12, 3, WrapMode> wrap_s; +            /// @note Only valid for texture 0 according to 3DBrew. +            BitField<28, 3, TextureType> type;          };          INSERT_PADDING_WORDS(0x1); -        u32 address; +        BitField<0, 28, u32> address;          PAddr GetPhysicalAddress() const {              return address * 8; @@ -122,7 +127,11 @@ struct TexturingRegs {          BitField<0, 1, u32> texture0_enable;          BitField<1, 1, u32> texture1_enable;          BitField<2, 1, u32> texture2_enable; -    }; +        BitField<8, 2, u32> texture3_coordinates; // TODO: unimplemented +        BitField<10, 1, u32> texture3_enable;     // TODO: unimplemented +        BitField<13, 1, u32> texture2_use_coord1; +        BitField<16, 1, u32> clear_texture_cache; // TODO: unimplemented +    } main_config;      TextureConfig texture0;      INSERT_PADDING_WORDS(0x8);      BitField<0, 4, TextureFormat> texture0_format; @@ -142,9 +151,9 @@ struct TexturingRegs {      };      const std::array<FullTextureConfig, 3> GetTextures() const {          return {{ -            {texture0_enable.ToBool(), texture0, texture0_format}, -            {texture1_enable.ToBool(), texture1, texture1_format}, -            {texture2_enable.ToBool(), texture2, texture2_format}, +            {main_config.texture0_enable.ToBool(), texture0, texture0_format}, +            {main_config.texture1_enable.ToBool(), texture1, texture1_format}, +            {main_config.texture2_enable.ToBool(), texture2, texture2_format},          }};      } @@ -199,7 +208,7 @@ struct TexturingRegs {              Lerp = 4,              Subtract = 5,              Dot3_RGB = 6, - +            Dot3_RGBA = 7,              MultiplyThenAdd = 8,              AddThenMultiply = 9,          }; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index de1d5eba7..12ac9bbd9 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -20,7 +20,6 @@  #include "video_core/regs_texturing.h"  #include "video_core/renderer_opengl/gl_rasterizer.h"  #include "video_core/renderer_opengl/gl_shader_gen.h" -#include "video_core/renderer_opengl/gl_shader_util.h"  #include "video_core/renderer_opengl/pica_to_gl.h"  #include "video_core/renderer_opengl/renderer_opengl.h" @@ -403,6 +402,10 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {          SyncLogicOp();          break; +    case PICA_REG_INDEX(texturing.main_config): +        shader_dirty = true; +        break; +      // Texture 0 type      case PICA_REG_INDEX(texturing.texture0.type):          shader_dirty = true; @@ -1005,7 +1008,7 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(  }  void RasterizerOpenGL::SetShader() { -    PicaShaderConfig config = PicaShaderConfig::CurrentConfig(); +    auto config = GLShader::PicaShaderConfig::BuildFromRegs(Pica::g_state.regs);      std::unique_ptr<PicaShader> shader = std::make_unique<PicaShader>();      // Find (or generate) the GLSL shader for the current TEV state diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index ecf737438..3e1770d77 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -25,210 +25,13 @@  #include "video_core/regs_texturing.h"  #include "video_core/renderer_opengl/gl_rasterizer_cache.h"  #include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/renderer_opengl/gl_shader_gen.h"  #include "video_core/renderer_opengl/gl_state.h"  #include "video_core/renderer_opengl/pica_to_gl.h"  #include "video_core/shader/shader.h"  struct ScreenInfo; -/** - * This struct contains all state used to generate the GLSL shader program that emulates the current - * Pica register configuration. This struct is used as a cache key for generated GLSL shader - * programs. The functions in gl_shader_gen.cpp should retrieve state from this struct only, not by - * directly accessing Pica registers. This should reduce the risk of bugs in shader generation where - * Pica state is not being captured in the shader cache key, thereby resulting in (what should be) - * two separate shaders sharing the same key. - * - * We use a union because "implicitly-defined copy/move constructor for a union X copies the object - * representation of X." and "implicitly-defined copy assignment operator for a union X copies the - * object representation (3.9) of X." = Bytewise copy instead of memberwise copy. This is important - * because the padding bytes are included in the hash and comparison between objects. - */ -union PicaShaderConfig { - -    /// Construct a PicaShaderConfig with the current Pica register configuration. -    static PicaShaderConfig CurrentConfig() { -        PicaShaderConfig res; - -        auto& state = res.state; -        std::memset(&state, 0, sizeof(PicaShaderConfig::State)); - -        const auto& regs = Pica::g_state.regs; - -        state.scissor_test_mode = regs.rasterizer.scissor_test.mode; - -        state.depthmap_enable = regs.rasterizer.depthmap_enable; - -        state.alpha_test_func = regs.framebuffer.output_merger.alpha_test.enable -                                    ? regs.framebuffer.output_merger.alpha_test.func.Value() -                                    : Pica::FramebufferRegs::CompareFunc::Always; - -        state.texture0_type = regs.texturing.texture0.type; - -        // Copy relevant tev stages fields. -        // We don't sync const_color here because of the high variance, it is a -        // shader uniform instead. -        const auto& tev_stages = regs.texturing.GetTevStages(); -        DEBUG_ASSERT(state.tev_stages.size() == tev_stages.size()); -        for (size_t i = 0; i < tev_stages.size(); i++) { -            const auto& tev_stage = tev_stages[i]; -            state.tev_stages[i].sources_raw = tev_stage.sources_raw; -            state.tev_stages[i].modifiers_raw = tev_stage.modifiers_raw; -            state.tev_stages[i].ops_raw = tev_stage.ops_raw; -            state.tev_stages[i].scales_raw = tev_stage.scales_raw; -        } - -        state.fog_mode = regs.texturing.fog_mode; -        state.fog_flip = regs.texturing.fog_flip != 0; - -        state.combiner_buffer_input = -            regs.texturing.tev_combiner_buffer_input.update_mask_rgb.Value() | -            regs.texturing.tev_combiner_buffer_input.update_mask_a.Value() << 4; - -        // Fragment lighting - -        state.lighting.enable = !regs.lighting.disable; -        state.lighting.src_num = regs.lighting.max_light_index + 1; - -        for (unsigned light_index = 0; light_index < state.lighting.src_num; ++light_index) { -            unsigned num = regs.lighting.light_enable.GetNum(light_index); -            const auto& light = regs.lighting.light[num]; -            state.lighting.light[light_index].num = num; -            state.lighting.light[light_index].directional = light.config.directional != 0; -            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.lut_d0.enable = regs.lighting.config1.disable_lut_d0 == 0; -        state.lighting.lut_d0.abs_input = regs.lighting.abs_lut_input.disable_d0 == 0; -        state.lighting.lut_d0.type = regs.lighting.lut_input.d0.Value(); -        state.lighting.lut_d0.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d0); - -        state.lighting.lut_d1.enable = regs.lighting.config1.disable_lut_d1 == 0; -        state.lighting.lut_d1.abs_input = regs.lighting.abs_lut_input.disable_d1 == 0; -        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); - -        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(); -        state.lighting.lut_fr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.fr); - -        state.lighting.lut_rr.enable = regs.lighting.config1.disable_lut_rr == 0; -        state.lighting.lut_rr.abs_input = regs.lighting.abs_lut_input.disable_rr == 0; -        state.lighting.lut_rr.type = regs.lighting.lut_input.rr.Value(); -        state.lighting.lut_rr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rr); - -        state.lighting.lut_rg.enable = regs.lighting.config1.disable_lut_rg == 0; -        state.lighting.lut_rg.abs_input = regs.lighting.abs_lut_input.disable_rg == 0; -        state.lighting.lut_rg.type = regs.lighting.lut_input.rg.Value(); -        state.lighting.lut_rg.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rg); - -        state.lighting.lut_rb.enable = regs.lighting.config1.disable_lut_rb == 0; -        state.lighting.lut_rb.abs_input = regs.lighting.abs_lut_input.disable_rb == 0; -        state.lighting.lut_rb.type = regs.lighting.lut_input.rb.Value(); -        state.lighting.lut_rb.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rb); - -        state.lighting.config = regs.lighting.config0.config; -        state.lighting.fresnel_selector = regs.lighting.config0.fresnel_selector; -        state.lighting.bump_mode = regs.lighting.config0.bump_mode; -        state.lighting.bump_selector = regs.lighting.config0.bump_selector; -        state.lighting.bump_renorm = regs.lighting.config0.disable_bump_renorm == 0; -        state.lighting.clamp_highlights = regs.lighting.config0.clamp_highlights != 0; - -        return res; -    } - -    bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const { -        return (stage_index < 4) && (state.combiner_buffer_input & (1 << stage_index)); -    } - -    bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const { -        return (stage_index < 4) && ((state.combiner_buffer_input >> 4) & (1 << stage_index)); -    } - -    bool operator==(const PicaShaderConfig& o) const { -        return std::memcmp(&state, &o.state, sizeof(PicaShaderConfig::State)) == 0; -    }; - -    // NOTE: MSVC15 (Update 2) doesn't think `delete`'d constructors and operators are TC. -    //       This makes BitField not TC when used in a union or struct so we have to resort -    //       to this ugly hack. -    //       Once that bug is fixed we can use Pica::Regs::TevStageConfig here. -    //       Doesn't include const_color because we don't sync it, see comment in CurrentConfig() -    struct TevStageConfigRaw { -        u32 sources_raw; -        u32 modifiers_raw; -        u32 ops_raw; -        u32 scales_raw; -        explicit operator Pica::TexturingRegs::TevStageConfig() const noexcept { -            Pica::TexturingRegs::TevStageConfig stage; -            stage.sources_raw = sources_raw; -            stage.modifiers_raw = modifiers_raw; -            stage.ops_raw = ops_raw; -            stage.const_color = 0; -            stage.scales_raw = scales_raw; -            return stage; -        } -    }; - -    struct State { -        Pica::FramebufferRegs::CompareFunc alpha_test_func; -        Pica::RasterizerRegs::ScissorMode scissor_test_mode; -        Pica::TexturingRegs::TextureConfig::TextureType texture0_type; -        std::array<TevStageConfigRaw, 6> tev_stages; -        u8 combiner_buffer_input; - -        Pica::RasterizerRegs::DepthBuffering depthmap_enable; -        Pica::TexturingRegs::FogMode fog_mode; -        bool fog_flip; - -        struct { -            struct { -                unsigned num; -                bool directional; -                bool two_sided_diffuse; -                bool dist_atten_enable; -            } light[8]; - -            bool enable; -            unsigned src_num; -            Pica::LightingRegs::LightingBumpMode bump_mode; -            unsigned bump_selector; -            bool bump_renorm; -            bool clamp_highlights; - -            Pica::LightingRegs::LightingConfig config; -            Pica::LightingRegs::LightingFresnelSelector fresnel_selector; - -            struct { -                bool enable; -                bool abs_input; -                Pica::LightingRegs::LightingLutInput type; -                float scale; -            } lut_d0, lut_d1, lut_fr, lut_rr, lut_rg, lut_rb; -        } lighting; - -    } state; -}; -#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER) -static_assert(std::is_trivially_copyable<PicaShaderConfig::State>::value, -              "PicaShaderConfig::State must be trivially copyable"); -#endif - -namespace std { - -template <> -struct hash<PicaShaderConfig> { -    size_t operator()(const PicaShaderConfig& k) const { -        return Common::ComputeHash64(&k.state, sizeof(PicaShaderConfig::State)); -    } -}; - -} // namespace std -  class RasterizerOpenGL : public VideoCore::RasterizerInterface {  public:      RasterizerOpenGL(); @@ -437,7 +240,7 @@ private:      std::vector<HardwareVertex> vertex_batch; -    std::unordered_map<PicaShaderConfig, std::unique_ptr<PicaShader>> shader_cache; +    std::unordered_map<GLShader::PicaShaderConfig, std::unique_ptr<PicaShader>> shader_cache;      const PicaShader* current_shader = nullptr;      bool shader_dirty; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 7abdeba05..7b44dade8 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -4,6 +4,7 @@  #include <array>  #include <cstddef> +#include <cstring>  #include "common/assert.h"  #include "common/bit_field.h"  #include "common/logging/log.h" @@ -23,6 +24,99 @@ using TevStageConfig = TexturingRegs::TevStageConfig;  namespace GLShader { +PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) { +    PicaShaderConfig res; + +    auto& state = res.state; +    std::memset(&state, 0, sizeof(PicaShaderConfig::State)); + +    state.scissor_test_mode = regs.rasterizer.scissor_test.mode; + +    state.depthmap_enable = regs.rasterizer.depthmap_enable; + +    state.alpha_test_func = regs.framebuffer.output_merger.alpha_test.enable +                                ? regs.framebuffer.output_merger.alpha_test.func.Value() +                                : Pica::FramebufferRegs::CompareFunc::Always; + +    state.texture0_type = regs.texturing.texture0.type; + +    state.texture2_use_coord1 = regs.texturing.main_config.texture2_use_coord1 != 0; + +    // Copy relevant tev stages fields. +    // We don't sync const_color here because of the high variance, it is a +    // shader uniform instead. +    const auto& tev_stages = regs.texturing.GetTevStages(); +    DEBUG_ASSERT(state.tev_stages.size() == tev_stages.size()); +    for (size_t i = 0; i < tev_stages.size(); i++) { +        const auto& tev_stage = tev_stages[i]; +        state.tev_stages[i].sources_raw = tev_stage.sources_raw; +        state.tev_stages[i].modifiers_raw = tev_stage.modifiers_raw; +        state.tev_stages[i].ops_raw = tev_stage.ops_raw; +        state.tev_stages[i].scales_raw = tev_stage.scales_raw; +    } + +    state.fog_mode = regs.texturing.fog_mode; +    state.fog_flip = regs.texturing.fog_flip != 0; + +    state.combiner_buffer_input = regs.texturing.tev_combiner_buffer_input.update_mask_rgb.Value() | +                                  regs.texturing.tev_combiner_buffer_input.update_mask_a.Value() +                                      << 4; + +    // Fragment lighting + +    state.lighting.enable = !regs.lighting.disable; +    state.lighting.src_num = regs.lighting.max_light_index + 1; + +    for (unsigned light_index = 0; light_index < state.lighting.src_num; ++light_index) { +        unsigned num = regs.lighting.light_enable.GetNum(light_index); +        const auto& light = regs.lighting.light[num]; +        state.lighting.light[light_index].num = num; +        state.lighting.light[light_index].directional = light.config.directional != 0; +        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.lut_d0.enable = regs.lighting.config1.disable_lut_d0 == 0; +    state.lighting.lut_d0.abs_input = regs.lighting.abs_lut_input.disable_d0 == 0; +    state.lighting.lut_d0.type = regs.lighting.lut_input.d0.Value(); +    state.lighting.lut_d0.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d0); + +    state.lighting.lut_d1.enable = regs.lighting.config1.disable_lut_d1 == 0; +    state.lighting.lut_d1.abs_input = regs.lighting.abs_lut_input.disable_d1 == 0; +    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); + +    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(); +    state.lighting.lut_fr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.fr); + +    state.lighting.lut_rr.enable = regs.lighting.config1.disable_lut_rr == 0; +    state.lighting.lut_rr.abs_input = regs.lighting.abs_lut_input.disable_rr == 0; +    state.lighting.lut_rr.type = regs.lighting.lut_input.rr.Value(); +    state.lighting.lut_rr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rr); + +    state.lighting.lut_rg.enable = regs.lighting.config1.disable_lut_rg == 0; +    state.lighting.lut_rg.abs_input = regs.lighting.abs_lut_input.disable_rg == 0; +    state.lighting.lut_rg.type = regs.lighting.lut_input.rg.Value(); +    state.lighting.lut_rg.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rg); + +    state.lighting.lut_rb.enable = regs.lighting.config1.disable_lut_rb == 0; +    state.lighting.lut_rb.abs_input = regs.lighting.abs_lut_input.disable_rb == 0; +    state.lighting.lut_rb.type = regs.lighting.lut_input.rb.Value(); +    state.lighting.lut_rb.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rb); + +    state.lighting.config = regs.lighting.config0.config; +    state.lighting.fresnel_selector = regs.lighting.config0.fresnel_selector; +    state.lighting.bump_mode = regs.lighting.config0.bump_mode; +    state.lighting.bump_selector = regs.lighting.config0.bump_selector; +    state.lighting.bump_renorm = regs.lighting.config0.disable_bump_renorm == 0; +    state.lighting.clamp_highlights = regs.lighting.config0.clamp_highlights != 0; + +    return res; +} +  /// Detects if a TEV stage is configured to be skipped (to avoid generating unnecessary code)  static bool IsPassThroughTevStage(const TevStageConfig& stage) {      return (stage.color_op == TevStageConfig::Operation::Replace && @@ -34,6 +128,15 @@ static bool IsPassThroughTevStage(const TevStageConfig& stage) {              stage.GetColorMultiplier() == 1 && stage.GetAlphaMultiplier() == 1);  } +static std::string TexCoord(const PicaShaderConfig& config, int texture_unit) { +    if (texture_unit == 2 && config.state.texture2_use_coord1) { +        return "texcoord[1]"; +    } +    // TODO: if texture unit 3 (procedural texture) implementation also uses this function, +    //       config.state.texture3_coordinates should be repected here. +    return "texcoord[" + std::to_string(texture_unit) + "]"; +} +  /// Writes the specified TEV stage source component(s)  static void AppendSource(std::string& out, const PicaShaderConfig& config,                           TevStageConfig::Source source, const std::string& index_name) { @@ -70,7 +173,7 @@ static void AppendSource(std::string& out, const PicaShaderConfig& config,          out += "texture(tex[1], texcoord[1])";          break;      case Source::Texture2: -        out += "texture(tex[2], texcoord[2])"; +        out += "texture(tex[2], " + TexCoord(config, 2) + ")";          break;      case Source::PreviousBuffer:          out += "combiner_buffer"; @@ -214,8 +317,6 @@ static void AppendColorCombiner(std::string& out, TevStageConfig::Operation oper          out += variable_name + "[0] + " + variable_name + "[1] - vec3(0.5)";          break;      case Operation::Lerp: -        // TODO(bunnei): Verify if HW actually does this per-component, otherwise we can just use -        // builtin lerp          out += variable_name + "[0] * " + variable_name + "[2] + " + variable_name +                 "[1] * (vec3(1.0) - " + variable_name + "[2])";          break; @@ -230,6 +331,7 @@ static void AppendColorCombiner(std::string& out, TevStageConfig::Operation oper                 variable_name + "[2]";          break;      case Operation::Dot3_RGB: +    case Operation::Dot3_RGBA:          out += "vec3(dot(" + variable_name + "[0] - vec3(0.5), " + variable_name +                 "[1] - vec3(0.5)) * 4.0)";          break; @@ -329,17 +431,25 @@ static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsi          AppendColorCombiner(out, stage.color_op, "color_results_" + index_name);          out += ";\n"; -        out += "float alpha_results_" + index_name + "[3] = float[3]("; -        AppendAlphaModifier(out, config, stage.alpha_modifier1, stage.alpha_source1, index_name); -        out += ", "; -        AppendAlphaModifier(out, config, stage.alpha_modifier2, stage.alpha_source2, index_name); -        out += ", "; -        AppendAlphaModifier(out, config, stage.alpha_modifier3, stage.alpha_source3, index_name); -        out += ");\n"; - -        out += "float alpha_output_" + index_name + " = "; -        AppendAlphaCombiner(out, stage.alpha_op, "alpha_results_" + index_name); -        out += ";\n"; +        if (stage.color_op == TevStageConfig::Operation::Dot3_RGBA) { +            // result of Dot3_RGBA operation is also placed to the alpha component +            out += "float alpha_output_" + index_name + " = color_output_" + index_name + "[0];\n"; +        } else { +            out += "float alpha_results_" + index_name + "[3] = float[3]("; +            AppendAlphaModifier(out, config, stage.alpha_modifier1, stage.alpha_source1, +                                index_name); +            out += ", "; +            AppendAlphaModifier(out, config, stage.alpha_modifier2, stage.alpha_source2, +                                index_name); +            out += ", "; +            AppendAlphaModifier(out, config, stage.alpha_modifier3, stage.alpha_source3, +                                index_name); +            out += ");\n"; + +            out += "float alpha_output_" + index_name + " = "; +            AppendAlphaCombiner(out, stage.alpha_op, "alpha_results_" + index_name); +            out += ";\n"; +        }          out += "last_tex_env_out = vec4("                 "clamp(color_output_" + @@ -374,8 +484,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {          // Bump mapping is enabled using a normal map, read perturbation vector from the selected          // texture          std::string bump_selector = std::to_string(lighting.bump_selector); -        out += "vec3 surface_normal = 2.0 * texture(tex[" + bump_selector + "], texcoord[" + -               bump_selector + "]).rgb - 1.0;\n"; +        out += "vec3 surface_normal = 2.0 * texture(tex[" + bump_selector + "], " + +               TexCoord(config, lighting.bump_selector) + ").rgb - 1.0;\n";          // Recompute Z-component of perturbation if 'renorm' is enabled, this provides a higher          // precision result diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index bef3249cf..3fb046b76 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -4,12 +4,122 @@  #pragma once +#include <array> +#include <cstring> +#include <functional>  #include <string> - -union PicaShaderConfig; +#include <type_traits> +#include "video_core/regs.h"  namespace GLShader { +enum Attributes { +    ATTRIBUTE_POSITION, +    ATTRIBUTE_COLOR, +    ATTRIBUTE_TEXCOORD0, +    ATTRIBUTE_TEXCOORD1, +    ATTRIBUTE_TEXCOORD2, +    ATTRIBUTE_TEXCOORD0_W, +    ATTRIBUTE_NORMQUAT, +    ATTRIBUTE_VIEW, +}; + +/** + * This struct contains all state used to generate the GLSL shader program that emulates the current + * Pica register configuration. This struct is used as a cache key for generated GLSL shader + * programs. The functions in gl_shader_gen.cpp should retrieve state from this struct only, not by + * directly accessing Pica registers. This should reduce the risk of bugs in shader generation where + * Pica state is not being captured in the shader cache key, thereby resulting in (what should be) + * two separate shaders sharing the same key. + * + * We use a union because "implicitly-defined copy/move constructor for a union X copies the object + * representation of X." and "implicitly-defined copy assignment operator for a union X copies the + * object representation (3.9) of X." = Bytewise copy instead of memberwise copy. This is important + * because the padding bytes are included in the hash and comparison between objects. + */ +union PicaShaderConfig { + +    /// Construct a PicaShaderConfig with the given Pica register configuration. +    static PicaShaderConfig BuildFromRegs(const Pica::Regs& regs); + +    bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const { +        return (stage_index < 4) && (state.combiner_buffer_input & (1 << stage_index)); +    } + +    bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const { +        return (stage_index < 4) && ((state.combiner_buffer_input >> 4) & (1 << stage_index)); +    } + +    bool operator==(const PicaShaderConfig& o) const { +        return std::memcmp(&state, &o.state, sizeof(PicaShaderConfig::State)) == 0; +    }; + +    // NOTE: MSVC15 (Update 2) doesn't think `delete`'d constructors and operators are TC. +    //       This makes BitField not TC when used in a union or struct so we have to resort +    //       to this ugly hack. +    //       Once that bug is fixed we can use Pica::Regs::TevStageConfig here. +    //       Doesn't include const_color because we don't sync it, see comment in BuildFromRegs() +    struct TevStageConfigRaw { +        u32 sources_raw; +        u32 modifiers_raw; +        u32 ops_raw; +        u32 scales_raw; +        explicit operator Pica::TexturingRegs::TevStageConfig() const noexcept { +            Pica::TexturingRegs::TevStageConfig stage; +            stage.sources_raw = sources_raw; +            stage.modifiers_raw = modifiers_raw; +            stage.ops_raw = ops_raw; +            stage.const_color = 0; +            stage.scales_raw = scales_raw; +            return stage; +        } +    }; + +    struct State { +        Pica::FramebufferRegs::CompareFunc alpha_test_func; +        Pica::RasterizerRegs::ScissorMode scissor_test_mode; +        Pica::TexturingRegs::TextureConfig::TextureType texture0_type; +        bool texture2_use_coord1; +        std::array<TevStageConfigRaw, 6> tev_stages; +        u8 combiner_buffer_input; + +        Pica::RasterizerRegs::DepthBuffering depthmap_enable; +        Pica::TexturingRegs::FogMode fog_mode; +        bool fog_flip; + +        struct { +            struct { +                unsigned num; +                bool directional; +                bool two_sided_diffuse; +                bool dist_atten_enable; +            } light[8]; + +            bool enable; +            unsigned src_num; +            Pica::LightingRegs::LightingBumpMode bump_mode; +            unsigned bump_selector; +            bool bump_renorm; +            bool clamp_highlights; + +            Pica::LightingRegs::LightingConfig config; +            Pica::LightingRegs::LightingFresnelSelector fresnel_selector; + +            struct { +                bool enable; +                bool abs_input; +                Pica::LightingRegs::LightingLutInput type; +                float scale; +            } lut_d0, lut_d1, lut_fr, lut_rr, lut_rg, lut_rb; +        } lighting; + +    } state; +}; +#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER) +static_assert(std::is_trivially_copyable<PicaShaderConfig::State>::value, +              "PicaShaderConfig::State must be trivially copyable"); +#endif +  /**   * Generates the GLSL vertex shader program source code for the current Pica state   * @returns String of the shader source code @@ -25,3 +135,12 @@ std::string GenerateVertexShader();  std::string GenerateFragmentShader(const PicaShaderConfig& config);  } // namespace GLShader + +namespace std { +template <> +struct hash<GLShader::PicaShaderConfig> { +    size_t operator()(const GLShader::PicaShaderConfig& k) const { +        return Common::ComputeHash64(&k.state, sizeof(GLShader::PicaShaderConfig::State)); +    } +}; +} // namespace std diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h index f59912f79..c66e8acd3 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.h +++ b/src/video_core/renderer_opengl/gl_shader_util.h @@ -8,17 +8,6 @@  namespace GLShader { -enum Attributes { -    ATTRIBUTE_POSITION, -    ATTRIBUTE_COLOR, -    ATTRIBUTE_TEXCOORD0, -    ATTRIBUTE_TEXCOORD1, -    ATTRIBUTE_TEXCOORD2, -    ATTRIBUTE_TEXCOORD0_W, -    ATTRIBUTE_NORMQUAT, -    ATTRIBUTE_VIEW, -}; -  /**   * Utility function to create and compile an OpenGL GLSL shader program (vertex + fragment shader)   * @param vertex_shader String of the GLSL vertex shader program diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h index 38ea717ab..e156f6aef 100644 --- a/src/video_core/shader/shader.h +++ b/src/video_core/shader/shader.h @@ -24,6 +24,9 @@ namespace Pica {  namespace Shader { +constexpr unsigned MAX_PROGRAM_CODE_LENGTH = 4096; +constexpr unsigned MAX_SWIZZLE_DATA_LENGTH = 4096; +  struct AttributeBuffer {      alignas(16) Math::Vec4<float24> attr[16];  }; @@ -144,8 +147,8 @@ struct ShaderSetup {          return offsetof(ShaderSetup, uniforms.i) + index * sizeof(Math::Vec4<u8>);      } -    std::array<u32, 1024> program_code; -    std::array<u32, 1024> swizzle_data; +    std::array<u32, MAX_PROGRAM_CODE_LENGTH> program_code; +    std::array<u32, MAX_SWIZZLE_DATA_LENGTH> swizzle_data;      /// Data private to ShaderEngines      struct EngineData { diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp index f4d1c46c5..aa1cec81f 100644 --- a/src/video_core/shader/shader_interpreter.cpp +++ b/src/video_core/shader/shader_interpreter.cpp @@ -653,7 +653,7 @@ static void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData  }  void InterpreterEngine::SetupBatch(ShaderSetup& setup, unsigned int entry_point) { -    ASSERT(entry_point < 1024); +    ASSERT(entry_point < MAX_PROGRAM_CODE_LENGTH);      setup.engine_data.entry_point = entry_point;  } diff --git a/src/video_core/shader/shader_jit_x64.cpp b/src/video_core/shader/shader_jit_x64.cpp index 0ee0dd9ef..73c21871c 100644 --- a/src/video_core/shader/shader_jit_x64.cpp +++ b/src/video_core/shader/shader_jit_x64.cpp @@ -15,7 +15,7 @@ JitX64Engine::JitX64Engine() = default;  JitX64Engine::~JitX64Engine() = default;  void JitX64Engine::SetupBatch(ShaderSetup& setup, unsigned int entry_point) { -    ASSERT(entry_point < 1024); +    ASSERT(entry_point < MAX_PROGRAM_CODE_LENGTH);      setup.engine_data.entry_point = entry_point;      u64 code_hash = Common::ComputeHash64(&setup.program_code, sizeof(setup.program_code)); diff --git a/src/video_core/shader/shader_jit_x64_compiler.cpp b/src/video_core/shader/shader_jit_x64_compiler.cpp index 2dbc8b147..5d9b6448c 100644 --- a/src/video_core/shader/shader_jit_x64_compiler.cpp +++ b/src/video_core/shader/shader_jit_x64_compiler.cpp @@ -834,8 +834,8 @@ void JitShader::FindReturnOffsets() {      std::sort(return_offsets.begin(), return_offsets.end());  } -void JitShader::Compile(const std::array<u32, 1024>* program_code_, -                        const std::array<u32, 1024>* swizzle_data_) { +void JitShader::Compile(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>* program_code_, +                        const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>* swizzle_data_) {      program_code = program_code_;      swizzle_data = swizzle_data_; diff --git a/src/video_core/shader/shader_jit_x64_compiler.h b/src/video_core/shader/shader_jit_x64_compiler.h index f27675560..31af0ca48 100644 --- a/src/video_core/shader/shader_jit_x64_compiler.h +++ b/src/video_core/shader/shader_jit_x64_compiler.h @@ -22,8 +22,8 @@ namespace Pica {  namespace Shader { -/// Memory allocated for each compiled shader (64Kb) -constexpr size_t MAX_SHADER_SIZE = 1024 * 64; +/// Memory allocated for each compiled shader +constexpr size_t MAX_SHADER_SIZE = MAX_PROGRAM_CODE_LENGTH * 64;  /**   * This class implements the shader JIT compiler. It recompiles a Pica shader program into x86_64 @@ -37,8 +37,8 @@ public:          program(&setup, &state, instruction_labels[offset].getAddress());      } -    void Compile(const std::array<u32, 1024>* program_code, -                 const std::array<u32, 1024>* swizzle_data); +    void Compile(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>* program_code, +                 const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>* swizzle_data);      void Compile_ADD(Instruction instr);      void Compile_DP3(Instruction instr); @@ -104,11 +104,11 @@ private:       */      void FindReturnOffsets(); -    const std::array<u32, 1024>* program_code = nullptr; -    const std::array<u32, 1024>* swizzle_data = nullptr; +    const std::array<u32, MAX_PROGRAM_CODE_LENGTH>* program_code = nullptr; +    const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>* swizzle_data = nullptr;      /// Mapping of Pica VS instructions to pointers in the emitted code -    std::array<Xbyak::Label, 1024> instruction_labels; +    std::array<Xbyak::Label, MAX_PROGRAM_CODE_LENGTH> instruction_labels;      /// Offsets in code where a return needs to be inserted      std::vector<unsigned> return_offsets; diff --git a/src/video_core/swrasterizer/clipper.cpp b/src/video_core/swrasterizer/clipper.cpp index 2d80822d9..6fb923756 100644 --- a/src/video_core/swrasterizer/clipper.cpp +++ b/src/video_core/swrasterizer/clipper.cpp @@ -69,13 +69,14 @@ static void InitScreenCoordinates(Vertex& vtx) {      viewport.offset_y = float24::FromFloat32(static_cast<float>(regs.rasterizer.viewport_corner.y));      float24 inv_w = float24::FromFloat32(1.f) / vtx.pos.w; -    vtx.color *= inv_w; -    vtx.view *= inv_w; +    vtx.pos.w = inv_w;      vtx.quat *= inv_w; +    vtx.color *= inv_w;      vtx.tc0 *= inv_w;      vtx.tc1 *= inv_w; +    vtx.tc0_w *= inv_w; +    vtx.view *= inv_w;      vtx.tc2 *= inv_w; -    vtx.pos.w = inv_w;      vtx.screenpos[0] =          (vtx.pos.x * inv_w + float24::FromFloat32(1.0)) * viewport.halfsize_x + viewport.offset_x; diff --git a/src/video_core/swrasterizer/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp index 7557fcb89..20addf0bd 100644 --- a/src/video_core/swrasterizer/rasterizer.cpp +++ b/src/video_core/swrasterizer/rasterizer.cpp @@ -276,8 +276,10 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve                  DEBUG_ASSERT(0 != texture.config.address); -                float24 u = uv[i].u(); -                float24 v = uv[i].v(); +                int coordinate_i = +                    (i == 2 && regs.texturing.main_config.texture2_use_coord1) ? 1 : i; +                float24 u = uv[coordinate_i].u(); +                float24 v = uv[coordinate_i].v();                  // Only unit 0 respects the texturing type (according to 3DBrew)                  // TODO: Refactor so cubemaps and shadowmaps can be handled @@ -403,13 +405,22 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve                  };                  auto color_output = ColorCombine(tev_stage.color_op, color_result); -                // alpha combiner -                std::array<u8, 3> alpha_result = {{ -                    GetAlphaModifier(tev_stage.alpha_modifier1, GetSource(tev_stage.alpha_source1)), -                    GetAlphaModifier(tev_stage.alpha_modifier2, GetSource(tev_stage.alpha_source2)), -                    GetAlphaModifier(tev_stage.alpha_modifier3, GetSource(tev_stage.alpha_source3)), -                }}; -                auto alpha_output = AlphaCombine(tev_stage.alpha_op, alpha_result); +                u8 alpha_output; +                if (tev_stage.color_op == TexturingRegs::TevStageConfig::Operation::Dot3_RGBA) { +                    // result of Dot3_RGBA operation is also placed to the alpha component +                    alpha_output = color_output.x; +                } else { +                    // alpha combiner +                    std::array<u8, 3> alpha_result = {{ +                        GetAlphaModifier(tev_stage.alpha_modifier1, +                                         GetSource(tev_stage.alpha_source1)), +                        GetAlphaModifier(tev_stage.alpha_modifier2, +                                         GetSource(tev_stage.alpha_source2)), +                        GetAlphaModifier(tev_stage.alpha_modifier3, +                                         GetSource(tev_stage.alpha_source3)), +                    }}; +                    alpha_output = AlphaCombine(tev_stage.alpha_op, alpha_result); +                }                  combiner_output[0] =                      std::min((unsigned)255, color_output.r() * tev_stage.GetColorMultiplier()); diff --git a/src/video_core/swrasterizer/rasterizer.h b/src/video_core/swrasterizer/rasterizer.h index 3a72ac343..2f0877581 100644 --- a/src/video_core/swrasterizer/rasterizer.h +++ b/src/video_core/swrasterizer/rasterizer.h @@ -23,13 +23,15 @@ struct Vertex : Shader::OutputVertex {          pos = pos * factor + vtx.pos * (float24::FromFloat32(1) - factor);          // TODO: Should perform perspective correct interpolation here... +        quat = quat * factor + vtx.quat * (float24::FromFloat32(1) - factor); +        color = color * factor + vtx.color * (float24::FromFloat32(1) - factor);          tc0 = tc0 * factor + vtx.tc0 * (float24::FromFloat32(1) - factor);          tc1 = tc1 * factor + vtx.tc1 * (float24::FromFloat32(1) - factor); +        tc0_w = tc0_w * factor + vtx.tc0_w * (float24::FromFloat32(1) - factor); +        view = view * factor + vtx.view * (float24::FromFloat32(1) - factor);          tc2 = tc2 * factor + vtx.tc2 * (float24::FromFloat32(1) - factor);          screenpos = screenpos * factor + vtx.screenpos * (float24::FromFloat32(1) - factor); - -        color = color * factor + vtx.color * (float24::FromFloat32(1) - factor);      }      // Linear interpolation diff --git a/src/video_core/swrasterizer/texturing.cpp b/src/video_core/swrasterizer/texturing.cpp index eb18e4ba4..aeb6aeb8c 100644 --- a/src/video_core/swrasterizer/texturing.cpp +++ b/src/video_core/swrasterizer/texturing.cpp @@ -169,7 +169,8 @@ Math::Vec3<u8> ColorCombine(TevStageConfig::Operation op, const Math::Vec3<u8> i          result = (result * input[2].Cast<int>()) / 255;          return result.Cast<u8>();      } -    case Operation::Dot3_RGB: { +    case Operation::Dot3_RGB: +    case Operation::Dot3_RGBA: {          // Not fully accurate.  Worst case scenario seems to yield a +/-3 error.  Some HW results          // indicate that the per-component computation can't have a higher precision than 1/256,          // while dot3_rgb((0x80,g0,b0), (0x7F,g1,b1)) and dot3_rgb((0x80,g0,b0), (0x80,g1,b1)) give  | 
