code:
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <boost/algorithm/string.hpp>
#include "common/common_types.h"
#include "common/literals.h"
#include "core/core.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/fs_filesystem.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/submission_package.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "core/loader/nca.h"

namespace ContentManager {

enum class InstallResult {
    Success,
    Overwrite,
    Failure,
    BaseInstallAttempted,
};

enum class GameVerificationResult {
    Success,
    Failed,
    NotImplemented,
};

/**
 * \brief Removes a single installed DLC
 * \param fs_controller [FileSystemController] reference from the Core::System instance
 * \param title_id Unique title ID representing the DLC which will be removed
 * \return 'true' if successful
 */
inline bool RemoveDLC(const Service::FileSystem::FileSystemController& fs_controller,
                      const u64 title_id) {
    return fs_controller.GetUserNANDContents()->RemoveExistingEntry(title_id) ||
           fs_controller.GetSDMCContents()->RemoveExistingEntry(title_id);
}

/**
 * \brief Removes all DLC for a game
 * \param system Reference to the system instance
 * \param program_id Program ID for the game that will have all of its DLC removed
 * \return Number of DLC removed
 */
inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) {
    size_t count{};
    const auto& fs_controller = system.GetFileSystemController();
    const auto dlc_entries = system.GetContentProvider().ListEntriesFilter(
        FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
    std::vector<u64> program_dlc_entries;

    for (const auto& entry : dlc_entries) {
        if (FileSys::GetBaseTitleID(entry.title_id) == program_id) {
            program_dlc_entries.push_back(entry.title_id);
        }
    }

    for (const auto& entry : program_dlc_entries) {
        if (RemoveDLC(fs_controller, entry)) {
            ++count;
        }
    }
    return count;
}

/**
 * \brief Removes the installed update for a game
 * \param fs_controller [FileSystemController] reference from the Core::System instance
 * \param program_id Program ID for the game that will have its installed update removed
 * \return 'true' if successful
 */
inline bool RemoveUpdate(const Service::FileSystem::FileSystemController& fs_controller,
                         const u64 program_id) {
    const auto update_id = program_id | 0x800;
    return fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) ||
           fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id);
}

/**
 * \brief Removes the base content for a game
 * \param fs_controller [FileSystemController] reference from the Core::System instance
 * \param program_id Program ID for the game that will have its base content removed
 * \return 'true' if successful
 */
inline bool RemoveBaseContent(const Service::FileSystem::FileSystemController& fs_controller,
                              const u64 program_id) {
    return fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) ||
           fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id);
}

/**
 * \brief Removes a mod for a game
 * \param fs_controller [FileSystemController] reference from the Core::System instance
 * \param program_id Program ID for the game where [mod_name] will be removed
 * \param mod_name The name of a mod as given by FileSys::PatchManager::GetPatches. This corresponds
 * with the name of the mod's directory in a game's load folder.
 * \return 'true' if successful
 */
inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_controller,
                      const u64 program_id, const std::string& mod_name) {
    // Check general Mods (LayeredFS and IPS)
    const auto mod_dir = fs_controller.GetModificationLoadRoot(program_id);
    if (mod_dir != nullptr) {
        return mod_dir->DeleteSubdirectoryRecursive(mod_name);
    }

    // Check SDMC mod directory (RomFS LayeredFS)
    const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(program_id);
    if (sdmc_mod_dir != nullptr) {
        return sdmc_mod_dir->DeleteSubdirectoryRecursive(mod_name);
    }

    return false;
}

/**
 * \brief Installs an NSP
 * \param system Reference to the system instance
 * \param vfs Reference to the VfsFilesystem instance in Core::System
 * \param filename Path to the NSP file
 * \param callback Callback to report the progress of the installation. The first size_t
 * parameter is the total size of the virtual file and the second is the current progress. If you
 * return true to the callback, it will cancel the installation as soon as possible.
 * \return [InstallResult] representing how the installation finished
 */
inline InstallResult InstallNSP(Core::System& system, FileSys::VfsFilesystem& vfs,
                                const std::string& filename,
                                const std::function<bool(size_t, size_t)>& callback) {
    const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
                                 std::size_t block_size) {
        if (src == nullptr || dest == nullptr) {
            return false;
        }
        if (!dest->Resize(src->GetSize())) {
            return false;
        }

        using namespace Common::Literals;
        std::vector<u8> buffer(1_MiB);

        for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
            if (callback(src->GetSize(), i)) {
                dest->Resize(0);
                return false;
            }
            const auto read = src->Read(buffer.data(), buffer.size(), i);
            dest->Write(buffer.data(), read, i);
        }
        return true;
    };

    std::shared_ptr<FileSys::NSP> nsp;
    FileSys::VirtualFile file = vfs.OpenFile(filename, FileSys::OpenMode::Read);
    if (boost::to_lower_copy(file->GetName()).ends_with(std::string("nsp"))) {
        nsp = std::make_shared<FileSys::NSP>(file);
        if (nsp->IsExtractedType()) {
            return InstallResult::Failure;
        }
    } else {
        return InstallResult::Failure;
    }

    if (nsp->GetStatus() != Loader::ResultStatus::Success) {
        return InstallResult::Failure;
    }
    const auto res =
        system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true, copy);
    switch (res) {
    case FileSys::InstallResult::Success:
        return InstallResult::Success;
    case FileSys::InstallResult::OverwriteExisting:
        return InstallResult::Overwrite;
    case FileSys::InstallResult::ErrorBaseInstall:
        return InstallResult::BaseInstallAttempted;
    default:
        return InstallResult::Failure;
    }
}

/**
 * \brief Installs an NCA
 * \param vfs Reference to the VfsFilesystem instance in Core::System
 * \param filename Path to the NCA file
 * \param registered_cache Reference to the registered cache that the NCA will be installed to
 * \param title_type Type of NCA package to install
 * \param callback Callback to report the progress of the installation. The first size_t
 * parameter is the total size of the virtual file and the second is the current progress. If you
 * return true to the callback, it will cancel the installation as soon as possible.
 * \return [InstallResult] representing how the installation finished
 */
inline InstallResult InstallNCA(FileSys::VfsFilesystem& vfs, const std::string& filename,
                                FileSys::RegisteredCache& registered_cache,
                                const FileSys::TitleType title_type,
                                const std::function<bool(size_t, size_t)>& callback) {
    const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
                                 std::size_t block_size) {
        if (src == nullptr || dest == nullptr) {
            return false;
        }
        if (!dest->Resize(src->GetSize())) {
            return false;
        }

        using namespace Common::Literals;
        std::vector<u8> buffer(1_MiB);

        for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
            if (callback(src->GetSize(), i)) {
                dest->Resize(0);
                return false;
            }
            const auto read = src->Read(buffer.data(), buffer.size(), i);
            dest->Write(buffer.data(), read, i);
        }
        return true;
    };

    const auto nca =
        std::make_shared<FileSys::NCA>(vfs.OpenFile(filename, FileSys::OpenMode::Read));
    const auto id = nca->GetStatus();

    // Game updates necessary are missing base RomFS
    if (id != Loader::ResultStatus::Success &&
        id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
        return InstallResult::Failure;
    }

    const auto res = registered_cache.InstallEntry(*nca, title_type, true, copy);
    if (res == FileSys::InstallResult::Success) {
        return InstallResult::Success;
    } else if (res == FileSys::InstallResult::OverwriteExisting) {
        return InstallResult::Overwrite;
    } else {
        return InstallResult::Failure;
    }
}

/**
 * \brief Verifies the installed contents for a given ManualContentProvider
 * \param system Reference to the system instance
 * \param provider Reference to the content provider that's tracking indexed games
 * \param callback Callback to report the progress of the installation. The first size_t
 * parameter is the total size of the installed contents and the second is the current progress. If
 * you return true to the callback, it will cancel the installation as soon as possible.
 * \param firmware_only Set to true to only scan system nand NCAs (firmware), post firmware install.
 * \return A list of entries that failed to install. Returns an empty vector if successful.
 */
inline std::vector<std::string> VerifyInstalledContents(
    Core::System& system, FileSys::ManualContentProvider& provider,
    const std::function<bool(size_t, size_t)>& callback, bool firmware_only = false) {
    // Get content registries.
    auto bis_contents = system.GetFileSystemController().GetSystemNANDContents();
    auto user_contents = system.GetFileSystemController().GetUserNANDContents();

    std::vector<FileSys::RegisteredCache*> content_providers;
    if (bis_contents) {
        content_providers.push_back(bis_contents);
    }
    if (user_contents && !firmware_only) {
        content_providers.push_back(user_contents);
    }

    // Get associated NCA files.
    std::vector<FileSys::VirtualFile> nca_files;

    // Get all installed IDs.
    size_t total_size = 0;
    for (auto nca_provider : content_providers) {
        const auto entries = nca_provider->ListEntriesFilter();

        for (const auto& entry : entries) {
            auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type);
            if (!nca_file) {
                continue;
            }

            total_size += nca_file->GetSize();
            nca_files.push_back(std::move(nca_file));
        }
    }

    // Declare a list of file names which failed to verify.
    std::vector<std::string> failed;

    size_t processed_size = 0;
    bool cancelled = false;
    auto nca_callback = [&](size_t nca_processed, size_t nca_total) {
        cancelled = callback(total_size, processed_size + nca_processed);
        return !cancelled;
    };

    // Using the NCA loader, determine if all NCAs are valid.
    for (auto& nca_file : nca_files) {
        Loader::AppLoader_NCA nca_loader(nca_file);

        auto status = nca_loader.VerifyIntegrity(nca_callback);
        if (cancelled) {
            break;
        }
        if (status != Loader::ResultStatus::Success) {
            FileSys::NCA nca(nca_file);
            const auto title_id = nca.GetTitleId();
            std::string title_name = "unknown";

            const auto control = provider.GetEntry(FileSys::GetBaseTitleID(title_id),
                                                   FileSys::ContentRecordType::Control);
            if (control && control->GetStatus() == Loader::ResultStatus::Success) {
                const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
                                               provider};
                const auto [nacp, logo] = pm.ParseControlNCA(*control);
                if (nacp) {
                    title_name = nacp->GetApplicationName();
                }
            }

            if (title_id > 0) {
                failed.push_back(
                    fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name));
            } else {
                failed.push_back(fmt::format("{} (unknown)", nca_file->GetName()));
            }
        }

        processed_size += nca_file->GetSize();
    }
    return failed;
}

/**
 * \brief Verifies the contents of a given game
 * \param system Reference to the system instance
 * \param game_path Patch to the game file
 * \param callback Callback to report the progress of the installation. The first size_t
 * parameter is the total size of the installed contents and the second is the current progress. If
 * you return true to the callback, it will cancel the installation as soon as possible.
 * \return GameVerificationResult representing how the verification process finished
 */
inline GameVerificationResult VerifyGameContents(
    Core::System& system, const std::string& game_path,
    const std::function<bool(size_t, size_t)>& callback) {
    const auto loader = Loader::GetLoader(
        system, system.GetFilesystem()->OpenFile(game_path, FileSys::OpenMode::Read));
    if (loader == nullptr) {
        return GameVerificationResult::NotImplemented;
    }

    bool cancelled = false;
    auto loader_callback = [&](size_t processed, size_t total) {
        cancelled = callback(total, processed);
        return !cancelled;
    };

    const auto status = loader->VerifyIntegrity(loader_callback);
    if (cancelled || status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) {
        return GameVerificationResult::NotImplemented;
    }

    if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) {
        return GameVerificationResult::Failed;
    }
    return GameVerificationResult::Success;
}

/**
 * Checks if the keys required for decrypting firmware and games are available
 */
inline bool AreKeysPresent() {
    return !Core::Crypto::KeyManager::Instance().BaseDeriveNecessary();
}

} // namespace ContentManager


code:
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later

add_library(hid_core STATIC
    frontend/emulated_console.cpp
    frontend/emulated_console.h
    frontend/emulated_controller.cpp
    frontend/emulated_controller.h
    frontend/emulated_devices.cpp
    frontend/emulated_devices.h
    frontend/input_converter.cpp
    frontend/input_converter.h
    frontend/input_interpreter.cpp
    frontend/input_interpreter.h
    frontend/motion_input.cpp
    frontend/motion_input.h
    hidbus/hidbus_base.cpp
    hidbus/hidbus_base.h
    hidbus/ringcon.cpp
    hidbus/ringcon.h
    hidbus/starlink.cpp
    hidbus/starlink.h
    hidbus/stubbed.cpp
    hidbus/stubbed.h
    irsensor/clustering_processor.cpp
    irsensor/clustering_processor.h
    irsensor/image_transfer_processor.cpp
    irsensor/image_transfer_processor.h
    irsensor/ir_led_processor.cpp
    irsensor/ir_led_processor.h
    irsensor/moment_processor.cpp
    irsensor/moment_processor.h
    irsensor/pointing_processor.cpp
    irsensor/pointing_processor.h
    irsensor/processor_base.cpp
    irsensor/processor_base.h
    irsensor/tera_plugin_processor.cpp
    irsensor/tera_plugin_processor.h
    resources/abstracted_pad/abstract_battery_handler.cpp
    resources/abstracted_pad/abstract_battery_handler.h
    resources/abstracted_pad/abstract_button_handler.cpp
    resources/abstracted_pad/abstract_button_handler.h
    resources/abstracted_pad/abstract_ir_sensor_handler.cpp
    resources/abstracted_pad/abstract_ir_sensor_handler.h
    resources/abstracted_pad/abstract_led_handler.cpp
    resources/abstracted_pad/abstract_led_handler.h
    resources/abstracted_pad/abstract_mcu_handler.cpp
    resources/abstracted_pad/abstract_mcu_handler.h
    resources/abstracted_pad/abstract_nfc_handler.cpp
    resources/abstracted_pad/abstract_nfc_handler.h
    resources/abstracted_pad/abstract_pad.cpp
    resources/abstracted_pad/abstract_pad.h
    resources/abstracted_pad/abstract_pad_holder.cpp
    resources/abstracted_pad/abstract_pad_holder.h
    resources/abstracted_pad/abstract_palma_handler.cpp
    resources/abstracted_pad/abstract_palma_handler.h
    resources/abstracted_pad/abstract_properties_handler.cpp
    resources/abstracted_pad/abstract_properties_handler.h
    resources/abstracted_pad/abstract_sixaxis_handler.cpp
    resources/abstracted_pad/abstract_sixaxis_handler.h
    resources/abstracted_pad/abstract_vibration_handler.cpp
    resources/abstracted_pad/abstract_vibration_handler.h
    resources/debug_pad/debug_pad.cpp
    resources/debug_pad/debug_pad.h
    resources/debug_pad/debug_pad_types.h
    resources/digitizer/digitizer.cpp
    resources/digitizer/digitizer.h
    resources/keyboard/keyboard.cpp
    resources/keyboard/keyboard.h
    resources/keyboard/keyboard_types.h
    resources/mouse/debug_mouse.cpp
    resources/mouse/debug_mouse.h
    resources/mouse/mouse.cpp
    resources/mouse/mouse.h
    resources/mouse/mouse_types.h
    resources/npad/npad.cpp
    resources/npad/npad.h
    resources/npad/npad_data.cpp
    resources/npad/npad_data.h
    resources/npad/npad_resource.cpp
    resources/npad/npad_resource.h
    resources/npad/npad_types.h
    resources/npad/npad_vibration.cpp
    resources/npad/npad_vibration.h
    resources/palma/palma.cpp
    resources/palma/palma.h
    resources/six_axis/console_six_axis.cpp
    resources/six_axis/console_six_axis.h
    resources/six_axis/seven_six_axis.cpp
    resources/six_axis/seven_six_axis.h
    resources/six_axis/six_axis.cpp
    resources/six_axis/six_axis.h
    resources/system_buttons/capture_button.cpp
    resources/system_buttons/capture_button.h
    resources/system_buttons/home_button.cpp
    resources/system_buttons/home_button.h
    resources/system_buttons/sleep_button.cpp
    resources/system_buttons/sleep_button.h
    resources/system_buttons/system_button_types.h
    resources/touch_screen/gesture.cpp
    resources/touch_screen/gesture.h
    resources/touch_screen/gesture_handler.cpp
    resources/touch_screen/gesture_handler.h
    resources/touch_screen/touch_screen.cpp
    resources/touch_screen/touch_screen.h
    resources/touch_screen/touch_screen_driver.cpp
    resources/touch_screen/touch_screen_driver.h
    resources/touch_screen/touch_screen_resource.cpp
    resources/touch_screen/touch_screen_resource.h
    resources/touch_screen/touch_types.h
    resources/unique_pad/unique_pad.cpp
    resources/unique_pad/unique_pad.h
    resources/vibration/gc_vibration_device.h
    resources/vibration/gc_vibration_device.cpp
    resources/vibration/n64_vibration_device.h
    resources/vibration/n64_vibration_device.cpp
    resources/vibration/vibration_base.h
    resources/vibration/vibration_base.cpp
    resources/vibration/vibration_device.h
    resources/vibration/vibration_device.cpp
    resources/applet_resource.cpp
    resources/applet_resource.h
    resources/controller_base.cpp
    resources/controller_base.h
    resources/hid_firmware_settings.cpp
    resources/hid_firmware_settings.h
    resources/irs_ring_lifo.h
    resources/ring_lifo.h
    resources/shared_memory_format.h
    resources/shared_memory_holder.cpp
    resources/shared_memory_holder.h
    hid_core.cpp
    hid_core.h
    hid_result.h
    hid_types.h
    hid_util.h
    precompiled_headers.h
    resource_manager.cpp
    resource_manager.h
)

if (MSVC)
    target_compile_options(hid_core PRIVATE
        /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
        /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data
        /we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch
        /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
        /we4800 # Implicit conversion from 'type' to bool. Possible information loss
    )
else()
    target_compile_options(hid_core PRIVATE
        -Werror=conversion

        -Wno-sign-conversion
        -Wno-cast-function-type

        $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
    )
endif()

create_target_directory_groups(hid_core)
target_link_libraries(hid_core PUBLIC core)

if (YUZU_USE_PRECOMPILED_HEADERS)
    target_precompile_headers(hid_core PRIVATE precompiled_headers.h)
endif()

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/settings.h"
#include "hid_core/frontend/emulated_console.h"
#include "hid_core/frontend/input_converter.h"

namespace Core::HID {
EmulatedConsole::EmulatedConsole() = default;

EmulatedConsole::~EmulatedConsole() = default;

void EmulatedConsole::ReloadFromSettings() {
    // Using first motion device from player 1. No need to assign any unique config at the moment
    const auto& player = Settings::values.players.GetValue()[0];
    motion_params[0] = Common::ParamPackage(player.motions[0]);

    ReloadInput();
}

void EmulatedConsole::SetTouchParams() {
    std::size_t index = 0;

    // We can't use mouse as touch if native mouse is enabled
    if (!Settings::values.mouse_enabled) {
        touch_params[index++] =
            Common::ParamPackage{"engine:mouse,axis_x:0,axis_y:1,button:0,port:2"};
    }

    touch_params[index++] =
        Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536"};
    touch_params[index++] =
        Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072"};

    for (int i = 0; i < static_cast<int>(MaxActiveTouchInputs); i++) {
        Common::ParamPackage touchscreen_param{};
        touchscreen_param.Set("engine", "touch");
        touchscreen_param.Set("axis_x", i * 2);
        touchscreen_param.Set("axis_y", (i * 2) + 1);
        touchscreen_param.Set("button", i);
        touch_params[index++] = std::move(touchscreen_param);
    }

    if (Settings::values.touch_from_button_maps.empty()) {
        LOG_WARNING(Input, "touch_from_button_maps is unset by frontend config");
        return;
    }

    const auto button_index =
        static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
    const auto& touch_buttons = Settings::values.touch_from_button_maps[button_index].buttons;

    // Map the rest of the fingers from touch from button configuration
    for (const auto& config_entry : touch_buttons) {
        if (index >= MaxTouchDevices) {
            continue;
        }
        Common::ParamPackage params{config_entry};
        Common::ParamPackage touch_button_params;
        const int x = params.Get("x", 0);
        const int y = params.Get("y", 0);
        params.Erase("x");
        params.Erase("y");
        touch_button_params.Set("engine", "touch_from_button");
        touch_button_params.Set("button", params.Serialize());
        touch_button_params.Set("x", x);
        touch_button_params.Set("y", y);
        touch_params[index] = std::move(touch_button_params);
        index++;
    }
}

void EmulatedConsole::ReloadInput() {
    // If you load any device here add the equivalent to the UnloadInput() function
    SetTouchParams();

    motion_params[1] = Common::ParamPackage{"engine:virtual_gamepad,port:8,motion:0"};

    for (std::size_t index = 0; index < motion_devices.size(); ++index) {
        motion_devices[index] = Common::Input::CreateInputDevice(motion_params[index]);
        if (!motion_devices[index]) {
            continue;
        }
        motion_devices[index]->SetCallback({
            .on_change =
                [this](const Common::Input::CallbackStatus& callback) { SetMotion(callback); },
        });
    }

    // Restore motion state
    auto& emulated_motion = console.motion_values.emulated;
    auto& motion = console.motion_state;
    emulated_motion.ResetRotations();
    emulated_motion.ResetQuaternion();
    motion.accel = emulated_motion.GetAcceleration();
    motion.gyro = emulated_motion.GetGyroscope();
    motion.rotation = emulated_motion.GetRotations();
    motion.orientation = emulated_motion.GetOrientation();
    motion.is_at_rest = !emulated_motion.IsMoving(motion_sensitivity);

    // Unique index for identifying touch device source
    std::size_t index = 0;
    for (auto& touch_device : touch_devices) {
        touch_device = Common::Input::CreateInputDevice(touch_params[index]);
        if (!touch_device) {
            continue;
        }
        touch_device->SetCallback({
            .on_change =
                [this, index](const Common::Input::CallbackStatus& callback) {
                    SetTouch(callback, index);
                },
        });
        index++;
    }
}

void EmulatedConsole::UnloadInput() {
    for (auto& motion : motion_devices) {
        motion.reset();
    }
    for (auto& touch : touch_devices) {
        touch.reset();
    }
}

void EmulatedConsole::EnableConfiguration() {
    is_configuring = true;
    SaveCurrentConfig();
}

void EmulatedConsole::DisableConfiguration() {
    is_configuring = false;
}

bool EmulatedConsole::IsConfiguring() const {
    return is_configuring;
}

void EmulatedConsole::SaveCurrentConfig() {
    if (!is_configuring) {
        return;
    }
}

void EmulatedConsole::RestoreConfig() {
    if (!is_configuring) {
        return;
    }
    ReloadFromSettings();
}

Common::ParamPackage EmulatedConsole::GetMotionParam() const {
    return motion_params[0];
}

void EmulatedConsole::SetMotionParam(Common::ParamPackage param) {
    motion_params[0] = std::move(param);
    ReloadInput();
}

void EmulatedConsole::SetMotion(const Common::Input::CallbackStatus& callback) {
    std::unique_lock lock{mutex};
    auto& raw_status = console.motion_values.raw_status;
    auto& emulated = console.motion_values.emulated;

    raw_status = TransformToMotion(callback);
    emulated.SetAcceleration(Common::Vec3f{
        raw_status.accel.x.value,
        raw_status.accel.y.value,
        raw_status.accel.z.value,
    });
    emulated.SetGyroscope(Common::Vec3f{
        raw_status.gyro.x.value,
        raw_status.gyro.y.value,
        raw_status.gyro.z.value,
    });
    emulated.UpdateRotation(raw_status.delta_timestamp);
    emulated.UpdateOrientation(raw_status.delta_timestamp);

    if (is_configuring) {
        lock.unlock();
        TriggerOnChange(ConsoleTriggerType::Motion);
        return;
    }

    auto& motion = console.motion_state;
    motion.accel = emulated.GetAcceleration();
    motion.gyro = emulated.GetGyroscope();
    motion.rotation = emulated.GetRotations();
    motion.orientation = emulated.GetOrientation();
    motion.quaternion = emulated.GetQuaternion();
    motion.gyro_bias = emulated.GetGyroBias();
    motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
    // Find what is this value
    motion.verticalization_error = 0.0f;

    lock.unlock();
    TriggerOnChange(ConsoleTriggerType::Motion);
}

void EmulatedConsole::SetTouch(const Common::Input::CallbackStatus& callback, std::size_t index) {
    if (index >= MaxTouchDevices) {
        return;
    }
    std::unique_lock lock{mutex};

    const auto touch_input = TransformToTouch(callback);
    auto touch_index = GetIndexFromFingerId(index);
    bool is_new_input = false;

    if (!touch_index.has_value() && touch_input.pressed.value) {
        touch_index = GetNextFreeIndex();
        is_new_input = true;
    }

    // No free entries or invalid state. Ignore input
    if (!touch_index.has_value()) {
        return;
    }

    auto& touch_value = console.touch_values[touch_index.value()];

    if (is_new_input) {
        touch_value.pressed.value = true;
        touch_value.id = static_cast<int>(index);
    }

    touch_value.x = touch_input.x;
    touch_value.y = touch_input.y;

    if (!touch_input.pressed.value) {
        touch_value.pressed.value = false;
    }

    if (is_configuring) {
        lock.unlock();
        TriggerOnChange(ConsoleTriggerType::Touch);
        return;
    }

    // Touch outside allowed range. Ignore input
    if (touch_index.value() >= MaxActiveTouchInputs) {
        return;
    }

    console.touch_state[touch_index.value()] = {
        .position = {touch_value.x.value, touch_value.y.value},
        .id = static_cast<u32>(touch_index.value()),
        .pressed = touch_input.pressed.value,
    };

    lock.unlock();
    TriggerOnChange(ConsoleTriggerType::Touch);
}

ConsoleMotionValues EmulatedConsole::GetMotionValues() const {
    std::scoped_lock lock{mutex};
    return console.motion_values;
}

TouchValues EmulatedConsole::GetTouchValues() const {
    std::scoped_lock lock{mutex};
    return console.touch_values;
}

ConsoleMotion EmulatedConsole::GetMotion() const {
    std::scoped_lock lock{mutex};
    return console.motion_state;
}

TouchFingerState EmulatedConsole::GetTouch() const {
    std::scoped_lock lock{mutex};
    return console.touch_state;
}

std::optional<std::size_t> EmulatedConsole::GetIndexFromFingerId(std::size_t finger_id) const {
    for (std::size_t index = 0; index < MaxTouchDevices; ++index) {
        const auto& finger = console.touch_values[index];
        if (!finger.pressed.value) {
            continue;
        }
        if (finger.id == static_cast<int>(finger_id)) {
            return index;
        }
    }
    return std::nullopt;
}

std::optional<std::size_t> EmulatedConsole::GetNextFreeIndex() const {
    for (std::size_t index = 0; index < MaxTouchDevices; ++index) {
        if (!console.touch_values[index].pressed.value) {
            return index;
        }
    }
    return std::nullopt;
}

void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) {
    std::scoped_lock lock{callback_mutex};
    for (const auto& poller_pair : callback_list) {
        const ConsoleUpdateCallback& poller = poller_pair.second;
        if (poller.on_change) {
            poller.on_change(type);
        }
    }
}

int EmulatedConsole::SetCallback(ConsoleUpdateCallback update_callback) {
    std::scoped_lock lock{callback_mutex};
    callback_list.insert_or_assign(last_callback_key, std::move(update_callback));
    return last_callback_key++;
}

void EmulatedConsole::DeleteCallback(int key) {
    std::scoped_lock lock{callback_mutex};
    const auto& iterator = callback_list.find(key);
    if (iterator == callback_list.end()) {
        LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
        return;
    }
    callback_list.erase(iterator);
}
} // namespace Core::HID


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <unordered_map>

#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/input.h"
#include "common/param_package.h"
#include "common/point.h"
#include "common/quaternion.h"
#include "common/vector_math.h"
#include "hid_core/frontend/motion_input.h"
#include "hid_core/hid_types.h"

namespace Core::HID {
static constexpr std::size_t MaxTouchDevices = 32;
static constexpr std::size_t MaxActiveTouchInputs = 16;

struct ConsoleMotionInfo {
    Common::Input::MotionStatus raw_status{};
    MotionInput emulated{};
};

using ConsoleMotionDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, 2>;
using TouchDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, MaxTouchDevices>;

using ConsoleMotionParams = std::array<Common::ParamPackage, 2>;
using TouchParams = std::array<Common::ParamPackage, MaxTouchDevices>;

using ConsoleMotionValues = ConsoleMotionInfo;
using TouchValues = std::array<Common::Input::TouchStatus, MaxTouchDevices>;

// Contains all motion related data that is used on the services
struct ConsoleMotion {
    Common::Vec3f accel{};
    Common::Vec3f gyro{};
    Common::Vec3f rotation{};
    std::array<Common::Vec3f, 3> orientation{};
    Common::Quaternion<f32> quaternion{};
    Common::Vec3f gyro_bias{};
    f32 verticalization_error{};
    bool is_at_rest{};
};

using TouchFingerState = std::array<TouchFinger, MaxActiveTouchInputs>;

struct ConsoleStatus {
    // Data from input_common
    ConsoleMotionValues motion_values{};
    TouchValues touch_values{};

    // Data for HID services
    ConsoleMotion motion_state{};
    TouchFingerState touch_state{};
};

enum class ConsoleTriggerType {
    Motion,
    Touch,
    All,
};

struct ConsoleUpdateCallback {
    std::function<void(ConsoleTriggerType)> on_change;
};

class EmulatedConsole {
public:
    /**
     * Contains all input data within the emulated switch console tablet such as touch and motion
     */
    explicit EmulatedConsole();
    ~EmulatedConsole();

    YUZU_NON_COPYABLE(EmulatedConsole);
    YUZU_NON_MOVEABLE(EmulatedConsole);

    /// Removes all callbacks created from input devices
    void UnloadInput();

    /**
     * Sets the emulated console into configuring mode
     * This prevents the modification of the HID state of the emulated console by input commands
     */
    void EnableConfiguration();

    /// Returns the emulated console into normal mode, allowing the modification of the HID state
    void DisableConfiguration();

    /// Returns true if the emulated console is in configuring mode
    bool IsConfiguring() const;

    /// Reload all input devices
    void ReloadInput();

    /// Overrides current mapped devices with the stored configuration and reloads all input devices
    void ReloadFromSettings();

    /// Saves the current mapped configuration
    void SaveCurrentConfig();

    /// Reverts any mapped changes made that weren't saved
    void RestoreConfig();

    // Returns the current mapped motion device
    Common::ParamPackage GetMotionParam() const;

    /**
     * Updates the current mapped motion device
     * @param param ParamPackage with controller data to be mapped
     */
    void SetMotionParam(Common::ParamPackage param);

    /// Returns the latest status of motion input from the console with parameters
    ConsoleMotionValues GetMotionValues() const;

    /// Returns the latest status of touch input from the console with parameters
    TouchValues GetTouchValues() const;

    /// Returns the latest status of motion input from the console
    ConsoleMotion GetMotion() const;

    /// Returns the latest status of touch input from the console
    TouchFingerState GetTouch() const;

    /**
     * Adds a callback to the list of events
     * @param update_callback A ConsoleUpdateCallback that will be triggered
     * @return an unique key corresponding to the callback index in the list
     */
    int SetCallback(ConsoleUpdateCallback update_callback);

    /**
     * Removes a callback from the list stopping any future events to this object
     * @param key Key corresponding to the callback index in the list
     */
    void DeleteCallback(int key);

private:
    /// Creates and stores the touch params
    void SetTouchParams();

    /**
     * Updates the motion status of the console
     * @param callback A CallbackStatus containing gyro and accelerometer data
     */
    void SetMotion(const Common::Input::CallbackStatus& callback);

    /**
     * Updates the touch status of the console
     * @param callback A CallbackStatus containing the touch position
     * @param index Finger ID to be updated
     */
    void SetTouch(const Common::Input::CallbackStatus& callback, std::size_t index);

    std::optional<std::size_t> GetIndexFromFingerId(std::size_t finger_id) const;

    std::optional<std::size_t> GetNextFreeIndex() const;

    /**
     * Triggers a callback that something has changed on the console status
     * @param type Input type of the event to trigger
     */
    void TriggerOnChange(ConsoleTriggerType type);

    bool is_configuring{false};
    f32 motion_sensitivity{0.01f};

    ConsoleMotionParams motion_params;
    TouchParams touch_params;

    ConsoleMotionDevices motion_devices;
    TouchDevices touch_devices;

    mutable std::mutex mutex;
    mutable std::mutex callback_mutex;
    std::unordered_map<int, ConsoleUpdateCallback> callback_list;
    int last_callback_key = 0;

    // Stores the current status of all console input
    ConsoleStatus console;
};

} // namespace Core::HID