diff --git a/CMakeLists.txt b/CMakeLists.txt index e0e877a529..0d88323782 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -298,7 +298,7 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/ngs2/ngs2_impl.cpp src/core/libraries/ngs2/ngs2_impl.h src/core/libraries/ajm/ajm_error.h - src/core/libraries/audio3d/audio3d.cpp + src/core/libraries/audio3d/audio3d.cpp src/core/libraries/audio3d/audio3d.h src/core/libraries/audio3d/audio3d_error.h src/core/libraries/audio3d/audio3d_impl.cpp @@ -461,6 +461,7 @@ set(COMMON src/common/logging/backend.cpp src/common/types.h src/common/uint128.h src/common/unique_function.h + src/common/va_ctx.h src/common/version.h src/common/ntapi.h src/common/ntapi.cpp @@ -485,6 +486,11 @@ set(CORE src/core/aerolib/stubs.cpp src/core/crypto/crypto.cpp src/core/crypto/crypto.h src/core/crypto/keys.h + src/core/devices/base_device.cpp + src/core/devices/base_device.h + src/core/devices/hid_device.cpp + src/core/devices/hid_device.h + src/core/devices/hid_device_mouse.cpp src/core/file_format/pfs.h src/core/file_format/pkg.cpp src/core/file_format/pkg.h @@ -724,6 +730,8 @@ set(IMGUI src/imgui/imgui_config.h set(INPUT src/input/controller.cpp src/input/controller.h + src/input/mouse.cpp + src/input/mouse.h ) set(EMULATOR src/emulator.cpp diff --git a/src/common/io_file.cpp b/src/common/io_file.cpp index dd3a40cae4..067010a261 100644 --- a/src/common/io_file.cpp +++ b/src/common/io_file.cpp @@ -377,16 +377,18 @@ bool IOFile::Seek(s64 offset, SeekOrigin origin) const { return false; } - u64 size = GetSize(); - if (origin == SeekOrigin::CurrentPosition && Tell() + offset > size) { - LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); - return false; - } else if (origin == SeekOrigin::SetOrigin && (u64)offset > size) { - LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); - return false; - } else if (origin == SeekOrigin::End && offset > 0) { - LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); - return false; + if (False(file_access_mode & (FileAccessMode::Write | FileAccessMode::Append))) { + u64 size = GetSize(); + if (origin == SeekOrigin::CurrentPosition && Tell() + offset > size) { + LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); + return false; + } else if (origin == SeekOrigin::SetOrigin && (u64)offset > size) { + LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); + return false; + } else if (origin == SeekOrigin::End && offset > 0) { + LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); + return false; + } } errno = 0; diff --git a/src/common/io_file.h b/src/common/io_file.h index 8fed4981f3..feb2110ac8 100644 --- a/src/common/io_file.h +++ b/src/common/io_file.h @@ -10,6 +10,7 @@ #include "common/concepts.h" #include "common/types.h" +#include "enum.h" namespace Common::FS { @@ -42,6 +43,7 @@ enum class FileAccessMode { */ ReadAppend = Read | Append, }; +DECLARE_ENUM_FLAG_OPERATORS(FileAccessMode); enum class FileType { BinaryFile, diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index 5ca594bf7b..47ce2eb03b 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -69,6 +69,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Common, Memory) \ CLS(Core) \ SUB(Core, Linker) \ + SUB(Core, Devices) \ CLS(Config) \ CLS(Debug) \ CLS(Kernel) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 2821729d40..63823f4ea6 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -35,6 +35,7 @@ enum class Class : u8 { Common_Memory, ///< Memory mapping and management functions Core, ///< LLE emulation core Core_Linker, ///< The module linker + Core_Devices, ///< Devices emulation Config, ///< Emulator configuration (including commandline) Debug, ///< Debugging tools Kernel, ///< The HLE implementation of the PS4 kernel. diff --git a/src/common/va_ctx.h b/src/common/va_ctx.h new file mode 100644 index 0000000000..e0b8c0bab6 --- /dev/null +++ b/src/common/va_ctx.h @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include "common/types.h" + +#define VA_ARGS \ + uint64_t rdi, uint64_t rsi, uint64_t rdx, uint64_t rcx, uint64_t r8, uint64_t r9, \ + uint64_t overflow_arg_area, __m128 xmm0, __m128 xmm1, __m128 xmm2, __m128 xmm3, \ + __m128 xmm4, __m128 xmm5, __m128 xmm6, __m128 xmm7, ... + +#define VA_CTX(ctx) \ + alignas(16)::Common::VaCtx ctx{}; \ + (ctx).reg_save_area.gp[0] = rdi; \ + (ctx).reg_save_area.gp[1] = rsi; \ + (ctx).reg_save_area.gp[2] = rdx; \ + (ctx).reg_save_area.gp[3] = rcx; \ + (ctx).reg_save_area.gp[4] = r8; \ + (ctx).reg_save_area.gp[5] = r9; \ + (ctx).reg_save_area.fp[0] = xmm0; \ + (ctx).reg_save_area.fp[1] = xmm1; \ + (ctx).reg_save_area.fp[2] = xmm2; \ + (ctx).reg_save_area.fp[3] = xmm3; \ + (ctx).reg_save_area.fp[4] = xmm4; \ + (ctx).reg_save_area.fp[5] = xmm5; \ + (ctx).reg_save_area.fp[6] = xmm6; \ + (ctx).reg_save_area.fp[7] = xmm7; \ + (ctx).va_list.reg_save_area = &(ctx).reg_save_area; \ + (ctx).va_list.gp_offset = offsetof(::Common::VaRegSave, gp); \ + (ctx).va_list.fp_offset = offsetof(::Common::VaRegSave, fp); \ + (ctx).va_list.overflow_arg_area = &overflow_arg_area; + +namespace Common { + +// https://stackoverflow.com/questions/4958384/what-is-the-format-of-the-x86-64-va-list-structure + +struct VaList { + u32 gp_offset; + u32 fp_offset; + void* overflow_arg_area; + void* reg_save_area; +}; + +struct VaRegSave { + u64 gp[6]; + __m128 fp[8]; +}; + +struct VaCtx { + VaRegSave reg_save_area; + VaList va_list; +}; + +template +T vaArgRegSaveAreaGp(VaList* l) { + auto* addr = reinterpret_cast(static_cast(l->reg_save_area) + l->gp_offset); + l->gp_offset += Size; + return *addr; +} +template +T vaArgOverflowArgArea(VaList* l) { + auto ptr = ((reinterpret_cast(l->overflow_arg_area) + (Align - 1)) & ~(Align - 1)); + auto* addr = reinterpret_cast(ptr); + l->overflow_arg_area = reinterpret_cast(ptr + Size); + return *addr; +} + +template +T vaArgRegSaveAreaFp(VaList* l) { + auto* addr = reinterpret_cast(static_cast(l->reg_save_area) + l->fp_offset); + l->fp_offset += Size; + return *addr; +} + +inline int vaArgInteger(VaList* l) { + if (l->gp_offset <= 40) { + return vaArgRegSaveAreaGp(l); + } + return vaArgOverflowArgArea(l); +} + +inline long long vaArgLongLong(VaList* l) { + if (l->gp_offset <= 40) { + return vaArgRegSaveAreaGp(l); + } + return vaArgOverflowArgArea(l); +} +inline long vaArgLong(VaList* l) { + if (l->gp_offset <= 40) { + return vaArgRegSaveAreaGp(l); + } + return vaArgOverflowArgArea(l); +} + +inline double vaArgDouble(VaList* l) { + if (l->fp_offset <= 160) { + return vaArgRegSaveAreaFp(l); + } + return vaArgOverflowArgArea(l); +} + +template +T* vaArgPtr(VaList* l) { + if (l->gp_offset <= 40) { + return vaArgRegSaveAreaGp(l); + } + return vaArgOverflowArgArea(l); +} + +} // namespace Common diff --git a/src/core/devices/base_device.cpp b/src/core/devices/base_device.cpp new file mode 100644 index 0000000000..4f91c81c79 --- /dev/null +++ b/src/core/devices/base_device.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "base_device.h" + +namespace Core::Devices { + +BaseDevice::BaseDevice() = default; + +BaseDevice::~BaseDevice() = default; + +} // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/base_device.h b/src/core/devices/base_device.h new file mode 100644 index 0000000000..38ab894672 --- /dev/null +++ b/src/core/devices/base_device.h @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" +#include "common/va_ctx.h" +#include "core/libraries/error_codes.h" + +namespace Libraries::Kernel { +struct OrbisKernelStat; +struct SceKernelIovec; +} // namespace Libraries::Kernel + +namespace Core::Devices { + +class BaseDevice { +public: + explicit BaseDevice(); + + virtual ~BaseDevice() = 0; + + virtual int ioctl(u64 cmd, Common::VaCtx* args) { + return ORBIS_KERNEL_ERROR_ENOTTY; + } + + virtual s64 write(const void* buf, size_t nbytes) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual size_t readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual s64 lseek(s64 offset, int whence) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual s64 read(void* buf, size_t nbytes) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual s64 pread(void* buf, size_t nbytes, u64 offset) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual int fstat(Libraries::Kernel::OrbisKernelStat* sb) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual s32 fsync() { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual int ftruncate(s64 length) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual int getdents(void* buf, u32 nbytes, s64* basep) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual s64 pwrite(const void* buf, size_t nbytes, u64 offset) { + return ORBIS_KERNEL_ERROR_EBADF; + } +}; + +} // namespace Core::Devices diff --git a/src/core/devices/hid_device.cpp b/src/core/devices/hid_device.cpp new file mode 100644 index 0000000000..ce567b1077 --- /dev/null +++ b/src/core/devices/hid_device.cpp @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "common/logging/log.h" +#include "common/singleton.h" +#include "common/va_ctx.h" +#include "core/libraries/error_codes.h" +#include "hid_device.h" +#include "input/mouse.h" +#include "ioccom.h" + +namespace Core::Devices { + +struct InitHidRaw { + u32 user_id; + u32 type; + u32 _unknown2; +}; +static_assert(sizeof(InitHidRaw) == 0xC); + +constexpr auto HID_CMD_INIT_HANDLE_FOR_USER_ID = _IOW('H', 0x2, InitHidRaw); + +int HidDevice::GenericCallback(u64 cmd, Common::VaCtx* args) { + LOG_TRACE(Core_Devices, "HID({:X}) generic: ioctl cmd={:X}", handle, cmd); + if (cmd == HID_CMD_INIT_HANDLE_FOR_USER_ID) { + auto data = vaArgPtr(&args->va_list); + this->user_id = data->user_id; + if (data->type == 0) { + LOG_INFO(Core_Devices, "HID({:X}) open: type=mouse, user_id={}", handle, data->user_id); + this->m_callback = &HidDevice::MouseCallback; + // FIXME Replace by status bar + if (!Common::Singleton::Instance()->m_connected) { + using namespace ::Libraries::MsgDialog; + ShowMsgDialog(MsgDialogState(MsgDialogState::UserState{ + .type = ButtonType::YESNO, + .msg = "Game wants to use your mouse.\nDo you want to allow it?", + }), + false, [](DialogResult result) { + if (result.buttonId == ButtonId::YES) { + auto* mouse = Common::Singleton::Instance(); + mouse->m_connected = true; + } + }); + } + } else { + LOG_WARNING(Core_Devices, "HID({:X}) open: unknown type={}", handle, data->type); + } + return static_cast(handle); + } + LOG_WARNING(Core_Devices, "HID({:X}) generic: unknown ioctl cmd = {:X}", handle, cmd); + return ORBIS_KERNEL_ERROR_ENOTTY; +} + +BaseDevice* HidDevice::Create(u32 handle, const char*, int, u16) { + return new HidDevice(handle); +} + +HidDevice::HidDevice(u32 handle) : handle{handle} {} + +HidDevice::~HidDevice() = default; + +int HidDevice::ioctl(u64 cmd, Common::VaCtx* args) { + return (this->*m_callback)(cmd, args); +} + +} // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/hid_device.h b/src/core/devices/hid_device.h new file mode 100644 index 0000000000..051481ecdf --- /dev/null +++ b/src/core/devices/hid_device.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "base_device.h" + +namespace Core::Devices { + +class HidDevice final : BaseDevice { + u32 handle; + u32 user_id{}; + + int MouseCallback(u64 cmd, Common::VaCtx* args); + + int GenericCallback(u64 cmd, Common::VaCtx* args); + + int (HidDevice::*m_callback)(u64 cmd, Common::VaCtx* args) = &HidDevice::GenericCallback; + +public: + static BaseDevice* Create(u32 handle, const char*, int, u16); + + explicit HidDevice(u32 handle); + + ~HidDevice() override; + + int ioctl(u64 cmd, Common::VaCtx* args) override; +}; + +} // namespace Core::Devices diff --git a/src/core/devices/hid_device_mouse.cpp b/src/core/devices/hid_device_mouse.cpp new file mode 100644 index 0000000000..5cc256a7fb --- /dev/null +++ b/src/core/devices/hid_device_mouse.cpp @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "common/singleton.h" +#include "hid_device.h" +#include "input/mouse.h" +#include "ioccom.h" + +struct MouseInfo {}; + +struct MouseDataEntry { + std::array _unknown1; + s16 x; + s16 y; + u8 buttons; + bool system_capture; + s8 wheel; + s8 tilt; + std::array _reserved; +}; + +static_assert(sizeof(MouseDataEntry) == 0x18); + +struct MouseData { + u32 handle; + std::array _unknown1; + MouseDataEntry* data; + int max_data; + std::array _unknown2; + int* read_count; +}; +static_assert(sizeof(MouseData) == 0x20); + +constexpr auto HID_CMD_READ_MOUSE_STATE = _IOW('H', 0x19, MouseData); + +namespace Core::Devices { + +int HidDevice::MouseCallback(u64 cmd, Common::VaCtx* args) { + switch (cmd) { + case HID_CMD_READ_DEVICE_INFO: { + auto info = vaArgPtr>(&args->va_list); + info->data = nullptr; + // TODO Fill data with the mouse info + return ORBIS_OK; + } + case HID_CMD_READ_MOUSE_STATE: { + auto mouse_data = vaArgPtr(&args->va_list); + auto* mouse = Common::Singleton::Instance(); + if (!mouse->m_connected) { + *mouse_data->read_count = 0; + return 0; + } + std::array states; + int total = std::min((int)states.size(), mouse_data->max_data); + total = mouse->ReadStates(states.data(), total); + for (int i = 0; i < total; ++i) { + const auto& s = states[i]; + mouse_data->data[i] = MouseDataEntry{ + .x = static_cast(s.x_axis), + .y = static_cast(s.y_axis), + .buttons = static_cast(s.button_state), + .system_capture = false, + .wheel = static_cast(s.wheel), + .tilt = static_cast(s.tilt), + }; + } + return total; + } + default: + LOG_WARNING(Core_Devices, "HID({:X}) mouse: unknown ioctl request: {:X}", handle, cmd); + return ORBIS_OK; + } +} + +} // namespace Core::Devices diff --git a/src/core/devices/ioccom.h b/src/core/devices/ioccom.h new file mode 100644 index 0000000000..5ce58f5331 --- /dev/null +++ b/src/core/devices/ioccom.h @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +/*- + * Copyright (c) 1982, 1986, 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ioccom.h 8.2 (Berkeley) 3/28/94 + * $FreeBSD$ + */ + +#define IOCPARM_SHIFT 13 /* number of bits for ioctl size */ +#define IOCPARM_MASK ((1 << IOCPARM_SHIFT) - 1) /* parameter length mask */ +#define IOCPARM_LEN(x) (((x) >> 16) & IOCPARM_MASK) +#define IOCBASECMD(x) ((x) & ~(IOCPARM_MASK << 16)) +#define IOCGROUP(x) (((x) >> 8) & 0xff) + +#define IOCPARM_MAX (1 << IOCPARM_SHIFT) /* max size of ioctl */ +#define IOC_VOID 0x20000000 /* no parameters */ +#define IOC_OUT 0x40000000 /* copy out parameters */ +#define IOC_IN 0x80000000 /* copy in parameters */ +#define IOC_INOUT (IOC_IN | IOC_OUT) +#define IOC_DIRMASK (IOC_VOID | IOC_OUT | IOC_IN) + +#define _IOC(inout, group, num, len) \ + ((unsigned long)((inout) | (((len) & IOCPARM_MASK) << 16) | ((group) << 8) | (num))) +#define _IO(g, n) _IOC(IOC_VOID, (g), (n), 0) +#define _IOWINT(g, n) _IOC(IOC_VOID, (g), (n), sizeof(int)) +#define _IOR(g, n, t) _IOC(IOC_OUT, (g), (n), sizeof(t)) +#define _IOW(g, n, t) _IOC(IOC_IN, (g), (n), sizeof(t)) +/* this should be _IORW, but stdio got there first */ +#define _IOWR(g, n, t) _IOC(IOC_INOUT, (g), (n), sizeof(t)) + +/* +# Simple parse of ioctl cmd +def parse(v): + print('inout', (v >> 24 & 0xFF)) + print('len', hex(v >> 16 & 0xFF)) + print('group', chr(v >> 8 & 0xFF)) + print('num', hex(v & 0xFF)) +*/ + +template +struct DeviceInfo { + int handle; + std::array _pad1; + T* data; +}; +static_assert(sizeof(DeviceInfo) == 0x10); + +constexpr auto HID_CMD_READ_DEVICE_INFO = _IOW('H', 0x1, DeviceInfo); diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 769940cf0a..ab1eb439f0 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -135,7 +135,6 @@ int HandleTable::CreateHandle() { std::scoped_lock lock{m_mutex}; auto* file = new File{}; - file->is_directory = false; file->is_opened = false; int existingFilesNum = m_files.size(); diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index eeaeaf7810..6ef2458f12 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -9,6 +9,7 @@ #include #include #include "common/io_file.h" +#include "core/devices/base_device.h" namespace Core::FileSys { @@ -55,15 +56,22 @@ struct DirEntry { bool isFile; }; +enum class FileType { + Regular, // standard file + Directory, + Device, +}; + struct File { std::atomic_bool is_opened{}; - std::atomic_bool is_directory{}; + std::atomic type{FileType::Regular}; std::filesystem::path m_host_name; std::string m_guest_name; Common::FS::IOFile f; std::vector dirents; u32 dirents_index; std::mutex m_mutex; + std::unique_ptr device; // only valid for type == Device }; class HandleTable { diff --git a/src/core/libraries/error_codes.h b/src/core/libraries/error_codes.h index 6bbdc30807..332293484d 100644 --- a/src/core/libraries/error_codes.h +++ b/src/core/libraries/error_codes.h @@ -425,6 +425,13 @@ constexpr int ORBIS_PAD_ERROR_INVALID_REPORT_LENGTH = 0x80920103; constexpr int ORBIS_PAD_ERROR_INVALID_REPORT_ID = 0x80920104; constexpr int ORBIS_PAD_ERROR_SEND_AGAIN = 0x80920105; +// Mouse library +constexpr int ORBIS_MOUSE_ERROR_INVALID_ARG = 0x80DF0001; +constexpr int ORBIS_MOUSE_ERROR_INVALID_HANDLE = 0x80DF0003; +constexpr int ORBIS_MOUSE_ERROR_ALREADY_OPENED = 0x80DF0004; +constexpr int ORBIS_MOUSE_ERROR_NOT_INITIALIZED = 0x80DF0005; +constexpr int ORBIS_MOUSE_ERROR_FATAL = 0x80DF00FF; + // UserService library constexpr int ORBIS_USER_SERVICE_ERROR_INTERNAL = 0x80960001; constexpr int ORBIS_USER_SERVICE_ERROR_NOT_INITIALIZED = 0x80960002; diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index 7f86ee5401..4a025374f0 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -11,6 +11,18 @@ #include "core/libraries/libs.h" #include "libkernel.h" +#include +#include + +#include "core/devices/hid_device.h" + +using FactoryDevice = std::function; + +// prefix path +static std::map available_device = { + {"/dev/hid", &Core::Devices::HidDevice::Create}, +}; + namespace Libraries::Kernel { std::vector GetDirectoryEntries(const std::filesystem::path& path) { @@ -24,8 +36,8 @@ std::vector GetDirectoryEntries(const std::filesystem:: return files; } -int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, u16 mode) { - LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {}", path, flags, mode); +int PS4_SYSV_ABI sceKernelOpen(const char* raw_path, int flags, u16 mode) { + LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {}", raw_path, flags, mode); auto* h = Common::Singleton::Instance(); auto* mnt = Common::Singleton::Instance(); @@ -44,22 +56,37 @@ int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, u16 mode) { bool direct = (flags & ORBIS_KERNEL_O_DIRECT) != 0; bool directory = (flags & ORBIS_KERNEL_O_DIRECTORY) != 0; - if (std::string_view{path} == "/dev/console") { + std::string_view path{raw_path}; + + if (path == "/dev/console") { return 2000; } - if (std::string_view{path} == "/dev/deci_tty6") { + if (path == "/dev/deci_tty6") { return 2001; } - if (std::string_view{path} == "/dev/stdout") { + if (path == "/dev/stdout") { return 2002; } - if (std::string_view{path} == "/dev/urandom") { + if (path == "/dev/urandom") { return 2003; } + u32 handle = h->CreateHandle(); auto* file = h->GetFile(handle); + + for (const auto& [prefix, factory] : available_device) { + if (path.starts_with(prefix)) { + file->is_opened = true; + file->type = Core::FileSys::FileType::Device; + file->m_guest_name = path; + file->device = std::unique_ptr{ + factory(handle, path.data(), flags, mode)}; + return handle; + } + } + if (directory) { - file->is_directory = true; + file->type = Core::FileSys::FileType::Directory; file->m_guest_name = path; file->m_host_name = mnt->GetHostPath(file->m_guest_name); if (!std::filesystem::is_directory(file->m_host_name)) { // directory doesn't exist @@ -135,11 +162,12 @@ int PS4_SYSV_ABI sceKernelClose(int d) { if (file == nullptr) { return SCE_KERNEL_ERROR_EBADF; } - if (!file->is_directory) { + if (file->type == Core::FileSys::FileType::Regular) { file->f.Close(); } file->is_opened = false; LOG_INFO(Kernel_Fs, "Closing {}", file->m_guest_name); + // FIXME: Lock file mutex before deleting it? h->DeleteHandle(d); return SCE_OK; } @@ -170,6 +198,9 @@ size_t PS4_SYSV_ABI sceKernelWrite(int d, const void* buf, size_t nbytes) { } std::scoped_lock lk{file->m_mutex}; + if (file->type == Core::FileSys::FileType::Device) { + return file->device->write(buf, nbytes); + } return file->f.WriteRaw(buf, nbytes); } @@ -207,8 +238,16 @@ int PS4_SYSV_ABI sceKernelUnlink(const char* path) { size_t PS4_SYSV_ABI _readv(int d, const SceKernelIovec* iov, int iovcnt) { auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(d); - size_t total_read = 0; std::scoped_lock lk{file->m_mutex}; + if (file->type == Core::FileSys::FileType::Device) { + int r = file->device->readv(iov, iovcnt); + if (r < 0) { + ErrSceToPosix(r); + return -1; + } + return r; + } + size_t total_read = 0; for (int i = 0; i < iovcnt; i++) { total_read += file->f.ReadRaw(iov[i].iov_base, iov[i].iov_len); } @@ -219,6 +258,11 @@ s64 PS4_SYSV_ABI sceKernelLseek(int d, s64 offset, int whence) { auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(d); + std::scoped_lock lk{file->m_mutex}; + if (file->type == Core::FileSys::FileType::Device) { + return file->device->lseek(offset, whence); + } + Common::FS::SeekOrigin origin{}; if (whence == 0) { origin = Common::FS::SeekOrigin::SetOrigin; @@ -228,7 +272,6 @@ s64 PS4_SYSV_ABI sceKernelLseek(int d, s64 offset, int whence) { origin = Common::FS::SeekOrigin::End; } - std::scoped_lock lk{file->m_mutex}; if (!file->f.Seek(offset, origin)) { LOG_CRITICAL(Kernel_Fs, "sceKernelLseek: failed to seek"); return SCE_KERNEL_ERROR_EINVAL; @@ -261,6 +304,9 @@ s64 PS4_SYSV_ABI sceKernelRead(int d, void* buf, size_t nbytes) { } std::scoped_lock lk{file->m_mutex}; + if (file->type == Core::FileSys::FileType::Device) { + return file->device->read(buf, nbytes); + } return file->f.ReadRaw(buf, nbytes); } @@ -409,7 +455,13 @@ int PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) { int PS4_SYSV_ABI sceKernelCheckReachability(const char* path) { auto* mnt = Common::Singleton::Instance(); - const auto path_name = mnt->GetHostPath(path); + std::string_view guest_path{path}; + for (const auto& prefix : available_device | std::views::keys) { + if (guest_path.starts_with(prefix)) { + return ORBIS_OK; + } + } + const auto path_name = mnt->GetHostPath(guest_path); if (!std::filesystem::exists(path_name)) { return SCE_KERNEL_ERROR_ENOENT; } @@ -431,6 +483,10 @@ s64 PS4_SYSV_ABI sceKernelPread(int d, void* buf, size_t nbytes, s64 offset) { } std::scoped_lock lk{file->m_mutex}; + if (file->type == Core::FileSys::FileType::Device) { + return file->device->pread(buf, nbytes, offset); + } + const s64 pos = file->f.Tell(); SCOPE_EXIT { file->f.Seek(pos); @@ -457,18 +513,25 @@ int PS4_SYSV_ABI sceKernelFStat(int fd, OrbisKernelStat* sb) { } std::memset(sb, 0, sizeof(OrbisKernelStat)); - if (file->is_directory) { - sb->st_mode = 0000777u | 0040000u; - sb->st_size = 0; - sb->st_blksize = 512; - sb->st_blocks = 0; - // TODO incomplete - } else { + switch (file->type) { + case Core::FileSys::FileType::Device: + return file->device->fstat(sb); + case Core::FileSys::FileType::Regular: sb->st_mode = 0000777u | 0100000u; sb->st_size = file->f.GetSize(); sb->st_blksize = 512; sb->st_blocks = (sb->st_size + 511) / 512; // TODO incomplete + break; + case Core::FileSys::FileType::Directory: + sb->st_mode = 0000777u | 0040000u; + sb->st_size = 0; + sb->st_blksize = 512; + sb->st_blocks = 0; + // TODO incomplete + break; + default: + UNREACHABLE(); } return ORBIS_OK; } @@ -486,6 +549,9 @@ int PS4_SYSV_ABI posix_fstat(int fd, OrbisKernelStat* sb) { s32 PS4_SYSV_ABI sceKernelFsync(int fd) { auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(fd); + if (file->type == Core::FileSys::FileType::Device) { + return file->device->fsync(); + } file->f.Flush(); return ORBIS_OK; } @@ -498,6 +564,10 @@ int PS4_SYSV_ABI sceKernelFtruncate(int fd, s64 length) { return SCE_KERNEL_ERROR_EBADF; } + if (file->type == Core::FileSys::FileType::Device) { + return file->device->ftruncate(length); + } + if (file->m_host_name.empty()) { return SCE_KERNEL_ERROR_EACCES; } @@ -519,10 +589,15 @@ static int GetDents(int fd, char* buf, int nbytes, s64* basep) { if (file == nullptr) { return ORBIS_KERNEL_ERROR_EBADF; } + if (file->type != Core::FileSys::FileType::Device) { + return file->device->getdents(buf, nbytes, basep); + } + if (file->dirents_index == file->dirents.size()) { return ORBIS_OK; } - if (!file->is_directory || nbytes < 512 || file->dirents_index > file->dirents.size()) { + if (file->type != Core::FileSys::FileType::Directory || nbytes < 512 || + file->dirents_index > file->dirents.size()) { return ORBIS_KERNEL_ERROR_EINVAL; } const auto& entry = file->dirents.at(file->dirents_index++); @@ -568,6 +643,10 @@ s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) { } std::scoped_lock lk{file->m_mutex}; + + if (file->type == Core::FileSys::FileType::Device) { + return file->device->pwrite(buf, nbytes, offset); + } const s64 pos = file->f.Tell(); SCOPE_EXIT { file->f.Seek(pos); diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp index c621b4bca3..b3167a8d5a 100644 --- a/src/core/libraries/kernel/libkernel.cpp +++ b/src/core/libraries/kernel/libkernel.cpp @@ -13,6 +13,7 @@ #include "common/polyfill_thread.h" #include "common/singleton.h" #include "common/thread.h" +#include "common/va_ctx.h" #include "core/file_format/psf.h" #include "core/file_sys/fs.h" #include "core/libraries/error_codes.h" @@ -258,7 +259,7 @@ int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver) { int version = Common::ElfInfo::Instance().RawFirmwareVer(); LOG_DEBUG(Kernel, "returned system version = {:#x}", version); *ver = version; - return (version > 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL; + return (version >= 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL; } s64 PS4_SYSV_ABI ps4__read(int d, void* buf, u64 nbytes) { @@ -392,6 +393,28 @@ int PS4_SYSV_ABI sceKernelUuidCreate(OrbisKernelUuid* orbisUuid) { return 0; } +int PS4_SYSV_ABI kernel_ioctl(int fd, u64 cmd, VA_ARGS) { + auto* h = Common::Singleton::Instance(); + auto* file = h->GetFile(fd); + if (file == nullptr) { + LOG_INFO(Lib_Kernel, "ioctl: fd = {:X} cmd = {:X} file == nullptr", fd, cmd); + g_posix_errno = POSIX_EBADF; + return -1; + } + if (file->type != Core::FileSys::FileType::Device) { + LOG_WARNING(Lib_Kernel, "ioctl: fd = {:X} cmd = {:X} file->type != Device", fd, cmd); + g_posix_errno = ENOTTY; + return -1; + } + VA_CTX(ctx); + int result = file->device->ioctl(cmd, &ctx); + if (result < 0) { + ErrSceToPosix(result); + return -1; + } + return result; +} + const char* PS4_SYSV_ABI sceKernelGetFsSandboxRandomWord() { const char* path = "sys"; return path; @@ -416,6 +439,7 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) { LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", 1, 1, &g_stack_chk_guard); // misc + LIB_FUNCTION("PfccT7qURYE", "libkernel", 1, "libkernel", 1, 1, kernel_ioctl); LIB_FUNCTION("JGfTMBOdUJo", "libkernel", 1, "libkernel", 1, 1, sceKernelGetFsSandboxRandomWord); LIB_FUNCTION("XVL8So3QJUk", "libkernel", 1, "libkernel", 1, 1, posix_connect); LIB_FUNCTION("6xVpy0Fdq+I", "libkernel", 1, "libkernel", 1, 1, _sigprocmask); diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index d786647c2b..43a18da84a 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -12,8 +12,62 @@ namespace Libraries::Pad { +static bool g_initialized = false; + +static bool g_pad_standard_connected = false; +static bool g_pad_standard_special_connected = false; + +constexpr auto PAD_STANDARD_HANDLER = 0xBC1; +constexpr auto PAD_SPECIAL_HANDLER = 0xBC2; + +void OrbisPadData::CopyFromState(const Input::State& state) { + buttons = state.buttonsState; + leftStick.x = state.axes[static_cast(Input::Axis::LeftX)]; + leftStick.y = state.axes[static_cast(Input::Axis::LeftY)]; + rightStick.x = state.axes[static_cast(Input::Axis::RightX)]; + rightStick.y = state.axes[static_cast(Input::Axis::RightY)]; + analogButtons.l2 = state.axes[static_cast(Input::Axis::TriggerLeft)]; + analogButtons.r2 = state.axes[static_cast(Input::Axis::TriggerRight)]; + orientation.x = 0.0f; + orientation.y = 0.0f; + orientation.z = 0.0f; + orientation.w = 1.0f; + acceleration.x = 0.0f; + acceleration.y = 0.0f; + acceleration.z = 0.0f; + angularVelocity.x = 0.0f; + angularVelocity.y = 0.0f; + angularVelocity.z = 0.0f; + touchData.touchNum = (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0); + touchData.touch[0].x = state.touchpad[0].x; + touchData.touch[0].y = state.touchpad[0].y; + touchData.touch[0].id = 1; + touchData.touch[1].x = state.touchpad[1].x; + touchData.touch[1].y = state.touchpad[1].y; + touchData.touch[1].id = 2; + connected = true; + timestamp = state.time; + connectedCount = 1; + deviceUniqueDataLen = 0; +} + int PS4_SYSV_ABI scePadClose(s32 handle) { - LOG_ERROR(Lib_Pad, "(STUBBED) called"); + LOG_DEBUG(Lib_Pad, "called handle = {}", handle); + if (!g_initialized) { + return ORBIS_PAD_ERROR_NOT_INITIALIZED; + } + if (handle == PAD_STANDARD_HANDLER) { + if (!g_pad_standard_connected) { + return ORBIS_PAD_ERROR_INVALID_HANDLE; + } + g_pad_standard_connected = false; + } + if (handle == PAD_SPECIAL_HANDLER) { + if (!g_pad_standard_special_connected) { + return ORBIS_PAD_ERROR_INVALID_HANDLE; + } + g_pad_standard_special_connected = false; + } return ORBIS_OK; } @@ -24,16 +78,135 @@ int PS4_SYSV_ABI scePadConnectPort() { int PS4_SYSV_ABI scePadDeviceClassGetExtendedInformation( s32 handle, OrbisPadDeviceClassExtendedInformation* pExtInfo) { - LOG_ERROR(Lib_Pad, "(STUBBED) called"); - if (Config::getUseSpecialPad()) { + LOG_DEBUG(Lib_Pad, "called handle = {}", handle); + + if (!g_initialized) { + return ORBIS_PAD_ERROR_NOT_INITIALIZED; + } + + if (pExtInfo == nullptr) { + return ORBIS_PAD_ERROR_INVALID_ARG; + } + + if (handle == PAD_STANDARD_HANDLER) { + if (!g_pad_standard_connected) { + return ORBIS_PAD_ERROR_INVALID_HANDLE; + } + pExtInfo->deviceClass = ORBIS_PAD_DEVICE_CLASS_STANDARD; + return ORBIS_OK; + } + if (handle == PAD_SPECIAL_HANDLER) { + if (!g_pad_standard_special_connected) { + return ORBIS_PAD_ERROR_INVALID_HANDLE; + } pExtInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass(); + switch (pExtInfo->deviceClass) { + case ORBIS_PAD_DEVICE_CLASS_STEERING_WHEEL: { + auto& data = pExtInfo->classData.steeringWheel; + data.maxPhysicalWheelAngle = 360; + data.capability = 0b1110; // Handbrake, Shift, 3 pedals + } break; + default: + memset(pExtInfo->classData.data, 0, sizeof(pExtInfo->classData.data)); + break; + } + return ORBIS_OK; } - return ORBIS_OK; + + return ORBIS_PAD_ERROR_INVALID_HANDLE; } int PS4_SYSV_ABI scePadDeviceClassParseData(s32 handle, const OrbisPadData* pData, OrbisPadDeviceClassData* pDeviceClassData) { - LOG_ERROR(Lib_Pad, "(STUBBED) called"); + LOG_TRACE(Lib_Pad, "called handle = {}", handle); + if (!g_initialized) { + return ORBIS_PAD_ERROR_NOT_INITIALIZED; + } + if (pData == nullptr || pDeviceClassData == nullptr) { + return ORBIS_PAD_ERROR_INVALID_ARG; + } + + if (handle == PAD_STANDARD_HANDLER) { + if (!g_pad_standard_connected) { + return ORBIS_PAD_ERROR_INVALID_HANDLE; + } + pDeviceClassData->deviceClass = ORBIS_PAD_DEVICE_CLASS_STANDARD; + return ORBIS_OK; + } + + if (handle == PAD_SPECIAL_HANDLER) { + if (!g_pad_standard_special_connected) { + return ORBIS_PAD_ERROR_INVALID_HANDLE; + } + auto pad_class = (OrbisPadDeviceClass)Config::getSpecialPadClass(); + pDeviceClassData->deviceClass = pad_class; + switch (pad_class) { + case ORBIS_PAD_DEVICE_CLASS_GUITAR: { + LOG_ERROR(Lib_Pad, "(STUBBED) guitar not implemented"); + auto& data = pDeviceClassData->classData.guitar; + // TODO implement guitar + } break; + case ORBIS_PAD_DEVICE_CLASS_DRUM: { + LOG_ERROR(Lib_Pad, "(STUBBED) drum not implemented"); + auto& data = pDeviceClassData->classData.drum; + // TODO implement drum + } break; + case ORBIS_PAD_DEVICE_CLASS_STEERING_WHEEL: { + auto& data = pDeviceClassData->classData.steeringWheel; + // TODO proper implement steering wheel + auto& left_stick = pData->leftStick.x; + data.steeringWheelAngle = static_cast(left_stick - 0x7F) / 127.0f * 180.0f; + if (data.steeringWheelAngle == 0.0) { + data.steeringWheel = 0x80; + } else { + data.steeringWheel = + static_cast((data.steeringWheelAngle / 360.0f + 0.5f) * 0xFFFF); + } + data.acceleratorPedal = static_cast(pData->analogButtons.r2) * 0x102; + data.brakePedal = static_cast(pData->analogButtons.l2) * 0x102; + data.clutchPedal = pData->buttons & ORBIS_PAD_BUTTON_L1 ? 0xFFFF : 0x0000; + data.handBrake = pData->buttons & ORBIS_PAD_BUTTON_R1 ? 0xFFFF : 0x0000; + + static int gear = 1; + static bool switch_gear_up_pressed_last = false; + static bool switch_gear_down_pressed_last = false; + bool switch_gear_up_pressed = pData->buttons & ORBIS_PAD_BUTTON_SQUARE; + bool switch_gear_down_pressed = pData->buttons & ORBIS_PAD_BUTTON_CROSS; + if (switch_gear_up_pressed != switch_gear_up_pressed_last) { + switch_gear_up_pressed_last = switch_gear_up_pressed; + if (switch_gear_up_pressed) { + if (gear < 7) { + ++gear; + } + } + } + if (switch_gear_down_pressed != switch_gear_down_pressed_last) { + switch_gear_down_pressed_last = switch_gear_down_pressed; + if (switch_gear_down_pressed) { + if (gear > 0) { + --gear; + } + } + } + + if (gear == 0) { + data.gear = 1 << 7; + } else { + data.gear = 1 << (gear - 1); + } + } break; + case ORBIS_PAD_DEVICE_CLASS_FLIGHT_STICK: { + LOG_ERROR(Lib_Pad, "(STUBBED) flight stick not implemented"); + auto& data = pDeviceClassData->classData.flightStick; + // TODO implement flight stick + } break; + default: + pDeviceClassData->bDataValid = false; + break; + } + return ORBIS_OK; + } + return ORBIS_OK; } @@ -89,32 +262,39 @@ int PS4_SYSV_ABI scePadGetCapability() { int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerInformation* pInfo) { LOG_DEBUG(Lib_Pad, "called handle = {}", handle); - if (handle < 0) { - pInfo->touchPadInfo.pixelDensity = 1; - pInfo->touchPadInfo.resolution.x = 1920; - pInfo->touchPadInfo.resolution.y = 950; - pInfo->stickInfo.deadZoneLeft = 2; - pInfo->stickInfo.deadZoneRight = 2; - pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD; - pInfo->connectedCount = 1; - pInfo->connected = false; - pInfo->deviceClass = ORBIS_PAD_DEVICE_CLASS_STANDARD; - return SCE_OK; + + if (!g_initialized) { + return ORBIS_PAD_ERROR_NOT_INITIALIZED; + } + + if (handle == PAD_STANDARD_HANDLER) { + if (!g_pad_standard_connected) { + return ORBIS_PAD_ERROR_INVALID_HANDLE; + } + } else if (handle == PAD_SPECIAL_HANDLER) { + if (!g_pad_standard_special_connected) { + return ORBIS_PAD_ERROR_INVALID_HANDLE; + } + } else { + return ORBIS_PAD_ERROR_INVALID_HANDLE; } + pInfo->touchPadInfo.pixelDensity = 1; pInfo->touchPadInfo.resolution.x = 1920; pInfo->touchPadInfo.resolution.y = 950; pInfo->stickInfo.deadZoneLeft = 2; pInfo->stickInfo.deadZoneRight = 2; - pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD; + pInfo->connectionType = 0; // Local connection pInfo->connectedCount = 1; pInfo->connected = true; - pInfo->deviceClass = ORBIS_PAD_DEVICE_CLASS_STANDARD; - if (Config::getUseSpecialPad()) { - pInfo->connectionType = ORBIS_PAD_PORT_TYPE_SPECIAL; + if (handle == PAD_STANDARD_HANDLER) { + pInfo->deviceClass = ORBIS_PAD_DEVICE_CLASS_STANDARD; + } else if (handle == PAD_SPECIAL_HANDLER) { pInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass(); + } else { + UNREACHABLE(); } - return SCE_OK; + return ORBIS_OK; } int PS4_SYSV_ABI scePadGetDataInternal() { @@ -134,7 +314,7 @@ int PS4_SYSV_ABI scePadGetDeviceInfo() { int PS4_SYSV_ABI scePadGetExtControllerInformation(s32 handle, OrbisPadExtendedControllerInformation* pInfo) { - LOG_INFO(Lib_Pad, "called handle = {}", handle); + LOG_DEBUG(Lib_Pad, "called handle = {}", handle); pInfo->padType1 = 0; pInfo->padType2 = 0; @@ -200,7 +380,8 @@ int PS4_SYSV_ABI scePadGetVersionInfo() { } int PS4_SYSV_ABI scePadInit() { - LOG_ERROR(Lib_Pad, "(STUBBED) called"); + LOG_DEBUG(Lib_Pad, "called"); + g_initialized = true; return ORBIS_OK; } @@ -245,28 +426,40 @@ int PS4_SYSV_ABI scePadMbusTerm() { } int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenParam* pParam) { - LOG_INFO(Lib_Pad, "(DUMMY) called user_id = {} type = {} index = {}", userId, type, index); - if (Config::getUseSpecialPad()) { - if (type != ORBIS_PAD_PORT_TYPE_SPECIAL) - return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; - } else { - if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) + LOG_DEBUG(Lib_Pad, "Called user_id = {} type = {} index = {}", userId, + type == ORBIS_PAD_PORT_TYPE_STANDARD ? "standard" + : type == ORBIS_PAD_PORT_TYPE_SPECIAL ? "special" + : "unknown", + index); + if (!g_initialized) { + return ORBIS_PAD_ERROR_NOT_INITIALIZED; + } + if (type == ORBIS_PAD_PORT_TYPE_STANDARD) { + if (g_pad_standard_connected) { + return ORBIS_PAD_ERROR_ALREADY_OPENED; + } + g_pad_standard_connected = true; + return PAD_STANDARD_HANDLER; + } + + if (type == ORBIS_PAD_PORT_TYPE_SPECIAL) { + if (!Config::getUseSpecialPad()) { return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; + } + if (g_pad_standard_special_connected) { + return ORBIS_PAD_ERROR_ALREADY_OPENED; + } + g_pad_standard_special_connected = true; + return PAD_SPECIAL_HANDLER; } - return 1; // dummy + + return ORBIS_PAD_ERROR_INVALID_ARG; } int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index, const OrbisPadOpenExtParam* pParam) { - LOG_ERROR(Lib_Pad, "(STUBBED) called"); - if (Config::getUseSpecialPad()) { - if (type != ORBIS_PAD_PORT_TYPE_SPECIAL) - return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; - } else { - if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) - return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; - } - return 1; // dummy + LOG_DEBUG(Lib_Pad, "Redirecting call to scePadOpen"); + return scePadOpen(userId, type, index, nullptr); } int PS4_SYSV_ABI scePadOpenExt2() { @@ -280,49 +473,80 @@ int PS4_SYSV_ABI scePadOutputReport() { } int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { - int connected_count = 0; - bool connected = false; - Input::State states[64]; - auto* controller = Common::Singleton::Instance(); - int ret_num = controller->ReadStates(states, num, &connected, &connected_count); - - if (!connected) { - ret_num = 1; + LOG_TRACE(Lib_Pad, "called handle = {} num = {}", handle, num); + + if (!g_initialized) { + return ORBIS_PAD_ERROR_NOT_INITIALIZED; } - for (int i = 0; i < ret_num; i++) { - pData[i].buttons = states[i].buttonsState; - pData[i].leftStick.x = states[i].axes[static_cast(Input::Axis::LeftX)]; - pData[i].leftStick.y = states[i].axes[static_cast(Input::Axis::LeftY)]; - pData[i].rightStick.x = states[i].axes[static_cast(Input::Axis::RightX)]; - pData[i].rightStick.y = states[i].axes[static_cast(Input::Axis::RightY)]; - pData[i].analogButtons.l2 = states[i].axes[static_cast(Input::Axis::TriggerLeft)]; - pData[i].analogButtons.r2 = states[i].axes[static_cast(Input::Axis::TriggerRight)]; - pData[i].orientation.x = 0.0f; - pData[i].orientation.y = 0.0f; - pData[i].orientation.z = 0.0f; - pData[i].orientation.w = 1.0f; - pData[i].acceleration.x = 0.0f; - pData[i].acceleration.y = 0.0f; - pData[i].acceleration.z = 0.0f; - pData[i].angularVelocity.x = 0.0f; - pData[i].angularVelocity.y = 0.0f; - pData[i].angularVelocity.z = 0.0f; - pData[i].touchData.touchNum = - (states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0); - pData[i].touchData.touch[0].x = states[i].touchpad[0].x; - pData[i].touchData.touch[0].y = states[i].touchpad[0].y; - pData[i].touchData.touch[0].id = 1; - pData[i].touchData.touch[1].x = states[i].touchpad[1].x; - pData[i].touchData.touch[1].y = states[i].touchpad[1].y; - pData[i].touchData.touch[1].id = 2; - pData[i].connected = connected; - pData[i].timestamp = states[i].time; - pData[i].connectedCount = connected_count; - pData[i].deviceUniqueDataLen = 0; + if (num < 1 || num > 64) { + return ORBIS_PAD_ERROR_INVALID_ARG; } - return ret_num; + // Hack to copy state between pads + static bool connected = false; + static std::array states; + static int state_count = 0; + static bool has_std_data = false; + static bool has_special_data = false; + + if (handle == PAD_STANDARD_HANDLER) { + if (!g_pad_standard_connected) { + return ORBIS_PAD_ERROR_INVALID_HANDLE; + } + + if (!has_special_data) { + int connected_count = 0; + auto* controller = Common::Singleton::Instance(); + state_count = controller->ReadStates(states.data(), num, &connected, &connected_count); + has_std_data = true; + } else { + has_special_data = false; + } + + if (!connected) { + pData[0] = OrbisPadData{ + .connected = false, + }; + return 1; + } + + for (int i = 0; i < state_count; i++) { + pData[i].CopyFromState(states[i]); + } + + return state_count; + } + + if (handle == PAD_SPECIAL_HANDLER) { + if (!g_pad_standard_special_connected) { + return ORBIS_PAD_ERROR_INVALID_HANDLE; + } + + if (!has_std_data) { + int connected_count = 0; + auto* controller = Common::Singleton::Instance(); + state_count = controller->ReadStates(states.data(), num, &connected, &connected_count); + has_special_data = true; + } else { + has_std_data = false; + } + + if (!connected) { + pData[0] = OrbisPadData{ + .connected = false, + }; + return 1; + } + + for (int i = 0; i < state_count; i++) { + pData[i].CopyFromState(states[i]); + } + + return state_count; + } + + return ORBIS_PAD_ERROR_INVALID_HANDLE; } int PS4_SYSV_ABI scePadReadBlasterForTracker() { @@ -346,42 +570,36 @@ int PS4_SYSV_ABI scePadReadHistory() { } int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { - auto* controller = Common::Singleton::Instance(); - int connectedCount = 0; - bool isConnected = false; - Input::State state; - controller->ReadState(&state, &isConnected, &connectedCount); - pData->buttons = state.buttonsState; - pData->leftStick.x = state.axes[static_cast(Input::Axis::LeftX)]; - pData->leftStick.y = state.axes[static_cast(Input::Axis::LeftY)]; - pData->rightStick.x = state.axes[static_cast(Input::Axis::RightX)]; - pData->rightStick.y = state.axes[static_cast(Input::Axis::RightY)]; - pData->analogButtons.l2 = state.axes[static_cast(Input::Axis::TriggerLeft)]; - pData->analogButtons.r2 = state.axes[static_cast(Input::Axis::TriggerRight)]; - pData->orientation.x = 0; - pData->orientation.y = 0; - pData->orientation.z = 0; - pData->orientation.w = 1; - pData->acceleration.x = 0.0f; - pData->acceleration.y = 0.0f; - pData->acceleration.z = 0.0f; - pData->angularVelocity.x = 0.0f; - pData->angularVelocity.y = 0.0f; - pData->angularVelocity.z = 0.0f; - pData->touchData.touchNum = - (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0); - pData->touchData.touch[0].x = state.touchpad[0].x; - pData->touchData.touch[0].y = state.touchpad[0].y; - pData->touchData.touch[0].id = 1; - pData->touchData.touch[1].x = state.touchpad[1].x; - pData->touchData.touch[1].y = state.touchpad[1].y; - pData->touchData.touch[1].id = 2; - pData->timestamp = state.time; - pData->connected = true; // isConnected; //TODO fix me proper - pData->connectedCount = 1; // connectedCount; - pData->deviceUniqueDataLen = 0; - - return SCE_OK; + LOG_TRACE(Lib_Pad, "called handle = {}", handle); + + if (!g_initialized) { + return ORBIS_PAD_ERROR_NOT_INITIALIZED; + } + + if (handle == PAD_STANDARD_HANDLER) { + if (!g_pad_standard_connected) { + return ORBIS_PAD_ERROR_INVALID_HANDLE; + } + + standard_handler: + auto* controller = Common::Singleton::Instance(); + int connectedCount = 0; + bool isConnected = false; + Input::State state; + controller->ReadState(&state, &isConnected, &connectedCount); + + pData->CopyFromState(state); + + return ORBIS_OK; + } + if (handle == PAD_SPECIAL_HANDLER) { + if (!g_pad_standard_special_connected) { + return ORBIS_PAD_ERROR_INVALID_HANDLE; + } + // TODO implement special pad + goto standard_handler; + } + return ORBIS_PAD_ERROR_INVALID_HANDLE; } int PS4_SYSV_ABI scePadReadStateExt() { diff --git a/src/core/libraries/pad/pad.h b/src/core/libraries/pad/pad.h index f94a642cfb..4b851cd18f 100644 --- a/src/core/libraries/pad/pad.h +++ b/src/core/libraries/pad/pad.h @@ -9,6 +9,10 @@ namespace Core::Loader { class SymbolsResolver; } +namespace Input { +struct State; +} + namespace Libraries::Pad { constexpr int ORBIS_PAD_MAX_TOUCH_NUM = 2; @@ -188,6 +192,8 @@ struct OrbisPadData { u8 reserve[2]; u8 deviceUniqueDataLen; u8 deviceUniqueData[ORBIS_PAD_MAX_DEVICE_UNIQUE_DATA_SIZE]; + + void CopyFromState(const Input::State& state); }; struct OrbisPadTouchPadInformation { diff --git a/src/core/libraries/system/msgdialog_ui.cpp b/src/core/libraries/system/msgdialog_ui.cpp index 862f5a569f..305263047b 100644 --- a/src/core/libraries/system/msgdialog_ui.cpp +++ b/src/core/libraries/system/msgdialog_ui.cpp @@ -238,11 +238,21 @@ void MsgDialogUi::Finish(ButtonId buttonId, Result r) { } if (status) { *status = Status::FINISHED; + if (callback.has_value()) { + callback.value()(DialogResult{ + .result = r, + .buttonId = buttonId, + }); + } } state = nullptr; status = nullptr; result = nullptr; RemoveLayer(this); + if (self_destruct) { + self_destruct = false; + delete this; + } } void MsgDialogUi::Draw() { @@ -282,19 +292,24 @@ void MsgDialogUi::Draw() { first_render = false; } -DialogResult Libraries::MsgDialog::ShowMsgDialog(MsgDialogState p_state, bool block) { - static DialogResult result{}; - static Status status; - static MsgDialogUi dialog; - static MsgDialogState state; - dialog = MsgDialogUi{}; - status = Status::RUNNING; - state = std::move(p_state); - dialog = MsgDialogUi(&state, &status, &result); +void Libraries::MsgDialog::ShowMsgDialog( + MsgDialogState p_state, bool block, std::optional> callback) { + auto status = new Status{Status::RUNNING}; + auto state = new MsgDialogState{std::move(p_state)}; + auto dialog = new MsgDialogUi(state, status, nullptr); + bool running = true; + dialog->SetSelfDestruct(); + dialog->SetCallback([&, status, state, callback = std::move(callback)](auto result) { + running = false; + delete status; + delete state; + if (callback.has_value()) { + callback.value()(result); + } + }); if (block) { - while (status == Status::RUNNING) { + while (running) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } - return result; } diff --git a/src/core/libraries/system/msgdialog_ui.h b/src/core/libraries/system/msgdialog_ui.h index d24ec067c5..c56d6faa1a 100644 --- a/src/core/libraries/system/msgdialog_ui.h +++ b/src/core/libraries/system/msgdialog_ui.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -11,6 +12,8 @@ #include "core/libraries/system/commondialog.h" #include "imgui/imgui_layer.h" +#include + namespace Libraries::MsgDialog { using OrbisUserServiceUserId = s32; @@ -157,6 +160,10 @@ class MsgDialogUi final : public ImGui::Layer { CommonDialog::Status* status{}; DialogResult* result{}; + // HOST ONLY + bool self_destruct{false}; + std::optional> callback; + void DrawUser(); void DrawProgressBar(); void DrawSystemMessage(); @@ -169,13 +176,22 @@ class MsgDialogUi final : public ImGui::Layer { MsgDialogUi(MsgDialogUi&& other) noexcept; MsgDialogUi& operator=(MsgDialogUi other); + void SetSelfDestruct() { + self_destruct = true; + } + + void SetCallback(std::function callback) { + this->callback = std::move(callback); + } + void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK); void Draw() override; }; -// Utility function to show a message dialog -// !!! This function can block !!! -DialogResult ShowMsgDialog(MsgDialogState state, bool block = true); +// Utility function to show a message dialog from host code +// callback is called from the present thread +void ShowMsgDialog(MsgDialogState state, bool block = false, + std::optional> callback = std::nullopt); }; // namespace Libraries::MsgDialog \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index d9d32ec1d7..6470dcdec5 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -117,10 +117,10 @@ void Emulator::Run(const std::filesystem::path& file) { auto& game_info = Common::ElfInfo::Instance(); // Loading param.sfo file if exists - std::string id; - std::string title; - std::string app_version; - u32 fw_version; + std::string id = "unknown_serial"; + std::string title = "unknown_title"; + std::string app_version = "unknown_version"; + u32 fw_version = 0x4700000; std::filesystem::path sce_sys_folder = eboot_path.parent_path() / "sce_sys"; if (std::filesystem::is_directory(sce_sys_folder)) { @@ -274,18 +274,20 @@ void Emulator::Run(const std::filesystem::path& file) { } void Emulator::LoadSystemModules(const std::filesystem::path& file, std::string game_serial) { - constexpr std::array ModulesToLoad{ - {{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterlibSceNgs2}, - {"libSceFiber.sprx", &Libraries::Fiber::RegisterlibSceFiber}, - {"libSceUlt.sprx", nullptr}, - {"libSceJson.sprx", nullptr}, - {"libSceJson2.sprx", nullptr}, - {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterlibSceLibcInternal}, - {"libSceDiscMap.sprx", &Libraries::DiscMap::RegisterlibSceDiscMap}, - {"libSceRtc.sprx", &Libraries::Rtc::RegisterlibSceRtc}, - {"libSceJpegEnc.sprx", nullptr}, - {"libSceRazorCpu.sprx", nullptr}, - {"libSceCesCs.sprx", nullptr}}}; + constexpr std::array ModulesToLoad{{ + {"libSceNgs2.sprx", &Libraries::Ngs2::RegisterlibSceNgs2}, + {"libSceFiber.sprx", &Libraries::Fiber::RegisterlibSceFiber}, + {"libSceUlt.sprx", nullptr}, + {"libSceJson.sprx", nullptr}, + {"libSceJson2.sprx", nullptr}, + {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterlibSceLibcInternal}, + {"libSceDiscMap.sprx", &Libraries::DiscMap::RegisterlibSceDiscMap}, + {"libSceRtc.sprx", &Libraries::Rtc::RegisterlibSceRtc}, + {"libSceJpegEnc.sprx", nullptr}, + {"libSceRazorCpu.sprx", nullptr}, + {"libSceCesCs.sprx", nullptr}, + {"libSceMouse.sprx", nullptr}, + }}; std::vector found_modules; const auto& sys_module_path = Common::FS::GetUserPath(Common::FS::PathType::SysModuleDir); diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp index 311e86a3c7..5bf8d56e90 100644 --- a/src/imgui/renderer/imgui_core.cpp +++ b/src/imgui/renderer/imgui_core.cpp @@ -83,7 +83,7 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w StyleColorsDark(); ::Core::Devtools::Layer::SetupSettings(); - Sdl::Init(window.GetSdlWindow()); + Sdl::Init(&window); const Vulkan::InitInfo vk_info{ .instance = instance.GetInstance(), diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp index 60b440c245..e1b1d0df7c 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.cpp +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -9,6 +9,9 @@ // SDL #include + +#include "sdl_window.h" +#include "video_core/renderer_vulkan/vk_instance.h" #if defined(__APPLE__) #include #include @@ -24,6 +27,9 @@ namespace ImGui::Sdl { // SDL Data struct SdlData { + // SDL Wrapper + const Frontend::WindowSDL* wrapper{}; + SDL_Window* window{}; SDL_WindowID window_id{}; Uint64 time{}; @@ -518,7 +524,7 @@ static void SetupPlatformHandles(ImGuiViewport* viewport, SDL_Window* window) { #endif } -bool Init(SDL_Window* window) { +bool Init(const Frontend::WindowSDL* wrapper) { ImGuiIO& io = ImGui::GetIO(); IMGUI_CHECKVERSION(); IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); @@ -531,8 +537,9 @@ bool Init(SDL_Window* window) { io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests // (optional, rarely used) - bd->window = window; - bd->window_id = SDL_GetWindowID(window); + bd->wrapper = wrapper; + bd->window = wrapper->GetSdlWindow(); + bd->window_id = SDL_GetWindowID(bd->window); ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); platform_io.Platform_SetClipboardTextFn = SetClipboardText; @@ -560,7 +567,7 @@ bool Init(SDL_Window* window) { // Set platform dependent data in viewport // Our mouse update function expect PlatformHandle to be filled for the main viewport ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - SetupPlatformHandles(main_viewport, window); + SetupPlatformHandles(main_viewport, bd->window); // From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't // emit the event. Without this, when clicking to gain focus, our widgets wouldn't activate even @@ -610,7 +617,6 @@ static void UpdateMouseData() { // (below) // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries // shouldn't e.g. trigger other operations outside - SDL_CaptureMouse((bd->mouse_buttons_down != 0) ? true : false); SDL_Window* focused_window = SDL_GetKeyboardFocus(); const bool is_app_focused = (bd->window == focused_window); @@ -679,7 +685,9 @@ static void UpdateMouseCursor() { SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113) bd->mouse_last_cursor = expected_cursor; } - SDL_ShowCursor(); + if (!bd->wrapper->isCapturingMouse()) { + SDL_ShowCursor(); + } } } diff --git a/src/imgui/renderer/imgui_impl_sdl3.h b/src/imgui/renderer/imgui_impl_sdl3.h index 59b1a68567..a67cea65d7 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.h +++ b/src/imgui/renderer/imgui_impl_sdl3.h @@ -10,9 +10,13 @@ struct SDL_Renderer; struct SDL_Gamepad; typedef union SDL_Event SDL_Event; +namespace Frontend { +class WindowSDL; +} + namespace ImGui::Sdl { -bool Init(SDL_Window* window); +bool Init(const Frontend::WindowSDL* window); void Shutdown(); void NewFrame(); bool ProcessEvent(const SDL_Event* event); diff --git a/src/input/mouse.cpp b/src/input/mouse.cpp new file mode 100644 index 0000000000..f4373075d4 --- /dev/null +++ b/src/input/mouse.cpp @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/libraries/kernel/time_management.h" +#include "input/mouse.h" + +namespace Input { + +GameMouse::GameMouse() { + m_states_num = 0; + m_last_state = MouseState(); +} + +int GameMouse::ReadStates(MouseState* states, int states_num) { + if (states_num == 0) { + return 0; + } + std::scoped_lock lock{m_mutex}; + + const u32 count = std::min(m_states_num, u32(states_num)); + + if (count == 0) { + states[0] = m_last_state; + return 1; + } + + u32 begin = (m_index - m_states_num + 1) % MAX_MOUSE_STATES; + for (u32 i = 0; i < count; i++) { + u32 idx = (begin + i) % MAX_MOUSE_STATES; + states[i] = m_states[idx]; + } + + m_states_num -= count; + return static_cast(count); +} + +void GameMouse::AddState(const MouseState& state) { + std::scoped_lock lock{m_mutex}; + + m_index = (m_index + 1) % MAX_MOUSE_STATES; + if (m_states_num < MAX_MOUSE_STATES) { + ++m_states_num; + } + m_states[m_index] = state; + m_last_state = MouseState{ + .button_state = state.button_state, + }; +} + +void GameMouse::CheckButton(u32 button, bool isPressed) { + if (!m_connected) { + return; + } + MouseState state = m_last_state; + state.time = Libraries::Kernel::sceKernelGetProcessTime(); + if (isPressed) { + state.button_state |= button; + } else { + state.button_state &= ~button; + } + + AddState(state); +} + +void GameMouse::CheckMove(int x, int y) { + if (!m_connected) { + return; + } + MouseState state = m_last_state; + state.time = Libraries::Kernel::sceKernelGetProcessTime(); + state.x_axis = x; + state.y_axis = y; + + AddState(state); +} + +void GameMouse::CheckWheel(int x, int y) { + if (!m_connected) { + return; + } + MouseState state = m_last_state; + state.time = Libraries::Kernel::sceKernelGetProcessTime(); + state.wheel = y; + state.tilt = x; + + AddState(state); +} + +}; // namespace Input diff --git a/src/input/mouse.h b/src/input/mouse.h new file mode 100644 index 0000000000..8f2899efe9 --- /dev/null +++ b/src/input/mouse.h @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include +#include "common/types.h" + +namespace Input { + +struct MouseState { + u64 time = 0; + u32 button_state = 0; + s32 x_axis = 0; + s32 y_axis = 0; + s32 wheel = 0; + s32 tilt = 0; +}; + +enum OrbisMouseButtonDataOffset { + ORBIS_MOUSE_BUTTON_PRIMARY = 0x00000001, + ORBIS_MOUSE_BUTTON_SECONDARY = 0x00000002, + ORBIS_MOUSE_BUTTON_OPTIONAL = 0x00000004, + ORBIS_MOUSE_BUTTON_OPTIONAL2 = 0x00000008, + ORBIS_MOUSE_BUTTON_OPTIONAL3 = 0x00000010, +}; + +constexpr u32 MAX_MOUSE_STATES = 64; + +class GameMouse { +public: + GameMouse(); + virtual ~GameMouse() = default; + + int ReadStates(MouseState* states, int states_num); + + void CheckButton(u32 button, bool isPressed); + void CheckMove(int x, int y); + void CheckWheel(int x, int y); + + bool m_connected = false; + float speed = 1.0f; + +private: + void AddState(const MouseState& state); + + std::mutex m_mutex; + MouseState m_last_state; + u32 m_states_num = 0; + u32 m_index = 0; + std::array m_states; +}; + +} // namespace Input \ No newline at end of file diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index ad7d1b4a6b..51f79d87ff 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -18,6 +18,8 @@ #ifdef __APPLE__ #include #endif +#include +#include namespace Frontend { @@ -120,6 +122,13 @@ void WindowSDL::waitEvent() { case SDL_EVENT_QUIT: is_open = false; break; + + case SDL_EVENT_MOUSE_MOTION: + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: + case SDL_EVENT_MOUSE_WHEEL: + onMouseAction(&event); + break; default: break; } @@ -129,6 +138,51 @@ void WindowSDL::initTimers() { SDL_AddTimer(100, &PollController, controller); } +void WindowSDL::onMouseAction(const SDL_Event* event) { + auto& mouse = *Common::Singleton::Instance(); + + if (mouse.m_connected && !is_capturing_mouse) { + SDL_SetWindowRelativeMouseMode(window, true); + is_capturing_mouse = true; + } else if (!mouse.m_connected && is_capturing_mouse) { + SDL_SetWindowRelativeMouseMode(window, false); + is_capturing_mouse = false; + } + + bool pressed_down = false; + switch (event->type) { + case SDL_EVENT_MOUSE_BUTTON_DOWN: + pressed_down = true; + [[fallthrough]]; + case SDL_EVENT_MOUSE_BUTTON_UP: { + auto btn = event->button.button; + if (btn < 1 || btn > 5) { // 1..5 range + return; + } + constexpr static std::array sdl_to_orbis_buttons = { + static_cast(0x00), + Input::ORBIS_MOUSE_BUTTON_PRIMARY, + Input::ORBIS_MOUSE_BUTTON_OPTIONAL, + Input::ORBIS_MOUSE_BUTTON_SECONDARY, + Input::ORBIS_MOUSE_BUTTON_OPTIONAL2, + Input::ORBIS_MOUSE_BUTTON_OPTIONAL3, + }; + mouse.CheckButton(sdl_to_orbis_buttons[btn], pressed_down); + } break; + case SDL_EVENT_MOUSE_MOTION: { + const auto x = static_cast(event->motion.xrel * mouse.speed); + const auto y = static_cast(event->motion.yrel * mouse.speed); + mouse.CheckMove(x, y); + } break; + case SDL_EVENT_MOUSE_WHEEL: { + const auto x = static_cast(event->wheel.x); + const auto y = static_cast(event->wheel.y); + mouse.CheckWheel(x, y); + } break; + default: + break; + } +} void WindowSDL::onResize() { SDL_GetWindowSizeInPixels(window, &width, &height); ImGui::Core::OnResize(); diff --git a/src/sdl_window.h b/src/sdl_window.h index ec8de354b8..5c1e780e7a 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -58,6 +58,10 @@ class WindowSDL { return is_open; } + bool isCapturingMouse() const { + return is_capturing_mouse; + } + [[nodiscard]] SDL_Window* GetSdlWindow() const { return window; } @@ -74,7 +78,7 @@ class WindowSDL { void onResize(); void onKeyPress(const SDL_Event* event); void onGamepadEvent(const SDL_Event* event); - + void onMouseAction(const SDL_Event* event); int sdlGamepadToOrbisButton(u8 button); private: @@ -85,6 +89,8 @@ class WindowSDL { SDL_Window* window{}; bool is_shown{}; bool is_open{true}; + + bool is_capturing_mouse{false}; }; } // namespace Frontend