From 8c7aa58f6b4d2c9ac737cbb2402e221e94f70460 Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Thu, 5 Nov 2026 06:56:38 +0100 Subject: [PATCH] Remove UPnP, the unused host input backends, disc conversion and DolphinTool None of these run in the core: UPnP served NetPlay; the DInput, XInput, WGInput, Win32, Quartz, Xlib, evdev and Pipes input backends are never enabled, since input comes from libretro; and disc conversion, with its multithreaded compressor, was only reached from DolphinTool. The WIA, RVZ and GCZ readers stay. --- .gitmodules | 4 - Externals/licenses.md | 2 - Externals/miniupnpc/miniupnp | 1 - Makefile.common | 1 - Readme.md | 83 -- Source/Core/Common/UPnP.cpp | 181 --- Source/Core/Common/UPnP.h | 16 - Source/Core/DiscIO/Blob.h | 12 - Source/Core/DiscIO/CompressedBlob.cpp | 226 --- Source/Core/DiscIO/FileBlob.cpp | 73 - Source/Core/DiscIO/MultithreadedCompressor.h | 222 --- Source/Core/DiscIO/WIABlob.cpp | 1239 ----------------- Source/Core/DiscIO/WIABlob.h | 125 -- Source/Core/DolphinTool/ConvertCommand.cpp | 351 ----- Source/Core/DolphinTool/ConvertCommand.h | 12 - .../Core/DolphinTool/DolphinTool.exe.manifest | 27 - Source/Core/DolphinTool/DolphinTool.filters | 26 - Source/Core/DolphinTool/DolphinTool.rc | 6 - Source/Core/DolphinTool/ExtractCommand.cpp | 382 ----- Source/Core/DolphinTool/ExtractCommand.h | 12 - Source/Core/DolphinTool/HeaderCommand.cpp | 170 --- Source/Core/DolphinTool/HeaderCommand.h | 12 - .../Core/DolphinTool/ToolHeadlessPlatform.cpp | 85 -- Source/Core/DolphinTool/ToolMain.cpp | 69 - Source/Core/DolphinTool/VerifyCommand.cpp | 198 --- Source/Core/DolphinTool/VerifyCommand.h | 12 - Source/Core/DolphinTool/resource.h | 23 - .../ControllerInterface.cpp | 30 - .../ControllerInterface/ControllerInterface.h | 15 - .../ControllerInterface/DInput/DInput.cpp | 106 -- .../ControllerInterface/DInput/DInput.h | 24 - .../ControllerInterface/DInput/DInput8.h | 7 - .../DInput/DInputJoystick.cpp | 311 ----- .../DInput/DInputJoystick.h | 78 -- .../DInput/DInputKeyboardMouse.cpp | 340 ----- .../DInput/DInputKeyboardMouse.h | 116 -- .../ControllerInterface/DInput/NamedKeys.h | 44 - .../DInput/XInputFilter.cpp | 78 -- .../ControllerInterface/DInput/XInputFilter.h | 12 - .../ForceFeedback/ForceFeedbackDevice.cpp | 257 ---- .../ForceFeedback/ForceFeedbackDevice.h | 80 -- .../ControllerInterface/Pipes/Pipes.cpp | 175 --- .../ControllerInterface/Pipes/Pipes.h | 60 - .../ControllerInterface/Quartz/Quartz.h | 15 - .../ControllerInterface/Quartz/Quartz.mm | 47 - .../Quartz/QuartzKeyboardAndMouse.h | 81 -- .../Quartz/QuartzKeyboardAndMouse.mm | 325 ----- .../ControllerInterface/WGInput/WGInput.cpp | 835 ----------- .../ControllerInterface/WGInput/WGInput.h | 12 - .../ControllerInterface/Win32/Win32.cpp | 128 -- .../ControllerInterface/Win32/Win32.h | 11 - .../ControllerInterface/XInput/XInput.cpp | 321 ----- .../ControllerInterface/XInput/XInput.h | 50 - .../ControllerInterface/Xlib/XInput2.cpp | 523 ------- .../ControllerInterface/Xlib/XInput2.h | 142 -- .../ControllerInterface/evdev/evdev.cpp | 878 ------------ .../ControllerInterface/evdev/evdev.h | 104 -- 57 files changed, 8775 deletions(-) delete mode 160000 Externals/miniupnpc/miniupnp delete mode 100644 Source/Core/Common/UPnP.cpp delete mode 100644 Source/Core/Common/UPnP.h delete mode 100644 Source/Core/DiscIO/MultithreadedCompressor.h delete mode 100644 Source/Core/DolphinTool/ConvertCommand.cpp delete mode 100644 Source/Core/DolphinTool/ConvertCommand.h delete mode 100644 Source/Core/DolphinTool/DolphinTool.exe.manifest delete mode 100644 Source/Core/DolphinTool/DolphinTool.filters delete mode 100644 Source/Core/DolphinTool/DolphinTool.rc delete mode 100644 Source/Core/DolphinTool/ExtractCommand.cpp delete mode 100644 Source/Core/DolphinTool/ExtractCommand.h delete mode 100644 Source/Core/DolphinTool/HeaderCommand.cpp delete mode 100644 Source/Core/DolphinTool/HeaderCommand.h delete mode 100644 Source/Core/DolphinTool/ToolHeadlessPlatform.cpp delete mode 100644 Source/Core/DolphinTool/ToolMain.cpp delete mode 100644 Source/Core/DolphinTool/VerifyCommand.cpp delete mode 100644 Source/Core/DolphinTool/VerifyCommand.h delete mode 100644 Source/Core/DolphinTool/resource.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/DInput/DInput.cpp delete mode 100644 Source/Core/InputCommon/ControllerInterface/DInput/DInput.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/DInput/DInput8.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp delete mode 100644 Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.cpp delete mode 100644 Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/DInput/NamedKeys.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/DInput/XInputFilter.cpp delete mode 100644 Source/Core/InputCommon/ControllerInterface/DInput/XInputFilter.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp delete mode 100644 Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/Pipes/Pipes.cpp delete mode 100644 Source/Core/InputCommon/ControllerInterface/Pipes/Pipes.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/Quartz/Quartz.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/Quartz/Quartz.mm delete mode 100644 Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.mm delete mode 100644 Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp delete mode 100644 Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp delete mode 100644 Source/Core/InputCommon/ControllerInterface/Win32/Win32.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp delete mode 100644 Source/Core/InputCommon/ControllerInterface/XInput/XInput.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp delete mode 100644 Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp delete mode 100644 Source/Core/InputCommon/ControllerInterface/evdev/evdev.h diff --git a/.gitmodules b/.gitmodules index e3b751ab99..2268e7c926 100644 --- a/.gitmodules +++ b/.gitmodules @@ -86,10 +86,6 @@ path = Externals/zstd/zstd url = https://github.com/facebook/zstd.git shallow = true -[submodule "Externals/miniupnpc/miniupnp"] - path = Externals/miniupnpc/miniupnp - url = https://github.com/miniupnp/miniupnp.git - shallow = true [submodule "Externals/glslang/glslang"] path = Externals/glslang/glslang url = https://github.com/KhronosGroup/glslang.git diff --git a/Externals/licenses.md b/Externals/licenses.md index cfdb938650..10b7d274cf 100644 --- a/Externals/licenses.md +++ b/Externals/licenses.md @@ -42,8 +42,6 @@ Dolphin includes or links code of the following third-party software projects: [GPLv2+](http://www.oberhumer.com/opensource/gpl.html) - [mGBA](http://mgba.io) [MPL 2.0](https://github.com/mgba-emu/mgba/blob/master/LICENSE) -- [MiniUPnPc](http://miniupnp.free.fr/): - [BSD 3-Clause](https://github.com/miniupnp/miniupnp/blob/master/miniupnpc/LICENSE) - [Microsoft Visual C++ Runtime Library](http://www.microsoft.com/en-us/download/details.aspx?id=40784): [System Library if not distributed](https://www.gnu.org/licenses/gpl-faq.html#WindowsRuntimeAndGPL) - [OpenGL Header (MESA)](http://mesa3d.org/): diff --git a/Externals/miniupnpc/miniupnp b/Externals/miniupnpc/miniupnp deleted file mode 160000 index bf4215a757..0000000000 --- a/Externals/miniupnpc/miniupnp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bf4215a7574f88aa55859db9db00e3ae58cf42d6 diff --git a/Makefile.common b/Makefile.common index 9da40feb43..f833ac3ccf 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1166,7 +1166,6 @@ DOLPHIN_SOURCES_CXX += \ $(CORE_DIR)/Source/Core/Common/Thread.cpp \ $(CORE_DIR)/Source/Core/Common/Timer.cpp \ $(CORE_DIR)/Source/Core/Common/TimeUtil.cpp \ - $(CORE_DIR)/Source/Core/Common/UPnP.cpp \ $(CORE_DIR)/Source/Core/Common/Version.cpp \ $(CORE_DIR)/Source/Core/Common/GL/GLContext.cpp \ $(CORE_DIR)/Source/Core/Common/GL/GLUtil.cpp \ diff --git a/Readme.md b/Readme.md index c130ddb16d..84daf0ad33 100644 --- a/Readme.md +++ b/Readme.md @@ -111,86 +111,3 @@ Available video backends are "D3D" and "D3D12" (they are only available on Windo There's also "Null", which will not render anything, and "Software Renderer", which uses the CPU for rendering and is intended for debugging purposes only. - -## DolphinTool Usage -``` -usage: dolphin-tool COMMAND -h - -commands supported: [convert, verify, header, extract] -``` - -``` -Usage: convert [options]... [FILE]... - -Options: - -h, --help show this help message and exit - -u USER, --user=USER User folder path, required for temporary processing - files.Will be automatically created if this option is - not set. - -i FILE, --input=FILE - Path to disc image FILE. - -o FILE, --output=FILE - Path to the destination FILE. - -f FORMAT, --format=FORMAT - Container format to use. Default is RVZ. [iso|gcz|wia|rvz] - -s, --scrub Scrub junk data as part of conversion. - -b BLOCK_SIZE, --block_size=BLOCK_SIZE - Block size for GCZ/WIA/RVZ formats, as an integer. - Suggested value for RVZ: 131072 (128 KiB) - -c COMPRESSION, --compression=COMPRESSION - Compression method to use when converting to WIA/RVZ. - Suggested value for RVZ: zstd [none|zstd|bzip|lzma|lzma2] - -l COMPRESSION_LEVEL, --compression_level=COMPRESSION_LEVEL - Level of compression for the selected method. Ignored - if 'none'. Suggested value for zstd: 5 -``` - -``` -Usage: verify [options]... - -Options: - -h, --help show this help message and exit - -u USER, --user=USER User folder path, required for temporary processing - files.Will be automatically created if this option is - not set. - -i FILE, --input=FILE - Path to disc image FILE. - -a ALGORITHM, --algorithm=ALGORITHM - Optional. Compute and print the digest using the - selected algorithm, then exit. [crc32|md5|sha1|rchash] -``` - -``` -Usage: header [options]... - -Options: - -h, --help show this help message and exit - -i FILE, --input=FILE - Path to disc image FILE. - -b, --block_size Optional. Print the block size of GCZ/WIA/RVZ formats, -then exit. - -c, --compression Optional. Print the compression method of GCZ/WIA/RVZ - formats, then exit. - -l, --compression_level - Optional. Print the level of compression for WIA/RVZ - formats, then exit. -``` - -``` -Usage: extract [options]... - -Options: - -h, --help show this help message and exit - -i FILE, --input=FILE - Path to disc image FILE. - -o FOLDER, --output=FOLDER - Path to the destination FOLDER. - -p PARTITION, --partition=PARTITION - Which specific partition you want to extract. - -s SINGLE, --single=SINGLE - Which specific file/directory you want to extract. - -l, --list List all files in volume/partition. Will print the - directory/file specified with --single if defined. - -q, --quiet Mute all messages except for errors. - -g, --gameonly Only extracts the DATA partition. -``` diff --git a/Source/Core/Common/UPnP.cpp b/Source/Core/Common/UPnP.cpp deleted file mode 100644 index 81807f1e3c..0000000000 --- a/Source/Core/Common/UPnP.cpp +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2017 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#ifdef USE_UPNP - -#include "Common/UPnP.h" - -#include "Common/Logging/Log.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static UPNPUrls s_urls; -static IGDdatas s_data; -static std::array s_our_ip; -static u16 s_mapped = 0; -static std::thread s_thread; - -// called from ---UPnP--- thread -// discovers the IGD -static bool InitUPnP() -{ - static bool s_inited = false; - static bool s_error = false; - - // Don't init if already inited - if (s_inited) - return true; - - // Don't init if it failed before - if (s_error) - return false; - - s_urls = {}; - s_data = {}; - - // Find all UPnP devices - int upnperror = 0; - std::unique_ptr devlist(nullptr, freeUPNPDevlist); -#if MINIUPNPC_API_VERSION >= 14 - devlist.reset(upnpDiscover(2000, nullptr, nullptr, 0, 0, 2, &upnperror)); -#else - devlist.reset(upnpDiscover(2000, nullptr, nullptr, 0, 0, &upnperror)); -#endif - if (!devlist) - { - if (upnperror == UPNPDISCOVER_SUCCESS) - { - WARN_LOG_FMT(NETPLAY, "No UPnP devices could be found."); - } - else - { - WARN_LOG_FMT(NETPLAY, "An error occurred trying to discover UPnP devices: {}", - strupnperror(upnperror)); - } - - s_error = true; - - return false; - } - - // Look for the IGD - bool found_valid_igd = false; - for (UPNPDev* dev = devlist.get(); dev; dev = dev->pNext) - { - if (!std::strstr(dev->st, "InternetGatewayDevice")) - continue; - - int desc_xml_size = 0; - std::unique_ptr desc_xml(nullptr, std::free); - int statusCode = 200; -#if MINIUPNPC_API_VERSION >= 16 - desc_xml.reset( - static_cast(miniwget_getaddr(dev->descURL, &desc_xml_size, s_our_ip.data(), - static_cast(s_our_ip.size()), 0, &statusCode))); -#else - desc_xml.reset(static_cast(miniwget_getaddr( - dev->descURL, &desc_xml_size, s_our_ip.data(), static_cast(s_our_ip.size()), 0))); -#endif - if (desc_xml && statusCode == 200) - { - parserootdesc(desc_xml.get(), desc_xml_size, &s_data); - GetUPNPUrls(&s_urls, &s_data, dev->descURL, 0); - - found_valid_igd = true; - NOTICE_LOG_FMT(NETPLAY, "Got info from IGD at {}.", dev->descURL); - break; - } - else - { - WARN_LOG_FMT(NETPLAY, "Error getting info from IGD at {}.", dev->descURL); - } - } - - if (!found_valid_igd) - WARN_LOG_FMT(NETPLAY, "Could not find a valid IGD in the discovered UPnP devices."); - - s_inited = true; - - return true; -} - -// called from ---UPnP--- thread -// Attempt to stop portforwarding. -// -- -// NOTE: It is important that this happens! A few very crappy routers -// apparently do not delete UPnP mappings on their own, so if you leave them -// hanging, the NVRAM will fill with portmappings, and eventually all UPnP -// requests will fail silently, with the only recourse being a factory reset. -// -- -static bool UnmapPort(const u16 port) -{ - std::string port_str = std::to_string(port); - UPNP_DeletePortMapping(s_urls.controlURL, s_data.first.servicetype, port_str.c_str(), "UDP", - nullptr); - - return true; -} - -// called from ---UPnP--- thread -// Attempt to portforward! -static bool MapPort(const char* addr, const u16 port) -{ - if (s_mapped > 0) - UnmapPort(s_mapped); - - std::string port_str = std::to_string(port); - int result = UPNP_AddPortMapping( - s_urls.controlURL, s_data.first.servicetype, port_str.c_str(), port_str.c_str(), addr, - (std::string("dolphin-emu UDP on ") + addr).c_str(), "UDP", nullptr, nullptr); - - if (result != 0) - return false; - - s_mapped = port; - - return true; -} - -// UPnP thread: try to map a port -static void MapPortThread(const u16 port) -{ - if (InitUPnP() && MapPort(s_our_ip.data(), port)) - { - NOTICE_LOG_FMT(NETPLAY, "Successfully mapped port {} to {}.", port, s_our_ip.data()); - return; - } - - WARN_LOG_FMT(NETPLAY, "Failed to map port {} to {}.", port, s_our_ip.data()); -} - -// UPnP thread: try to unmap a port -static void UnmapPortThread() -{ - if (s_mapped > 0) - UnmapPort(s_mapped); -} - -void Common::UPnP::TryPortmapping(u16 port) -{ - if (s_thread.joinable()) - s_thread.join(); - s_thread = std::thread(&MapPortThread, port); -} - -void Common::UPnP::StopPortmapping() -{ - if (s_thread.joinable()) - s_thread.join(); - s_thread = std::thread(&UnmapPortThread); - s_thread.join(); -} - -#endif diff --git a/Source/Core/Common/UPnP.h b/Source/Core/Common/UPnP.h deleted file mode 100644 index 5545097ec1..0000000000 --- a/Source/Core/Common/UPnP.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2017 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#ifdef USE_UPNP - -#include "Common/CommonTypes.h" - -namespace Common::UPnP -{ -void TryPortmapping(u16 port); -void StopPortmapping(); -} // namespace Common::UPnP - -#endif diff --git a/Source/Core/DiscIO/Blob.h b/Source/Core/DiscIO/Blob.h index 3551c31d44..8119694961 100644 --- a/Source/Core/DiscIO/Blob.h +++ b/Source/Core/DiscIO/Blob.h @@ -195,16 +195,4 @@ private: // Factory function - examines the path to choose the right type of BlobReader, and returns one. std::unique_ptr CreateBlobReader(const std::string& filename); -using CompressCB = std::function; - -bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path, - const std::string& outfile_path, u32 sub_type, int sector_size, - const CompressCB& callback); -bool ConvertToPlain(BlobReader* infile, const std::string& infile_path, - const std::string& outfile_path, const CompressCB& callback); -bool ConvertToWIAOrRVZ(BlobReader* infile, const std::string& infile_path, - const std::string& outfile_path, bool rvz, - WIARVZCompressionType compression_type, int compression_level, - int chunk_size, const CompressCB& callback); - } // namespace DiscIO diff --git a/Source/Core/DiscIO/CompressedBlob.cpp b/Source/Core/DiscIO/CompressedBlob.cpp index 46573b6111..3ba7addb86 100644 --- a/Source/Core/DiscIO/CompressedBlob.cpp +++ b/Source/Core/DiscIO/CompressedBlob.cpp @@ -26,7 +26,6 @@ #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "DiscIO/Blob.h" -#include "DiscIO/MultithreadedCompressor.h" #include "DiscIO/Volume.h" namespace DiscIO @@ -251,231 +250,6 @@ bool CompressedBlobReader::ValidateBlockPointers() const return invalid_pointers == 0; } -struct CompressThreadState -{ - CompressThreadState() : z{} {} - ~CompressThreadState() { deflateEnd(&z); } - - // z_stream will stop working if it changes address, so this object must not be moved - CompressThreadState(const CompressThreadState&) = delete; - CompressThreadState(CompressThreadState&&) = delete; - CompressThreadState& operator=(const CompressThreadState&) = delete; - CompressThreadState& operator=(CompressThreadState&&) = delete; - - std::vector compressed_buffer; - z_stream z; -}; - -struct CompressParameters -{ - std::vector data{}; - u32 block_number = 0; - u64 inpos = 0; -}; - -struct OutputParameters -{ - std::vector data{}; - u32 block_number = 0; - bool compressed = false; - u64 inpos = 0; -}; - -static ConversionResultCode SetUpCompressThreadState(CompressThreadState* state) -{ - return deflateInit(&state->z, 9) == Z_OK ? ConversionResultCode::Success : - ConversionResultCode::InternalError; -} - -static ConversionResult Compress(CompressThreadState* state, - CompressParameters parameters, int block_size, - std::vector* hashes, int* num_stored, - int* num_compressed) -{ - state->compressed_buffer.resize(block_size); - - int retval = deflateReset(&state->z); - state->z.next_in = parameters.data.data(); - state->z.avail_in = block_size; - state->z.next_out = state->compressed_buffer.data(); - state->z.avail_out = block_size; - - if (retval != Z_OK) - { - ERROR_LOG_FMT(DISCIO, "Deflate failed"); - return std::unexpected{ConversionResultCode::InternalError}; - } - - const int status = deflate(&state->z, Z_FINISH); - - state->compressed_buffer.resize(block_size - state->z.avail_out); - - OutputParameters output_parameters; - if ((status != Z_STREAM_END) || (state->z.avail_out < 10)) - { - // let's store uncompressed - ++*num_stored; - output_parameters = OutputParameters{std::move(parameters.data), parameters.block_number, false, - parameters.inpos}; - } - else - { - // let's store compressed - ++*num_compressed; - output_parameters = OutputParameters{std::move(state->compressed_buffer), - parameters.block_number, true, parameters.inpos}; - } - - (*hashes)[parameters.block_number] = - Common::HashAdler32(output_parameters.data.data(), output_parameters.data.size()); - - return std::move(output_parameters); -} - -static ConversionResultCode Output(OutputParameters parameters, File::DirectIOFile* outfile, - u64* position, std::vector* offsets, int progress_monitor, - u32 num_blocks, const CompressCB& callback) -{ - u64 offset = *position; - if (!parameters.compressed) - offset |= uncompressed_flag; - (*offsets)[parameters.block_number] = offset; - - *position += parameters.data.size(); - - if (!outfile->Write(parameters.data)) - return ConversionResultCode::WriteFailed; - - if (parameters.block_number % progress_monitor == 0) - { - const int ratio = - parameters.inpos == 0 ? 0 : static_cast(100 * *position / parameters.inpos); - - const std::string text = Common::FmtFormatT("{0} of {1} blocks. Compression ratio {2}%", - parameters.block_number, num_blocks, ratio); - - const float completion = static_cast(parameters.block_number) / num_blocks; - - if (!callback(text, completion)) - return ConversionResultCode::Canceled; - } - - return ConversionResultCode::Success; -} - -bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path, - const std::string& outfile_path, u32 sub_type, int block_size, - const CompressCB& callback) -{ - ASSERT(infile->GetDataSizeType() == DataSizeType::Accurate); - - File::DirectIOFile outfile(outfile_path, File::AccessMode::Write); - if (!outfile.IsOpen()) - { - PanicAlertFmtT( - "Failed to open the output file \"{0}\".\n" - "Check that you have permissions to write the target folder and that the media can " - "be written.", - outfile_path); - return false; - } - - callback(Common::GetStringT("Files opened, ready to compress."), 0); - - CompressedBlobHeader header; - header.magic_cookie = GCZ_MAGIC; - header.sub_type = sub_type; - header.block_size = block_size; - header.disc_size = infile->GetDataSize(); - - // round upwards! - header.num_blocks = (u32)((header.disc_size + (block_size - 1)) / block_size); - - std::vector offsets(header.num_blocks); - std::vector hashes(header.num_blocks); - - // seek past the header (we will write it at the end) - outfile.Seek(sizeof(CompressedBlobHeader), File::SeekOrigin::Current); - // seek past the offset and hash tables (we will write them at the end) - outfile.Seek((sizeof(u64) + sizeof(u32)) * header.num_blocks, File::SeekOrigin::Current); - - // Now we are ready to write compressed data! - u64 inpos = 0; - u64 position = 0; - int num_compressed = 0; - int num_stored = 0; - int progress_monitor = std::max(1, header.num_blocks / 1000); - - const auto compress = [&](CompressThreadState* state, CompressParameters parameters) { - return Compress(state, std::move(parameters), block_size, &hashes, &num_stored, - &num_compressed); - }; - - const auto output = [&](OutputParameters parameters) { - return Output(std::move(parameters), &outfile, &position, &offsets, progress_monitor, - header.num_blocks, callback); - }; - - MultithreadedCompressor compressor( - SetUpCompressThreadState, compress, output); - - std::vector in_buf(block_size); - for (u32 i = 0; i < header.num_blocks; i++) - { - if (compressor.GetStatus() != ConversionResultCode::Success) - break; - - const u64 bytes_to_read = std::min(block_size, header.disc_size - inpos); - - if (!infile->Read(inpos, bytes_to_read, in_buf.data())) - { - compressor.SetError(ConversionResultCode::ReadFailed); - break; - } - - std::fill(in_buf.begin() + bytes_to_read, in_buf.begin() + header.block_size, 0); - - inpos += block_size; - - compressor.CompressAndWrite(CompressParameters{in_buf, i, inpos}); - } - - compressor.Shutdown(); - - header.compressed_data_size = position; - - const ConversionResultCode result = compressor.GetStatus(); - - if (result != ConversionResultCode::Success) - { - // Remove the incomplete output file. - outfile.Close(); - File::Delete(outfile_path); - } - else - { - // Okay, go back and fill in headers - outfile.Seek(0, File::SeekOrigin::Begin); - outfile.Write(Common::AsU8Span(header)); - outfile.Write(Common::AsU8Span(offsets)); - outfile.Write(Common::AsU8Span(hashes)); - - callback(Common::GetStringT("Done compressing disc image."), 1.0f); - } - - if (result == ConversionResultCode::ReadFailed) - PanicAlertFmtT("Failed to read from the input file \"{0}\".", infile_path); - - if (result == ConversionResultCode::WriteFailed) - { - PanicAlertFmtT("Failed to write the output file \"{0}\".\n" - "Check that you have enough space available on the target drive.", - outfile_path); - } - - return result == ConversionResultCode::Success; -} - bool IsGCZBlob(File::DirectIOFile& file) { CompressedBlobHeader header; diff --git a/Source/Core/DiscIO/FileBlob.cpp b/Source/Core/DiscIO/FileBlob.cpp index 3a30e2a255..ccc73fec6a 100644 --- a/Source/Core/DiscIO/FileBlob.cpp +++ b/Source/Core/DiscIO/FileBlob.cpp @@ -38,77 +38,4 @@ bool PlainFileReader::Read(u64 offset, u64 nbytes, u8* out_ptr) return m_file.OffsetRead(offset, out_ptr, nbytes); } -bool ConvertToPlain(BlobReader* infile, const std::string& infile_path, - const std::string& outfile_path, const CompressCB& callback) -{ - ASSERT(infile->GetDataSizeType() == DataSizeType::Accurate); - - File::DirectIOFile outfile(outfile_path, File::AccessMode::Write); - if (!outfile.IsOpen()) - { - PanicAlertFmtT( - "Failed to open the output file \"{0}\".\n" - "Check that you have permissions to write the target folder and that the media can " - "be written.", - outfile_path); - return false; - } - - constexpr size_t DESIRED_BUFFER_SIZE = 0x80000; - u64 buffer_size = infile->GetBlockSize(); - if (buffer_size == 0) - { - buffer_size = DESIRED_BUFFER_SIZE; - } - else - { - while (buffer_size < DESIRED_BUFFER_SIZE) - buffer_size *= 2; - } - - std::vector buffer(buffer_size); - const u64 num_buffers = (infile->GetDataSize() + buffer_size - 1) / buffer_size; - int progress_monitor = std::max(1, num_buffers / 100); - bool success = true; - - for (u64 i = 0; i < num_buffers; i++) - { - if (i % progress_monitor == 0) - { - const bool was_cancelled = - !callback(Common::GetStringT("Unpacking"), (float)i / (float)num_buffers); - if (was_cancelled) - { - success = false; - break; - } - } - const u64 inpos = i * buffer_size; - const u64 sz = std::min(buffer_size, infile->GetDataSize() - inpos); - if (!infile->Read(inpos, sz, buffer.data())) - { - PanicAlertFmtT("Failed to read from the input file \"{0}\".", infile_path); - success = false; - break; - } - if (!outfile.Write(buffer.data(), sz)) - { - PanicAlertFmtT("Failed to write the output file \"{0}\".\n" - "Check that you have enough space available on the target drive.", - outfile_path); - success = false; - break; - } - } - - if (!success) - { - // Remove the incomplete output file. - outfile.Close(); - File::Delete(outfile_path); - } - - return success; -} - } // namespace DiscIO diff --git a/Source/Core/DiscIO/MultithreadedCompressor.h b/Source/Core/DiscIO/MultithreadedCompressor.h deleted file mode 100644 index 5afaa1cca5..0000000000 --- a/Source/Core/DiscIO/MultithreadedCompressor.h +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2020 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "Common/Assert.h" -#include "Common/Event.h" - -namespace DiscIO -{ -enum class ConversionResultCode -{ - Success, - Canceled, - ReadFailed, - WriteFailed, - InternalError, -}; - -template -using ConversionResult = std::expected; - -// This class starts a number of compression threads and one output thread. -// The set_up_compress_thread_state function is called at the start of each compression thread. -// When CompressAndWrite is called, the compress function will be called on one of the -// compression threads, and then the output function will be called on the output thread. -// The output thread handles data in the order that data was submitted using CompressAndWrite, -// but the compression threads are not guaranteed to handle data in a predictable order. -// Remember to check GetStatus regularly and cancel if it doesn't return Success, -// and call Shutdown when you want to ensure that everything finishes. -template -class MultithreadedCompressor -{ -public: - MultithreadedCompressor( - std::function set_up_compress_thread_state, - std::function(CompressThreadState*, CompressParameters)> - compress, - std::function output) - : m_set_up_compress_thread_state(std::move(set_up_compress_thread_state)), - m_compress(std::move(compress)), m_output(std::move(output)), - m_threads(std::max(1, std::thread::hardware_concurrency())) - { - m_compress_threads = std::make_unique(m_threads); - - for (size_t i = 0; i < m_threads; ++i) - { - m_compress_threads[i].thread = - std::thread(std::mem_fn(&MultithreadedCompressor::CompressThreadFunction), this, - &m_compress_threads[i]); - } - - m_output_thread = - std::thread(std::mem_fn(&MultithreadedCompressor::OutputThreadFunction), this); - } - - ~MultithreadedCompressor() - { - if (!m_shutting_down.load()) - Shutdown(); - } - - void CompressAndWrite(CompressParameters parameters) - { - if (GetStatus() != ConversionResultCode::Success) - return; - - CompressThread& compress_thread = m_compress_threads[m_current_index]; - - compress_thread.compress_ready_event.Wait(); - compress_thread.compress_parameters = std::move(parameters); - compress_thread.compress_event.Set(); - - ++m_current_index; - if (m_current_index >= m_threads) - m_current_index -= m_threads; - } - - void SetError(ConversionResultCode result) - { - ASSERT(result != ConversionResultCode::Success); - - // If we already have an error, don't overwrite it - ConversionResultCode expected = ConversionResultCode::Success; - m_result.compare_exchange_strong(expected, result); - } - - ConversionResultCode GetStatus() const { return m_result.load(); } - - void Shutdown() - { - for (size_t i = 0; i < m_threads; ++i) - m_compress_threads[i].compress_ready_event.Wait(); - for (size_t i = 0; i < m_threads; ++i) - m_compress_threads[i].compress_done_event.Wait(); - for (size_t i = 0; i < m_threads; ++i) - m_compress_threads[i].output_ready_event.Wait(); - - m_shutting_down.store(true); - - for (size_t i = 0; i < m_threads; ++i) - m_compress_threads[i].compress_event.Set(); - for (size_t i = 0; i < m_threads; ++i) - m_compress_threads[i].output_event.Set(); - - for (size_t i = 0; i < m_threads; ++i) - m_compress_threads[i].thread.join(); - - m_output_thread.join(); - } - -private: - struct CompressThread - { - std::thread thread; - - Common::Event compress_ready_event; - Common::Event compress_event; - Common::Event compress_done_event; - Common::Event output_ready_event; - Common::Event output_event; - - CompressParameters compress_parameters; - OutputParameters output_parameters; - }; - - void CompressThreadFunction(CompressThread* state) - { - CompressThreadState compress_thread_state; - - ConversionResultCode setup_result = m_set_up_compress_thread_state(&compress_thread_state); - if (setup_result != ConversionResultCode::Success) - SetError(setup_result); - - state->compress_ready_event.Set(); - state->compress_done_event.Set(); - - while (true) - { - state->compress_event.Wait(); - - if (m_shutting_down.load()) - return; - - CompressParameters parameters = std::move(state->compress_parameters); - - state->compress_done_event.Reset(); - state->compress_ready_event.Set(); - - ConversionResult result = - m_compress(&compress_thread_state, std::move(parameters)); - - if (result) - { - state->output_ready_event.Wait(); - state->output_parameters = std::move(*result); - state->output_event.Set(); - } - else - { - SetError(result.error()); - } - - state->compress_done_event.Set(); - } - } - - void OutputThreadFunction() - { - for (size_t i = 0; i < m_threads; ++i) - m_compress_threads[i].output_ready_event.Set(); - - size_t index = 0; - - while (true) - { - CompressThread& compress_thread = m_compress_threads[index]; - - compress_thread.output_event.Wait(); - - if (m_shutting_down.load()) - return; - - OutputParameters parameters = std::move(compress_thread.output_parameters); - - compress_thread.output_ready_event.Set(); - - const ConversionResultCode result = m_output(std::move(parameters)); - - if (result != ConversionResultCode::Success) - SetError(result); - - ++index; - if (index >= m_threads) - index -= m_threads; - } - } - - std::function m_set_up_compress_thread_state; - std::function(CompressThreadState*, CompressParameters)> - m_compress; - std::function m_output; - - // We can't use std::vector for this, because Common::Event is not movable - std::unique_ptr m_compress_threads; - std::thread m_output_thread; - - const size_t m_threads; - size_t m_current_index = 0; - - std::atomic m_result = ConversionResultCode::Success; - std::atomic m_shutting_down = false; -}; - -} // namespace DiscIO diff --git a/Source/Core/DiscIO/WIABlob.cpp b/Source/Core/DiscIO/WIABlob.cpp index 3f7b274ca8..e90df66ec4 100644 --- a/Source/Core/DiscIO/WIABlob.cpp +++ b/Source/Core/DiscIO/WIABlob.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -33,7 +32,6 @@ #include "DiscIO/DiscUtils.h" #include "DiscIO/Filesystem.h" #include "DiscIO/LaggedFibonacciGenerator.h" -#include "DiscIO/MultithreadedCompressor.h" #include "DiscIO/Volume.h" #include "DiscIO/VolumeWii.h" #include "DiscIO/WIACompression.h" @@ -41,43 +39,6 @@ namespace DiscIO { -static void PushBack(std::vector* vector, const u8* begin, const u8* end) -{ - const size_t offset_in_vector = vector->size(); - vector->resize(offset_in_vector + (end - begin)); - std::copy(begin, end, vector->data() + offset_in_vector); -} - -template -static void PushBack(std::vector* vector, const T& x) -{ - static_assert(std::is_trivially_copyable_v); - - const u8* x_ptr = reinterpret_cast(&x); - PushBack(vector, x_ptr, x_ptr + sizeof(T)); -} - -std::pair GetAllowedCompressionLevels(WIARVZCompressionType compression_type, bool gui) -{ - switch (compression_type) - { - case WIARVZCompressionType::Bzip2: - case WIARVZCompressionType::LZMA: - case WIARVZCompressionType::LZMA2: - return {1, 9}; - case WIARVZCompressionType::Zstd: - // The actual minimum level can be gotten by calling ZSTD_minCLevel(). However, returning that - // would make the UI rather weird, because it is a negative number with very large magnitude. - // Note: Level 0 is a special number which means "default level" (level 3 as of this writing). - if (gui) - return {1, ZSTD_maxCLevel()}; - else - return {ZSTD_minCLevel(), ZSTD_maxCLevel()}; - default: - return {0, -1}; - } -} - template WIARVZFileReader::WIARVZFileReader(File::DirectIOFile file, const std::string& path) : m_file(std::move(file)), m_path(path), m_encryption_cache(this) @@ -882,1206 +843,6 @@ bool WIARVZFileReader::ApplyHashExceptions( return true; } -template -bool WIARVZFileReader::PadTo4(File::DirectIOFile* file, u64* bytes_written) -{ - constexpr u32 ZEROES = 0; - const u64 bytes_to_write = Common::AlignUp(*bytes_written, 4) - *bytes_written; - if (bytes_to_write == 0) - return true; - - *bytes_written += bytes_to_write; - return file->Write(Common::AsU8Span(ZEROES).first(bytes_to_write)); -} - -template -void WIARVZFileReader::AddRawDataEntry(u64 offset, u64 size, int chunk_size, u32* total_groups, - std::vector* raw_data_entries, - std::vector* data_entries) -{ - constexpr size_t SKIP_SIZE = sizeof(WIAHeader2::disc_header); - const u64 skip = offset < SKIP_SIZE ? std::min(SKIP_SIZE - offset, size) : 0; - - offset += skip; - size -= skip; - - if (size == 0) - return; - - const u32 group_index = *total_groups; - const u32 groups = static_cast(Common::AlignUp(size, chunk_size) / chunk_size); - *total_groups += groups; - - data_entries->emplace_back(raw_data_entries->size()); - raw_data_entries->emplace_back(RawDataEntry{Common::swap64(offset), Common::swap64(size), - Common::swap32(group_index), Common::swap32(groups)}); -} - -template -typename WIARVZFileReader::PartitionDataEntry WIARVZFileReader::CreatePartitionDataEntry( - u64 offset, u64 size, u32 index, int chunk_size, u32* total_groups, - const std::vector& partition_entries, std::vector* data_entries) -{ - const u32 group_index = *total_groups; - const u64 rounded_size = Common::AlignDown(size, VolumeWii::BLOCK_TOTAL_SIZE); - const u32 groups = static_cast(Common::AlignUp(rounded_size, chunk_size) / chunk_size); - *total_groups += groups; - - data_entries->emplace_back(partition_entries.size(), index); - return PartitionDataEntry{Common::swap32(offset / VolumeWii::BLOCK_TOTAL_SIZE), - Common::swap32(size / VolumeWii::BLOCK_TOTAL_SIZE), - Common::swap32(group_index), Common::swap32(groups)}; -} - -template -ConversionResultCode WIARVZFileReader::SetUpDataEntriesForWriting( - const VolumeDisc* volume, int chunk_size, u64 iso_size, u32* total_groups, - std::vector* partition_entries, std::vector* raw_data_entries, - std::vector* data_entries, std::vector* partition_file_systems) -{ - std::vector partitions; - if (volume && volume->HasWiiHashes() && volume->HasWiiEncryption()) - partitions = volume->GetPartitions(); - - std::ranges::sort(partitions, {}, &Partition::offset); - - *total_groups = 0; - - u64 last_partition_end_offset = 0; - - const auto add_raw_data_entry = [&](u64 offset, u64 size) { - return AddRawDataEntry(offset, size, chunk_size, total_groups, raw_data_entries, data_entries); - }; - - const auto create_partition_data_entry = [&](u64 offset, u64 size, u32 index) { - return CreatePartitionDataEntry(offset, size, index, chunk_size, total_groups, - *partition_entries, data_entries); - }; - - for (const Partition& partition : partitions) - { - // If a partition is odd in some way that prevents us from encoding it as a partition, - // we encode it as raw data instead by skipping the current loop iteration. - // Partitions can always be encoded as raw data, but it is less space efficient. - - if (partition.offset < last_partition_end_offset) - { - WARN_LOG_FMT(DISCIO, "Overlapping partitions at {:x}", partition.offset); - continue; - } - - if (volume->ReadSwapped(partition.offset, PARTITION_NONE) != 0x10001U) - { - // This looks more like garbage data than an actual partition. - // The values of data_offset and data_size will very likely also be garbage. - // Some WBFS writing programs scrub the SSBB Masterpiece partitions without - // removing them from the partition table, causing this problem. - WARN_LOG_FMT(DISCIO, "Invalid partition at {:x}", partition.offset); - continue; - } - - std::optional data_offset = - volume->ReadSwappedAndShifted(partition.offset + 0x2b8, PARTITION_NONE); - std::optional data_size = - volume->ReadSwappedAndShifted(partition.offset + 0x2bc, PARTITION_NONE); - - if (!data_offset || !data_size) - return ConversionResultCode::ReadFailed; - - const u64 data_start = partition.offset + *data_offset; - const u64 data_end = data_start + *data_size; - - if (data_start % VolumeWii::BLOCK_TOTAL_SIZE != 0) - { - WARN_LOG_FMT(DISCIO, "Misaligned partition at {:x}", partition.offset); - continue; - } - - if (*data_size < VolumeWii::BLOCK_TOTAL_SIZE) - { - WARN_LOG_FMT(DISCIO, "Very small partition at {:x}", partition.offset); - continue; - } - - if (data_end > iso_size) - { - WARN_LOG_FMT(DISCIO, "Too large partition at {:x}", partition.offset); - *data_size = iso_size - *data_offset - partition.offset; - } - - const std::optional fst_offset = GetFSTOffset(*volume, partition); - const std::optional fst_size = GetFSTSize(*volume, partition); - - if (!fst_offset || !fst_size) - return ConversionResultCode::ReadFailed; - - const IOS::ES::TicketReader& ticket = volume->GetTicket(partition); - if (!ticket.IsValid()) - return ConversionResultCode::ReadFailed; - - add_raw_data_entry(last_partition_end_offset, partition.offset - last_partition_end_offset); - - add_raw_data_entry(partition.offset, *data_offset); - - const u64 fst_end = volume->PartitionOffsetToRawOffset(*fst_offset + *fst_size, partition); - const u64 split_point = std::min( - data_end, Common::AlignUp(fst_end - data_start, VolumeWii::GROUP_TOTAL_SIZE) + data_start); - - PartitionEntry partition_entry; - partition_entry.partition_key = ticket.GetTitleKey(); - partition_entry.data_entries[0] = - create_partition_data_entry(data_start, split_point - data_start, 0); - partition_entry.data_entries[1] = - create_partition_data_entry(split_point, data_end - split_point, 1); - - // Note: We can't simply set last_partition_end_offset to data_end, - // because construct_partition_data_entry may have rounded it - last_partition_end_offset = - (Common::swap32(partition_entry.data_entries[1].first_sector) + - Common::swap32(partition_entry.data_entries[1].number_of_sectors)) * - VolumeWii::BLOCK_TOTAL_SIZE; - - partition_entries->emplace_back(std::move(partition_entry)); - partition_file_systems->emplace_back(volume->GetFileSystem(partition)); - } - - add_raw_data_entry(last_partition_end_offset, iso_size - last_partition_end_offset); - - return ConversionResultCode::Success; -} - -template -std::optional> WIARVZFileReader::Compress(Compressor* compressor, - const u8* data, size_t size) -{ - if (compressor) - { - if (!compressor->Start(size) || !compressor->Compress(data, size) || !compressor->End()) - return std::nullopt; - - data = compressor->GetData(); - size = compressor->GetSize(); - } - - return std::vector(data, data + size); -} - -template -void WIARVZFileReader::SetUpCompressor(std::unique_ptr* compressor, - WIARVZCompressionType compression_type, - int compression_level, WIAHeader2* header_2) -{ - switch (compression_type) - { - case WIARVZCompressionType::None: - *compressor = nullptr; - break; - case WIARVZCompressionType::Purge: - *compressor = std::make_unique(); - break; - case WIARVZCompressionType::Bzip2: - *compressor = std::make_unique(compression_level); - break; - case WIARVZCompressionType::LZMA: - case WIARVZCompressionType::LZMA2: - { - u8* compressor_data = nullptr; - u8* compressor_data_size = nullptr; - - if (header_2) - { - compressor_data = header_2->compressor_data; - compressor_data_size = &header_2->compressor_data_size; - } - - const bool lzma2 = compression_type == WIARVZCompressionType::LZMA2; - *compressor = std::make_unique(lzma2, compression_level, compressor_data, - compressor_data_size); - break; - } - case WIARVZCompressionType::Zstd: - *compressor = std::make_unique(compression_level); - break; - } -} - -template -bool WIARVZFileReader::TryReuse(std::map* reusable_groups, - std::mutex* reusable_groups_mutex, - OutputParametersEntry* entry) -{ - if (entry->reused_group) - return true; - - if (!entry->reuse_id) - return false; - - std::lock_guard guard(*reusable_groups_mutex); - const auto it = reusable_groups->find(*entry->reuse_id); - if (it == reusable_groups->end()) - return false; - - entry->reused_group = it->second; - return true; -} - -static bool AllAre(const std::vector& data, u8 x) -{ - return std::ranges::all_of(data, [x](u8 y) { return x == y; }); -} - -static bool AllAre(const u8* begin, const u8* end, u8 x) -{ - return std::all_of(begin, end, [x](u8 y) { return x == y; }); -} - -static bool AllZero(const std::vector& data) -{ - return AllAre(data, 0); -} - -static bool AllSame(const std::vector& data) -{ - return AllAre(data, data.front()); -} - -static bool AllSame(const u8* begin, const u8* end) -{ - return AllAre(begin, end, *begin); -} - -template -static void RVZPack(const u8* in, OutputParametersEntry* out, u64 bytes_per_chunk, size_t chunks, - u64 total_size, u64 data_offset, bool multipart, bool allow_junk_reuse, - bool compression, const FileSystem* file_system) -{ - using Seed = std::array; - struct JunkInfo - { - size_t start_offset; - Seed seed; - }; - - constexpr size_t SEED_SIZE = LaggedFibonacciGenerator::SEED_SIZE * sizeof(u32); - - // Maps end_offset -> (start_offset, seed) - std::map junk_info; - - size_t position = 0; - while (position < total_size) - { - // Skip the 0 to 32 zero bytes that typically come after a file - size_t zeroes = 0; - while (position + zeroes < total_size && in[position + zeroes] == 0) - ++zeroes; - - // If there are very many zero bytes (perhaps the PRNG junk data has been scrubbed?) - // and we aren't using compression, it makes sense to encode the zero bytes as junk. - // If we are using compression, the compressor will likely encode zeroes better than we can - if (!compression && zeroes > SEED_SIZE) - junk_info.emplace(position + zeroes, JunkInfo{position, {}}); - - position += zeroes; - data_offset += zeroes; - - const size_t bytes_to_read = - std::min(Common::AlignUp(data_offset + 1, VolumeWii::BLOCK_TOTAL_SIZE) - data_offset, - total_size - position); - - const size_t data_offset_mod = static_cast(data_offset % VolumeWii::BLOCK_TOTAL_SIZE); - - Seed seed; - const size_t bytes_reconstructed = LaggedFibonacciGenerator::GetSeed( - in + position, bytes_to_read, data_offset_mod, seed.data()); - - if (bytes_reconstructed > 0) - junk_info.emplace(position + bytes_reconstructed, JunkInfo{position, seed}); - - if (file_system) - { - const std::unique_ptr file_info = - file_system->FindFileInfo(data_offset + bytes_reconstructed); - - // If we're at a file and there's more space in this block after the file, - // continue after the file instead of skipping to the next block - if (file_info) - { - const u64 file_end_offset = file_info->GetOffset() + file_info->GetSize(); - if (file_end_offset < data_offset + bytes_to_read) - { - position += file_end_offset - data_offset; - data_offset = file_end_offset; - continue; - } - } - } - - position += bytes_to_read; - data_offset += bytes_to_read; - } - - for (size_t i = 0; i < chunks; ++i) - { - OutputParametersEntry& entry = out[i]; - if (entry.reused_group) - continue; - - u64 current_offset = i * bytes_per_chunk; - const u64 end_offset = std::min(current_offset + bytes_per_chunk, total_size); - - const bool store_junk_efficiently = allow_junk_reuse || !entry.reuse_id; - - // TODO: It would be possible to support skipping RVZ packing even when the chunk size is larger - // than 2 MiB (multipart == true), but it would be more effort than it's worth since Dolphin's - // converter doesn't expose chunk sizes larger than 2 MiB to the user anyway - bool first_loop_iteration = !multipart; - - while (current_offset < end_offset) - { - u64 next_junk_start = end_offset; - u64 next_junk_end = end_offset; - Seed* seed = nullptr; - if (store_junk_efficiently && end_offset - current_offset > SEED_SIZE) - { - const auto next_junk_it = junk_info.upper_bound(current_offset + SEED_SIZE); - if (next_junk_it != junk_info.end() && - next_junk_it->second.start_offset + SEED_SIZE < end_offset) - { - next_junk_start = std::max(current_offset, next_junk_it->second.start_offset); - next_junk_end = std::min(end_offset, next_junk_it->first); - seed = &next_junk_it->second.seed; - } - } - - if (first_loop_iteration) - { - if (next_junk_start == end_offset) - { - // Storing this chunk with RVZ packing would be inefficient, so store it without - PushBack(&entry.main_data, in + current_offset, in + end_offset); - break; - } - - first_loop_iteration = false; - } - - const u64 non_junk_bytes = next_junk_start - current_offset; - if (non_junk_bytes > 0) - { - const u8* ptr = in + current_offset; - - PushBack(&entry.main_data, Common::swap32(static_cast(non_junk_bytes))); - PushBack(&entry.main_data, ptr, ptr + non_junk_bytes); - - current_offset += non_junk_bytes; - entry.rvz_packed_size += sizeof(u32) + non_junk_bytes; - } - - const u64 junk_bytes = next_junk_end - current_offset; - if (junk_bytes > 0) - { - PushBack(&entry.main_data, Common::swap32(static_cast(junk_bytes) | 0x80000000)); - PushBack(&entry.main_data, *seed); - - current_offset += junk_bytes; - entry.rvz_packed_size += sizeof(u32) + SEED_SIZE; - } - } - } -} - -template -static void RVZPack(const u8* in, OutputParametersEntry* out, u64 size, u64 data_offset, - bool allow_junk_reuse, bool compression, const FileSystem* file_system) -{ - RVZPack(in, out, size, 1, size, data_offset, false, allow_junk_reuse, compression, file_system); -} - -template -ConversionResult::OutputParameters> -WIARVZFileReader::ProcessAndCompress(CompressThreadState* state, CompressParameters parameters, - const std::vector& partition_entries, - const std::vector& data_entries, - const FileSystem* file_system, - std::map* reusable_groups, - std::mutex* reusable_groups_mutex, - u64 chunks_per_wii_group, u64 exception_lists_per_chunk, - bool compressed_exception_lists, bool compression) -{ - std::vector output_entries; - - if (!parameters.data_entry->is_partition) - { - OutputParametersEntry& entry = output_entries.emplace_back(); - std::vector& data = parameters.data; - - if (AllSame(data)) - entry.reuse_id = ReuseID{WiiKey{}, data.size(), false, data.front()}; - - if constexpr (RVZ) - { - RVZPack(data.data(), output_entries.data(), data.size(), parameters.data_offset, true, - compression, file_system); - } - else - { - entry.main_data = std::move(data); - } - } - else - { - const PartitionEntry& partition_entry = partition_entries[parameters.data_entry->index]; - - auto aes_context = Common::AES::CreateContextDecrypt(partition_entry.partition_key.data()); - - const u64 groups = Common::AlignUp(parameters.data.size(), VolumeWii::GROUP_TOTAL_SIZE) / - VolumeWii::GROUP_TOTAL_SIZE; - - ASSERT(parameters.data.size() % VolumeWii::BLOCK_TOTAL_SIZE == 0); - const u64 blocks = parameters.data.size() / VolumeWii::BLOCK_TOTAL_SIZE; - - const u64 blocks_per_chunk = chunks_per_wii_group == 1 ? - exception_lists_per_chunk * VolumeWii::BLOCKS_PER_GROUP : - VolumeWii::BLOCKS_PER_GROUP / chunks_per_wii_group; - - const u64 chunks = Common::AlignUp(blocks, blocks_per_chunk) / blocks_per_chunk; - - const u64 in_data_per_chunk = blocks_per_chunk * VolumeWii::BLOCK_TOTAL_SIZE; - const u64 out_data_per_chunk = blocks_per_chunk * VolumeWii::BLOCK_DATA_SIZE; - - const size_t first_chunk = output_entries.size(); - - const auto create_reuse_id = [&partition_entry, blocks, - blocks_per_chunk](u8 value, bool encrypted, u64 block) { - const u64 size = std::min(blocks - block, blocks_per_chunk) * VolumeWii::BLOCK_DATA_SIZE; - return ReuseID{partition_entry.partition_key, size, encrypted, value}; - }; - - const u8* parameters_data_end = parameters.data.data() + parameters.data.size(); - for (u64 i = 0; i < chunks; ++i) - { - const u64 block_index = i * blocks_per_chunk; - - OutputParametersEntry& entry = output_entries.emplace_back(); - std::optional& reuse_id = entry.reuse_id; - - // Set this chunk as reusable if the encrypted data is AllSame - const u8* data = parameters.data.data() + block_index * VolumeWii::BLOCK_TOTAL_SIZE; - if (AllSame(data, std::min(parameters_data_end, data + in_data_per_chunk))) - reuse_id = create_reuse_id(parameters.data.front(), true, i * blocks_per_chunk); - - TryReuse(reusable_groups, reusable_groups_mutex, &entry); - if (!entry.reused_group && reuse_id) - { - const auto it = std::ranges::find(output_entries.begin(), output_entries.begin() + i, - reuse_id, &OutputParametersEntry::reuse_id); - if (it != output_entries.begin() + i) - entry.reused_group = it->reused_group; - } - } - - if (!std::ranges::all_of(output_entries, - [](const auto& entry) { return entry.reused_group.has_value(); })) - { - const u64 number_of_exception_lists = - chunks_per_wii_group == 1 ? exception_lists_per_chunk : chunks; - std::vector> exception_lists(number_of_exception_lists); - - for (u64 i = 0; i < groups; ++i) - { - const u64 offset_of_group = i * VolumeWii::GROUP_TOTAL_SIZE; - const u64 write_offset_of_group = i * VolumeWii::GROUP_DATA_SIZE; - - const u64 blocks_in_this_group = - std::min(VolumeWii::BLOCKS_PER_GROUP, blocks - i * VolumeWii::BLOCKS_PER_GROUP); - - for (u32 j = 0; j < VolumeWii::BLOCKS_PER_GROUP; ++j) - { - if (j < blocks_in_this_group) - { - const u64 offset_of_block = offset_of_group + j * VolumeWii::BLOCK_TOTAL_SIZE; - VolumeWii::DecryptBlockData(parameters.data.data() + offset_of_block, - state->decryption_buffer[j].data(), aes_context.get()); - } - else - { - state->decryption_buffer[j].fill(0); - } - } - - VolumeWii::HashGroup(state->decryption_buffer.data(), state->hash_buffer.data()); - - for (u64 j = 0; j < blocks_in_this_group; ++j) - { - const u64 chunk_index = j / blocks_per_chunk; - const u64 block_index_in_chunk = j % blocks_per_chunk; - - if (output_entries[chunk_index].reused_group) - continue; - - const u64 exception_list_index = chunks_per_wii_group == 1 ? i : chunk_index; - - const u64 offset_of_block = offset_of_group + j * VolumeWii::BLOCK_TOTAL_SIZE; - const u64 hash_offset_of_block = block_index_in_chunk * VolumeWii::BLOCK_HEADER_SIZE; - - VolumeWii::HashBlock hashes; - VolumeWii::DecryptBlockHashes(parameters.data.data() + offset_of_block, &hashes, - aes_context.get()); - - const auto compare_hash = [&](size_t offset_in_block) { - ASSERT(offset_in_block + Common::SHA1::DIGEST_LEN <= VolumeWii::BLOCK_HEADER_SIZE); - - const u8* desired_hash = reinterpret_cast(&hashes) + offset_in_block; - const u8* computed_hash = - reinterpret_cast(&state->hash_buffer[j]) + offset_in_block; - - // We want to store a hash exception either if there is a hash mismatch, or if this - // chunk might get reused in a context where it is paired up (within a 2 MiB Wii group) - // with chunks that are different from the chunks it currently is paired up with, since - // that affects the recalculated hashes. Chunks which have been marked as reusable at - // this point normally have zero matching hashes anyway, so this shouldn't waste space. - if ((chunks_per_wii_group != 1 && output_entries[chunk_index].reuse_id) || - !std::equal(desired_hash, desired_hash + Common::SHA1::DIGEST_LEN, computed_hash)) - { - const u64 hash_offset = hash_offset_of_block + offset_in_block; - ASSERT(hash_offset <= std::numeric_limits::max()); - - HashExceptionEntry& exception = exception_lists[exception_list_index].emplace_back(); - exception.offset = Common::swap16(static_cast(hash_offset)); - std::memcpy(exception.hash.data(), desired_hash, Common::SHA1::DIGEST_LEN); - } - }; - - const auto compare_hashes = [&compare_hash](size_t offset, size_t size) { - for (size_t l = 0; l < size; l += Common::SHA1::DIGEST_LEN) - // The std::min is to ensure that we don't go beyond the end of HashBlock with - // padding_2, which is 32 bytes long (not divisible by SHA1::DIGEST_LEN, which is 20). - compare_hash(offset + std::min(l, size - Common::SHA1::DIGEST_LEN)); - }; - - using HashBlock = VolumeWii::HashBlock; - compare_hashes(offsetof(HashBlock, h0), sizeof(HashBlock::h0)); - compare_hashes(offsetof(HashBlock, padding_0), sizeof(HashBlock::padding_0)); - compare_hashes(offsetof(HashBlock, h1), sizeof(HashBlock::h1)); - compare_hashes(offsetof(HashBlock, padding_1), sizeof(HashBlock::padding_1)); - compare_hashes(offsetof(HashBlock, h2), sizeof(HashBlock::h2)); - compare_hashes(offsetof(HashBlock, padding_2), sizeof(HashBlock::padding_2)); - } - - static_assert(std::is_trivially_copyable_v< - typename decltype(CompressThreadState::decryption_buffer)::value_type>); - if constexpr (RVZ) - { - // We must not store junk efficiently for chunks that may get reused at a position - // which has a different value of data_offset % VolumeWii::BLOCK_TOTAL_SIZE - const bool allow_junk_reuse = chunks_per_wii_group == 1; - - const u64 bytes_per_chunk = std::min(out_data_per_chunk, VolumeWii::GROUP_DATA_SIZE); - const u64 total_size = blocks_in_this_group * VolumeWii::BLOCK_DATA_SIZE; - const u64 data_offset = parameters.data_offset + write_offset_of_group; - - RVZPack(state->decryption_buffer[0].data(), output_entries.data() + first_chunk, - bytes_per_chunk, chunks, total_size, data_offset, groups > 1, allow_junk_reuse, - compression, file_system); - } - else - { - const u8* in_ptr = state->decryption_buffer[0].data(); - for (u64 j = 0; j < chunks; ++j) - { - OutputParametersEntry& entry = output_entries[first_chunk + j]; - - if (!entry.reused_group) - { - const u64 bytes_left = (blocks - j * blocks_per_chunk) * VolumeWii::BLOCK_DATA_SIZE; - const u64 bytes_to_write_total = std::min(out_data_per_chunk, bytes_left); - - if (i == 0) - entry.main_data.resize(bytes_to_write_total); - - const u64 bytes_to_write = std::min(bytes_to_write_total, VolumeWii::GROUP_DATA_SIZE); - - std::memcpy(entry.main_data.data() + write_offset_of_group, in_ptr, bytes_to_write); - - // Set this chunk as reusable if the decrypted data is AllSame. - // There is also a requirement that it lacks exceptions, but this is checked later - if (i == 0 && !entry.reuse_id) - { - if (AllSame(in_ptr, in_ptr + bytes_to_write)) - entry.reuse_id = create_reuse_id(*in_ptr, false, j * blocks_per_chunk); - } - else - { - if (entry.reuse_id && !entry.reuse_id->encrypted && - (!AllSame(in_ptr, in_ptr + bytes_to_write) || entry.reuse_id->value != *in_ptr)) - { - entry.reuse_id.reset(); - } - } - } - - in_ptr += out_data_per_chunk; - } - } - } - - for (size_t i = 0; i < exception_lists.size(); ++i) - { - OutputParametersEntry& entry = output_entries[chunks_per_wii_group == 1 ? 0 : i]; - if (entry.reused_group) - continue; - - const std::vector& in = exception_lists[i]; - std::vector& out = entry.exception_lists; - - const u16 exceptions = Common::swap16(static_cast(in.size())); - PushBack(&out, exceptions); - for (const HashExceptionEntry& exception : in) - PushBack(&out, exception); - } - - for (u64 i = 0; i < output_entries.size(); ++i) - { - OutputParametersEntry& entry = output_entries[i]; - - // If this chunk was set as reusable because the decrypted data is AllSame, - // but it has exceptions, unmark it as reusable - if (entry.reuse_id && !entry.reuse_id->encrypted && !AllZero(entry.exception_lists)) - entry.reuse_id.reset(); - } - } - } - - for (OutputParametersEntry& entry : output_entries) - { - TryReuse(reusable_groups, reusable_groups_mutex, &entry); - if (entry.reused_group) - continue; - - // Special case - a compressed size of zero is treated by WIA as meaning the data is all zeroes - if (entry.reuse_id && !entry.reuse_id->encrypted && entry.reuse_id->value == 0) - { - entry.exception_lists.clear(); - entry.main_data.clear(); - if constexpr (RVZ) - { - entry.rvz_packed_size = 0; - entry.compressed = false; - } - continue; - } - - const auto pad_exception_lists = [&entry] { - while (entry.exception_lists.size() % 4 != 0) - entry.exception_lists.push_back(0); - }; - - if (state->compressor) - { - if (!state->compressor->Start(entry.exception_lists.size() + entry.main_data.size())) - return std::unexpected{ConversionResultCode::InternalError}; - } - - if (!entry.exception_lists.empty()) - { - if (compressed_exception_lists && state->compressor) - { - if (!state->compressor->Compress(entry.exception_lists.data(), - entry.exception_lists.size())) - { - return std::unexpected{ConversionResultCode::InternalError}; - } - } - else - { - if (!compressed_exception_lists) - pad_exception_lists(); - - if (state->compressor) - { - if (!state->compressor->AddPrecedingDataOnlyForPurgeHashing(entry.exception_lists.data(), - entry.exception_lists.size())) - { - return std::unexpected{ConversionResultCode::InternalError}; - } - } - } - } - - if (state->compressor) - { - if (!state->compressor->Compress(entry.main_data.data(), entry.main_data.size())) - return std::unexpected{ConversionResultCode::InternalError}; - if (!state->compressor->End()) - return std::unexpected{ConversionResultCode::InternalError}; - } - - bool compressed = !!state->compressor; - if constexpr (RVZ) - { - size_t uncompressed_size = entry.main_data.size(); - if (compressed_exception_lists) - uncompressed_size += Common::AlignUp(entry.exception_lists.size(), 4); - - compressed = state->compressor && state->compressor->GetSize() < uncompressed_size; - entry.compressed = compressed; - - if (!compressed) - pad_exception_lists(); - } - - if (compressed) - { - const u8* data = state->compressor->GetData(); - const size_t size = state->compressor->GetSize(); - - entry.main_data.resize(size); - std::copy_n(data, size, entry.main_data.data()); - - if (compressed_exception_lists) - entry.exception_lists.clear(); - } - } - - return OutputParameters{std::move(output_entries), parameters.bytes_read, parameters.group_index}; -} - -template -ConversionResultCode WIARVZFileReader::Output(std::vector* entries, - File::DirectIOFile* outfile, - std::map* reusable_groups, - std::mutex* reusable_groups_mutex, - GroupEntry* group_entry, u64* bytes_written) -{ - for (OutputParametersEntry& entry : *entries) - { - TryReuse(reusable_groups, reusable_groups_mutex, &entry); - if (entry.reused_group) - { - *group_entry = *entry.reused_group; - ++group_entry; - continue; - } - - if (*bytes_written >> 2 > std::numeric_limits::max()) - return ConversionResultCode::InternalError; - - ASSERT((*bytes_written & 3) == 0); - group_entry->data_offset = Common::swap32(static_cast(*bytes_written >> 2)); - - u32 data_size = static_cast(entry.exception_lists.size() + entry.main_data.size()); - if constexpr (RVZ) - { - data_size = (data_size & 0x7FFFFFFF) | (static_cast(entry.compressed) << 31); - group_entry->rvz_packed_size = Common::swap32(static_cast(entry.rvz_packed_size)); - } - group_entry->data_size = Common::swap32(data_size); - - if (!outfile->Write(entry.exception_lists)) - return ConversionResultCode::WriteFailed; - if (!outfile->Write(entry.main_data)) - return ConversionResultCode::WriteFailed; - - *bytes_written += entry.exception_lists.size() + entry.main_data.size(); - - if (entry.reuse_id) - { - std::lock_guard guard(*reusable_groups_mutex); - reusable_groups->emplace(*entry.reuse_id, *group_entry); - } - - if (!PadTo4(outfile, bytes_written)) - return ConversionResultCode::WriteFailed; - - ++group_entry; - } - - return ConversionResultCode::Success; -} - -template -ConversionResultCode WIARVZFileReader::RunCallback(size_t groups_written, u64 bytes_read, - u64 bytes_written, u32 total_groups, - u64 iso_size, const CompressCB& callback) -{ - int ratio = 0; - if (bytes_read != 0) - ratio = static_cast(100 * bytes_written / bytes_read); - - const std::string text = Common::FmtFormatT("{0} of {1} blocks. Compression ratio {2}%", - groups_written, total_groups, ratio); - - const float completion = static_cast(bytes_read) / iso_size; - - return callback(text, completion) ? ConversionResultCode::Success : - ConversionResultCode::Canceled; -} - -template -bool WIARVZFileReader::WriteHeader(File::DirectIOFile* file, const u8* data, size_t size, - u64 upper_bound, u64* bytes_written, u64* offset_out) -{ - // The first part of the check is to prevent this from running more than once. If *bytes_written - // is past the upper bound, we are already at the end of the file, so we don't need to do anything - if (*bytes_written <= upper_bound && *bytes_written + size > upper_bound) - { - WARN_LOG_FMT(DISCIO, - "Headers did not fit in the allocated space. Writing to end of file instead"); - if (!file->Seek(0, File::SeekOrigin::End)) - return false; - *bytes_written = file->Tell(); - } - - *offset_out = *bytes_written; - if (!file->Write(data, size)) - return false; - *bytes_written += size; - return PadTo4(file, bytes_written); -} - -template -ConversionResultCode -WIARVZFileReader::Convert(BlobReader* infile, const VolumeDisc* infile_volume, - File::DirectIOFile* outfile, WIARVZCompressionType compression_type, - int compression_level, int chunk_size, CompressCB callback) -{ - ASSERT(infile->GetDataSizeType() == DataSizeType::Accurate); - ASSERT(chunk_size > 0); - - const u64 iso_size = infile->GetDataSize(); - const u64 chunks_per_wii_group = std::max(1, VolumeWii::GROUP_TOTAL_SIZE / chunk_size); - const u64 exception_lists_per_chunk = std::max(1, chunk_size / VolumeWii::GROUP_TOTAL_SIZE); - const bool compressed_exception_lists = compression_type > WIARVZCompressionType::Purge; - - u64 bytes_read = 0; - u64 bytes_written = 0; - size_t groups_processed = 0; - - WIAHeader1 header_1{}; - WIAHeader2 header_2{}; - - std::vector partition_entries; - std::vector raw_data_entries; - std::vector group_entries; - - u32 total_groups; - std::vector data_entries; - - const FileSystem* non_partition_file_system = - infile_volume ? infile_volume->GetFileSystem(PARTITION_NONE) : nullptr; - std::vector partition_file_systems; - - const ConversionResultCode set_up_data_entries_result = SetUpDataEntriesForWriting( - infile_volume, chunk_size, iso_size, &total_groups, &partition_entries, &raw_data_entries, - &data_entries, &partition_file_systems); - if (set_up_data_entries_result != ConversionResultCode::Success) - return set_up_data_entries_result; - - group_entries.resize(total_groups); - - const size_t partition_entries_size = partition_entries.size() * sizeof(PartitionEntry); - const size_t raw_data_entries_size = raw_data_entries.size() * sizeof(RawDataEntry); - const size_t group_entries_size = group_entries.size() * sizeof(GroupEntry); - - // An estimate for how much space will be taken up by headers. - // We will reserve this much space at the beginning of the file, and if the headers don't - // fit in that space, we will need to write them at the end of the file instead. - const u64 headers_size_upper_bound = [&] { - // 0x100 is added to account for compression overhead (in particular for Purge). - u64 upper_bound = sizeof(WIAHeader1) + sizeof(WIAHeader2) + partition_entries_size + - raw_data_entries_size + 0x100; - - // Compared to WIA, RVZ adds an extra member to the GroupEntry struct. This added data usually - // compresses well, so we'll assume the compression ratio for RVZ GroupEntries is 9 / 16 or - // better. This constant is somewhat arbitrarily chosen, but no games were found that get a - // worse compression ratio than that. There are some games that get a worse ratio than 1 / 2, - // such as Metroid: Other M (PAL) with the default settings. - if (RVZ && compression_type > WIARVZCompressionType::Purge) - upper_bound += static_cast(group_entries_size) * 9 / 16; - else - upper_bound += group_entries_size; - - // This alignment is also somewhat arbitrary. - return Common::AlignUp(upper_bound, VolumeWii::BLOCK_TOTAL_SIZE); - }(); - - std::vector buffer; - - buffer.resize(headers_size_upper_bound); - outfile->Write(buffer); - bytes_written = headers_size_upper_bound; - - if (!infile->Read(0, header_2.disc_header.size(), header_2.disc_header.data())) - return ConversionResultCode::ReadFailed; - // We intentionally do not increment bytes_read here, since these bytes will be read again - - std::map reusable_groups; - std::mutex reusable_groups_mutex; - - const auto set_up_compress_thread_state = [&](CompressThreadState* state) { - SetUpCompressor(&state->compressor, compression_type, compression_level, nullptr); - return ConversionResultCode::Success; - }; - - const auto process_and_compress = [&](CompressThreadState* state, CompressParameters parameters) { - const DataEntry& data_entry = *parameters.data_entry; - const FileSystem* file_system = data_entry.is_partition ? - partition_file_systems[data_entry.index] : - non_partition_file_system; - - const bool compression = compression_type != WIARVZCompressionType::None; - - return ProcessAndCompress(state, std::move(parameters), partition_entries, data_entries, - file_system, &reusable_groups, &reusable_groups_mutex, - chunks_per_wii_group, exception_lists_per_chunk, - compressed_exception_lists, compression); - }; - - const auto output = [&](OutputParameters parameters) { - const ConversionResultCode result = - Output(¶meters.entries, outfile, &reusable_groups, &reusable_groups_mutex, - &group_entries[parameters.group_index], &bytes_written); - - if (result != ConversionResultCode::Success) - return result; - - return RunCallback(parameters.group_index + parameters.entries.size(), parameters.bytes_read, - bytes_written, total_groups, iso_size, callback); - }; - - MultithreadedCompressor mt_compressor( - set_up_compress_thread_state, process_and_compress, output); - - for (const DataEntry& data_entry : data_entries) - { - u32 first_group; - u32 last_group; - - u64 data_offset; - u64 data_size; - - u64 data_offset_in_partition; - - if (data_entry.is_partition) - { - const PartitionEntry& partition_entry = partition_entries[data_entry.index]; - const PartitionDataEntry& partition_data_entry = - partition_entry.data_entries[data_entry.partition_data_index]; - - first_group = Common::swap32(partition_data_entry.group_index); - last_group = first_group + Common::swap32(partition_data_entry.number_of_groups); - - const u32 first_sector = Common::swap32(partition_data_entry.first_sector); - data_offset = first_sector * VolumeWii::BLOCK_TOTAL_SIZE; - data_size = - Common::swap32(partition_data_entry.number_of_sectors) * VolumeWii::BLOCK_TOTAL_SIZE; - - const u32 block_in_partition = - first_sector - Common::swap32(partition_entry.data_entries[0].first_sector); - data_offset_in_partition = block_in_partition * VolumeWii::BLOCK_DATA_SIZE; - } - else - { - const RawDataEntry& raw_data_entry = raw_data_entries[data_entry.index]; - - first_group = Common::swap32(raw_data_entry.group_index); - last_group = first_group + Common::swap32(raw_data_entry.number_of_groups); - - data_offset = Common::swap64(raw_data_entry.data_offset); - data_size = Common::swap64(raw_data_entry.data_size); - - const u64 skipped_data = data_offset % VolumeWii::BLOCK_TOTAL_SIZE; - data_offset -= skipped_data; - data_size += skipped_data; - - data_offset_in_partition = data_offset; - } - - ASSERT(groups_processed == first_group); - ASSERT(bytes_read == data_offset); - - while (groups_processed < last_group) - { - const ConversionResultCode status = mt_compressor.GetStatus(); - if (status != ConversionResultCode::Success) - return status; - - u64 bytes_to_read = chunk_size; - if (data_entry.is_partition) - bytes_to_read = std::max(bytes_to_read, VolumeWii::GROUP_TOTAL_SIZE); - bytes_to_read = std::min(bytes_to_read, data_offset + data_size - bytes_read); - - buffer.resize(bytes_to_read); - if (!infile->Read(bytes_read, bytes_to_read, buffer.data())) - return ConversionResultCode::ReadFailed; - bytes_read += bytes_to_read; - - mt_compressor.CompressAndWrite(CompressParameters{ - buffer, &data_entry, data_offset_in_partition, bytes_read, groups_processed}); - - data_offset += bytes_to_read; - data_size -= bytes_to_read; - - if (data_entry.is_partition) - { - data_offset_in_partition += - bytes_to_read / VolumeWii::BLOCK_TOTAL_SIZE * VolumeWii::BLOCK_DATA_SIZE; - } - else - { - data_offset_in_partition += bytes_to_read; - } - - groups_processed += Common::AlignUp(bytes_to_read, chunk_size) / chunk_size; - } - - ASSERT(data_size == 0); - } - - ASSERT(groups_processed == total_groups); - ASSERT(bytes_read == iso_size); - - mt_compressor.Shutdown(); - - const ConversionResultCode status = mt_compressor.GetStatus(); - if (status != ConversionResultCode::Success) - return status; - - std::unique_ptr compressor; - SetUpCompressor(&compressor, compression_type, compression_level, &header_2); - - const std::optional> compressed_raw_data_entries = Compress( - compressor.get(), reinterpret_cast(raw_data_entries.data()), raw_data_entries_size); - if (!compressed_raw_data_entries) - return ConversionResultCode::InternalError; - - const std::optional> compressed_group_entries = - Compress(compressor.get(), reinterpret_cast(group_entries.data()), group_entries_size); - if (!compressed_group_entries) - return ConversionResultCode::InternalError; - - bytes_written = sizeof(WIAHeader1) + sizeof(WIAHeader2); - if (!outfile->Seek(sizeof(WIAHeader1) + sizeof(WIAHeader2), File::SeekOrigin::Begin)) - return ConversionResultCode::WriteFailed; - - u64 partition_entries_offset; - if (!WriteHeader(outfile, reinterpret_cast(partition_entries.data()), partition_entries_size, - headers_size_upper_bound, &bytes_written, &partition_entries_offset)) - { - return ConversionResultCode::WriteFailed; - } - - u64 raw_data_entries_offset; - if (!WriteHeader(outfile, compressed_raw_data_entries->data(), - compressed_raw_data_entries->size(), headers_size_upper_bound, &bytes_written, - &raw_data_entries_offset)) - { - return ConversionResultCode::WriteFailed; - } - - u64 group_entries_offset; - if (!WriteHeader(outfile, compressed_group_entries->data(), compressed_group_entries->size(), - headers_size_upper_bound, &bytes_written, &group_entries_offset)) - { - return ConversionResultCode::WriteFailed; - } - - u32 disc_type = 0; - if (infile_volume) - { - if (infile_volume->GetVolumeType() == Platform::GameCubeDisc) - disc_type = 1; - else if (infile_volume->GetVolumeType() == Platform::WiiDisc) - disc_type = 2; - } - - header_2.disc_type = Common::swap32(disc_type); - header_2.compression_type = Common::swap32(static_cast(compression_type)); - header_2.compression_level = - static_cast(Common::swap32(static_cast(compression_level))); - header_2.chunk_size = Common::swap32(static_cast(chunk_size)); - - header_2.number_of_partition_entries = Common::swap32(static_cast(partition_entries.size())); - header_2.partition_entry_size = Common::swap32(sizeof(PartitionEntry)); - header_2.partition_entries_offset = Common::swap64(partition_entries_offset); - - header_2.partition_entries_hash = Common::SHA1::CalculateDigest(partition_entries); - - header_2.number_of_raw_data_entries = Common::swap32(static_cast(raw_data_entries.size())); - header_2.raw_data_entries_offset = Common::swap64(raw_data_entries_offset); - header_2.raw_data_entries_size = - Common::swap32(static_cast(compressed_raw_data_entries->size())); - - header_2.number_of_group_entries = Common::swap32(static_cast(group_entries.size())); - header_2.group_entries_offset = Common::swap64(group_entries_offset); - header_2.group_entries_size = Common::swap32(static_cast(compressed_group_entries->size())); - - header_1.magic = RVZ ? RVZ_MAGIC : WIA_MAGIC; - header_1.version = Common::swap32(RVZ ? RVZ_VERSION : WIA_VERSION); - header_1.version_compatible = - Common::swap32(RVZ ? RVZ_VERSION_WRITE_COMPATIBLE : WIA_VERSION_WRITE_COMPATIBLE); - header_1.header_2_size = Common::swap32(sizeof(WIAHeader2)); - header_1.header_2_hash = - Common::SHA1::CalculateDigest(reinterpret_cast(&header_2), sizeof(header_2)); - header_1.iso_file_size = Common::swap64(infile->GetDataSize()); - header_1.wia_file_size = Common::swap64(outfile->GetSize()); - header_1.header_1_hash = Common::SHA1::CalculateDigest(reinterpret_cast(&header_1), - offsetof(WIAHeader1, header_1_hash)); - - if (!outfile->Seek(0, File::SeekOrigin::Begin)) - return ConversionResultCode::WriteFailed; - - if (!outfile->Write(Common::AsU8Span(header_1))) - return ConversionResultCode::WriteFailed; - if (!outfile->Write(Common::AsU8Span(header_2))) - return ConversionResultCode::WriteFailed; - - return ConversionResultCode::Success; -} - -bool ConvertToWIAOrRVZ(BlobReader* infile, const std::string& infile_path, - const std::string& outfile_path, bool rvz, - WIARVZCompressionType compression_type, int compression_level, - int chunk_size, const CompressCB& callback) -{ - File::DirectIOFile outfile(outfile_path, File::AccessMode::Write); - if (!outfile.IsOpen()) - { - PanicAlertFmtT( - "Failed to open the output file \"{0}\".\n" - "Check that you have permissions to write the target folder and that the media can " - "be written.", - outfile_path); - return false; - } - - std::unique_ptr infile_volume = CreateDisc(infile_path); - - const auto convert = rvz ? RVZFileReader::Convert : WIAFileReader::Convert; - const ConversionResultCode result = - convert(infile, infile_volume.get(), &outfile, compression_type, compression_level, - chunk_size, callback); - - if (result == ConversionResultCode::ReadFailed) - PanicAlertFmtT("Failed to read from the input file \"{0}\".", infile_path); - - if (result == ConversionResultCode::WriteFailed) - { - PanicAlertFmtT("Failed to write the output file \"{0}\".\n" - "Check that you have enough space available on the target drive.", - outfile_path); - } - - if (result != ConversionResultCode::Success) - { - // Remove the incomplete output file - outfile.Close(); - File::Delete(outfile_path); - } - - return result == ConversionResultCode::Success; -} - template class WIARVZFileReader; template class WIARVZFileReader; diff --git a/Source/Core/DiscIO/WIABlob.h b/Source/Core/DiscIO/WIABlob.h index cb9acaa1a5..aa2bd50d58 100644 --- a/Source/Core/DiscIO/WIABlob.h +++ b/Source/Core/DiscIO/WIABlob.h @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -16,7 +15,6 @@ #include "Common/DirectIOFile.h" #include "Common/Swap.h" #include "DiscIO/Blob.h" -#include "DiscIO/MultithreadedCompressor.h" #include "DiscIO/WIACompression.h" #include "DiscIO/WiiEncryptionCache.h" @@ -35,8 +33,6 @@ enum class WIARVZCompressionType : u32 Zstd = 5, }; -std::pair GetAllowedCompressionLevels(WIARVZCompressionType compression_type, bool gui); - constexpr u32 WIA_MAGIC = 0x01414957; // "WIA\x1" (byteswapped to little endian) constexpr u32 RVZ_MAGIC = 0x015A5652; // "RVZ\x1" (byteswapped to little endian) @@ -67,11 +63,6 @@ public: bool SupportsReadWiiDecrypted(u64 offset, u64 size, u64 partition_data_offset) const override; bool ReadWiiDecrypted(u64 offset, u64 size, u8* out_ptr, u64 partition_data_offset) override; - static ConversionResultCode Convert(BlobReader* infile, const VolumeDisc* infile_volume, - File::DirectIOFile* outfile, - WIARVZCompressionType compression_type, int compression_level, - int chunk_size, CompressCB callback); - private: using WiiKey = std::array; @@ -247,122 +238,6 @@ private: static std::string VersionToString(u32 version); - struct ReuseID - { - bool operator==(const ReuseID& other) const - { - return std::tie(partition_key, data_size, encrypted, value) == - std::tie(other.partition_key, other.data_size, other.encrypted, other.value); - } - bool operator<(const ReuseID& other) const - { - return std::tie(partition_key, data_size, encrypted, value) < - std::tie(other.partition_key, other.data_size, other.encrypted, other.value); - } - bool operator>(const ReuseID& other) const - { - return std::tie(partition_key, data_size, encrypted, value) > - std::tie(other.partition_key, other.data_size, other.encrypted, other.value); - } - - bool operator>=(const ReuseID& other) const { return !operator<(other); } - bool operator<=(const ReuseID& other) const { return !operator>(other); } - - WiiKey partition_key; - u64 data_size; - bool encrypted; - u8 value; - }; - - struct CompressThreadState - { - using WiiBlockData = std::array; - - std::unique_ptr compressor; - - std::vector decryption_buffer = - std::vector(VolumeWii::BLOCKS_PER_GROUP); - - std::vector hash_buffer = - std::vector(VolumeWii::BLOCKS_PER_GROUP); - }; - - struct CompressParameters - { - std::vector data{}; - const DataEntry* data_entry = nullptr; - u64 data_offset = 0; - u64 bytes_read = 0; - size_t group_index = 0; - }; - - struct WIAOutputParametersEntry - { - std::vector exception_lists; - std::vector main_data; - std::optional reuse_id; - std::optional reused_group; - }; - - struct RVZOutputParametersEntry - { - std::vector exception_lists; - std::vector main_data; - std::optional reuse_id; - std::optional reused_group; - size_t rvz_packed_size = 0; - bool compressed = false; - }; - - using OutputParametersEntry = - std::conditional_t; - - struct OutputParameters - { - std::vector entries; - u64 bytes_read = 0; - size_t group_index = 0; - }; - - static bool PadTo4(File::DirectIOFile* file, u64* bytes_written); - static void AddRawDataEntry(u64 offset, u64 size, int chunk_size, u32* total_groups, - std::vector* raw_data_entries, - std::vector* data_entries); - static PartitionDataEntry - CreatePartitionDataEntry(u64 offset, u64 size, u32 index, int chunk_size, u32* total_groups, - const std::vector& partition_entries, - std::vector* data_entries); - static ConversionResultCode SetUpDataEntriesForWriting( - const VolumeDisc* volume, int chunk_size, u64 iso_size, u32* total_groups, - std::vector* partition_entries, std::vector* raw_data_entries, - std::vector* data_entries, std::vector* partition_file_systems); - static std::optional> Compress(Compressor* compressor, const u8* data, - size_t size); - static bool WriteHeader(File::DirectIOFile* file, const u8* data, size_t size, u64 upper_bound, - u64* bytes_written, u64* offset_out); - - static void SetUpCompressor(std::unique_ptr* compressor, - WIARVZCompressionType compression_type, int compression_level, - WIAHeader2* header_2); - static bool TryReuse(std::map* reusable_groups, - std::mutex* reusable_groups_mutex, OutputParametersEntry* entry); - static ConversionResult - ProcessAndCompress(CompressThreadState* state, CompressParameters parameters, - const std::vector& partition_entries, - const std::vector& data_entries, const FileSystem* file_system, - std::map* reusable_groups, - std::mutex* reusable_groups_mutex, u64 chunks_per_wii_group, - u64 exception_lists_per_chunk, bool compressed_exception_lists, - bool compression); - static ConversionResultCode Output(std::vector* entries, - File::DirectIOFile* outfile, - std::map* reusable_groups, - std::mutex* reusable_groups_mutex, GroupEntry* group_entry, - u64* bytes_written); - static ConversionResultCode RunCallback(size_t groups_written, u64 bytes_read, u64 bytes_written, - u32 total_groups, u64 iso_size, - const CompressCB& callback); - bool m_valid; WIARVZCompressionType m_compression_type; diff --git a/Source/Core/DolphinTool/ConvertCommand.cpp b/Source/Core/DolphinTool/ConvertCommand.cpp deleted file mode 100644 index a590c3071c..0000000000 --- a/Source/Core/DolphinTool/ConvertCommand.cpp +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright 2021 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "DolphinTool/ConvertCommand.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "Common/CommonTypes.h" -#include "DiscIO/Blob.h" -#include "DiscIO/DiscUtils.h" -#include "DiscIO/ScrubbedBlob.h" -#include "DiscIO/Volume.h" -#include "DiscIO/WIABlob.h" -#include "UICommon/UICommon.h" - -namespace DolphinTool -{ -static std::optional -ParseCompressionTypeString(const std::string& compression_str) -{ - if (compression_str == "none") - return DiscIO::WIARVZCompressionType::None; - else if (compression_str == "purge") - return DiscIO::WIARVZCompressionType::Purge; - else if (compression_str == "bzip2") - return DiscIO::WIARVZCompressionType::Bzip2; - else if (compression_str == "lzma") - return DiscIO::WIARVZCompressionType::LZMA; - else if (compression_str == "lzma2") - return DiscIO::WIARVZCompressionType::LZMA2; - else if (compression_str == "zstd") - return DiscIO::WIARVZCompressionType::Zstd; - return std::nullopt; -} - -static std::optional ParseFormatString(const std::string& format_str) -{ - if (format_str == "iso") - return DiscIO::BlobType::PLAIN; - else if (format_str == "gcz") - return DiscIO::BlobType::GCZ; - else if (format_str == "wia") - return DiscIO::BlobType::WIA; - else if (format_str == "rvz") - return DiscIO::BlobType::RVZ; - return std::nullopt; -} - -int ConvertCommand(const std::vector& args) -{ - optparse::OptionParser parser; - - parser.usage("usage: convert [options]... [FILE]..."); - - parser.add_option("-u", "--user") - .type("string") - .action("store") - .help("User folder path, required for temporary processing files. " - "Will be automatically created if this option is not set.") - .set_default(""); - - parser.add_option("-i", "--input") - .type("string") - .action("store") - .help("Path to disc image FILE.") - .metavar("FILE"); - - parser.add_option("-o", "--output") - .type("string") - .action("store") - .help("Path to the destination FILE.") - .metavar("FILE"); - - parser.add_option("-f", "--format") - .type("string") - .action("store") - .help("Container format to use. Default is RVZ. [%choices]") - .choices({"iso", "gcz", "wia", "rvz"}); - - parser.add_option("-s", "--scrub") - .action("store_true") - .help("Scrub junk data as part of conversion."); - - parser.add_option("-b", "--block_size") - .type("int") - .action("store") - .help("Block size for GCZ/WIA/RVZ formats, as an integer. Suggested value for RVZ: 131072 " - "(128 KiB)"); - - parser.add_option("-c", "--compression") - .type("string") - .action("store") - .help("Compression method to use when converting to WIA/RVZ. Suggested value for RVZ: zstd " - "[%choices]") - .choices({"none", "zstd", "bzip2", "lzma", "lzma2"}); - - parser.add_option("-l", "--compression_level") - .type("int") - .action("store") - .help("Level of compression for the selected method. Ignored if 'none'. Suggested value for " - "zstd: 5"); - - const optparse::Values& options = parser.parse_args(args); - - // Initialize the dolphin user directory, required for temporary processing files - // If this is not set, destructive file operations could occur due to path confusion - UICommon::SetUserDirectory(options["user"]); - UICommon::Init(); - - // Validate options - - // --input - if (!options.is_set("input")) - { - fmt::print(std::cerr, "Error: No input set\n"); - return EXIT_FAILURE; - } - const std::string& input_file_path = options["input"]; - - // --output - if (!options.is_set("output")) - { - fmt::print(std::cerr, "Error: No output set\n"); - return EXIT_FAILURE; - } - const std::string& output_file_path = options["output"]; - - // --format - const std::optional format_o = ParseFormatString(options["format"]); - if (!format_o.has_value()) - { - fmt::print(std::cerr, "Error: No output format set\n"); - return EXIT_FAILURE; - } - const DiscIO::BlobType format = format_o.value(); - - // Open the blob reader - std::unique_ptr blob_reader = DiscIO::CreateBlobReader(input_file_path); - if (!blob_reader) - { - fmt::print(std::cerr, "Error: The input file could not be opened.\n"); - return EXIT_FAILURE; - } - - // --scrub - const bool scrub = static_cast(options.get("scrub")); - - // Open the volume - const std::unique_ptr volume = DiscIO::CreateDisc(input_file_path); - if (!volume) - { - if (scrub) - { - fmt::print(std::cerr, "Error: Scrubbing is only supported for GC/Wii disc images.\n"); - return EXIT_FAILURE; - } - - fmt::print(std::cerr, - "Warning: The input file is not a GC/Wii disc image. Continuing anyway.\n"); - } - - if (scrub) - { - if (volume->IsDatelDisc()) - { - fmt::print(std::cerr, "Error: Scrubbing a Datel disc is not supported.\n"); - return EXIT_FAILURE; - } - - blob_reader = DiscIO::ScrubbedBlob::Create(input_file_path); - - if (!blob_reader) - { - fmt::print(std::cerr, "Error: Unable to process disc image. Try again without --scrub.\n"); - return EXIT_FAILURE; - } - } - - if (scrub && format == DiscIO::BlobType::RVZ) - { - fmt::print(std::cerr, "Warning: Scrubbing an RVZ container does not offer significant space " - "advantages. Continuing anyway.\n"); - } - - if (scrub && format == DiscIO::BlobType::PLAIN) - { - fmt::print(std::cerr, "Warning: Scrubbing does not save space when converting to ISO unless " - "using external compression. Continuing anyway.\n"); - } - - if (!scrub && format == DiscIO::BlobType::GCZ && volume && - volume->GetVolumeType() == DiscIO::Platform::WiiDisc && !volume->IsDatelDisc()) - { - fmt::print(std::cerr, "Warning: Converting Wii disc images to GCZ without scrubbing may not " - "offer space advantages over ISO. Continuing anyway.\n"); - } - - if (volume && volume->IsNKit()) - { - fmt::print(std::cerr, - "Warning: Converting an NKit file, output will still be NKit! Continuing anyway.\n"); - } - - // --block_size - std::optional block_size_o; - if (options.is_set("block_size")) - block_size_o = static_cast(options.get("block_size")); - - if (format == DiscIO::BlobType::GCZ || format == DiscIO::BlobType::WIA || - format == DiscIO::BlobType::RVZ) - { - if (!block_size_o.has_value()) - { - fmt::print(std::cerr, "Error: Block size must be set for GCZ/RVZ/WIA\n"); - return EXIT_FAILURE; - } - - if (!DiscIO::IsDiscImageBlockSizeValid(block_size_o.value(), format)) - { - fmt::print(std::cerr, "Error: Block size is not valid for this format\n"); - return EXIT_FAILURE; - } - - if (block_size_o.value() < DiscIO::PREFERRED_MIN_BLOCK_SIZE || - block_size_o.value() > DiscIO::PREFERRED_MAX_BLOCK_SIZE) - { - fmt::print(std::cerr, - "Warning: Block size is not ideal for performance. Continuing anyway.\n"); - } - - if (format == DiscIO::BlobType::GCZ && volume && - !DiscIO::IsGCZBlockSizeLegacyCompatible(block_size_o.value(), volume->GetDataSize())) - { - fmt::print(std::cerr, - "Warning: For GCZs to be compatible with Dolphin < 5.0-11893, the file size " - "must be an integer multiple of the block size and must not be an integer " - "multiple of the block size multiplied by 32. Continuing anyway.\n"); - } - } - - // --compress, --compress_level - const std::optional compression_o = - ParseCompressionTypeString(options["compression"]); - - std::optional compression_level_o; - if (options.is_set("compression_level")) - compression_level_o = static_cast(options.get("compression_level")); - - if (format == DiscIO::BlobType::WIA || format == DiscIO::BlobType::RVZ) - { - if (!compression_o.has_value()) - { - fmt::print(std::cerr, "Error: Compression method must be set for WIA or RVZ\n"); - return EXIT_FAILURE; - } - - if ((format == DiscIO::BlobType::WIA && - compression_o.value() == DiscIO::WIARVZCompressionType::Zstd) || - (format == DiscIO::BlobType::RVZ && - compression_o.value() == DiscIO::WIARVZCompressionType::Purge)) - { - fmt::print(std::cerr, "Error: Compression type is not supported for the container format\n"); - return EXIT_FAILURE; - } - - if (compression_o.value() == DiscIO::WIARVZCompressionType::None) - { - compression_level_o = 0; - } - else - { - if (!compression_level_o.has_value()) - { - fmt::print(std::cerr, - "Error: Compression level must be set when compression type is not 'none'\n"); - return EXIT_FAILURE; - } - - const std::pair range = - DiscIO::GetAllowedCompressionLevels(compression_o.value(), false); - if (compression_level_o.value() < range.first || compression_level_o.value() > range.second) - { - fmt::print(std::cerr, "Error: Compression level not in acceptable range\n"); - return EXIT_FAILURE; - } - } - } - - // Perform the conversion - const auto NOOP_STATUS_CALLBACK = [](const std::string& text, float percent) { return true; }; - - bool success = false; - - switch (format) - { - case DiscIO::BlobType::PLAIN: - { - success = DiscIO::ConvertToPlain(blob_reader.get(), input_file_path, output_file_path, - NOOP_STATUS_CALLBACK); - break; - } - - case DiscIO::BlobType::GCZ: - { - u32 sub_type = std::numeric_limits::max(); - if (volume) - { - if (volume->GetVolumeType() == DiscIO::Platform::GameCubeDisc) - sub_type = 0; - else if (volume->GetVolumeType() == DiscIO::Platform::WiiDisc) - sub_type = 1; - } - success = DiscIO::ConvertToGCZ(blob_reader.get(), input_file_path, output_file_path, sub_type, - block_size_o.value(), NOOP_STATUS_CALLBACK); - break; - } - - case DiscIO::BlobType::WIA: - case DiscIO::BlobType::RVZ: - { - success = DiscIO::ConvertToWIAOrRVZ(blob_reader.get(), input_file_path, output_file_path, - format == DiscIO::BlobType::RVZ, compression_o.value(), - compression_level_o.value(), block_size_o.value(), - NOOP_STATUS_CALLBACK); - break; - } - - default: - { - ASSERT(false); - break; - } - } - - if (!success) - { - fmt::print(std::cerr, "Error: Conversion failed\n"); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} -} // namespace DolphinTool diff --git a/Source/Core/DolphinTool/ConvertCommand.h b/Source/Core/DolphinTool/ConvertCommand.h deleted file mode 100644 index 9248df329c..0000000000 --- a/Source/Core/DolphinTool/ConvertCommand.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2021 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -namespace DolphinTool -{ -int ConvertCommand(const std::vector& args); -} // namespace DolphinTool diff --git a/Source/Core/DolphinTool/DolphinTool.exe.manifest b/Source/Core/DolphinTool/DolphinTool.exe.manifest deleted file mode 100644 index 8239320153..0000000000 --- a/Source/Core/DolphinTool/DolphinTool.exe.manifest +++ /dev/null @@ -1,27 +0,0 @@ - - - -Dolphin Tool - - - - - - - - - - - \ No newline at end of file diff --git a/Source/Core/DolphinTool/DolphinTool.filters b/Source/Core/DolphinTool/DolphinTool.filters deleted file mode 100644 index 0ee07add9f..0000000000 --- a/Source/Core/DolphinTool/DolphinTool.filters +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Source/Core/DolphinTool/DolphinTool.rc b/Source/Core/DolphinTool/DolphinTool.rc deleted file mode 100644 index b1471b1760..0000000000 --- a/Source/Core/DolphinTool/DolphinTool.rc +++ /dev/null @@ -1,6 +0,0 @@ -// Microsoft Visual C++ generated resource script. -// -#include "resource.h" -IDI_ICON1 ICON "..\\..\\..\\Installer\\Dolphin.ico" -"dolphin" ICON "..\\..\\..\\Installer\\Dolphin.ico" - diff --git a/Source/Core/DolphinTool/ExtractCommand.cpp b/Source/Core/DolphinTool/ExtractCommand.cpp deleted file mode 100644 index 57106d6406..0000000000 --- a/Source/Core/DolphinTool/ExtractCommand.cpp +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright 2024 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "DolphinTool/ExtractCommand.h" - -#include -#include - -#include -#include - -#include - -#include "Common/FileUtil.h" - -#include "DiscIO/DiscExtractor.h" -#include "DiscIO/DiscUtils.h" -#include "DiscIO/Filesystem.h" -#include "DiscIO/Volume.h" - -namespace DolphinTool -{ -static void ExtractFile(const DiscIO::Volume& disc_volume, const DiscIO::Partition& partition, - const std::string& path, const std::string& out) -{ - const DiscIO::FileSystem* filesystem = disc_volume.GetFileSystem(partition); - if (!filesystem) - return; - - ExportFile(disc_volume, partition, filesystem->FindFileInfo(path).get(), out); -} - -static std::unique_ptr GetFileInfo(const DiscIO::Volume& disc_volume, - const DiscIO::Partition& partition, - const std::string& path) -{ - const DiscIO::FileSystem* filesystem = disc_volume.GetFileSystem(partition); - if (!filesystem) - return nullptr; - - return filesystem->FindFileInfo(path); -} - -static bool VolumeSupported(const DiscIO::Volume& disc_volume) -{ - switch (disc_volume.GetVolumeType()) - { - case DiscIO::Platform::WiiWAD: - fmt::println(std::cerr, "Error: Wii WADs are not supported."); - return false; - case DiscIO::Platform::ELFOrDOL: - fmt::println(std::cerr, - "Error: *.elf or *.dol have no filesystem and are therefore not supported."); - return false; - case DiscIO::Platform::WiiDisc: - case DiscIO::Platform::GameCubeDisc: - return true; - default: - fmt::println(std::cerr, "Error: Unknown volume type."); - return false; - } -} - -static void ExtractDirectory(const DiscIO::Volume& disc_volume, const DiscIO::Partition& partition, - const std::string& path, const std::string& out, bool quiet) -{ - const DiscIO::FileSystem* filesystem = disc_volume.GetFileSystem(partition); - if (!filesystem) - return; - - const std::unique_ptr info = filesystem->FindFileInfo(path); - u32 size = info->GetTotalChildren(); - u32 files = 0; - ExportDirectory( - disc_volume, partition, *info, true, "", out, - [&files, &size, &quiet](const std::string& current) { - files++; - const float progress = static_cast(files) / static_cast(size) * 100; - if (!quiet) - fmt::println(std::cerr, "Extracting: {} | {}%", current, static_cast(progress)); - return false; - }); -} - -static bool ExtractSystemData(const DiscIO::Volume& disc_volume, const DiscIO::Partition& partition, - const std::string& out) -{ - return ExportSystemData(disc_volume, partition, out); -} - -static void ExtractPartition(const DiscIO::Volume& disc_volume, const DiscIO::Partition& partition, - const std::string& out, bool quiet) -{ - ExtractDirectory(disc_volume, partition, "", out + "/files", quiet); - ExtractSystemData(disc_volume, partition, out); -} - -static void ListRecursively(const std::string& path, const DiscIO::FileInfo& info, - std::string* result_text) -{ - // Don't print the root. - if (!path.empty()) - { - const std::string line = fmt::format("{}\n", path); - fmt::print("{}", line); - result_text->append(line); - } - for (const DiscIO::FileInfo& child_info : info) - { - std::string child_path = path + child_info.GetName(); - if (child_info.IsDirectory()) - child_path += '/'; - ListRecursively(child_path, child_info, result_text); - } -} - -static bool ListPartition(const DiscIO::Volume& disc_volume, const DiscIO::Partition& partition, - const std::string& partition_name, const std::string& path, - std::string* result_text) -{ - const DiscIO::FileSystem* filesystem = disc_volume.GetFileSystem(partition); - if (!filesystem) - { - fmt::println(std::cerr, "Warning: partition {} has no filesystem.", partition_name); - return false; - } - const std::unique_ptr info = filesystem->FindFileInfo(path); - - if (!info) - { - if (!partition_name.empty()) - { - fmt::println(std::cerr, "Warning: {} does not exist in this partition.", path); - } - return false; - } - - // Canonicalize user-provided path by reconstructing it using GetPath(). - ListRecursively(info->GetPath(), *info, result_text); - return true; -} - -static bool ListVolume(const DiscIO::Volume& disc_volume, const std::string& path, - const std::string& specific_partition_name, bool quiet, - std::string* result_text) -{ - if (disc_volume.GetPartitions().empty()) - { - return ListPartition(disc_volume, DiscIO::PARTITION_NONE, specific_partition_name, path, - result_text); - } - - bool success = false; - for (DiscIO::Partition& p : disc_volume.GetPartitions()) - { - const std::optional partition_type = disc_volume.GetPartitionType(p); - if (!partition_type) - { - fmt::println(std::cerr, "Error: Could not get partition type."); - return false; - } - const std::string partition_name = DiscIO::NameForPartitionType(*partition_type, true); - - if (!specific_partition_name.empty() && - !Common::CaseInsensitiveEquals(partition_name, specific_partition_name)) - { - continue; - } - - const std::string partition_start = - fmt::format("/// PARTITION: {} <{}> ///\n", partition_name, path); - fmt::print(std::cout, "{}", partition_start); - result_text->append(partition_start); - - success |= ListPartition(disc_volume, p, partition_name, path, result_text); - } - - return success; -} - -static bool HandleExtractPartition(const std::string& output, const std::string& single_file_path, - const std::string& partition_name, - const DiscIO::Volume& disc_volume, - const DiscIO::Partition& partition, bool quiet, bool single) -{ - std::string file; - file.append(output).append("/"); - file.append(partition_name).append("/"); - if (!single) - { - ExtractPartition(disc_volume, partition, file, quiet); - return true; - } - - const auto file_info = GetFileInfo(disc_volume, partition, single_file_path); - if (file_info != nullptr) - { - file.append("files/").append(single_file_path); - File::CreateFullPath(file); - if (file_info->IsDirectory()) - { - file = PathToString(StringToPath(file).remove_filename()); - ExtractDirectory(disc_volume, partition, single_file_path, file, quiet); - } - else - { - ExtractFile(disc_volume, partition, single_file_path, file); - } - - return true; - } - return false; -} - -int Extract(const std::vector& args) -{ - optparse::OptionParser parser; - - parser.usage("usage: extract [options]..."); - - parser.add_option("-i", "--input") - .type("string") - .action("store") - .help("Path to disc image FILE.") - .metavar("FILE"); - parser.add_option("-o", "--output") - .type("string") - .action("store") - .help("Path to the destination FOLDER.") - .metavar("FOLDER"); - parser.add_option("-p", "--partition") - .type("string") - .action("store") - .help("Which specific partition you want to extract."); - parser.add_option("-s", "--single") - .type("string") - .action("store") - .help("Which specific file/directory you want to extract."); - parser.add_option("-l", "--list") - .action("store_true") - .help("List all files in volume/partition. Will print the directory/file specified with " - "--single if defined."); - parser.add_option("-q", "--quiet") - .action("store_true") - .help("Mute all messages except for errors."); - parser.add_option("-g", "--gameonly") - .action("store_true") - .help("Only extracts the DATA partition."); - - const optparse::Values& options = parser.parse_args(args); - - const bool quiet = options.is_set("quiet"); - const bool gameonly = options.is_set("gameonly"); - - if (!options.is_set("input")) - { - fmt::println(std::cerr, "Error: No input image set"); - return EXIT_FAILURE; - } - const std::string& input_file_path = options["input"]; - - const std::string& output_folder_path = options["output"]; - - if (!options.is_set("output") && !options.is_set("list")) - { - fmt::println(std::cerr, "Error: No output folder set"); - return EXIT_FAILURE; - } - - const std::string& single_file_path = options["single"]; - std::string specific_partition = options["partition"]; - - if (options.is_set("output") && !options.is_set("list")) - File::CreateDirs(output_folder_path); - - if (gameonly) - specific_partition = std::string("data"); - - if (const std::unique_ptr blob_reader = - DiscIO::CreateBlobReader(input_file_path); - !blob_reader) - { - fmt::println(std::cerr, "Error: Unable to open disc image"); - return EXIT_FAILURE; - } - - const std::unique_ptr disc_volume = DiscIO::CreateVolume(input_file_path); - - if (!disc_volume) - { - fmt::println(std::cerr, "Error: Unable to open volume"); - return EXIT_FAILURE; - } - - if (!VolumeSupported(*disc_volume)) - return EXIT_FAILURE; - - if (options.is_set("list")) - { - std::string list_path = options.is_set("single") ? single_file_path : "/"; - if (quiet && !options.is_set("output")) - { - fmt::println(std::cerr, "Error: --quiet is set but no output file provided. Please either " - "remove the --quiet flag or specify --output"); - return EXIT_FAILURE; - } - - std::string text; - if (!ListVolume(*disc_volume, list_path, specific_partition, quiet, &text)) - { - fmt::println(std::cerr, "Error: Found nothing to list"); - return EXIT_FAILURE; - } - - if (options.is_set("output")) - { - File::CreateFullPath(output_folder_path); - std::ofstream output_file; - output_file.open(output_folder_path); - if (!output_file.is_open()) - { - fmt::println(std::cerr, "Error: Unable to open output file"); - return EXIT_FAILURE; - } - output_file << text; - } - - return EXIT_SUCCESS; - } - - bool extracted_one = false; - - if (disc_volume->GetPartitions().empty()) - { - if (options.is_set("partition")) - { - fmt::println( - std::cerr, - "Warning: --partition has a value even though this image doesn't have any partitions."); - } - - extracted_one = HandleExtractPartition(output_folder_path, single_file_path, "", *disc_volume, - DiscIO::PARTITION_NONE, quiet, options.is_set("single")); - } - else - { - for (DiscIO::Partition& p : disc_volume->GetPartitions()) - { - if (const std::optional partition_type = disc_volume->GetPartitionType(p)) - { - const std::string partition_name = DiscIO::NameForPartitionType(*partition_type, true); - - if (!specific_partition.empty() && - !Common::CaseInsensitiveEquals(specific_partition, partition_name)) - { - continue; - } - - extracted_one |= - HandleExtractPartition(output_folder_path, single_file_path, partition_name, - *disc_volume, p, quiet, options.is_set("single")); - } - } - } - - if (!extracted_one) - { - if (options.is_set("single")) - fmt::print(std::cerr, "Error: No file/folder was extracted."); - else - fmt::print(std::cerr, "Error: No partitions were extracted."); - if (options.is_set("partition")) - fmt::println(std::cerr, " Maybe you misspelled your specified partition?"); - fmt::println(std::cerr, "\n"); - return EXIT_FAILURE; - } - - if (!quiet) - fmt::println(std::cerr, "Finished Successfully!"); - return EXIT_SUCCESS; -} -} // namespace DolphinTool diff --git a/Source/Core/DolphinTool/ExtractCommand.h b/Source/Core/DolphinTool/ExtractCommand.h deleted file mode 100644 index c69ffdf386..0000000000 --- a/Source/Core/DolphinTool/ExtractCommand.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2024 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -namespace DolphinTool -{ -int Extract(const std::vector& args); -} // namespace DolphinTool diff --git a/Source/Core/DolphinTool/HeaderCommand.cpp b/Source/Core/DolphinTool/HeaderCommand.cpp deleted file mode 100644 index 71e4772cba..0000000000 --- a/Source/Core/DolphinTool/HeaderCommand.cpp +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2022 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "DolphinTool/HeaderCommand.h" - -#include -#include -#include -#include - -#include -#include -#include - -#include "DiscIO/Blob.h" -#include "DiscIO/Volume.h" - -namespace DolphinTool -{ -int HeaderCommand(const std::vector& args) -{ - optparse::OptionParser parser; - - parser.usage("usage: header [options]..."); - - parser.add_option("-i", "--input") - .type("string") - .action("store") - .help("Path to disc image FILE.") - .metavar("FILE"); - - parser.add_option("-j", "--json") - .action("store_true") - .help("Optional. Print the information as JSON, then exit. Overrides other print options."); - - parser.add_option("-b", "--block_size") - .action("store_true") - .help("Optional. Print the block size of GCZ/WIA/RVZ formats, then exit."); - - parser.add_option("-c", "--compression") - .action("store_true") - .help("Optional. Print the compression method of GCZ/WIA/RVZ formats, then exit."); - - parser.add_option("-l", "--compression_level") - .action("store_true") - .help("Optional. Print the level of compression for WIA/RVZ formats, then exit."); - - const optparse::Values& options = parser.parse_args(args); - - // Validate options - const std::string& input_file_path = options["input"]; - if (input_file_path.empty()) - { - fmt::print(std::cerr, "Error: No input set\n"); - return EXIT_FAILURE; - } - - const bool enable_json = options.is_set_by_user("json"); - const bool enable_block_size = options.is_set_by_user("block_size"); - const bool enable_compression_method = options.is_set_by_user("compression"); - const bool enable_compression_level = options.is_set_by_user("compression_level"); - - // Open the blob reader - const std::unique_ptr blob_reader = DiscIO::CreateBlobReader(input_file_path); - if (!blob_reader) - { - fmt::print(std::cerr, "Error: Unable to open disc image\n"); - return EXIT_FAILURE; - } - // Open the volume - const std::unique_ptr volume = DiscIO::CreateVolume(blob_reader->CopyReader()); - - if (enable_json) - { - auto json = picojson::object(); - - // File data - if (const u64 block_size = blob_reader->GetBlockSize()) - json["block_size"] = picojson::value((double)block_size); - - const std::string compression_method = blob_reader->GetCompressionMethod(); - if (compression_method != "") - json["compression_method"] = picojson::value(compression_method); - - if (const std::optional compression_level = blob_reader->GetCompressionLevel()) - json["compression_level"] = picojson::value((double)compression_level.value()); - - // Game data - if (volume) - { - json["internal_name"] = picojson::value(volume->GetInternalName()); - - if (const std::optional revision = volume->GetRevision()) - json["revision"] = picojson::value((double)revision.value()); - - json["game_id"] = picojson::value(volume->GetGameID()); - - if (const std::optional title_id = volume->GetTitleID()) - json["title_id"] = picojson::value((double)title_id.value()); - - json["region"] = picojson::value(DiscIO::GetName(volume->GetRegion(), false)); - - json["country"] = picojson::value(DiscIO::GetName(volume->GetCountry(), false)); - } - - // Print - std::cout << picojson::value(json) << '\n'; - } - else if (enable_block_size || enable_compression_method || enable_compression_level) - { - if (enable_block_size) - { - const auto block_size = blob_reader->GetBlockSize(); - if (block_size == 0) - fmt::print(std::cout, "N/A\n"); - else - fmt::print(std::cout, "{}\n", block_size); - } - if (enable_compression_method) - { - const auto compression_method = blob_reader->GetCompressionMethod(); - if (compression_method == "") - fmt::print(std::cout, "N/A\n"); - else - fmt::print(std::cout, "{}\n", compression_method); - } - if (enable_compression_level) - { - const auto compression_level_o = blob_reader->GetCompressionLevel(); - if (compression_level_o == std::nullopt) - fmt::print(std::cout, "N/A\n"); - else - fmt::print(std::cout, "{}\n", compression_level_o.value()); - } - } - else - { - // File data - if (const u64 block_size = blob_reader->GetBlockSize()) - fmt::print(std::cout, "Block Size: {}\n", block_size); - - const std::string compression_method = blob_reader->GetCompressionMethod(); - if (compression_method != "") - fmt::print(std::cout, "Compression Method: {}\n", compression_method); - - if (const std::optional compression_level = blob_reader->GetCompressionLevel()) - fmt::print(std::cout, "Compression Level: {}\n", compression_level.value()); - - // Game data - if (volume) - { - fmt::print(std::cout, "Internal Name: {}\n", volume->GetInternalName()); - - if (const std::optional revision = volume->GetRevision()) - fmt::print(std::cout, "Revision: {}\n", revision.value()); - - fmt::print(std::cout, "Game ID: {}\n", volume->GetGameID()); - - if (const std::optional title_id = volume->GetTitleID()) - fmt::print(std::cout, "Title ID: {:016x}\n", title_id.value()); - - fmt::print(std::cout, "Region: {}\n", DiscIO::GetName(volume->GetRegion(), false)); - - fmt::print(std::cout, "Country: {}\n", DiscIO::GetName(volume->GetCountry(), false)); - } - } - - return EXIT_SUCCESS; -} -} // namespace DolphinTool diff --git a/Source/Core/DolphinTool/HeaderCommand.h b/Source/Core/DolphinTool/HeaderCommand.h deleted file mode 100644 index 6b091af906..0000000000 --- a/Source/Core/DolphinTool/HeaderCommand.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2022 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -namespace DolphinTool -{ -int HeaderCommand(const std::vector& args); -} // namespace DolphinTool diff --git a/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp b/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp deleted file mode 100644 index ec54d08ab4..0000000000 --- a/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2021 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include - -#include "Core/Host.h" - -// Begin stubs needed to satisfy Core dependencies - -std::vector Host_GetPreferredLocales() -{ - return {}; -} - -void Host_PPCSymbolsChanged() -{ -} - -void Host_PPCBreakpointsChanged() -{ -} - -bool Host_UIBlocksControllerState() -{ - return false; -} - -void Host_Message(HostMessageID id) -{ -} - -void Host_UpdateTitle(const std::string& title) -{ -} - -void Host_UpdateDisasmDialog() -{ -} - -void Host_JitCacheInvalidation() -{ -} - -void Host_JitProfileDataWiped() -{ -} - -void Host_RequestRenderWindowSize(int width, int height) -{ -} - -bool Host_RendererHasFocus() -{ - return false; -} - -bool Host_RendererHasFullFocus() -{ - return false; -} - -bool Host_RendererIsFullscreen() -{ - return false; -} - -bool Host_TASInputHasFocus() -{ - return false; -} - -void Host_YieldToUI() -{ -} - -void Host_TitleChanged() -{ -} - -std::unique_ptr Host_CreateGBAHost(std::weak_ptr core) -{ - return nullptr; -} -// End stubs to satisfy Core dependencies diff --git a/Source/Core/DolphinTool/ToolMain.cpp b/Source/Core/DolphinTool/ToolMain.cpp deleted file mode 100644 index 95cc7c6aaa..0000000000 --- a/Source/Core/DolphinTool/ToolMain.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2021 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include -#include - -#include - -#include "DolphinTool/ConvertCommand.h" -#include "DolphinTool/ExtractCommand.h" -#include "DolphinTool/HeaderCommand.h" -#include "DolphinTool/VerifyCommand.h" - -#ifdef _WIN32 -#include "Common/StringUtil.h" -#endif - -static void PrintUsage() -{ - fmt::print(std::cerr, "usage: dolphin-tool COMMAND -h\n" - "\n" - "commands supported: [convert, verify, header, extract]\n"); -} - -#ifdef _WIN32 -#define main app_main -#endif - -int main(int argc, char* argv[]) -{ - if (argc < 2) - { - PrintUsage(); - return EXIT_FAILURE; - } - - const std::string_view command_str = argv[1]; - // Take off the program name and command selector before passing arguments down - const std::vector args(argv + 2, argv + argc); - - if (command_str == "convert") - return DolphinTool::ConvertCommand(args); - else if (command_str == "verify") - return DolphinTool::VerifyCommand(args); - else if (command_str == "header") - return DolphinTool::HeaderCommand(args); - else if (command_str == "extract") - return DolphinTool::Extract(args); - PrintUsage(); - return EXIT_FAILURE; -} - -#ifdef _WIN32 -int wmain(int, wchar_t*[], wchar_t*[]) -{ - std::vector args = Common::CommandLineToUtf8Argv(GetCommandLineW()); - const int argc = static_cast(args.size()); - std::vector argv(args.size()); - for (size_t i = 0; i < args.size(); ++i) - argv[i] = args[i].data(); - - return main(argc, argv.data()); -} - -#undef main -#endif diff --git a/Source/Core/DolphinTool/VerifyCommand.cpp b/Source/Core/DolphinTool/VerifyCommand.cpp deleted file mode 100644 index 975cfde14d..0000000000 --- a/Source/Core/DolphinTool/VerifyCommand.cpp +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2021 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "DolphinTool/VerifyCommand.h" - -#include -#include -#include - -#include -#include - -#include "Core/AchievementManager.h" -#include "DiscIO/Volume.h" -#include "DiscIO/VolumeVerifier.h" -#include "UICommon/UICommon.h" - -namespace DolphinTool -{ -static std::string HashToHexString(const std::vector& hash) -{ - std::stringstream ss; - ss << std::hex; - for (int i = 0; i < static_cast(hash.size()); ++i) - { - ss << std::setw(2) << std::setfill('0') << static_cast(hash[i]); - } - return ss.str(); -} - -static void PrintFullReport(const DiscIO::VolumeVerifier::Result& result) -{ - if (!result.hashes.crc32.empty()) - fmt::print(std::cout, "CRC32: {}\n", HashToHexString(result.hashes.crc32)); - else - fmt::print(std::cout, "CRC32 not computed\n"); - - if (!result.hashes.md5.empty()) - fmt::print(std::cout, "MD5: {}\n", HashToHexString(result.hashes.md5)); - else - fmt::print(std::cout, "MD5 not computed\n"); - - if (!result.hashes.sha1.empty()) - fmt::print(std::cout, "SHA1: {}\n", HashToHexString(result.hashes.sha1)); - else - fmt::print(std::cout, "SHA1 not computed\n"); - - fmt::print(std::cout, "Problems Found: {}\n", result.problems.empty() ? "No" : "Yes"); - - for (const auto& problem : result.problems) - { - fmt::print(std::cout, "\nSeverity: "); - switch (problem.severity) - { - case DiscIO::VolumeVerifier::Severity::Low: - fmt::print(std::cout, "Low"); - break; - case DiscIO::VolumeVerifier::Severity::Medium: - fmt::print(std::cout, "Medium"); - break; - case DiscIO::VolumeVerifier::Severity::High: - fmt::print(std::cout, "High"); - break; - case DiscIO::VolumeVerifier::Severity::None: - fmt::print(std::cout, "None"); - break; - default: - ASSERT(false); - break; - } - fmt::print(std::cout, "\nSummary: {}\n\n", problem.text); - } -} - -int VerifyCommand(const std::vector& args) -{ - optparse::OptionParser parser; - - parser.usage("usage: verify [options]..."); - - parser.add_option("-u", "--user") - .type("string") - .action("store") - .help("User folder path, required for temporary processing files. " - "Will be automatically created if this option is not set.") - .set_default(""); - - parser.add_option("-i", "--input") - .type("string") - .action("store") - .help("Path to input file.") - .metavar("FILE"); - - parser.add_option("-a", "--algorithm") - .type("string") - .action("store") - .help("Optional. Compute and print the digest using the selected algorithm, then exit. " - "[%choices]") - .choices({"crc32", "md5", "sha1", "rchash"}); - - const optparse::Values& options = parser.parse_args(args); - - // Initialize the dolphin user directory, required for temporary processing files - // If this is not set, destructive file operations could occur due to path confusion - UICommon::SetUserDirectory(options["user"]); - UICommon::Init(); - - // Validate options - if (!options.is_set("input")) - { - fmt::print(std::cerr, "Error: No input set\n"); - return EXIT_FAILURE; - } - const std::string& input_file_path = options["input"]; - - bool rc_hash_calculate = false; - std::string rc_hash_result = "0"; - - DiscIO::Hashes hashes_to_calculate{}; - const bool algorithm_is_set = options.is_set("algorithm"); - if (!algorithm_is_set) - { - hashes_to_calculate = DiscIO::VolumeVerifier::GetDefaultHashesToCalculate(); - } - else - { - const std::string& algorithm = options["algorithm"]; - if (algorithm == "crc32") - hashes_to_calculate.crc32 = true; - else if (algorithm == "md5") - hashes_to_calculate.md5 = true; - else if (algorithm == "sha1") - hashes_to_calculate.sha1 = true; -#ifdef USE_RETRO_ACHIEVEMENTS - else if (algorithm == "rchash") - rc_hash_calculate = true; -#endif - } - - if (!hashes_to_calculate.crc32 && !hashes_to_calculate.md5 && !hashes_to_calculate.sha1 && - !rc_hash_calculate) - { - // optparse should protect from this - fmt::print(std::cerr, "Error: No algorithms selected for the operation\n"); - return EXIT_FAILURE; - } - - // Open the volume - const std::unique_ptr volume = DiscIO::CreateVolume(input_file_path); - if (!volume) - { - fmt::print(std::cerr, "Error: Unable to open input file\n"); - return EXIT_FAILURE; - } - - // Verify the volume - DiscIO::VolumeVerifier verifier(*volume, false, hashes_to_calculate); - verifier.Start(); - while (verifier.GetBytesProcessed() != verifier.GetTotalBytes()) - { - verifier.Process(); - } - verifier.Finish(); - const DiscIO::VolumeVerifier::Result& result = verifier.GetResult(); - -#ifdef USE_RETRO_ACHIEVEMENTS - // Calculate rcheevos hash - if (rc_hash_calculate) - { - rc_hash_result = AchievementManager::CalculateHash(input_file_path); - } -#endif - - // Print the report - if (!algorithm_is_set) - { - PrintFullReport(result); - } - else - { - if (hashes_to_calculate.crc32 && !result.hashes.crc32.empty()) - fmt::print(std::cout, "{}\n", HashToHexString(result.hashes.crc32)); - else if (hashes_to_calculate.md5 && !result.hashes.md5.empty()) - fmt::print(std::cout, "{}\n", HashToHexString(result.hashes.md5)); - else if (hashes_to_calculate.sha1 && !result.hashes.sha1.empty()) - fmt::print(std::cout, "{}\n", HashToHexString(result.hashes.sha1)); - else if (rc_hash_calculate) - fmt::print(std::cout, "{}\n", rc_hash_result); - else - { - fmt::print(std::cerr, "Error: No hash computed\n"); - return EXIT_FAILURE; - } - } - - return EXIT_SUCCESS; -} -} // namespace DolphinTool diff --git a/Source/Core/DolphinTool/VerifyCommand.h b/Source/Core/DolphinTool/VerifyCommand.h deleted file mode 100644 index 337b52143c..0000000000 --- a/Source/Core/DolphinTool/VerifyCommand.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2021 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -namespace DolphinTool -{ -int VerifyCommand(const std::vector& args); -} // namespace DolphinTool diff --git a/Source/Core/DolphinTool/resource.h b/Source/Core/DolphinTool/resource.h deleted file mode 100644 index 084246e52f..0000000000 --- a/Source/Core/DolphinTool/resource.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2017 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by DolphinNoGui.rc -// -#ifdef RC_INVOKED -#define IDI_ICON1 101 -#else -#define IDI_ICON1 MAKEINTRESOURCE(101) -#endif - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index 715046c237..43ca21e6cb 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -9,24 +9,9 @@ #include "Common/Logging/Log.h" #include "Core/HW/WiimoteReal/WiimoteReal.h" -#ifdef CIFACE_USE_WIN32 -#include "InputCommon/ControllerInterface/Win32/Win32.h" -#endif -#ifdef CIFACE_USE_XLIB -#include "InputCommon/ControllerInterface/Xlib/XInput2.h" -#endif -#ifdef CIFACE_USE_OSX -#include "InputCommon/ControllerInterface/Quartz/Quartz.h" -#endif #ifdef CIFACE_USE_ANDROID #include "InputCommon/ControllerInterface/Android/Android.h" #endif -#ifdef CIFACE_USE_EVDEV -#include "InputCommon/ControllerInterface/evdev/evdev.h" -#endif -#ifdef CIFACE_USE_PIPES -#include "InputCommon/ControllerInterface/Pipes/Pipes.h" -#endif #ifdef CIFACE_USE_DUALSHOCKUDPCLIENT #include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h" #endif @@ -55,24 +40,9 @@ void ControllerInterface::Initialize(const WindowSystemInfo& wsi) m_populating_devices_counter = 1; -#if defined(CIFACE_USE_WIN32) - m_input_backends.emplace_back(ciface::Win32::CreateInputBackend(this)); -#endif -#ifdef CIFACE_USE_XLIB - m_input_backends.emplace_back(ciface::XInput2::CreateInputBackend(this)); -#endif -#ifdef CIFACE_USE_OSX - m_input_backends.emplace_back(ciface::Quartz::CreateInputBackend(this)); -#endif #if defined(CIFACE_USE_ANDROID) && !defined(__LIBRETRO__) m_input_backends.emplace_back(ciface::Android::CreateInputBackend(this)); #endif -#ifdef CIFACE_USE_EVDEV - m_input_backends.emplace_back(ciface::evdev::CreateInputBackend(this)); -#endif -#ifdef CIFACE_USE_PIPES - m_input_backends.emplace_back(ciface::Pipes::CreateInputBackend(this)); -#endif #ifdef CIFACE_USE_DUALSHOCKUDPCLIENT m_input_backends.emplace_back(ciface::DualShockUDPClient::CreateInputBackend(this)); #endif diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h index a2e498dba3..3c4f364d80 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h @@ -16,21 +16,6 @@ #include "InputCommon/ControllerInterface/InputBackend.h" // enable disable sources -#if defined(_WIN32) && !defined(__LIBRETRO__) -#define CIFACE_USE_WIN32 -#endif -#ifdef HAVE_X11 -#define CIFACE_USE_XLIB -#endif -#if defined(__APPLE__) && !defined(__LIBRETRO__) -#define CIFACE_USE_OSX -#endif -#if defined(HAVE_LIBEVDEV) && defined(HAVE_LIBUDEV) -#define CIFACE_USE_EVDEV -#endif -#if defined(USE_PIPES) -#define CIFACE_USE_PIPES -#endif #define CIFACE_USE_DUALSHOCKUDPCLIENT #if defined(HAVE_HIDAPI) #define CIFACE_USE_STEAMDECK diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInput.cpp b/Source/Core/InputCommon/ControllerInterface/DInput/DInput.cpp deleted file mode 100644 index aa1826ae45..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/DInput/DInput.cpp +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2010 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "InputCommon/ControllerInterface/DInput/DInput.h" - -#include "Common/HRWrap.h" -#include "Common/Logging/Log.h" -#include "Common/StringUtil.h" - -#include "InputCommon/ControllerInterface/ControllerInterface.h" -#include "InputCommon/ControllerInterface/DInput/DInputJoystick.h" -#include "InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h" - -#pragma comment(lib, "Dinput8.lib") -#pragma comment(lib, "dxguid.lib") - -namespace ciface::DInput -{ -static IDirectInput8* s_idi8 = nullptr; - -BOOL CALLBACK DIEnumDeviceObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef) -{ - ((std::list*)pvRef)->push_back(*lpddoi); - return DIENUM_CONTINUE; -} - -BOOL CALLBACK DIEnumDevicesCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef) -{ - ((std::list*)pvRef)->push_back(*lpddi); - return DIENUM_CONTINUE; -} - -std::string GetDeviceName(const LPDIRECTINPUTDEVICE8 device) -{ - DIPROPSTRING str = {}; - str.diph.dwSize = sizeof(str); - str.diph.dwHeaderSize = sizeof(str.diph); - str.diph.dwHow = DIPH_DEVICE; - - std::string result; - HRESULT hr = device->GetProperty(DIPROP_PRODUCTNAME, &str.diph); - if (SUCCEEDED(hr)) - { - result = StripWhitespace(WStringToUTF8(str.wsz)); - } - else - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "GetProperty(DIPROP_PRODUCTNAME) failed: {}", - Common::HRWrap(hr)); - } - - return result; -} - -// Assumes hwnd had not changed from the previous call -void PopulateDevices(HWND hwnd) -{ - if (!s_idi8) - { - HRESULT hr = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, - IID_IDirectInput8, (LPVOID*)&s_idi8, nullptr); - if (FAILED(hr)) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "DirectInput8Create failed: {}", Common::HRWrap(hr)); - return; - } - } - - // Remove old (invalid) devices. No need to ever remove the KeyboardMouse device. - // Note that if we have 2+ DInput controllers, not fully repopulating devices - // will mean that a device with index "2" could persist while there is no device with index "0". - // This is slightly inconsistent as when we refresh all devices, they will instead reset, and - // that happens a lot (for uncontrolled reasons, like starting/stopping the emulation). - g_controller_interface.RemoveDevice( - [](const auto* dev) { return dev->GetSource() == DINPUT_SOURCE_NAME && !dev->IsValid(); }); - - InitKeyboardMouse(s_idi8, hwnd); - InitJoystick(s_idi8, hwnd); -} - -void ChangeWindow(HWND hwnd) -{ - if (s_idi8) // Has init? Ignore if called before the first PopulateDevices() - { - // The KeyboardMouse device is marked as virtual device, so we avoid removing it. - // We need to force all the DInput joysticks to be destroyed now, or recreation would fail. - g_controller_interface.RemoveDevice( - [](const auto* dev) { - return dev->GetSource() == DINPUT_SOURCE_NAME && !dev->IsVirtualDevice(); - }, - true); - - SetKeyboardMouseWindow(hwnd); - InitJoystick(s_idi8, hwnd); - } -} - -void DeInit() -{ - if (s_idi8) - { - s_idi8->Release(); - s_idi8 = nullptr; - } -} -} // namespace ciface::DInput diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInput.h b/Source/Core/InputCommon/ControllerInterface/DInput/DInput.h deleted file mode 100644 index 3d1f67d64a..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/DInput/DInput.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2010 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#define DINPUT_SOURCE_NAME "DInput" - -#include -#include -#include - -#include "InputCommon/ControllerInterface/DInput/DInput8.h" - -namespace ciface::DInput -{ -// BOOL CALLBACK DIEnumEffectsCallback(LPCDIEFFECTINFO pdei, LPVOID pvRef); -BOOL CALLBACK DIEnumDeviceObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef); -BOOL CALLBACK DIEnumDevicesCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef); -std::string GetDeviceName(const LPDIRECTINPUTDEVICE8 device); - -void PopulateDevices(HWND hwnd); -void ChangeWindow(HWND hwnd); -void DeInit(); -} // namespace ciface::DInput diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInput8.h b/Source/Core/InputCommon/ControllerInterface/DInput/DInput8.h deleted file mode 100644 index 4b607638c8..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/DInput/DInput8.h +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2008 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#define DIRECTINPUT_VERSION 0x0800 -#include diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp deleted file mode 100644 index 95cacc6da7..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright 2010 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "InputCommon/ControllerInterface/DInput/DInputJoystick.h" - -#include -#include -#include -#include -#include - -#include - -#include "Common/HRWrap.h" -#include "Common/Logging/Log.h" -#include "InputCommon/ControllerInterface/ControllerInterface.h" -#include "InputCommon/ControllerInterface/DInput/DInput.h" -#include "InputCommon/ControllerInterface/DInput/XInputFilter.h" - -namespace ciface::DInput -{ -constexpr DWORD DATA_BUFFER_SIZE = 32; - -struct GUIDComparator -{ - bool operator()(const GUID& left, const GUID& right) const - { - static_assert(std::is_trivially_copyable_v); - - return memcmp(&left, &right, sizeof(left)) < 0; - } -}; - -static std::set s_guids_in_use; -static std::mutex s_guids_mutex; - -void InitJoystick(IDirectInput8* const idi8, HWND hwnd) -{ - std::list joysticks; - idi8->EnumDevices(DI8DEVCLASS_GAMECTRL, DIEnumDevicesCallback, (LPVOID)&joysticks, - DIEDFL_ATTACHEDONLY); - - std::unordered_set xinput_guids = GetXInputGUIDS(); - for (DIDEVICEINSTANCE& joystick : joysticks) - { - // Skip XInput Devices - if (xinput_guids.contains(joystick.guidProduct.Data1)) - { - continue; - } - - // Skip devices we are already using. - { - std::lock_guard lk(s_guids_mutex); - if (s_guids_in_use.contains(joystick.guidInstance)) - { - continue; - } - } - - LPDIRECTINPUTDEVICE8 js_device; - // Don't print any warnings on failure - if (SUCCEEDED(idi8->CreateDevice(joystick.guidInstance, &js_device, nullptr))) - { - if (SUCCEEDED(js_device->SetDataFormat(&c_dfDIJoystick))) - { - HRESULT hr = js_device->SetCooperativeLevel(GetAncestor(hwnd, GA_ROOT), - DISCL_BACKGROUND | DISCL_EXCLUSIVE); - if (FAILED(hr)) - { - WARN_LOG_FMT(CONTROLLERINTERFACE, - "DInput: Failed to acquire device exclusively. Force feedback will be " - "unavailable. {}", - Common::HRWrap(hr)); - // Fall back to non-exclusive mode, with no rumble - if (FAILED( - js_device->SetCooperativeLevel(nullptr, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE))) - { - js_device->Release(); - continue; - } - } - - auto js = std::make_shared(js_device); - // only add if it has some inputs/outputs. - // Don't even add it to our static list in case we first created it without a window handle, - // failing to get exclusive mode, and then later managed to obtain it, which mean it - // could now have some outputs if it didn't before. - if (js->Inputs().size() || js->Outputs().size()) - { - if (g_controller_interface.AddDevice(std::move(js))) - { - std::lock_guard lk(s_guids_mutex); - s_guids_in_use.insert(joystick.guidInstance); - } - } - } - else - { - js_device->Release(); - } - } - } -} - -Joystick::Joystick(const LPDIRECTINPUTDEVICE8 device) : m_device(device) -{ - // seems this needs to be done before GetCapabilities - // polled or buffered data - DIPROPDWORD dipdw; - dipdw.diph.dwSize = sizeof(DIPROPDWORD); - dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); - dipdw.diph.dwObj = 0; - dipdw.diph.dwHow = DIPH_DEVICE; - dipdw.dwData = DATA_BUFFER_SIZE; - // set the buffer size, - // if we can't set the property, we can't use buffered data - m_buffered = SUCCEEDED(m_device->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph)); - - // seems this needs to be done after SetProperty of buffer size - m_device->Acquire(); - - // get joystick caps - DIDEVCAPS js_caps; - js_caps.dwSize = sizeof(js_caps); - if (FAILED(m_device->GetCapabilities(&js_caps))) - return; - - // max of 32 buttons and 4 hats / the limit of the data format I am using - js_caps.dwButtons = std::min((DWORD)32, js_caps.dwButtons); - js_caps.dwPOVs = std::min((DWORD)4, js_caps.dwPOVs); - - // m_must_poll = (js_caps.dwFlags & DIDC_POLLEDDATAFORMAT) != 0; - - // buttons - for (u8 i = 0; i != js_caps.dwButtons; ++i) - AddInput(new Button(i, m_state_in.rgbButtons[i])); - - // hats - for (u8 i = 0; i != js_caps.dwPOVs; ++i) - { - // each hat gets 4 input instances associated with it, (up down left right) - for (u8 d = 0; d != 4; ++d) - AddInput(new Hat(i, m_state_in.rgdwPOV[i], d)); - } - - // get up to 6 axes and 2 sliders - DIPROPRANGE range; - range.diph.dwSize = sizeof(range); - range.diph.dwHeaderSize = sizeof(range.diph); - range.diph.dwHow = DIPH_BYOFFSET; - // screw EnumObjects, just go through all the axis offsets and try to GetProperty - // this should be more foolproof, less code, and probably faster - for (unsigned int offset = 0; offset < DIJOFS_BUTTON(0) / sizeof(LONG); ++offset) - { - range.diph.dwObj = offset * sizeof(LONG); - // Try to set a range with 16 bits of precision: - range.lMin = std::numeric_limits::min(); - range.lMax = std::numeric_limits::max(); - m_device->SetProperty(DIPROP_RANGE, &range.diph); - // Not all devices support setting DIPROP_RANGE so we must GetProperty right back. - // This also checks that the axis is present. - // Note: Even though not all devices support setting DIPROP_RANGE, some require it. - if (SUCCEEDED(m_device->GetProperty(DIPROP_RANGE, &range.diph))) - { - const LONG base = (range.lMin + range.lMax) / 2; - const LONG& ax = (&m_state_in.lX)[offset]; - - // each axis gets a negative and a positive input instance associated with it - AddFullAnalogSurfaceInputs(new Axis(offset, ax, base, range.lMin - base), - new Axis(offset, ax, base, range.lMax - base)); - } - } - - // Force feedback: - std::list objects; - if (SUCCEEDED(m_device->EnumObjects(DIEnumDeviceObjectsCallback, (LPVOID)&objects, DIDFT_AXIS))) - { - const int num_ff_axes = std::ranges::count_if( - objects, [](const auto& pdidoi) { return (pdidoi.dwFlags & DIDOI_FFACTUATOR) != 0; }); - InitForceFeedback(m_device, num_ff_axes); - } - - // Set hats to center: - // "The center position is normally reported as -1" -MSDN - std::ranges::fill(m_state_in.rgdwPOV, -1); -} - -Joystick::~Joystick() -{ - DIDEVICEINSTANCE info = {}; - info.dwSize = sizeof(info); - if (SUCCEEDED(m_device->GetDeviceInfo(&info))) - { - std::lock_guard lk(s_guids_mutex); - s_guids_in_use.erase(info.guidInstance); - } - else - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "DInputJoystick: GetDeviceInfo failed."); - } - - DeInitForceFeedback(); - - m_device->Unacquire(); - m_device->Release(); -} - -std::string Joystick::GetName() const -{ - return GetDeviceName(m_device); -} - -std::string Joystick::GetSource() const -{ - return DINPUT_SOURCE_NAME; -} - -bool Joystick::IsValid() const -{ - return SUCCEEDED(m_device->Acquire()); -} - -Core::DeviceRemoval Joystick::UpdateInput() -{ - HRESULT hr = 0; - - // just always poll, - // MSDN says if this isn't needed it doesn't do anything - m_device->Poll(); - - if (m_buffered) - { - DIDEVICEOBJECTDATA evtbuf[DATA_BUFFER_SIZE]; - DWORD numevents = DATA_BUFFER_SIZE; - hr = m_device->GetDeviceData(sizeof(*evtbuf), evtbuf, &numevents, 0); - - if (SUCCEEDED(hr)) - { - for (LPDIDEVICEOBJECTDATA evt = evtbuf; evt != (evtbuf + numevents); ++evt) - { - // all the buttons are at the end of the data format - // they are bytes rather than longs - if (evt->dwOfs < DIJOFS_BUTTON(0)) - *(DWORD*)(((BYTE*)&m_state_in) + evt->dwOfs) = evt->dwData; - else - ((BYTE*)&m_state_in)[evt->dwOfs] = (BYTE)evt->dwData; - } - - // seems like this needs to be done maybe... - if (DI_BUFFEROVERFLOW == hr) - hr = m_device->GetDeviceState(sizeof(m_state_in), &m_state_in); - } - } - else - { - hr = m_device->GetDeviceState(sizeof(m_state_in), &m_state_in); - } - - // try reacquire if input lost - if (DIERR_INPUTLOST == hr || DIERR_NOTACQUIRED == hr) - m_device->Acquire(); - - return Core::DeviceRemoval::Keep; -} - -// get name - -std::string Joystick::Button::GetName() const -{ - return fmt::format("Button {}", m_index); -} - -std::string Joystick::Axis::GetName() const -{ - const char sign = m_range < 0 ? '-' : '+'; - if (m_index < 6) // axis - return fmt::format("Axis {:c}{}{:c}", 'X' + m_index % 3, m_index > 2 ? "r" : "", sign); - else // slider - return fmt::format("Slider {}{:c}", m_index - 6, sign); -} - -std::string Joystick::Hat::GetName() const -{ - return fmt::format("Hat {} {:c}", m_index, "NESW"[m_direction]); -} - -// get / set state - -ControlState Joystick::Axis::GetState() const -{ - return ControlState(m_axis - m_base) / m_range; -} - -ControlState Joystick::Button::GetState() const -{ - return ControlState(m_button > 0); -} - -ControlState Joystick::Hat::GetState() const -{ - // "Some drivers report the centered position of the POV indicator as 65,535. - // Determine whether the indicator is centered as follows" -MSDN - const bool is_centered = (0xffff == LOWORD(m_hat)); - - if (is_centered) - return 0; - - return (std::abs(int(m_hat / 4500 - m_direction * 2 + 8) % 8 - 4) > 2); -} -} // namespace ciface::DInput diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.h b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.h deleted file mode 100644 index bf1f9aa91b..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.h +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2010 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "InputCommon/ControllerInterface/CoreDevice.h" -#include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h" - -namespace ciface::DInput -{ -void InitJoystick(IDirectInput8* const idi8, HWND hwnd); - -class Joystick : public ForceFeedback::ForceFeedbackDevice -{ -private: - class Button : public Input - { - public: - Button(u8 index, const BYTE& button) : m_button(button), m_index(index) {} - std::string GetName() const override; - ControlState GetState() const override; - - private: - const BYTE& m_button; - const u8 m_index; - }; - - class Axis : public Input - { - public: - Axis(u8 index, const LONG& axis, LONG base, LONG range) - : m_axis(axis), m_base(base), m_range(range), m_index(index) - { - } - std::string GetName() const override; - ControlState GetState() const override; - - private: - const LONG& m_axis; - const LONG m_base, m_range; - const u8 m_index; - }; - - class Hat : public Input - { - public: - Hat(u8 index, const DWORD& hat, u8 direction) - : m_hat(hat), m_direction(direction), m_index(index) - { - } - std::string GetName() const override; - ControlState GetState() const override; - - private: - const DWORD& m_hat; - const u8 m_index, m_direction; - }; - -public: - Core::DeviceRemoval UpdateInput() override; - - Joystick(const LPDIRECTINPUTDEVICE8 device); - ~Joystick() override; - - std::string GetName() const override; - std::string GetSource() const override; - int GetSortPriority() const override { return -3; } - - bool IsValid() const final; - -private: - const LPDIRECTINPUTDEVICE8 m_device; - - DIJOYSTATE m_state_in{}; - - bool m_buffered; -}; -} // namespace ciface::DInput diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.cpp b/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.cpp deleted file mode 100644 index 92ebf355fc..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.cpp +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright 2010 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h" - -#include - -#include "Common/Logging/Log.h" - -#include "Core/Core.h" -#include "Core/Host.h" - -#include "InputCommon/ControllerInterface/ControllerInterface.h" -#include "InputCommon/ControllerInterface/DInput/DInput.h" - -// (lower would be more sensitive) user can lower sensitivity by setting range -// seems decent here ( at 8 ), I don't think anyone would need more sensitive than this -// and user can lower it much farther than they would want to with the range -#define MOUSE_AXIS_SENSITIVITY 8 - -// if input hasn't been received for this many ms, mouse input will be skipped -// otherwise it is just some crazy value -#define DROP_INPUT_TIME 250 - -namespace ciface::DInput -{ -class RelativeMouseAxis final : public Core::Device::RelativeInput -{ -public: - std::string GetName() const override - { - return fmt::format("RelativeMouse {}{}", char('X' + m_index), (m_scale > 0) ? '+' : '-'); - } - - RelativeMouseAxis(u8 index, bool positive, const RelativeMouseState* state) - : m_state(*state), m_index(index), m_scale(positive * 2 - 1) - { - } - - ControlState GetState() const override - { - return ControlState(m_state.GetValue().data[m_index] * m_scale); - } - -private: - const RelativeMouseState& m_state; - const u8 m_index; - const s8 m_scale; -}; - -static const struct -{ - const BYTE code; - const char* const name; -} named_keys[] = { -#include "InputCommon/ControllerInterface/DInput/NamedKeys.h" // NOLINT -}; - -// Prevent duplicate keyboard/mouse devices. Modified by more threads. -static bool s_keyboard_mouse_exists; -static HWND s_hwnd; - -void InitKeyboardMouse(IDirectInput8* const idi8, HWND hwnd) -{ - if (s_keyboard_mouse_exists) - return; - - s_hwnd = hwnd; - - // Mouse and keyboard are a combined device, to allow shift+click and stuff - // if that's dumb, I will make a VirtualDevice class that just uses ranges of inputs/outputs from - // other devices - // so there can be a separated Keyboard and mouse, as well as combined KeyboardMouse - - LPDIRECTINPUTDEVICE8 kb_device = nullptr; - LPDIRECTINPUTDEVICE8 mo_device = nullptr; - - // These are "virtual" system devices, so they are always there even if we have no physical - // mouse and keyboard plugged into the computer - if (SUCCEEDED(idi8->CreateDevice(GUID_SysKeyboard, &kb_device, nullptr)) && - SUCCEEDED(kb_device->SetDataFormat(&c_dfDIKeyboard)) && - SUCCEEDED(kb_device->SetCooperativeLevel(nullptr, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE)) && - SUCCEEDED(idi8->CreateDevice(GUID_SysMouse, &mo_device, nullptr)) && - SUCCEEDED(mo_device->SetDataFormat(&c_dfDIMouse2)) && - SUCCEEDED(mo_device->SetCooperativeLevel(nullptr, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE))) - { - g_controller_interface.AddDevice(std::make_shared(kb_device, mo_device)); - return; - } - - ERROR_LOG_FMT(CONTROLLERINTERFACE, "KeyboardMouse device failed to be created"); - - if (kb_device) - kb_device->Release(); - if (mo_device) - mo_device->Release(); -} - -void SetKeyboardMouseWindow(HWND hwnd) -{ - s_hwnd = hwnd; -} - -KeyboardMouse::~KeyboardMouse() -{ - s_keyboard_mouse_exists = false; - - // Independently of the order in which we do these, if we put a breakpoint on Unacquire() (or in - // any place in the call stack before this), when refreshing devices from the UI, on the second - // attempt, it will get stuck in an infinite (while) loop inside dinput8.dll. Given that it can't - // be otherwise be reproduced (not even with sleeps), we can just ignore the problem. - - // kb - m_kb_device->Unacquire(); - m_kb_device->Release(); - // mouse - m_mo_device->Unacquire(); - m_mo_device->Release(); -} - -KeyboardMouse::KeyboardMouse(const LPDIRECTINPUTDEVICE8 kb_device, - const LPDIRECTINPUTDEVICE8 mo_device) - : m_kb_device(kb_device), m_mo_device(mo_device), m_last_update(GetTickCount()), m_state_in() -{ - s_keyboard_mouse_exists = true; - - if (FAILED(m_kb_device->Acquire())) - WARN_LOG_FMT(CONTROLLERINTERFACE, "Keyboard device failed to acquire. We'll retry later"); - if (FAILED(m_mo_device->Acquire())) - WARN_LOG_FMT(CONTROLLERINTERFACE, "Mouse device failed to acquire. We'll retry later"); - - // KEYBOARD - // add keys - for (u8 i = 0; i < sizeof(named_keys) / sizeof(*named_keys); ++i) - AddInput(new Key(i, m_state_in.keyboard[named_keys[i].code])); - - // Add combined left/right modifiers with consistent naming across platforms. - AddCombinedInput("Alt", {"LMENU", "RMENU"}); - AddCombinedInput("Shift", {"LSHIFT", "RSHIFT"}); - AddCombinedInput("Ctrl", {"LCONTROL", "RCONTROL"}); - - // MOUSE - DIDEVCAPS mouse_caps = {}; - mouse_caps.dwSize = sizeof(mouse_caps); - m_mo_device->GetCapabilities(&mouse_caps); - // mouse buttons - // We don't use caps here due to reporting inconsistencies - for (u8 i = 0; i < std::size(m_state_in.mouse.rgbButtons); ++i) - AddInput(new Button(i, m_state_in.mouse.rgbButtons[i])); - // mouse axes - for (unsigned int i = 0; i < mouse_caps.dwAxes; ++i) - { - const LONG& ax = (&m_state_in.mouse.lX)[i]; - - // each axis gets a negative and a positive input instance associated with it - AddInput(new Axis(i, ax, (2 == i) ? -1 : -MOUSE_AXIS_SENSITIVITY)); - AddInput(new Axis(i, ax, -(2 == i) ? 1 : MOUSE_AXIS_SENSITIVITY)); - } - - // cursor, with a hax for-loop - for (unsigned int i = 0; i < 4; ++i) - AddInput(new Cursor(!!(i & 2), (&m_state_in.cursor.x)[i / 2], !!(i & 1))); - - // Raw relative mouse movement. - for (unsigned int i = 0; i != mouse_caps.dwAxes; ++i) - { - AddInput(new RelativeMouseAxis(i, false, &m_state_in.relative_mouse)); - AddInput(new RelativeMouseAxis(i, true, &m_state_in.relative_mouse)); - } -} - -void KeyboardMouse::UpdateCursorInput() -{ - // Get the size of the current window (in my case Rect.top and Rect.left was zero). - RECT rect; - GetClientRect(s_hwnd, &rect); - - // Width and height are the size of the rendering window. They could be 0 - const auto win_width = std::max(rect.right - rect.left, 1l); - const auto win_height = std::max(rect.bottom - rect.top, 1l); - - POINT point = {}; - if (g_controller_interface.IsMouseCenteringRequested() && - (Host_RendererHasFocus() || Host_TASInputHasFocus())) - { - point.x = win_width / 2; - point.y = win_height / 2; - - POINT screen_point = point; - ClientToScreen(s_hwnd, &screen_point); - SetCursorPos(screen_point.x, screen_point.y); - g_controller_interface.SetMouseCenteringRequested(false); - } - else if (Host_TASInputHasFocus()) - { - // When a TAS Input window has focus and "Enable Controller Input" is checked most types of - // input should be read normally as if the render window had focus instead. The cursor is an - // exception, as otherwise using the mouse to set any control in the TAS Input window will also - // update the Wii IR value (or any other input controlled by the cursor). - - return; - } - else - { - GetCursorPos(&point); - - // Get the cursor position relative to the upper left corner of the current window - // (separate or render to main) - ScreenToClient(s_hwnd, &point); - } - - const auto window_scale = g_controller_interface.GetWindowInputScale(); - - // Convert the cursor position to a range from -1 to 1. - m_state_in.cursor.x = (ControlState(point.x) / win_width * 2 - 1) * window_scale.x; - m_state_in.cursor.y = (ControlState(point.y) / win_height * 2 - 1) * window_scale.y; -} - -Core::DeviceRemoval KeyboardMouse::UpdateInput() -{ - UpdateCursorInput(); - - DIMOUSESTATE2 tmp_mouse; - - // if mouse position hasn't been updated in a short while, skip a dev state - DWORD cur_time = GetTickCount(); - if (cur_time - m_last_update > DROP_INPUT_TIME) - { - // set axes to zero - m_state_in.mouse = {}; - m_state_in.relative_mouse = {}; - - // skip this input state - m_mo_device->GetDeviceState(sizeof(tmp_mouse), &tmp_mouse); - } - - m_last_update = cur_time; - - HRESULT mo_hr = m_mo_device->GetDeviceState(sizeof(tmp_mouse), &tmp_mouse); - if (DIERR_INPUTLOST == mo_hr || DIERR_NOTACQUIRED == mo_hr) - { - INFO_LOG_FMT(CONTROLLERINTERFACE, "Mouse device failed to get state"); - if (FAILED(m_mo_device->Acquire())) - INFO_LOG_FMT(CONTROLLERINTERFACE, "Mouse device failed to re-acquire, we'll retry later"); - } - else if (SUCCEEDED(mo_hr)) - { - m_state_in.relative_mouse.Move({tmp_mouse.lX, tmp_mouse.lY, tmp_mouse.lZ}); - m_state_in.relative_mouse.Update(); - - // need to smooth out the axes, otherwise it doesn't work for shit - for (unsigned int i = 0; i < 3; ++i) - ((&m_state_in.mouse.lX)[i] += (&tmp_mouse.lX)[i]) /= 2; - - // copy over the buttons - std::copy_n(tmp_mouse.rgbButtons, std::size(tmp_mouse.rgbButtons), m_state_in.mouse.rgbButtons); - } - - HRESULT kb_hr = m_kb_device->GetDeviceState(sizeof(m_state_in.keyboard), &m_state_in.keyboard); - if (kb_hr == DIERR_INPUTLOST || kb_hr == DIERR_NOTACQUIRED) - { - INFO_LOG_FMT(CONTROLLERINTERFACE, "Keyboard device failed to get state"); - if (SUCCEEDED(m_kb_device->Acquire())) - m_kb_device->GetDeviceState(sizeof(m_state_in.keyboard), &m_state_in.keyboard); - else - INFO_LOG_FMT(CONTROLLERINTERFACE, "Keyboard device failed to re-acquire, we'll retry later"); - } - - return Core::DeviceRemoval::Keep; -} - -std::string KeyboardMouse::GetName() const -{ - return "Keyboard Mouse"; -} - -std::string KeyboardMouse::GetSource() const -{ - return DINPUT_SOURCE_NAME; -} - -// Give this device a higher priority to make sure it shows first -int KeyboardMouse::GetSortPriority() const -{ - return DEFAULT_DEVICE_SORT_PRIORITY; -} - -bool KeyboardMouse::IsVirtualDevice() const -{ - return true; -} - -// names -std::string KeyboardMouse::Key::GetName() const -{ - return named_keys[m_index].name; -} - -std::string KeyboardMouse::Button::GetName() const -{ - return std::string("Click ") + char('0' + m_index); -} - -std::string KeyboardMouse::Axis::GetName() const -{ - char tmpstr[] = "Axis .."; - tmpstr[5] = (char)('X' + m_index); - tmpstr[6] = (m_range < 0 ? '-' : '+'); - return tmpstr; -} - -std::string KeyboardMouse::Cursor::GetName() const -{ - char tmpstr[] = "Cursor .."; - tmpstr[7] = (char)('X' + m_index); - tmpstr[8] = (m_positive ? '+' : '-'); - return tmpstr; -} - -// get/set state -ControlState KeyboardMouse::Key::GetState() const -{ - return (m_key != 0); -} - -ControlState KeyboardMouse::Button::GetState() const -{ - return (m_button != 0); -} - -ControlState KeyboardMouse::Axis::GetState() const -{ - return ControlState(m_axis) / m_range; -} - -ControlState KeyboardMouse::Cursor::GetState() const -{ - return m_axis / (m_positive ? 1.0 : -1.0); -} -} // namespace ciface::DInput diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h b/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h deleted file mode 100644 index 67721e3301..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2010 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include "Common/Matrix.h" -#include "InputCommon/ControllerInterface/ControllerInterface.h" -#include "InputCommon/ControllerInterface/CoreDevice.h" -#include "InputCommon/ControllerInterface/DInput/DInput8.h" - -namespace ciface::DInput -{ -void InitKeyboardMouse(IDirectInput8* const idi8, HWND hwnd); - -using RelativeMouseState = RelativeInputState>; -void SetKeyboardMouseWindow(HWND hwnd); - -class KeyboardMouse : public Core::Device -{ -private: - struct State - { - BYTE keyboard[256]{}; - - // Old smoothed relative mouse movement. - DIMOUSESTATE2 mouse{}; - - // Normalized mouse cursor position. - Common::TVec2 cursor; - - // Raw relative mouse movement. - RelativeMouseState relative_mouse; - }; - - // Keyboard key - class Key : public Input - { - public: - Key(u8 index, const BYTE& key) : m_key(key), m_index(index) {} - std::string GetName() const override; - ControlState GetState() const override; - - private: - const BYTE& m_key; - const u8 m_index; - }; - - // Mouse button - class Button : public Input - { - public: - Button(u8 index, const BYTE& button) : m_button(button), m_index(index) {} - std::string GetName() const override; - ControlState GetState() const override; - - private: - const BYTE& m_button; - const u8 m_index; - }; - - // Mouse movement offset axis. Includes mouse wheel - class Axis : public Input - { - public: - Axis(u8 index, const LONG& axis, LONG range) : m_axis(axis), m_range(range), m_index(index) {} - std::string GetName() const override; - bool IsDetectable() const override { return false; } - ControlState GetState() const override; - - private: - const LONG& m_axis; - const LONG m_range; - const u8 m_index; - }; - - // Mouse from window center - class Cursor : public Input - { - public: - Cursor(u8 index, const ControlState& axis, const bool positive) - : m_axis(axis), m_index(index), m_positive(positive) - { - } - std::string GetName() const override; - bool IsDetectable() const override { return false; } - ControlState GetState() const override; - - private: - const ControlState& m_axis; - const u8 m_index; - const bool m_positive; - }; - -public: - Core::DeviceRemoval UpdateInput() override; - - KeyboardMouse(const LPDIRECTINPUTDEVICE8 kb_device, const LPDIRECTINPUTDEVICE8 mo_device); - ~KeyboardMouse() override; - - std::string GetName() const override; - std::string GetSource() const override; - int GetSortPriority() const override; - bool IsVirtualDevice() const override; - -private: - void UpdateCursorInput(); - - const LPDIRECTINPUTDEVICE8 m_kb_device; - const LPDIRECTINPUTDEVICE8 m_mo_device; - - DWORD m_last_update; - State m_state_in; -}; -} // namespace ciface::DInput diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/NamedKeys.h b/Source/Core/InputCommon/ControllerInterface/DInput/NamedKeys.h deleted file mode 100644 index c6b9ebc1c2..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/DInput/NamedKeys.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2010 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#ifdef __MINGW32__ -#define DIK_PREVTRACK 0x98 -#endif - -{DIK_A, "A"}, {DIK_B, "B"}, {DIK_C, "C"}, {DIK_D, "D"}, {DIK_E, "E"}, {DIK_F, "F"}, {DIK_G, "G"}, - {DIK_H, "H"}, {DIK_I, "I"}, {DIK_J, "J"}, {DIK_K, "K"}, {DIK_L, "L"}, {DIK_M, "M"}, - {DIK_N, "N"}, {DIK_O, "O"}, {DIK_P, "P"}, {DIK_Q, "Q"}, {DIK_R, "R"}, {DIK_S, "S"}, - {DIK_T, "T"}, {DIK_U, "U"}, {DIK_V, "V"}, {DIK_W, "W"}, {DIK_X, "X"}, {DIK_Y, "Y"}, - {DIK_Z, "Z"}, {DIK_0, "0"}, {DIK_1, "1"}, {DIK_2, "2"}, {DIK_3, "3"}, {DIK_4, "4"}, - {DIK_5, "5"}, {DIK_6, "6"}, {DIK_7, "7"}, {DIK_8, "8"}, {DIK_9, "9"}, {DIK_UP, "UP"}, - {DIK_DOWN, "DOWN"}, {DIK_LEFT, "LEFT"}, {DIK_RIGHT, "RIGHT"}, {DIK_ABNT_C1, "ABNT_C1"}, - {DIK_ABNT_C2, "ABNT_C2"}, {DIK_ADD, "ADD"}, {DIK_APOSTROPHE, "APOSTROPHE"}, {DIK_APPS, "APPS"}, - {DIK_AT, "AT"}, {DIK_AX, "AX"}, {DIK_BACK, "BACK"}, {DIK_BACKSLASH, "BACKSLASH"}, - {DIK_CALCULATOR, "CALCULATOR"}, {DIK_CAPITAL, "CAPITAL"}, {DIK_COLON, "COLON"}, - {DIK_COMMA, "COMMA"}, {DIK_CONVERT, "CONVERT"}, {DIK_DECIMAL, "DECIMAL"}, - {DIK_DELETE, "DELETE"}, {DIK_DIVIDE, "DIVIDE"}, {DIK_EQUALS, "EQUALS"}, {DIK_ESCAPE, "ESCAPE"}, - {DIK_F1, "F1"}, {DIK_F2, "F2"}, {DIK_F3, "F3"}, {DIK_F4, "F4"}, {DIK_F5, "F5"}, {DIK_F6, "F6"}, - {DIK_F7, "F7"}, {DIK_F8, "F8"}, {DIK_F9, "F9"}, {DIK_F10, "F10"}, {DIK_F11, "F11"}, - {DIK_F12, "F12"}, {DIK_F13, "F13"}, {DIK_F14, "F14"}, {DIK_F15, "F15"}, {DIK_GRAVE, "GRAVE"}, - {DIK_HOME, "HOME"}, {DIK_END, "END"}, {DIK_INSERT, "INSERT"}, {DIK_KANA, "KANA"}, - {DIK_KANJI, "KANJI"}, {DIK_MAIL, "MAIL"}, {DIK_MEDIASELECT, "MEDIASELECT"}, - {DIK_MEDIASTOP, "MEDIASTOP"}, {DIK_MINUS, "MINUS"}, {DIK_MULTIPLY, "MULTIPLY"}, - {DIK_MUTE, "MUTE"}, {DIK_MYCOMPUTER, "MYCOMPUTER"}, {DIK_NEXTTRACK, "NEXTTRACK"}, - {DIK_NOCONVERT, "NOCONVERT"}, {DIK_NUMLOCK, "NUMLOCK"}, {DIK_NUMPAD0, "NUMPAD0"}, - {DIK_NUMPAD1, "NUMPAD1"}, {DIK_NUMPAD2, "NUMPAD2"}, {DIK_NUMPAD3, "NUMPAD3"}, - {DIK_NUMPAD4, "NUMPAD4"}, {DIK_NUMPAD5, "NUMPAD5"}, {DIK_NUMPAD6, "NUMPAD6"}, - {DIK_NUMPAD7, "NUMPAD7"}, {DIK_NUMPAD8, "NUMPAD8"}, {DIK_NUMPAD9, "NUMPAD9"}, - {DIK_NUMPADCOMMA, "NUMPADCOMMA"}, {DIK_NUMPADENTER, "NUMPADENTER"}, - {DIK_NUMPADEQUALS, "NUMPADEQUALS"}, {DIK_OEM_102, "OEM_102"}, {DIK_PAUSE, "PAUSE"}, - {DIK_PERIOD, "PERIOD"}, {DIK_PLAYPAUSE, "PLAYPAUSE"}, {DIK_POWER, "POWER"}, - {DIK_PREVTRACK, "PREVTRACK"}, {DIK_PRIOR, "PRIOR"}, {DIK_NEXT, "NEXT"}, {DIK_RETURN, "RETURN"}, - {DIK_LBRACKET, "LBRACKET"}, {DIK_RBRACKET, "RBRACKET"}, {DIK_LCONTROL, "LCONTROL"}, - {DIK_RCONTROL, "RCONTROL"}, {DIK_LMENU, "LMENU"}, {DIK_RMENU, "RMENU"}, {DIK_LSHIFT, "LSHIFT"}, - {DIK_RSHIFT, "RSHIFT"}, {DIK_LWIN, "LWIN"}, {DIK_RWIN, "RWIN"}, {DIK_SCROLL, "SCROLL"}, - {DIK_SEMICOLON, "SEMICOLON"}, {DIK_SLASH, "SLASH"}, {DIK_SLEEP, "SLEEP"}, {DIK_SPACE, "SPACE"}, - {DIK_STOP, "STOP"}, {DIK_SUBTRACT, "SUBTRACT"}, {DIK_SYSRQ, "SYSRQ"}, {DIK_TAB, "TAB"}, - {DIK_UNDERLINE, "UNDERLINE"}, {DIK_UNLABELED, "UNLABELED"}, {DIK_VOLUMEDOWN, "VOLUMEDOWN"}, - {DIK_VOLUMEUP, "VOLUMEUP"}, {DIK_WAKE, "WAKE"}, {DIK_WEBBACK, "WEBBACK"}, - {DIK_WEBFAVORITES, "WEBFAVORITES"}, {DIK_WEBFORWARD, "WEBFORWARD"}, {DIK_WEBHOME, "WEBHOME"}, - {DIK_WEBREFRESH, "WEBREFRESH"}, {DIK_WEBSEARCH, "WEBSEARCH"}, {DIK_WEBSTOP, "WEBSTOP"}, - {DIK_YEN, "YEN"}, diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/XInputFilter.cpp b/Source/Core/InputCommon/ControllerInterface/DInput/XInputFilter.cpp deleted file mode 100644 index 3d3d8049c0..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/DInput/XInputFilter.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2014 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "InputCommon/ControllerInterface/DInput/XInputFilter.h" - -#include -#include -#include - -#include -#include - -namespace ciface::DInput -{ -// Code for enumerating hardware devices that use the XINPUT device driver. -// The MSDN recommended code suffers from massive performance problems when using language packs, -// if the system and user languages differ then WMI Queries become incredibly slow (multiple -// seconds). This is more or less equivalent and much faster. -std::unordered_set GetXInputGUIDS() -{ - static const GUID s_GUID_devclass_HID = { - 0x745a17a0, 0x74d3, 0x11d0, {0xb6, 0xfe, 0x00, 0xa0, 0xc9, 0x0f, 0x57, 0xda}}; - std::unordered_set guids; - - // Enumerate everything under the "Human Interface Devices" tree in the Device Manager - // NOTE: Some devices show up multiple times due to sub-devices, we rely on the set to - // prevent duplicates. - HDEVINFO setup_enum = SetupDiGetClassDevsW(&s_GUID_devclass_HID, nullptr, nullptr, DIGCF_PRESENT); - if (setup_enum == INVALID_HANDLE_VALUE) - return guids; - - std::vector buffer(128); - SP_DEVINFO_DATA dev_info; - dev_info.cbSize = sizeof(SP_DEVINFO_DATA); - for (DWORD i = 0; SetupDiEnumDeviceInfo(setup_enum, i, &dev_info); ++i) - { - // Need to find the size of the data and set the buffer appropriately - DWORD buffer_size = 0; - while (!SetupDiGetDeviceRegistryPropertyW(setup_enum, &dev_info, SPDRP_HARDWAREID, nullptr, - reinterpret_cast(buffer.data()), - static_cast(buffer.size()), &buffer_size)) - { - if (buffer_size > buffer.size()) - buffer.resize(buffer_size); - else - break; - } - if (GetLastError() != ERROR_SUCCESS) - continue; - - // HARDWAREID is a REG_MULTI_SZ - // There are multiple strings separated by NULs, the list is ended by an empty string. - for (std::size_t j = 0; buffer[j]; j += std::wcslen(&buffer[j]) + 1) - { - // XINPUT devices have "IG_xx" embedded in their IDs which is what we look for. - if (!std::wcsstr(&buffer[j], L"IG_")) - continue; - - unsigned int vid = 0; - unsigned int pid = 0; - - // Extract Vendor and Product IDs for matching against DirectInput's device list. - wchar_t* pos = std::wcsstr(&buffer[j], L"VID_"); - if (!pos || !std::swscanf(pos, L"VID_%4X", &vid)) - continue; - pos = std::wcsstr(&buffer[j], L"PID_"); - if (!pos || !std::swscanf(pos, L"PID_%4X", &pid)) - continue; - - guids.insert(MAKELONG(vid, pid)); - break; - } - } - - SetupDiDestroyDeviceInfoList(setup_enum); - return guids; -} -} // namespace ciface::DInput diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/XInputFilter.h b/Source/Core/InputCommon/ControllerInterface/DInput/XInputFilter.h deleted file mode 100644 index 52911dabaa..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/DInput/XInputFilter.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2014 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -namespace ciface::DInput -{ -std::unordered_set GetXInputGUIDS(); -} // namespace ciface::DInput diff --git a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp deleted file mode 100644 index c057a27e95..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright 2014 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h" - -#include -#include - -#include "Common/Thread.h" - -namespace ciface::ForceFeedback -{ -// Template instantiation: -template class ForceFeedbackDevice::TypedForce; -template class ForceFeedbackDevice::TypedForce; -template class ForceFeedbackDevice::TypedForce; - -struct ForceType -{ - GUID guid; - const char* name; -}; - -static const ForceType force_type_names[] = { - {GUID_ConstantForce, "Constant"}, // DICONSTANTFORCE - {GUID_RampForce, "Ramp"}, // DIRAMPFORCE - {GUID_Square, "Square"}, // DIPERIODIC ... - {GUID_Sine, "Sine"}, - {GUID_Triangle, "Triangle"}, - {GUID_SawtoothUp, "Sawtooth Up"}, - {GUID_SawtoothDown, "Sawtooth Down"}, - //{GUID_Spring, "Spring"}, // DICUSTOMFORCE ... < I think - //{GUID_Damper, "Damper"}, - //{GUID_Inertia, "Inertia"}, - //{GUID_Friction, "Friction"}, -}; - -void ForceFeedbackDevice::DeInitForceFeedback() -{ - if (!m_run_thread.TestAndClear()) - return; - - SignalUpdateThread(); - m_update_thread.join(); -} - -void ForceFeedbackDevice::ThreadFunc() -{ - Common::SetCurrentThreadName("ForceFeedback update thread"); - - while (m_run_thread.IsSet()) - { - m_update_event.Wait(); - - for (auto output : Outputs()) - { - auto& force = *static_cast(output); - force.UpdateOutput(); - } - } - - for (auto output : Outputs()) - { - auto& force = *static_cast(output); - force.Release(); - } -} - -void ForceFeedbackDevice::SignalUpdateThread() -{ - m_update_event.Set(); -} - -bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, int axis_count) -{ - if (axis_count == 0) - return false; - - // We just use the X axis (for wheel left/right). - // Gamepads seem to not care which axis you use. - // These are temporary for creating the effect: - std::array rgdwAxes = {DIJOFS_X}; - std::array rglDirection = {-200}; - - DIEFFECT eff{}; - eff.dwSize = sizeof(eff); - eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; - eff.dwDuration = DI_SECONDS / 1000 * RUMBLE_LENGTH_MS; - eff.dwSamplePeriod = 0; - eff.dwGain = DI_FFNOMINALMAX; - eff.dwTriggerButton = DIEB_NOTRIGGER; - eff.dwTriggerRepeatInterval = 0; - eff.cAxes = DWORD(rgdwAxes.size()); - eff.rgdwAxes = rgdwAxes.data(); - eff.rglDirection = rglDirection.data(); - eff.dwStartDelay = 0; - - // Initialize parameters with zero force (their current state). - DICONSTANTFORCE diCF{}; - diCF.lMagnitude = 0; - DIRAMPFORCE diRF{}; - diRF.lStart = diRF.lEnd = 0; - DIPERIODIC diPE{}; - diPE.dwMagnitude = 0; - diPE.lOffset = 0; - diPE.dwPhase = 0; - diPE.dwPeriod = DI_SECONDS / 1000 * RUMBLE_PERIOD_MS; - - for (auto& f : force_type_names) - { - if (f.guid == GUID_ConstantForce) - { - eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE); - eff.lpvTypeSpecificParams = &diCF; - } - else if (f.guid == GUID_RampForce) - { - eff.cbTypeSpecificParams = sizeof(DIRAMPFORCE); - eff.lpvTypeSpecificParams = &diRF; - } - else - { - // All other forces need periodic parameters: - eff.cbTypeSpecificParams = sizeof(DIPERIODIC); - eff.lpvTypeSpecificParams = &diPE; - } - - LPDIRECTINPUTEFFECT pEffect; - if (SUCCEEDED(device->CreateEffect(f.guid, &eff, &pEffect, nullptr))) - { - if (f.guid == GUID_ConstantForce) - AddOutput(new ForceConstant(this, f.name, pEffect, diCF)); - else if (f.guid == GUID_RampForce) - AddOutput(new ForceRamp(this, f.name, pEffect, diRF)); - else - AddOutput(new ForcePeriodic(this, f.name, pEffect, diPE)); - } - } - - // Disable autocentering: - if (Outputs().size()) - { - DIPROPDWORD dipdw; - dipdw.diph.dwSize = sizeof(DIPROPDWORD); - dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); - dipdw.diph.dwObj = 0; - dipdw.diph.dwHow = DIPH_DEVICE; - dipdw.dwData = DIPROPAUTOCENTER_OFF; - device->SetProperty(DIPROP_AUTOCENTER, &dipdw.diph); - - m_run_thread.Set(); - m_update_thread = std::thread(&ForceFeedbackDevice::ThreadFunc, this); - } - - return true; -} - -template -void ForceFeedbackDevice::TypedForce

::PlayEffect() -{ - DIEFFECT eff{}; - eff.dwSize = sizeof(eff); - eff.cbTypeSpecificParams = sizeof(m_params); - eff.lpvTypeSpecificParams = &m_params; - - m_effect->SetParameters(&eff, DIEP_START | DIEP_TYPESPECIFICPARAMS); -} - -template -void ForceFeedbackDevice::TypedForce

::StopEffect() -{ - m_effect->Stop(); -} - -template <> -bool ForceFeedbackDevice::ForceConstant::UpdateParameters(int magnitude) -{ - const auto old_magnitude = m_params.lMagnitude; - - m_params.lMagnitude = magnitude; - - return old_magnitude != m_params.lMagnitude; -} - -template <> -bool ForceFeedbackDevice::ForceRamp::UpdateParameters(int magnitude) -{ - const auto old_magnitude = m_params.lStart; - - // Having the same "start" and "end" here is a bit odd.. - // But ramp forces don't really make sense for our rumble effects anyways.. - m_params.lStart = m_params.lEnd = magnitude; - - return old_magnitude != m_params.lStart; -} - -template <> -bool ForceFeedbackDevice::ForcePeriodic::UpdateParameters(int magnitude) -{ - const auto old_magnitude = m_params.dwMagnitude; - - m_params.dwMagnitude = magnitude; - - return old_magnitude != m_params.dwMagnitude; -} - -template -ForceFeedbackDevice::TypedForce

::TypedForce(ForceFeedbackDevice* parent, const char* name, - LPDIRECTINPUTEFFECT effect, const P& params) - : Force(parent, name, effect), m_params(params) -{ -} - -template -void ForceFeedbackDevice::TypedForce

::UpdateEffect(int magnitude) -{ - if (UpdateParameters(magnitude)) - { - if (magnitude) - PlayEffect(); - else - StopEffect(); - } -} - -std::string ForceFeedbackDevice::Force::GetName() const -{ - return m_name; -} - -ForceFeedbackDevice::Force::Force(ForceFeedbackDevice* parent, const char* name, - LPDIRECTINPUTEFFECT effect) - : m_effect(effect), m_parent(*parent), m_name(name), m_desired_magnitude() -{ -} - -void ForceFeedbackDevice::Force::SetState(ControlState state) -{ - const auto new_val = int(DI_FFNOMINALMAX * state); - - if (m_desired_magnitude.exchange(new_val) != new_val) - m_parent.SignalUpdateThread(); -} - -void ForceFeedbackDevice::Force::UpdateOutput() -{ - UpdateEffect(m_desired_magnitude); -} - -void ForceFeedbackDevice::Force::Release() -{ - // This isn't in the destructor because it should happen before the device is released. - m_effect->Stop(); - m_effect->Unload(); - m_effect->Release(); -} -} // namespace ciface::ForceFeedback diff --git a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h deleted file mode 100644 index dabc1d5c63..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2014 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include - -#include - -#include "Common/Event.h" -#include "Common/Flag.h" - -#include "InputCommon/ControllerInterface/CoreDevice.h" -#include "InputCommon/ControllerInterface/DInput/DInput8.h" - -namespace ciface::ForceFeedback -{ -class ForceFeedbackDevice : public Core::Device -{ -public: - bool InitForceFeedback(const LPDIRECTINPUTDEVICE8, int axis_count); - void DeInitForceFeedback(); - -private: - void ThreadFunc(); - - class Force : public Output - { - public: - Force(ForceFeedbackDevice* parent, const char* name, LPDIRECTINPUTEFFECT effect); - - void UpdateOutput(); - void Release(); - - void SetState(ControlState state) override; - std::string GetName() const override; - - protected: - const LPDIRECTINPUTEFFECT m_effect; - - private: - virtual void UpdateEffect(int magnitude) = 0; - - ForceFeedbackDevice& m_parent; - const char* const m_name; - std::atomic m_desired_magnitude; - }; - - template - class TypedForce : public Force - { - public: - TypedForce(ForceFeedbackDevice* parent, const char* name, LPDIRECTINPUTEFFECT effect, - const P& params); - - private: - void UpdateEffect(int magnitude) override; - - // Returns true if parameters changed. - bool UpdateParameters(int magnitude); - - void PlayEffect(); - void StopEffect(); - - P m_params = {}; - }; - - void SignalUpdateThread(); - - typedef TypedForce ForceConstant; - typedef TypedForce ForceRamp; - typedef TypedForce ForcePeriodic; - - std::thread m_update_thread; - Common::Event m_update_event; - Common::Flag m_run_thread; -}; -} // namespace ciface::ForceFeedback diff --git a/Source/Core/InputCommon/ControllerInterface/Pipes/Pipes.cpp b/Source/Core/InputCommon/ControllerInterface/Pipes/Pipes.cpp deleted file mode 100644 index ce0ddb3314..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/Pipes/Pipes.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2015 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "InputCommon/ControllerInterface/Pipes/Pipes.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Common/FileUtil.h" -#include "Common/StringUtil.h" -#include "InputCommon/ControllerInterface/ControllerInterface.h" - -namespace ciface::Pipes -{ -static const std::array s_button_tokens{ - {"A", "B", "X", "Y", "Z", "START", "L", "R", "D_UP", "D_DOWN", "D_LEFT", "D_RIGHT"}}; - -static const std::array s_shoulder_tokens{{"L", "R"}}; - -static const std::array s_axis_tokens{{"MAIN", "C"}}; - -static double StringToDouble(const std::string& text) -{ - std::istringstream is(text); - // ignore current locale - is.imbue(std::locale::classic()); - double result; - is >> result; - return result; -} - -class InputBackend final : public ciface::InputBackend -{ -public: - using ciface::InputBackend::InputBackend; - void PopulateDevices() override; -}; - -std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface) -{ - return std::make_unique(controller_interface); -} - -void InputBackend::PopulateDevices() -{ - // Search the Pipes directory for files that we can open in read-only, - // non-blocking mode. The device name is the virtual name of the file. - File::FSTEntry fst; - std::string dir_path = File::GetUserPath(D_PIPES_IDX); - if (!File::Exists(dir_path)) - return; - fst = File::ScanDirectoryTree(dir_path, false); - if (!fst.isDirectory) - return; - for (unsigned int i = 0; i < fst.size; ++i) - { - const File::FSTEntry& child = fst.children[i]; - if (child.isDirectory) - continue; - int fd = open(child.physicalName.c_str(), O_RDONLY | O_NONBLOCK); - if (fd < 0) - continue; - g_controller_interface.AddDevice(std::make_shared(fd, child.virtualName)); - } -} - -PipeDevice::PipeDevice(int fd, std::string name) : m_fd(fd), m_name(std::move(name)) -{ - for (const auto& tok : s_button_tokens) - { - PipeInput* btn = new PipeInput("Button " + tok); - AddInput(btn); - m_buttons[tok] = btn; - } - for (const auto& tok : s_shoulder_tokens) - { - AddAxis(tok, 0.0); - } - for (const auto& tok : s_axis_tokens) - { - AddAxis(tok + " X", 0.5); - AddAxis(tok + " Y", 0.5); - } -} - -PipeDevice::~PipeDevice() -{ - close(m_fd); -} - -Core::DeviceRemoval PipeDevice::UpdateInput() -{ - // Read any pending characters off the pipe. If we hit a newline, - // then dequeue a command off the front of m_buf and parse it. - char buf[32]; - ssize_t bytes_read = read(m_fd, buf, sizeof buf); - while (bytes_read > 0) - { - m_buf.append(buf, bytes_read); - bytes_read = read(m_fd, buf, sizeof buf); - } - std::size_t newline = m_buf.find("\n"); - while (newline != std::string::npos) - { - std::string command = m_buf.substr(0, newline); - ParseCommand(command); - m_buf.erase(0, newline + 1); - newline = m_buf.find("\n"); - } - return Core::DeviceRemoval::Keep; -} - -void PipeDevice::AddAxis(const std::string& name, double value) -{ - // Dolphin uses separate axes for left/right, which complicates things. - PipeInput* ax_hi = new PipeInput("Axis " + name + " +"); - ax_hi->SetState(value); - PipeInput* ax_lo = new PipeInput("Axis " + name + " -"); - ax_lo->SetState(value); - m_axes[name + " +"] = ax_hi; - m_axes[name + " -"] = ax_lo; - AddFullAnalogSurfaceInputs(ax_lo, ax_hi); -} - -void PipeDevice::SetAxis(const std::string& entry, double value) -{ - value = std::clamp(value, 0.0, 1.0); - double hi = std::max(0.0, value - 0.5) * 2.0; - double lo = (0.5 - std::min(0.5, value)) * 2.0; - auto search_hi = m_axes.find(entry + " +"); - if (search_hi != m_axes.end()) - search_hi->second->SetState(hi); - auto search_lo = m_axes.find(entry + " -"); - if (search_lo != m_axes.end()) - search_lo->second->SetState(lo); -} - -void PipeDevice::ParseCommand(const std::string& command) -{ - const std::vector tokens = SplitString(command, ' '); - if (tokens.size() < 2 || tokens.size() > 4) - return; - if (tokens[0] == "PRESS" || tokens[0] == "RELEASE") - { - auto search = m_buttons.find(tokens[1]); - if (search != m_buttons.end()) - search->second->SetState(tokens[0] == "PRESS" ? 1.0 : 0.0); - } - else if (tokens[0] == "SET") - { - if (tokens.size() == 3) - { - double value = StringToDouble(tokens[2]); - SetAxis(tokens[1], (value / 2.0) + 0.5); - } - else if (tokens.size() == 4) - { - double x = StringToDouble(tokens[2]); - double y = StringToDouble(tokens[3]); - SetAxis(tokens[1] + " X", x); - SetAxis(tokens[1] + " Y", y); - } - } -} -} // namespace ciface::Pipes diff --git a/Source/Core/InputCommon/ControllerInterface/Pipes/Pipes.h b/Source/Core/InputCommon/ControllerInterface/Pipes/Pipes.h deleted file mode 100644 index d9e2d7b4b7..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/Pipes/Pipes.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2015 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include - -#include "InputCommon/ControllerInterface/ControllerInterface.h" - -namespace ciface::Pipes -{ -// To create a piped controller input, create a named pipe in the -// Pipes directory and write commands out to it. Commands are separated -// by a newline character, with spaces separating command tokens. -// Command syntax is as follows, where curly brackets are one-of and square -// brackets are inclusive numeric ranges. Cases are sensitive. Numeric inputs -// are clamped to [0, 1] and otherwise invalid commands are discarded. -// {PRESS, RELEASE} {A, B, X, Y, Z, START, L, R, D_UP, D_DOWN, D_LEFT, D_RIGHT} -// SET {L, R} [0, 1] -// SET {MAIN, C} [0, 1] [0, 1] - -std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface); - -class PipeDevice : public Core::Device -{ -public: - PipeDevice(int fd, std::string name); - ~PipeDevice(); - - Core::DeviceRemoval UpdateInput() override; - std::string GetName() const override { return m_name; } - std::string GetSource() const override { return "Pipe"; } - -private: - class PipeInput : public Input - { - public: - PipeInput(const std::string& name) : m_name(name), m_state(0.0) {} - std::string GetName() const override { return m_name; } - ControlState GetState() const override { return m_state; } - void SetState(ControlState state) { m_state = state; } - - private: - const std::string m_name; - ControlState m_state; - }; - - void AddAxis(const std::string& name, double value); - void ParseCommand(const std::string& command); - void SetAxis(const std::string& entry, double value); - - const int m_fd; - const std::string m_name; - std::string m_buf; - std::map m_buttons; - std::map m_axes; -}; -} // namespace ciface::Pipes diff --git a/Source/Core/InputCommon/ControllerInterface/Quartz/Quartz.h b/Source/Core/InputCommon/ControllerInterface/Quartz/Quartz.h deleted file mode 100644 index b2c6906e42..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/Quartz/Quartz.h +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include "InputCommon/ControllerInterface/InputBackend.h" - -namespace ciface::Quartz -{ -std::string GetSourceName(); - -std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface); -} // namespace ciface::Quartz diff --git a/Source/Core/InputCommon/ControllerInterface/Quartz/Quartz.mm b/Source/Core/InputCommon/ControllerInterface/Quartz/Quartz.mm deleted file mode 100644 index e0a504604a..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/Quartz/Quartz.mm +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "InputCommon/ControllerInterface/Quartz/Quartz.h" - -#include "InputCommon/ControllerInterface/ControllerInterface.h" -#include "InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h" - -namespace ciface::Quartz -{ -std::string GetSourceName() -{ - return "Quartz"; -} - -class InputBackend final : public ciface::InputBackend -{ -public: - using ciface::InputBackend::InputBackend; - void PopulateDevices() override; - void HandleWindowChange() override; -}; - -std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface) -{ - return std::make_unique(controller_interface); -} - -void InputBackend::HandleWindowChange() -{ - const std::string source_name = GetSourceName(); - GetControllerInterface().RemoveDevice( - [&](const auto* dev) { return dev->GetSource() == source_name; }, true); - - PopulateDevices(); -} - -void InputBackend::PopulateDevices() -{ - const WindowSystemInfo wsi = GetControllerInterface().GetWindowSystemInfo(); - if (wsi.type != WindowSystemType::MacOS) - return; - - GetControllerInterface().AddDevice(std::make_shared(wsi.render_window)); -} - -} // namespace ciface::Quartz diff --git a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h b/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h deleted file mode 100644 index 7f526a07dd..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include "Common/Matrix.h" -#include "InputCommon/ControllerInterface/CoreDevice.h" - -#ifdef __OBJC__ -@class DolWindowPositionObserver; -#else -class DolWindowPositionObserver; -#endif - -namespace ciface::Quartz -{ -std::string KeycodeToName(const CGKeyCode keycode); - -class KeyboardAndMouse : public Core::Device -{ -private: - class Key : public Input - { - public: - explicit Key(CGKeyCode keycode); - std::string GetName() const override; - ControlState GetState() const override; - - private: - CGKeyCode m_keycode; - std::string m_name; - }; - - class Cursor : public Input - { - public: - Cursor(u8 index, const float& axis, const bool positive) - : m_axis(axis), m_index(index), m_positive(positive) - { - } - std::string GetName() const override; - bool IsDetectable() const override { return false; } - ControlState GetState() const override; - - private: - const float& m_axis; - const u8 m_index; - const bool m_positive; - }; - - class Button : public Input - { - public: - explicit Button(CGMouseButton button) : m_button(button) {} - std::string GetName() const override; - ControlState GetState() const override; - - private: - CGMouseButton m_button; - }; - -public: - Core::DeviceRemoval UpdateInput() override; - - explicit KeyboardAndMouse(void* view); - ~KeyboardAndMouse() override; - - std::string GetName() const override; - std::string GetSource() const override; - int GetSortPriority() const override; - -private: - void MainThreadInitialization(void* view); - - Common::Vec2 m_cursor; - - DolWindowPositionObserver* m_window_pos_observer; -}; -} // namespace ciface::Quartz diff --git a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.mm b/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.mm deleted file mode 100644 index 5f0022c79b..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.mm +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h" - -#include -#include - -#include -#include - -#include "Core/Host.h" - -#include "InputCommon/ControllerInterface/ControllerInterface.h" -#include "InputCommon/ControllerInterface/Quartz/Quartz.h" - -/// Helper class to get window position data from threads other than the main thread -@interface DolWindowPositionObserver : NSObject - -- (instancetype)initWithView:(NSView*)view; -@property(readonly) NSRect frame; - -@end - -@implementation DolWindowPositionObserver -{ - NSView* _view; - NSWindow* _window; - NSRect _frame; - std::mutex _mtx; -} - -- (NSRect)calcFrame -{ - return [_window convertRectToScreen:[_view frame]]; -} - -- (instancetype)initWithView:(NSView*)view -{ - self = [super init]; - if (self) - { - _view = view; - _window = [view window]; - _frame = [self calcFrame]; - [_window addObserver:self forKeyPath:@"frame" options:0 context:nil]; - } - return self; -} - -- (NSRect)frame -{ - std::lock_guard guard(_mtx); - return _frame; -} - -- (void)observeValueForKeyPath:(NSString*)keyPath - ofObject:(id)object - change:(NSDictionary*)change - context:(void*)context -{ - if (object == _window) - { - NSRect new_frame = [self calcFrame]; - std::lock_guard guard(_mtx); - _frame = new_frame; - } -} - -- (void)dealloc -{ - [_window removeObserver:self forKeyPath:@"frame"]; -} - -@end - -namespace ciface::Quartz -{ -std::string KeycodeToName(const CGKeyCode keycode) -{ - static const std::map named_keys = { - {kVK_ANSI_A, "A"}, - {kVK_ANSI_B, "B"}, - {kVK_ANSI_C, "C"}, - {kVK_ANSI_D, "D"}, - {kVK_ANSI_E, "E"}, - {kVK_ANSI_F, "F"}, - {kVK_ANSI_G, "G"}, - {kVK_ANSI_H, "H"}, - {kVK_ANSI_I, "I"}, - {kVK_ANSI_J, "J"}, - {kVK_ANSI_K, "K"}, - {kVK_ANSI_L, "L"}, - {kVK_ANSI_M, "M"}, - {kVK_ANSI_N, "N"}, - {kVK_ANSI_O, "O"}, - {kVK_ANSI_P, "P"}, - {kVK_ANSI_Q, "Q"}, - {kVK_ANSI_R, "R"}, - {kVK_ANSI_S, "S"}, - {kVK_ANSI_T, "T"}, - {kVK_ANSI_U, "U"}, - {kVK_ANSI_V, "V"}, - {kVK_ANSI_W, "W"}, - {kVK_ANSI_X, "X"}, - {kVK_ANSI_Y, "Y"}, - {kVK_ANSI_Z, "Z"}, - {kVK_ANSI_1, "1"}, - {kVK_ANSI_2, "2"}, - {kVK_ANSI_3, "3"}, - {kVK_ANSI_4, "4"}, - {kVK_ANSI_5, "5"}, - {kVK_ANSI_6, "6"}, - {kVK_ANSI_7, "7"}, - {kVK_ANSI_8, "8"}, - {kVK_ANSI_9, "9"}, - {kVK_ANSI_0, "0"}, - {kVK_Return, "Return"}, - {kVK_Escape, "Escape"}, - {kVK_Delete, "Backspace"}, - {kVK_Tab, "Tab"}, - {kVK_Space, "Space"}, - {kVK_ANSI_Minus, "-"}, - {kVK_ANSI_Equal, "="}, - {kVK_ANSI_LeftBracket, "["}, - {kVK_ANSI_RightBracket, "]"}, - {kVK_ANSI_Backslash, "\\"}, - {kVK_ANSI_Semicolon, ";"}, - {kVK_ANSI_Quote, "'"}, - {kVK_ANSI_Grave, "Tilde"}, - {kVK_ANSI_Comma, ","}, - {kVK_ANSI_Period, "."}, - {kVK_ANSI_Slash, "/"}, - {kVK_CapsLock, "Caps Lock"}, - {kVK_F1, "F1"}, - {kVK_F2, "F2"}, - {kVK_F3, "F3"}, - {kVK_F4, "F4"}, - {kVK_F5, "F5"}, - {kVK_F6, "F6"}, - {kVK_F7, "F7"}, - {kVK_F8, "F8"}, - {kVK_F9, "F9"}, - {kVK_F10, "F10"}, - {kVK_F11, "F11"}, - {kVK_F12, "F12"}, - {kVK_Home, "Home"}, - {kVK_PageUp, "Page Up"}, - {kVK_ForwardDelete, "Delete"}, - {kVK_End, "End"}, - {kVK_PageDown, "Page Down"}, - {kVK_RightArrow, "Right Arrow"}, - {kVK_LeftArrow, "Left Arrow"}, - {kVK_DownArrow, "Down Arrow"}, - {kVK_UpArrow, "Up Arrow"}, - {kVK_ANSI_KeypadDivide, "Keypad /"}, - {kVK_ANSI_KeypadMultiply, "Keypad *"}, - {kVK_ANSI_KeypadMinus, "Keypad -"}, - {kVK_ANSI_KeypadPlus, "Keypad +"}, - {kVK_ANSI_KeypadEnter, "Keypad Enter"}, - {kVK_ANSI_Keypad1, "Keypad 1"}, - {kVK_ANSI_Keypad2, "Keypad 2"}, - {kVK_ANSI_Keypad3, "Keypad 3"}, - {kVK_ANSI_Keypad4, "Keypad 4"}, - {kVK_ANSI_Keypad5, "Keypad 5"}, - {kVK_ANSI_Keypad6, "Keypad 6"}, - {kVK_ANSI_Keypad7, "Keypad 7"}, - {kVK_ANSI_Keypad8, "Keypad 8"}, - {kVK_ANSI_Keypad9, "Keypad 9"}, - {kVK_ANSI_Keypad0, "Keypad 0"}, - {kVK_ANSI_KeypadDecimal, "Keypad ."}, - {kVK_ANSI_KeypadEquals, "Keypad ="}, - {kVK_Control, "Left Control"}, - {kVK_Shift, "Left Shift"}, - {kVK_Option, "Left Alt"}, - {kVK_Command, "Command"}, - {kVK_RightControl, "Right Control"}, - {kVK_RightShift, "Right Shift"}, - {kVK_RightOption, "Right Alt"}, - }; - - if (named_keys.contains(keycode)) - return named_keys.at(keycode); - else - return "Key " + std::to_string(keycode); -} - -KeyboardAndMouse::Key::Key(CGKeyCode keycode) : m_keycode(keycode), m_name(KeycodeToName(keycode)) -{ -} - -ControlState KeyboardAndMouse::Key::GetState() const -{ - return CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, m_keycode) != 0; -} - -std::string KeyboardAndMouse::Key::GetName() const -{ - return m_name; -} - -KeyboardAndMouse::KeyboardAndMouse(void* view) -{ - // All keycodes in are 0x7e or lower. If you notice - // keys that aren't being recognized, bump this number up! - for (int keycode = 0; keycode < 0x80; ++keycode) - AddInput(new Key(keycode)); - - // Add combined left/right modifiers with consistent naming across platforms. - AddCombinedInput("Alt", {"Left Alt", "Right Alt"}); - AddCombinedInput("Shift", {"Left Shift", "Right Shift"}); - AddCombinedInput("Ctrl", {"Left Control", "Right Control"}); - - // PopulateDevices may be called on the Emuthread, so we need to ensure that - // these UI APIs are only ever called on the main thread. - if ([NSThread isMainThread]) - MainThreadInitialization(view); - else - dispatch_sync(dispatch_get_main_queue(), [this, view] { MainThreadInitialization(view); }); - - // cursor, with a hax for-loop - for (unsigned int i = 0; i < 4; ++i) - AddInput(new Cursor(!!(i & 2), (&m_cursor.x)[i / 2], !!(i & 1))); - - AddInput(new Button(kCGMouseButtonLeft)); - AddInput(new Button(kCGMouseButtonRight)); - AddInput(new Button(kCGMouseButtonCenter)); -} - -// Very important that this is here -// C++ and ObjC++ have different views of the header, and only ObjC++'s will deallocate properly -KeyboardAndMouse::~KeyboardAndMouse() = default; - -void KeyboardAndMouse::MainThreadInitialization(void* view) -{ - NSView* cocoa_view = (__bridge NSView*)view; - m_window_pos_observer = [[DolWindowPositionObserver alloc] initWithView:cocoa_view]; -} - -Core::DeviceRemoval KeyboardAndMouse::UpdateInput() -{ - NSRect bounds = [m_window_pos_observer frame]; - - const double window_width = std::max(bounds.size.width, 1.0); - const double window_height = std::max(bounds.size.height, 1.0); - - if (g_controller_interface.IsMouseCenteringRequested() && - (Host_RendererHasFocus() || Host_TASInputHasFocus())) - { - m_cursor.x = 0; - m_cursor.y = 0; - - const CGPoint window_center_global_coordinates = - CGPointMake(bounds.origin.x + window_width / 2.0, bounds.origin.y + window_height / 2.0); - CGWarpMouseCursorPosition(window_center_global_coordinates); - // Without this line there is a short but obvious delay after centering the cursor before it can - // be moved again - CGAssociateMouseAndMouseCursorPosition(true); - - g_controller_interface.SetMouseCenteringRequested(false); - } - else if (!Host_TASInputHasFocus()) - { - // When a TAS Input window has focus and "Enable Controller Input" is checked most types of - // input should be read normally as if the render window had focus instead. The cursor is an - // exception, as otherwise using the mouse to set any control in the TAS Input window will also - // update the Wii IR value (or any other input controlled by the cursor). - - NSPoint loc = [NSEvent mouseLocation]; - - const auto window_scale = g_controller_interface.GetWindowInputScale(); - - loc.x -= bounds.origin.x; - loc.y -= bounds.origin.y; - m_cursor.x = (loc.x / window_width * 2 - 1.0) * window_scale.x; - m_cursor.y = (loc.y / window_height * 2 - 1.0) * -window_scale.y; - } - - return Core::DeviceRemoval::Keep; -} - -std::string KeyboardAndMouse::GetName() const -{ - return "Keyboard & Mouse"; -} - -std::string KeyboardAndMouse::GetSource() const -{ - return Quartz::GetSourceName(); -} - -int KeyboardAndMouse::GetSortPriority() const -{ - return DEFAULT_DEVICE_SORT_PRIORITY; -} - -ControlState KeyboardAndMouse::Cursor::GetState() const -{ - return std::max(0.0, ControlState(m_axis) / (m_positive ? 1.0 : -1.0)); -} - -ControlState KeyboardAndMouse::Button::GetState() const -{ - return CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, m_button) != 0; -} - -std::string KeyboardAndMouse::Cursor::GetName() const -{ - char tmpstr[] = "Cursor .."; - tmpstr[7] = (char)('X' + m_index); - tmpstr[8] = (m_positive ? '+' : '-'); - return tmpstr; -} - -std::string KeyboardAndMouse::Button::GetName() const -{ - if (m_button == kCGMouseButtonLeft) - return "Left Click"; - if (m_button == kCGMouseButtonCenter) - return "Middle Click"; - if (m_button == kCGMouseButtonRight) - return "Right Click"; - return std::string("Click ") + char('0' + m_button); -} -} // namespace ciface::Quartz diff --git a/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp b/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp deleted file mode 100644 index fe897f8bc5..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp +++ /dev/null @@ -1,835 +0,0 @@ -// Copyright 2020 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "InputCommon/ControllerInterface/WGInput/WGInput.h" - -#include -#include -#include -#include - -// For CoGetApartmentType -#include -#pragma comment(lib, "ole32") - -// NOTE: winrt translates com failures to c++ exceptions, so we must use try/catch in this file to -// prevent possible errors from escaping and terminating Dolphin. -#ifdef _MSC_VER -#include -#include -#include -#include -#include -#include -#else -#ifdef __MINGW32__ -// for: https://github.com/msys2/MINGW-packages/issues/22160 -#define ____FIReference_1_boolean_INTERFACE_DEFINED__ -enum BatteryStatus -{ - BatteryStatus_NotPresent = 0, - BatteryStatus_Discharging = 1, - BatteryStatus_Idle = 2, - BatteryStatus_Charging = 3 -}; -namespace ABI -{ -namespace Windows -{ -namespace Storage -{ -enum KnownFoldersAccessStatus -{ - KnownFoldersAccessStatus_Denied = 0, - KnownFoldersAccessStatus_Allowed = 1 -}; -} // namespace Storage -} // namespace Windows -} // namespace ABI -#include -#include -#include -#include -#include -#include -#endif -#endif - -#pragma comment(lib, "windowsapp") - -#include - -#include "Common/HRWrap.h" -#include "Common/Logging/Log.h" -#include "Common/ScopeGuard.h" -#include "Common/StringUtil.h" -#include "Common/WorkQueueThread.h" -#include "InputCommon/ControllerInterface/ControllerInterface.h" - -namespace WGI = winrt::Windows::Gaming::Input; -namespace Haptics = winrt::Windows::Devices::Haptics; -using winrt::Windows::Foundation::Collections::IVectorView; - -namespace ciface::WGInput -{ -static constexpr std::string_view SOURCE_NAME = "WGInput"; - -// These names correspond to the values of the GameControllerButtonLabel enum. -// "None" is not used. -// There are some overlapping names assuming no device exposes both -// GameControllerButtonLabel::XboxLeftBumper and GameControllerButtonLabel::LeftBumper. -// If needed we can prepend "Xbox" to relevant input names on conflict in the future. -static constexpr std::array wgi_button_names = { - "None", "Back", "Start", "Menu", "View", "Pad N", - "Pad S", "Pad W", "Pad E", "Button A", "Button B", "Button X", - "Button Y", "Bumper L", "Trigger L", "Thumb L", "Bumper R", "Trigger R", - "Thumb R", "Paddle 1", "Paddle 2", "Paddle 3", "Paddle 4", "Mode", - "Select", "Menu", "View", "Back", "Start", "Options", - "Share", "Pad N", "Pad S", "Pad W", "Pad E", "Letter A", - "Letter B", "Letter C", "Letter L", "Letter R", "Letter X", "Letter Y", - "Letter Z", "Cross", "Circle", "Square", "Triangle", "Bumper L", - "Trigger L", "Thumb L", "Left 1", "Left 2", "Left 3", "Bumper R", - "Trigger R", "Thumb R", "Right 1", "Right 2", "Right 3", "Paddle 1", - "Paddle 2", "Paddle 3", "Paddle 4", "Plus", "Minus", "Down Left Arrow", - "Dial L", "Dial R", "Suspension", -}; - -template -struct MemberName -{ - M T::* ptr; - const char* name; -}; - -static constexpr MemberName gamepad_trigger_names[] = { - {&WGI::GamepadReading::LeftTrigger, "Trigger L"}, - {&WGI::GamepadReading::RightTrigger, "Trigger R"}}; - -static constexpr MemberName gamepad_axis_names[] = { - {&WGI::GamepadReading::LeftThumbstickX, "Left X"}, - {&WGI::GamepadReading::LeftThumbstickY, "Left Y"}, - {&WGI::GamepadReading::RightThumbstickX, "Right X"}, - {&WGI::GamepadReading::RightThumbstickY, "Right Y"}}; - -static constexpr MemberName gamepad_motor_names[] = { - {&WGI::GamepadVibration::LeftMotor, "Motor L"}, - {&WGI::GamepadVibration::RightMotor, "Motor R"}, - {&WGI::GamepadVibration::LeftTrigger, "Trigger L"}, - {&WGI::GamepadVibration::RightTrigger, "Trigger R"}}; - -class Device : public Core::Device -{ -public: - Device(std::string name, WGI::RawGameController raw_controller, WGI::Gamepad gamepad) - : m_name(std::move(name)), m_raw_controller(raw_controller), m_gamepad(gamepad) - { - // Buttons: - PopulateButtons(); - - // Add inputs for IGamepad interface if available. - if (m_gamepad) - { - // Axes: - for (auto& axis : gamepad_axis_names) - { - AddInput(new NamedAxis(&(m_gamepad_reading.*axis.ptr), 0.0, -1.0, axis.name)); - AddInput(new NamedAxis(&(m_gamepad_reading.*axis.ptr), 0.0, +1.0, axis.name)); - } - - // Triggers: - for (auto& trigger : gamepad_trigger_names) - AddInput(new NamedTrigger(&(m_gamepad_reading.*trigger.ptr), trigger.name)); - - // Motors: - for (auto& motor : gamepad_motor_names) - AddOutput(new NamedMotor(&(m_state_out.*motor.ptr), motor.name, this)); - } - - // Add IRawGameController's axes if IGamepad is not available. - // This may need to change if some devices expose additional axes. - // Maybe we can determine which additional axes are not in IGamepad's collection. - const bool use_raw_controller_axes = !m_gamepad; - - if (use_raw_controller_axes) - { - try - { - // Axes: - m_axes.resize(m_raw_controller.AxisCount()); - - u32 i = 0; - for (auto& axis : m_axes) - { - AddFullAnalogSurfaceInputs(new IndexedAxis(&axis, 0.5, +0.5, i), - new IndexedAxis(&axis, 0.5, -0.5, i)); - ++i; - } - } - catch (winrt::hresult_error) - { - // If AxisCount failed, m_axes will remain zero-sized; nothing to do. - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Error populating axes"); - } - } - - // Apparently some devices (e.g. DS4) provide the IGameController interface - // but expose the dpad only on a switch (IRawGameController interface). - // We'll need to add switches regardless of available interfaces. - constexpr bool use_raw_controller_switches = true; - - // Switches (Hats): - if (use_raw_controller_switches) - { - try - { - m_switches.resize(m_raw_controller.SwitchCount()); - // Accumulate switch kinds first, to ensure no inputs are added if there is any error. - std::vector switch_kinds; - for (u32 i = 0; i < m_switches.size(); i++) - switch_kinds.push_back(m_raw_controller.GetSwitchKind(i)); - - u32 i = 0; - for (auto& swtch : m_switches) - { - using gcsp = WGI::GameControllerSwitchPosition; - - AddInput(new IndexedSwitch(&swtch, i, gcsp::Up)); - AddInput(new IndexedSwitch(&swtch, i, gcsp::Down)); - - if (switch_kinds[i] != WGI::GameControllerSwitchKind::TwoWay) - { - // If it's not a "two-way" switch (up/down only) then add the left/right inputs. - AddInput(new IndexedSwitch(&swtch, i, gcsp::Left)); - AddInput(new IndexedSwitch(&swtch, i, gcsp::Right)); - } - - ++i; - } - } - catch (winrt::hresult_error) - { - // Safe as no inputs will have been added. - m_switches.clear(); - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Error populating switches"); - } - } - - // Haptics: - PopulateHaptics(); - - // Battery: - if (UpdateBatteryLevel()) - AddInput(new Battery(&m_battery_level)); - } - - int GetSortPriority() const override { return -1; } - - const WGI::RawGameController GetRawGameController() const { return m_raw_controller; } - -private: - using ButtonValueType = u8; - - class Button : public Input - { - public: - explicit Button(const ButtonValueType* button) : m_button(*button) {} - ControlState GetState() const override { return ControlState(m_button != 0); } - - private: - const ButtonValueType& m_button; - }; - - // A button with one of the "labels" that WGI provides. - class NamedButton final : public Button - { - public: - NamedButton(const ButtonValueType* button, std::string_view name) : Button(button), m_name(name) - { - } - std::string GetName() const override { return std::string(m_name); } - - private: - const std::string_view m_name; - }; - - // A button with no label so we name it by its index. - class IndexedButton final : public Button - { - public: - IndexedButton(const ButtonValueType* button, u32 index) : Button(button), m_index(index) {} - std::string GetName() const override { return fmt::format("Button {}", m_index); } - - private: - u32 m_index; - }; - - class Axis : public Input - { - public: - Axis(const double* axis, double base, double range) - : m_base(base), m_range(range), m_axis(*axis) - { - } - - ControlState GetState() const override { return ControlState(m_axis - m_base) / m_range; } - - protected: - const double m_base; - const double m_range; - - private: - const double& m_axis; - }; - - class NamedAxis final : public Axis - { - public: - NamedAxis(const double* axis, double base, double range, std::string_view name) - : Axis(axis, base, range), m_name(name) - { - } - std::string GetName() const override - { - return fmt::format("{}{}", m_name, m_range < 0 ? '-' : '+'); - } - - private: - const std::string_view m_name; - }; - - class NamedTrigger final : public Axis - { - public: - NamedTrigger(const double* axis, std::string_view name) : Axis(axis, 0.0, 1.0), m_name(name) {} - std::string GetName() const override { return std::string(m_name); } - - private: - const std::string_view m_name; - }; - - class NamedMotor final : public Output - { - public: - NamedMotor(double* motor, std::string_view name, Device* parent) - : m_motor(*motor), m_name(name), m_parent(*parent) - { - } - std::string GetName() const override { return std::string(m_name); } - void SetState(ControlState state) override - { - if (m_motor == state) - return; - - m_motor = state; - m_parent.UpdateMotors(); - } - - private: - double& m_motor; - const std::string_view m_name; - Device& m_parent; - }; - - class IndexedAxis final : public Axis - { - public: - IndexedAxis(const double* axis, double base, double range, u32 index) - : Axis(axis, base, range), m_index(index) - { - } - std::string GetName() const override - { - return fmt::format("Axis {}{}", m_index, m_range < 0 ? '-' : '+'); - } - - private: - const u32 m_index; - }; - - class IndexedSwitch final : public Input - { - public: - IndexedSwitch(const WGI::GameControllerSwitchPosition* swtch, u32 index, - WGI::GameControllerSwitchPosition direction) - : m_switch(*swtch), m_index(index), m_direction(static_cast(direction)) - { - } - std::string GetName() const override - { - return fmt::format("Switch {} {}", m_index, "NESW"[m_direction / 2]); - } - ControlState GetState() const override - { - if (m_switch == WGI::GameControllerSwitchPosition::Center) - return 0.0; - - // All of the "in between" states (e.g. Up-Right) are one-off from the four cardinal - // directions. This tests that the current switch state value is within 1 of the desired - // state. - const auto direction_diff = std::abs(static_cast(m_switch) - m_direction); - return ControlState(direction_diff <= 1 || direction_diff == 7); - } - - private: - const WGI::GameControllerSwitchPosition& m_switch; - const u32 m_index; - const int32_t m_direction; - }; - - class Battery : public Input - { - public: - Battery(const double* level) : m_level(*level) {} - - bool IsDetectable() const override { return false; } - - ControlState GetState() const override { return m_level; } - - std::string GetName() const override { return "Battery"; } - - private: - const double& m_level; - }; - - class SimpleHaptics : public Output - { - public: - SimpleHaptics(Haptics::SimpleHapticsController haptics, - Haptics::SimpleHapticsControllerFeedback feedback, u32 haptics_index) - : m_haptics(haptics), m_feedback(feedback), m_haptics_index(haptics_index) - { - } - - void SetState(ControlState state) override - { - if (m_current_state == state) - return; - - m_current_state = state; - - try - { - if (state) - m_haptics.SendHapticFeedback(m_feedback, state); - else - m_haptics.StopFeedback(); - } - catch (winrt::hresult_error) - { - // Ignore - } - } - - protected: - u32 GetHapticsIndex() const { return m_haptics_index; } - - private: - Haptics::SimpleHapticsController m_haptics; - Haptics::SimpleHapticsControllerFeedback m_feedback; - const u32 m_haptics_index; - ControlState m_current_state = 0; - }; - - class NamedFeedback final : public SimpleHaptics - { - public: - NamedFeedback(Haptics::SimpleHapticsController haptics, - Haptics::SimpleHapticsControllerFeedback feedback, u32 haptics_index, - std::string_view feedback_name) - : SimpleHaptics(haptics, feedback, haptics_index), m_feedback_name(feedback_name) - { - } - std::string GetName() const override - { - return fmt::format("{} {}", m_feedback_name, GetHapticsIndex()); - } - - private: - const std::string_view m_feedback_name; - }; - - void PopulateButtons() - { - try - { - // Using RawGameController for buttons because it gives us a nice array instead of a bitmask. - m_buttons.resize(m_raw_controller.ButtonCount()); - - u32 i = 0; - for (const auto& button : m_buttons) - { - WGI::GameControllerButtonLabel lbl{WGI::GameControllerButtonLabel::None}; - try - { - lbl = m_raw_controller.GetButtonLabel(i); - } - catch (winrt::hresult_error) - { - lbl = WGI::GameControllerButtonLabel::None; - } - - const int32_t button_name_idx = static_cast(lbl); - if (lbl != WGI::GameControllerButtonLabel::None && - button_name_idx < wgi_button_names.size()) - AddInput(new NamedButton(&button, wgi_button_names[button_name_idx])); - else - AddInput(new IndexedButton(&button, i)); - - ++i; - } - } - catch (winrt::hresult_error) - { - // If ButtonCount failed, m_buttons will be zero-sized. - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Error populating buttons"); - } - } - - void PopulateHaptics() - { - static const std::map waveform_name_map{ - {Haptics::KnownSimpleHapticsControllerWaveforms::Click(), "Click"}, - {Haptics::KnownSimpleHapticsControllerWaveforms::BuzzContinuous(), "Buzz"}, - {Haptics::KnownSimpleHapticsControllerWaveforms::RumbleContinuous(), "Rumble"}, - }; - - try - { - // SimpleHapticsControllers is available from Windows 1709. - u32 haptics_index = 0; - for (auto haptics_controller : m_raw_controller.SimpleHapticsControllers()) - { - for (Haptics::SimpleHapticsControllerFeedback feedback : - haptics_controller.SupportedFeedback()) - { - const uint16_t waveform = feedback.Waveform(); - auto waveform_name_it = waveform_name_map.find(waveform); - if (waveform_name_it == waveform_name_map.end()) - { - WARN_LOG_FMT(CONTROLLERINTERFACE, - "WGInput: Unhandled haptics feedback waveform: 0x{:04x}.", waveform); - continue; - } - AddOutput(new NamedFeedback(haptics_controller, feedback, haptics_index, - waveform_name_it->second)); - } - ++haptics_index; - } - } - catch (winrt::hresult_error) - { - // Don't need to cleanup any state. It's OK if some outputs were added. - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Error populating haptics"); - } - } - - std::string GetName() const override { return m_name; } - - std::string GetSource() const override { return std::string(SOURCE_NAME); } - - Core::DeviceRemoval UpdateInput() override - { - // IRawGameController: - static_assert(sizeof(bool) == sizeof(ButtonValueType)); - // This cludge is needed to workaround GetCurrentReading wanting array_view, while - // using std::vector would create a bit-packed array, which isn't wanted. So, we keep - // vector and view it as array. - auto buttons = - winrt::array_view(reinterpret_cast::pointer>(&m_buttons[0]), - static_cast::size_type>(m_buttons.size())); - try - { - m_raw_controller.GetCurrentReading(buttons, m_switches, m_axes); - } - catch (winrt::hresult_error error) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, - "WGInput: IRawGameController::GetCurrentReading failed: {}", error.code()); - } - - // IGamepad: - if (m_gamepad) - { - try - { - m_gamepad_reading = m_gamepad.GetCurrentReading(); - } - catch (winrt::hresult_error error) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: IGamepad::GetCurrentReading failed: {}", - error.code()); - } - } - - // IGameControllerBatteryInfo: - if (!UpdateBatteryLevel()) - DEBUG_LOG_FMT(CONTROLLERINTERFACE, "WGInput: UpdateBatteryLevel failed."); - - return Core::DeviceRemoval::Keep; - } - - void UpdateMotors() - { - try - { - m_gamepad.Vibration(m_state_out); - } - catch (winrt::hresult_error) - { - // Ignore - } - } - - bool UpdateBatteryLevel() - { - try - { - // Workaround for Steam. If Steam's GameOverlayRenderer64.dll is loaded, battery_info is null. - auto battery_info = m_raw_controller.try_as(); - if (!battery_info) - return false; - const winrt::Windows::Devices::Power::BatteryReport report = - battery_info.TryGetBatteryReport(); - if (!report) - return false; - - // TryGetBatteryReport causes a memleak of 0x58 bytes each time it is called, up to at - // least Windows 21H2. A hack to fix the memleak is recorded here for posterity. - // auto report_ = *(uintptr_t***)&report; - // auto rc = &report_[0x40 / 8][0x30 / 8]; - // if (*rc == 2) - // (*rc)--; - - using winrt::Windows::System::Power::BatteryStatus; - const BatteryStatus status = report.Status(); - switch (status) - { - case BatteryStatus::NotPresent: - m_battery_level = 0; - return true; - - case BatteryStatus::Idle: - case BatteryStatus::Charging: - m_battery_level = BATTERY_INPUT_MAX_VALUE; - return true; - - default: - break; - } - - const int32_t full_value = report.FullChargeCapacityInMilliwattHours().GetInt32(); - const int32_t remaining_value = report.RemainingCapacityInMilliwattHours().GetInt32(); - m_battery_level = BATTERY_INPUT_MAX_VALUE * remaining_value / full_value; - return true; - } - catch (winrt::hresult_error) - { - return false; - } - } - - const std::string m_name; - - const WGI::RawGameController m_raw_controller; - std::vector m_buttons; - std::vector m_switches; - std::vector m_axes; - - WGI::Gamepad m_gamepad = nullptr; - WGI::GamepadReading m_gamepad_reading{}; - WGI::GamepadVibration m_state_out{}; - - ControlState m_battery_level = 0; -}; - -enum class AddRemoveEventType -{ - AddOrReplace, - Remove, -}; - -struct AddRemoveEvent -{ - AddRemoveEventType type; - WGI::RawGameController raw_game_controller; -}; - -static thread_local bool s_initialized_winrt; -static winrt::event_token s_event_added, s_event_removed; -static Common::WorkQueueThread s_device_add_remove_queue; - -static bool COMIsInitialized() -{ - APTTYPE apt_type{}; - APTTYPEQUALIFIER apt_qualifier{}; - return CoGetApartmentType(&apt_type, &apt_qualifier) == S_OK; -} - -static void AddDevice(const WGI::RawGameController& raw_game_controller) -{ - // Get user-facing device name if available. Otherwise generate a name from vid/pid. - std::string device_name; - try - { - device_name = StripWhitespace(WStringToUTF8(raw_game_controller.DisplayName().c_str())); - if (device_name.empty()) - { - const u16 vid = raw_game_controller.HardwareVendorId(); - const u16 pid = raw_game_controller.HardwareProductId(); - device_name = fmt::format("Device_{:04x}:{:04x}", vid, pid); - INFO_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Empty device name, using {}", device_name); - } - } - catch (winrt::hresult_error) - { - device_name = "Device"; - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to get device name, using {}", device_name); - } - - try - { - WGI::Gamepad gamepad = WGI::Gamepad::FromGameController(raw_game_controller); - // Note that gamepad may be nullptr here. The Device class will deal with this. - auto dev = std::make_shared(std::move(device_name), raw_game_controller, gamepad); - - // Only add if it has some inputs/outputs. - if (dev->Inputs().size() || dev->Outputs().size()) - g_controller_interface.AddDevice(std::move(dev)); - } - catch (winrt::hresult_error) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to add device {}", device_name); - } -} - -static void RemoveDevice(const WGI::RawGameController& raw_game_controller) -{ - g_controller_interface.RemoveDevice([&](const auto* dev) { - if (dev->GetSource() != SOURCE_NAME) - return false; - return static_cast(dev)->GetRawGameController() == raw_game_controller; - }); -} - -#pragma warning(push) -// 'winrt::impl::implements_delegate,H>': -// class has virtual functions, but its non-trivial destructor is not virtual; instances of this -// class may not be destructed correctly -// (H is the lambda) -#pragma warning(disable : 4265) - -static void HandleAddRemoveEvent(AddRemoveEvent evt) -{ - try - { - winrt::init_apartment(); - } - catch (const winrt::hresult_error& ex) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, - "WGInput: Failed to CoInitialize for add/remove controller event: {}", - winrt::to_string(ex.message())); - return; - } - Common::ScopeGuard coinit_guard([] { winrt::uninit_apartment(); }); - - switch (evt.type) - { - case AddRemoveEventType::AddOrReplace: - RemoveDevice(evt.raw_game_controller); - AddDevice(evt.raw_game_controller); - break; - case AddRemoveEventType::Remove: - RemoveDevice(evt.raw_game_controller); - break; - default: -#ifdef __MINGW32__ - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Invalid add/remove controller event: {}", - static_cast>(evt.type)); -#else - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Invalid add/remove controller event: {}", - std::to_underlying(evt.type)); -#endif - } -} - -void Init() -{ - if (!COMIsInitialized()) - { - // NOTE: Devices in g_controller_interface should only be accessed by threads that have had - // winrt (com) initialized. - winrt::init_apartment(); - s_initialized_winrt = true; - } - - s_device_add_remove_queue.Reset("WGInput Add/Remove Device Thread", HandleAddRemoveEvent); - - try - { - // These events will be invoked from WGI-managed threadpool. - s_event_added = WGI::RawGameController::RawGameControllerAdded( - [](auto&&, WGI::RawGameController raw_game_controller) { - s_device_add_remove_queue.EmplaceItem( - AddRemoveEvent{AddRemoveEventType::AddOrReplace, std::move(raw_game_controller)}); - }); - - s_event_removed = WGI::RawGameController::RawGameControllerRemoved( - [](auto&&, WGI::RawGameController raw_game_controller) { - s_device_add_remove_queue.EmplaceItem( - AddRemoveEvent{AddRemoveEventType::Remove, std::move(raw_game_controller)}); - }); - } - catch (winrt::hresult_error) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to register event handlers"); - } -} - -#pragma warning(pop) - -void DeInit() -{ - s_device_add_remove_queue.Shutdown(); - - WGI::RawGameController::RawGameControllerAdded(s_event_added); - WGI::RawGameController::RawGameControllerRemoved(s_event_removed); - - if (s_initialized_winrt) - { - winrt::uninit_apartment(); - s_initialized_winrt = false; - } -} - -void PopulateDevices() -{ - // WGI Interfaces to potentially use: - // Gamepad: Buttons, 2x Sticks and 2x Triggers, 4x Vibration Motors - // RawGameController: Buttons, Switches (Hats), Axes, Haptics - // The following are not implemented: - // ArcadeStick: Buttons (probably no need to specialize, literally just buttons) - // FlightStick: Buttons, HatSwitch, Pitch, Roll, Throttle, Yaw - // RacingWheel: Buttons, Clutch, Handbrake, PatternShifterGear, Throttle, Wheel, WheelMotor - // UINavigationController: Directions, Scrolling, etc. - - // RawGameController available from Windows 15063. - // properties added in 1709: DisplayName, NonRoamableId, SimpleHapticsControllers - - try - { - for (const WGI::RawGameController& raw_game_controller : - WGI::RawGameController::RawGameControllers()) - { - RemoveDevice(raw_game_controller); - AddDevice(raw_game_controller); - } - } - catch (winrt::hresult_error) - { - // Only reach here if RawGameControllers() failed - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: PopulateDevices failed"); - } -} - -} // namespace ciface::WGInput diff --git a/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.h b/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.h deleted file mode 100644 index 54fa06fc12..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2020 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -namespace ciface::WGInput -{ -void Init(); -void DeInit(); -void PopulateDevices(); - -} // namespace ciface::WGInput diff --git a/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp b/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp deleted file mode 100644 index eb4bd6fd6b..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2017 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "InputCommon/ControllerInterface/Win32/Win32.h" - -#include -#include -// must be before hidclass -#include - -#include - -#include "Common/Flag.h" -#include "Common/Logging/Log.h" -#include "InputCommon/ControllerInterface/DInput/DInput.h" -#include "InputCommon/ControllerInterface/WGInput/WGInput.h" -#include "InputCommon/ControllerInterface/XInput/XInput.h" - -#pragma comment(lib, "OneCoreUAP.Lib") - -// TODO is this really needed? -static Common::Flag s_first_populate_devices_asked; -static HCMNOTIFICATION s_notify_handle; - -namespace ciface::Win32 -{ -class InputBackend final : public ciface::InputBackend -{ -public: - InputBackend(ControllerInterface* controller_interface); - ~InputBackend() override; - - void PopulateDevices() override; - void HandleWindowChange() override; - HWND GetHWND(); -}; -} // namespace ciface::Win32 - -_Pre_satisfies_(EventDataSize >= sizeof(CM_NOTIFY_EVENT_DATA)) static DWORD CALLBACK - OnDevicesChanged(_In_ HCMNOTIFICATION hNotify, _In_opt_ PVOID Context, - _In_ CM_NOTIFY_ACTION Action, - _In_reads_bytes_(EventDataSize) PCM_NOTIFY_EVENT_DATA EventData, - _In_ DWORD EventDataSize) -{ - if (Action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL || - Action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL) - { - // Windows automatically sends this message before we ask for it and before we are "ready" to - // listen for it. - if (s_first_populate_devices_asked.IsSet()) - { - // TODO: we could easily use the message passed alongside this event, which tells - // whether a device was added or removed, to avoid removing old, still connected, devices - g_controller_interface.PlatformPopulateDevices([&] { - ciface::DInput::PopulateDevices( - static_cast(Context)->GetHWND()); - ciface::XInput::PopulateDevices(); - }); - } - } - return ERROR_SUCCESS; -} - -namespace ciface::Win32 -{ -std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface) -{ - return std::make_unique(controller_interface); -} - -HWND InputBackend::GetHWND() -{ - return static_cast(GetControllerInterface().GetWindowSystemInfo().render_window); -} - -InputBackend::InputBackend(ControllerInterface* controller_interface) - : ciface::InputBackend(controller_interface) -{ - XInput::Init(); - WGInput::Init(); - - CM_NOTIFY_FILTER notify_filter{.cbSize = sizeof(notify_filter), - .FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE, - .u{.DeviceInterface{.ClassGuid = GUID_DEVINTERFACE_HID}}}; - const CONFIGRET cfg_rv = - CM_Register_Notification(¬ify_filter, this, OnDevicesChanged, &s_notify_handle); - if (cfg_rv != CR_SUCCESS) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "CM_Register_Notification failed: {:x}", cfg_rv); - } -} - -void InputBackend::PopulateDevices() -{ - g_controller_interface.PlatformPopulateDevices([this] { - s_first_populate_devices_asked.Set(); - ciface::DInput::PopulateDevices(GetHWND()); - ciface::XInput::PopulateDevices(); - ciface::WGInput::PopulateDevices(); - }); -} - -void InputBackend::HandleWindowChange() -{ - g_controller_interface.PlatformPopulateDevices( - [this] { ciface::DInput::ChangeWindow(GetHWND()); }); -} - -InputBackend::~InputBackend() -{ - s_first_populate_devices_asked.Clear(); - DInput::DeInit(); - - if (s_notify_handle) - { - const CONFIGRET cfg_rv = CM_Unregister_Notification(s_notify_handle); - if (cfg_rv != CR_SUCCESS) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "CM_Unregister_Notification failed: {:x}", cfg_rv); - } - s_notify_handle = nullptr; - } - - XInput::DeInit(); - WGInput::DeInit(); -} - -} // namespace ciface::Win32 diff --git a/Source/Core/InputCommon/ControllerInterface/Win32/Win32.h b/Source/Core/InputCommon/ControllerInterface/Win32/Win32.h deleted file mode 100644 index 17e46c54b9..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/Win32/Win32.h +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2017 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "InputCommon/ControllerInterface/InputBackend.h" - -namespace ciface::Win32 -{ -std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface); -} // namespace ciface::Win32 diff --git a/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp b/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp deleted file mode 100644 index 03a4a44634..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright 2010 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include "InputCommon/ControllerInterface/XInput/XInput.h" - -#ifndef XINPUT_GAMEPAD_GUIDE -#define XINPUT_GAMEPAD_GUIDE 0x0400 -#endif - -namespace ciface::XInput -{ -struct ButtonDef -{ - const char* name; - WORD bitmask; -}; -static constexpr std::array named_buttons{{ - {"Button A", XINPUT_GAMEPAD_A}, - {"Button B", XINPUT_GAMEPAD_B}, - {"Button X", XINPUT_GAMEPAD_X}, - {"Button Y", XINPUT_GAMEPAD_Y}, - {"Pad N", XINPUT_GAMEPAD_DPAD_UP}, - {"Pad S", XINPUT_GAMEPAD_DPAD_DOWN}, - {"Pad W", XINPUT_GAMEPAD_DPAD_LEFT}, - {"Pad E", XINPUT_GAMEPAD_DPAD_RIGHT}, - {"Start", XINPUT_GAMEPAD_START}, - {"Back", XINPUT_GAMEPAD_BACK}, - {"Shoulder L", XINPUT_GAMEPAD_LEFT_SHOULDER}, - {"Shoulder R", XINPUT_GAMEPAD_RIGHT_SHOULDER}, - {"Guide", XINPUT_GAMEPAD_GUIDE}, - {"Thumb L", XINPUT_GAMEPAD_LEFT_THUMB}, - {"Thumb R", XINPUT_GAMEPAD_RIGHT_THUMB}, -}}; - -static constexpr std::array named_triggers{"Trigger L", "Trigger R"}; - -static constexpr std::array named_axes{"Left X", "Left Y", "Right X", "Right Y"}; - -static constexpr std::array named_motors{"Motor L", "Motor R"}; - -class Button final : public Core::Device::Input -{ -public: - Button(u8 index, const WORD& buttons) : m_buttons(buttons), m_index(index) {} - std::string GetName() const override { return named_buttons[m_index].name; } - ControlState GetState() const override - { - return (m_buttons & named_buttons[m_index].bitmask) > 0; - } - -private: - const WORD& m_buttons; - const u8 m_index; -}; - -class Axis final : public Core::Device::Input -{ -public: - Axis(u8 index, const SHORT& axis, SHORT range) : m_axis(axis), m_range(range), m_index(index) {} - std::string GetName() const override - { - return std::string(named_axes[m_index]) + (m_range < 0 ? '-' : '+'); - } - ControlState GetState() const override { return ControlState(m_axis) / m_range; } - -private: - const SHORT& m_axis; - const SHORT m_range; - const u8 m_index; -}; - -class Trigger final : public Core::Device::Input -{ -public: - Trigger(u8 index, const BYTE& trigger, BYTE range) - : m_trigger(trigger), m_range(range), m_index(index) - { - } - std::string GetName() const override { return named_triggers[m_index]; } - ControlState GetState() const override { return ControlState(m_trigger) / m_range; } - -private: - const BYTE& m_trigger; - const BYTE m_range; - const u8 m_index; -}; - -class Motor final : public Core::Device::Output -{ -public: - Motor(u8 index, Device* parent, WORD& motor, WORD range) - : m_motor(motor), m_range(range), m_index(index), m_parent(parent) - { - } - - std::string GetName() const override { return named_motors[m_index]; } - void SetState(ControlState state) override - { - const auto old_value = m_motor; - m_motor = (WORD)(state * m_range); - - // Only update if the state changed. - if (m_motor != old_value) - m_parent->UpdateMotors(); - } - -private: - WORD& m_motor; - const WORD m_range; - const u8 m_index; - Device* m_parent; -}; - -class Battery final : public Core::Device::Input -{ -public: - Battery(const ControlState* level) : m_level(*level) {} - std::string GetName() const override { return "Battery"; } - ControlState GetState() const override { return m_level; } - bool IsDetectable() const override { return false; } - -private: - const ControlState& m_level; -}; - -static HMODULE hXInput = nullptr; - -typedef decltype(&XInputGetCapabilities) XInputGetCapabilities_t; -typedef decltype(&XInputSetState) XInputSetState_t; -typedef decltype(&XInputGetState) XInputGetState_t; -typedef decltype(&XInputGetBatteryInformation) XInputGetBatteryInformation_t; - -static XInputGetCapabilities_t PXInputGetCapabilities = nullptr; -static XInputSetState_t PXInputSetState = nullptr; -static XInputGetState_t PXInputGetState = nullptr; -static XInputGetBatteryInformation_t PXInputGetBatteryInformation = nullptr; - -static bool s_have_guide_button = false; - -void Init() -{ - if (!hXInput) - { -#ifdef __MINGW32__ -#if(_WIN32_WINNT >= _WIN32_WINNT_WIN8) -#define XINPUT_DLL_A "xinput1_4.dll" -#define XINPUT_DLL_W L"xinput1_4.dll" -#else -#define XINPUT_DLL_A "xinput9_1_0.dll" -#define XINPUT_DLL_W L"xinput9_1_0.dll" -#endif - -#ifdef UNICODE - #define XINPUT_DLL XINPUT_DLL_W -#else - #define XINPUT_DLL XINPUT_DLL_A -#endif -#endif - - // Try for the most recent version we were compiled against (will only work if running on Win8+) - hXInput = ::LoadLibrary(XINPUT_DLL); - if (!hXInput) - { - // Drop back to DXSDK June 2010 version. Requires DX June 2010 redist. - hXInput = ::LoadLibrary(TEXT("xinput1_3.dll")); - if (!hXInput) - { - return; - } - } - - PXInputGetCapabilities = - (XInputGetCapabilities_t)::GetProcAddress(hXInput, "XInputGetCapabilities"); - PXInputSetState = (XInputSetState_t)::GetProcAddress(hXInput, "XInputSetState"); - PXInputGetBatteryInformation = - (XInputGetBatteryInformation_t)::GetProcAddress(hXInput, "XInputGetBatteryInformation"); - - // Ordinal 100 is the same as XInputGetState, except it doesn't dummy out the guide - // button info. Try loading it and fall back if needed. - PXInputGetState = (XInputGetState_t)::GetProcAddress(hXInput, (LPCSTR)100); - - s_have_guide_button = PXInputGetState != nullptr; - - if (!PXInputGetState) - PXInputGetState = (XInputGetState_t)::GetProcAddress(hXInput, "XInputGetState"); - - if (!PXInputGetCapabilities || !PXInputSetState || !PXInputGetState || - !PXInputGetBatteryInformation) - { - ::FreeLibrary(hXInput); - hXInput = nullptr; - return; - } - } -} - -void PopulateDevices() -{ - if (!hXInput) - return; - - g_controller_interface.RemoveDevice([](const auto* dev) { return dev->GetSource() == "XInput"; }); - - XINPUT_CAPABILITIES caps; - for (int i = 0; i != 4; ++i) - if (ERROR_SUCCESS == PXInputGetCapabilities(i, 0, &caps)) - g_controller_interface.AddDevice(std::make_shared(caps, i)); -} - -void DeInit() -{ - if (hXInput) - { - ::FreeLibrary(hXInput); - hXInput = nullptr; - } -} - -Device::Device(const XINPUT_CAPABILITIES& caps, u8 index) : m_subtype(caps.SubType), m_index(index) -{ - // XInputGetCaps can be broken on some devices, so we'll just ignore it - // and assume all gamepad + vibration capabilities are supported - - // Buttons. - for (size_t i = 0; i != size(named_buttons); ++i) - { - // Only add guide button if we have the 100 ordinal XInputGetState. - if (named_buttons[i].bitmask == XINPUT_GAMEPAD_GUIDE && !s_have_guide_button) - continue; - - AddInput(new Button(u8(i), m_state_in.Gamepad.wButtons)); - } - - // Triggers. - for (size_t i = 0; i != size(named_triggers); ++i) - AddInput(new Trigger(u8(i), (&m_state_in.Gamepad.bLeftTrigger)[i], 255)); - - // Axes. - for (size_t i = 0; i != size(named_axes); ++i) - { - const SHORT& ax = (&m_state_in.Gamepad.sThumbLX)[i]; - - // Each axis gets a negative and a positive input instance associated with it. - AddInput(new Axis(u8(i), ax, -32768)); - AddInput(new Axis(u8(i), ax, 32767)); - } - - // Rumble motors. - for (size_t i = 0; i != size(named_motors); ++i) - AddOutput(new Motor(u8(i), this, (&m_state_out.wLeftMotorSpeed)[i], 65535)); - - AddInput(new Battery(&m_battery_level)); -} - -std::string Device::GetName() const -{ - switch (m_subtype) - { - case XINPUT_DEVSUBTYPE_GAMEPAD: - return "Gamepad"; - case XINPUT_DEVSUBTYPE_WHEEL: - return "Wheel"; - case XINPUT_DEVSUBTYPE_ARCADE_STICK: - return "Arcade Stick"; - case XINPUT_DEVSUBTYPE_FLIGHT_STICK: - return "Flight Stick"; - case XINPUT_DEVSUBTYPE_DANCE_PAD: - return "Dance Pad"; - case XINPUT_DEVSUBTYPE_GUITAR: - return "Guitar"; - case XINPUT_DEVSUBTYPE_DRUM_KIT: - return "Drum Kit"; - default: - return "Device"; - } -} - -std::string Device::GetSource() const -{ - return "XInput"; -} - -Core::DeviceRemoval Device::UpdateInput() -{ - PXInputGetState(m_index, &m_state_in); - - XINPUT_BATTERY_INFORMATION battery_info = {}; - if (SUCCEEDED(PXInputGetBatteryInformation(m_index, BATTERY_DEVTYPE_GAMEPAD, &battery_info))) - { - switch (battery_info.BatteryType) - { - case BATTERY_TYPE_DISCONNECTED: - case BATTERY_TYPE_UNKNOWN: - m_battery_level = 0; - break; - case BATTERY_TYPE_WIRED: - m_battery_level = BATTERY_INPUT_MAX_VALUE; - break; - default: - m_battery_level = - battery_info.BatteryLevel / ControlState(BATTERY_LEVEL_FULL) * BATTERY_INPUT_MAX_VALUE; - break; - } - } - - return Core::DeviceRemoval::Keep; -} - -void Device::UpdateMotors() -{ - PXInputSetState(m_index, &m_state_out); -} - -std::optional Device::GetPreferredId() const -{ - return m_index; -} - -} // namespace ciface::XInput diff --git a/Source/Core/InputCommon/ControllerInterface/XInput/XInput.h b/Source/Core/InputCommon/ControllerInterface/XInput/XInput.h deleted file mode 100644 index cb6e8d228c..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/XInput/XInput.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2010 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -// XInput suffers a similar issue as XAudio2. Since Win8, it is part of the OS. -// However, unlike XAudio2 they have not made the API incompatible - so we just -// compile against the latest version and fall back to dynamically loading the -// old DLL. - -#pragma once - -#include -#include - -#include "InputCommon/ControllerInterface/ControllerInterface.h" - -#if defined(__MINGW32__) && defined(XINPUT_DEVSUBTYPE_FLIGHT_SICK) -#define XINPUT_DEVSUBTYPE_FLIGHT_STICK XINPUT_DEVSUBTYPE_FLIGHT_SICK -#endif -#ifndef XINPUT_DEVSUBTYPE_FLIGHT_STICK -#error You are building this module against the wrong version of DirectX. You probably need to remove DXSDK_DIR from your include path and/or _WIN32_WINNT is wrong. -#endif - -namespace ciface::XInput -{ -void Init(); -void PopulateDevices(); -void DeInit(); - -class Device final : public Core::Device -{ -public: - Device(const XINPUT_CAPABILITIES& capabilities, u8 index); - - std::string GetName() const override; - std::string GetSource() const override; - std::optional GetPreferredId() const override; - int GetSortPriority() const override { return -2; } - - Core::DeviceRemoval UpdateInput() override; - - void UpdateMotors(); - -private: - XINPUT_STATE m_state_in{}; - XINPUT_VIBRATION m_state_out{}; - ControlState m_battery_level{}; - const BYTE m_subtype; - const u8 m_index; -}; -} // namespace ciface::XInput diff --git a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp deleted file mode 100644 index 309ed701cb..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp +++ /dev/null @@ -1,523 +0,0 @@ -// Copyright 2013 Max Eliaser -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "InputCommon/ControllerInterface/Xlib/XInput2.h" - -#include -#include - -#include -#include -#include -#include - -#include - -#include "Common/Logging/Log.h" - -#include "Core/Host.h" - -// This is an input plugin using the XInput 2.0 extension to the X11 protocol, -// loosely based on the old XLib plugin. (Has nothing to do with the XInput -// API on Windows.) - -// This plugin creates one KeyboardMouse object for each master pointer/ -// keyboard pair. Each KeyboardMouse object exports four types of controls: -// * Mouse button controls: hardcoded at 32 of them, but could be made to -// support infinitely many mouse buttons in theory; XInput2 has no limit. -// * Mouse cursor controls: one for each cardinal direction. Calculated by -// comparing the absolute position of the mouse pointer on screen to the -// center of the emulator window. -// * Mouse axis controls: one for each cardinal direction. Calculated using -// a running average of relative mouse motion on each axis. -// * Key controls: these correspond to a limited subset of the keyboard -// keys. - -// Mouse axis control tuning. Unlike absolute mouse position, relative mouse -// motion data needs to be tweaked and smoothed out a bit to be usable. - -// Mouse axis control output is simply divided by this number. In practice, -// that just means you can use a smaller "dead zone" if you bind axis controls -// to a joystick. No real need to make this customizable. -#define MOUSE_AXIS_SENSITIVITY 8.0f - -// The mouse axis controls use a weighted running average. Each frame, the new -// value is the average of the old value and the amount of relative mouse -// motion during that frame. The old value is weighted by a ratio of -// MOUSE_AXIS_SMOOTHING:1 compared to the new value. Increasing -// MOUSE_AXIS_SMOOTHING makes the controls smoother, decreasing it makes them -// more responsive. This might be useful as a user-customizable option. -#define MOUSE_AXIS_SMOOTHING 1.5f - -// The scroll axis value should decay a lot faster than the mouse axes since -// it should ideally register each click of the scroll wheel. Decreasing this -// value makes it more likely that a scroll wheel input is registered, but less -// likely to differentiate between different inputs, while increasing it will -// more cleanly separate each scroll wheel click, but risks dropping some inputs -#define SCROLL_AXIS_DECAY 1.1f - -namespace -{ -// We need XInput 2.1 to get raw events on the root window even while another -// client has a grab. If we request 2.2 or later, the server will not generate -// emulated button presses from touch events, so we want exactly 2.1. -constexpr int XINPUT_MAJOR = 2, XINPUT_MINOR = 1; -} // namespace - -namespace ciface::XInput2 -{ -constexpr std::string_view SOURCE_NAME = "XInput2"; - -class InputBackend final : public ciface::InputBackend -{ -public: - using ciface::InputBackend::InputBackend; - void PopulateDevices() override; - void HandleWindowChange() override; -}; - -std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface) -{ - return std::make_unique(controller_interface); -} - -void InputBackend::HandleWindowChange() -{ - GetControllerInterface().RemoveDevice( - [](const auto* dev) { return dev->GetSource() == SOURCE_NAME; }, true); - - PopulateDevices(); -} - -// This function will add zero or more KeyboardMouse objects to devices. -void InputBackend::PopulateDevices() -{ - const WindowSystemInfo wsi = GetControllerInterface().GetWindowSystemInfo(); - if (wsi.type != WindowSystemType::X11) - return; - - const auto hwnd = wsi.render_window; - - Display* dpy = XOpenDisplay(nullptr); - - // xi_opcode is important; it will be used to identify XInput events by - // the polling loop in UpdateInput. - int xi_opcode, event, error; - - // verify that the XInput extension is available - if (!XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error)) - { - WARN_LOG_FMT(CONTROLLERINTERFACE, "XInput extension not available (XQueryExtension)"); - return; - } - - int major = XINPUT_MAJOR, minor = XINPUT_MINOR; - if (XIQueryVersion(dpy, &major, &minor) != Success || major < XINPUT_MAJOR || - (major == XINPUT_MAJOR && minor < XINPUT_MINOR)) - { - WARN_LOG_FMT(CONTROLLERINTERFACE, "XInput extension not available (XIQueryVersion)"); - return; - } - - // register all master devices with Dolphin - - XIDeviceInfo* all_masters; - XIDeviceInfo* current_master; - double scroll_increment = 1.0f; - int num_masters; - - all_masters = XIQueryDevice(dpy, XIAllMasterDevices, &num_masters); - - for (int i = 0; i < num_masters; i++) - { - current_master = &all_masters[i]; - - if (current_master->use == XIMasterPointer) - { - // We need to query the master for the scroll wheel's increment, since the increment used - // varies depending on what input driver is being used. For example, xf86-libinput uses 120.0. - for (int j = 0; j < current_master->num_classes; j++) - { - if (current_master->classes[j]->type == XIScrollClass) - { - XIScrollClassInfo* scroll_event = - reinterpret_cast(current_master->classes[j]); - scroll_increment = scroll_event->increment; - break; - } - } - // Since current_master is a master pointer, its attachment must - // be a master keyboard. - GetControllerInterface().AddDevice( - std::make_shared((Window)hwnd, xi_opcode, current_master->deviceid, - current_master->attachment, scroll_increment)); - } - } - - XCloseDisplay(dpy); - - XIFreeDeviceInfo(all_masters); -} - -KeyboardMouse::KeyboardMouse(Window window, int opcode, int pointer, int keyboard, - double scroll_increment_) - : m_window(window), xi_opcode(opcode), pointer_deviceid(pointer), keyboard_deviceid(keyboard), - scroll_increment(scroll_increment_) -{ - // The cool thing about each KeyboardMouse object having its own Display - // is that each one gets its own separate copy of the X11 event stream, - // which it can individually filter to get just the events it's interested - // in. So be aware that each KeyboardMouse object actually has its own X11 - // "context." - m_display = XOpenDisplay(nullptr); - - int major = XINPUT_MAJOR, minor = XINPUT_MINOR; - XIQueryVersion(m_display, &major, &minor); - - // should always be 1 - int unused; - XIDeviceInfo* const pointer_device = XIQueryDevice(m_display, pointer_deviceid, &unused); - name = std::string(pointer_device->name); - XIFreeDeviceInfo(pointer_device); - - // Tell core X functions which keyboard is "the" keyboard for this - // X connection. - XISetClientPointer(m_display, None, pointer_deviceid); - - { - unsigned char mask_buf[(XI_LASTEVENT + 7) / 8] = {}; - XISetMask(mask_buf, XI_RawButtonPress); - XISetMask(mask_buf, XI_RawButtonRelease); - XISetMask(mask_buf, XI_RawMotion); - - XIEventMask mask; - mask.mask = mask_buf; - mask.mask_len = sizeof(mask_buf); - - mask.deviceid = pointer_deviceid; - XISelectEvents(m_display, DefaultRootWindow(m_display), &mask, 1); - } - - { - unsigned char mask_buf[(XI_LASTEVENT + 7) / 8] = {}; - XISetMask(mask_buf, XI_RawKeyPress); - XISetMask(mask_buf, XI_RawKeyRelease); - - XIEventMask mask; - mask.mask = mask_buf; - mask.mask_len = sizeof(mask_buf); - mask.deviceid = keyboard_deviceid; - XISelectEvents(m_display, DefaultRootWindow(m_display), &mask, 1); - } - - // Temporary map to combine keycodes with the same name. - std::map keys; - - // Keyboard Keys - int min_keycode = 0; - int max_keycode = 0; - XDisplayKeycodes(m_display, &min_keycode, &max_keycode); - for (int keycode = min_keycode; keycode <= max_keycode; ++keycode) - { - const auto* const keycode_name = Key::GetNameForKeyCode(m_display, keycode); - if (keycode_name == nullptr) - continue; - - auto& key_input = keys[keycode_name]; - if (key_input == nullptr) - AddInput(key_input = new Key(keycode_name, keycode, m_state.keyboard.data())); - else - key_input->AddKeyCode(keycode); - } - - // Add combined left/right modifiers with consistent naming across platforms. - AddCombinedInput("Alt", {"Alt_L", "Alt_R"}); - AddCombinedInput("Shift", {"Shift_L", "Shift_R"}); - AddCombinedInput("Ctrl", {"Control_L", "Control_R"}); - - // Mouse Buttons - for (int i = 0; i < 32; i++) - AddInput(new Button(i, &m_state.buttons)); - - // Mouse Cursor, X-/+ and Y-/+ - for (int i = 0; i != 4; ++i) - AddInput(new Cursor(!!(i & 2), !!(i & 1), (i & 2) ? &m_state.cursor.y : &m_state.cursor.x)); - - // Mouse Axis, X-/+, Y-/+ and Z-/+ - AddInput(new Axis(0, false, &m_state.axis.x)); - AddInput(new Axis(0, true, &m_state.axis.x)); - AddInput(new Axis(1, false, &m_state.axis.y)); - AddInput(new Axis(1, true, &m_state.axis.y)); - AddInput(new Axis(2, false, &m_state.axis.z)); - AddInput(new Axis(2, true, &m_state.axis.z)); - - // Relative Mouse, X-/+, Y-/+ and Z-/+ - AddInput(new RelativeMouse(0, false, &m_state.relative_mouse.x)); - AddInput(new RelativeMouse(0, true, &m_state.relative_mouse.x)); - AddInput(new RelativeMouse(1, false, &m_state.relative_mouse.y)); - AddInput(new RelativeMouse(1, true, &m_state.relative_mouse.y)); - AddInput(new RelativeMouse(2, false, &m_state.relative_mouse.z)); - AddInput(new RelativeMouse(2, true, &m_state.relative_mouse.z)); -} - -KeyboardMouse::~KeyboardMouse() -{ - XCloseDisplay(m_display); -} - -// Update the mouse cursor controls -void KeyboardMouse::UpdateCursor(bool should_center_mouse) -{ - double root_x, root_y, win_x, win_y; - Window root, child; - - XWindowAttributes win_attribs; - XGetWindowAttributes(m_display, m_window, &win_attribs); - const auto win_width = std::max(win_attribs.width, 1); - const auto win_height = std::max(win_attribs.height, 1); - - { - XIButtonState button_state; - XIModifierState mods; - XIGroupState group; - - // Get the absolute position of the mouse pointer and the button state. - XIQueryPointer(m_display, pointer_deviceid, m_window, &root, &child, &root_x, &root_y, &win_x, - &win_y, &button_state, &mods, &group); - - // X buttons are 1-indexed, so to get 32 button bits we need a larger type - // for the shift. - u64 buttons_zero_indexed = 0; - std::memcpy(&buttons_zero_indexed, button_state.mask, - std::min(button_state.mask_len, sizeof(m_state.buttons))); - m_state.buttons = buttons_zero_indexed >> 1; - - free(button_state.mask); - } - - if (should_center_mouse) - { - win_x = win_width / 2; - win_y = win_height / 2; - - XIWarpPointer(m_display, pointer_deviceid, None, m_window, 0.0, 0.0, 0, 0, win_x, win_y); - - g_controller_interface.SetMouseCenteringRequested(false); - } - - const auto window_scale = g_controller_interface.GetWindowInputScale(); - - // the mouse position as a range from -1 to 1 - m_state.cursor.x = (win_x / win_width * 2 - 1) * window_scale.x; - m_state.cursor.y = (win_y / win_height * 2 - 1) * window_scale.y; -} - -Core::DeviceRemoval KeyboardMouse::UpdateInput() -{ - XFlush(m_display); - - // for the axis controls - float delta_x = 0.0f, delta_y = 0.0f, delta_z = 0.0f; - double delta_delta; - bool update_mouse = false, update_keyboard = false; - - // Iterate through the event queue, processing raw pointer motion events and - // noting whether the button or key state has changed. - XEvent event; - while (XPending(m_display)) - { - XNextEvent(m_display, &event); - - if (event.xcookie.type != GenericEvent) - continue; - if (event.xcookie.extension != xi_opcode) - continue; - if (!XGetEventData(m_display, &event.xcookie)) - continue; - - switch (event.xcookie.evtype) - { - case XI_RawButtonPress: - case XI_RawButtonRelease: - update_mouse = true; - break; - case XI_RawKeyPress: - case XI_RawKeyRelease: - update_keyboard = true; - break; - case XI_RawMotion: - { - update_mouse = true; - - XIRawEvent* raw_event = (XIRawEvent*)event.xcookie.data; - float values[4] = {}; - size_t value_idx = 0; - - // We only care about the first 4 axes, which should always be available at minimum - for (int i = 0; i < 4; ++i) - { - if (XIMaskIsSet(raw_event->valuators.mask, i)) - { - values[i] = raw_event->raw_values[value_idx++]; - } - } - - delta_delta = values[0]; - // test for inf and nan - if (delta_delta == delta_delta && 1 + delta_delta != delta_delta) - delta_x += delta_delta; - - delta_delta = values[1]; - // test for inf and nan - if (delta_delta == delta_delta && 1 + delta_delta != delta_delta) - delta_y += delta_delta; - - // Scroll wheel input gets scaled to be similar to the mouse axes - delta_delta = values[3] * 8.0 / scroll_increment; - // test for inf and nan - if (delta_delta == delta_delta && 1 + delta_delta != delta_delta) - delta_z += delta_delta; - - break; - } - } - - XFreeEventData(m_display, &event.xcookie); - } - - m_state.relative_mouse.x = delta_x; - m_state.relative_mouse.y = delta_y; - m_state.relative_mouse.z = delta_z; - - // apply axis smoothing - m_state.axis.x *= MOUSE_AXIS_SMOOTHING; - m_state.axis.x += delta_x; - m_state.axis.x /= MOUSE_AXIS_SMOOTHING + 1.0f; - m_state.axis.y *= MOUSE_AXIS_SMOOTHING; - m_state.axis.y += delta_y; - m_state.axis.y /= MOUSE_AXIS_SMOOTHING + 1.0f; - m_state.axis.z += delta_z; - m_state.axis.z /= SCROLL_AXIS_DECAY; - - const bool should_center_mouse = g_controller_interface.IsMouseCenteringRequested() && - (Host_RendererHasFocus() || Host_TASInputHasFocus()); - - // When a TAS Input window has focus and "Enable Controller Input" is checked most types of - // input should be read normally as if the render window had focus instead. The cursor is an - // exception, as otherwise using the mouse to set any control in the TAS Input window will also - // update the Wii IR value (or any other input controlled by the cursor). - const bool should_update_mouse = update_mouse && !Host_TASInputHasFocus(); - - if (should_update_mouse || should_center_mouse) - UpdateCursor(should_center_mouse); - - if (update_keyboard) - XQueryKeymap(m_display, m_state.keyboard.data()); - - return Core::DeviceRemoval::Keep; -} - -std::string KeyboardMouse::GetName() const -{ - // This is the name string we got from the X server for this master - // pointer/keyboard pair. - return name; -} - -std::string KeyboardMouse::GetSource() const -{ - return std::string(SOURCE_NAME); -} - -int KeyboardMouse::GetSortPriority() const -{ - return DEFAULT_DEVICE_SORT_PRIORITY; -} - -KeyboardMouse::Key::Key(std::string key_name, KeyCode keycode, const char* keyboard) - : m_keyname{std::move(key_name)}, m_keyboard(keyboard), m_keycodes{keycode} -{ -} - -const char* KeyboardMouse::Key::GetNameForKeyCode(Display* const display, KeyCode keycode) -{ - int i = 0; - KeySym keysym = 0; - do - { - keysym = XkbKeycodeToKeysym(display, keycode, i, 0); - i++; - } while (keysym == NoSymbol && i < 8); - - // Convert to upper case for the keyname - if (keysym >= 97 && keysym <= 122) - keysym -= 32; - - // 0x0110ffff is the top of the unicode character range according - // to keysymdef.h although it is probably more than we need. - if (keysym == NoSymbol || keysym > 0x0110ffff || XKeysymToString(keysym) == nullptr) - return nullptr; - - return XKeysymToString(keysym); -} - -ControlState KeyboardMouse::Key::GetState() const -{ - return ControlState( - std::ranges::any_of(m_keycodes, std::bind_front(&Key::IsKeyCodePressed, this))); -} - -void KeyboardMouse::Key::AddKeyCode(KeyCode keycode) -{ - m_keycodes.emplace_back(keycode); -} - -bool KeyboardMouse::Key::IsKeyCodePressed(KeyCode keycode) const -{ - return (m_keyboard[keycode / 8] & (1 << (keycode % 8))) != 0; -} - -KeyboardMouse::Button::Button(unsigned int index, u32* buttons) : m_buttons(buttons), m_index(index) -{ - name = fmt::format("Click {}", m_index + 1); -} - -ControlState KeyboardMouse::Button::GetState() const -{ - return ((*m_buttons & (1 << m_index)) != 0); -} - -KeyboardMouse::Cursor::Cursor(u8 index, bool positive, const float* cursor) - : m_cursor(cursor), m_index(index), m_positive(positive) -{ - name = fmt::format("Cursor {}{}", static_cast('X' + m_index), (m_positive ? '+' : '-')); -} - -ControlState KeyboardMouse::Cursor::GetState() const -{ - return std::max(0.0f, *m_cursor / (m_positive ? 1.0f : -1.0f)); -} - -KeyboardMouse::Axis::Axis(u8 index, bool positive, const float* axis) - : m_axis(axis), m_index(index), m_positive(positive) -{ - name = fmt::format("Axis {}{}", static_cast('X' + m_index), (m_positive ? '+' : '-')); -} - -KeyboardMouse::RelativeMouse::RelativeMouse(u8 index, bool positive, const float* axis) - : m_axis(axis), m_index(index), m_positive(positive) -{ - name = - fmt::format("RelativeMouse {}{}", static_cast('X' + m_index), (m_positive ? '+' : '-')); -} - -ControlState KeyboardMouse::Axis::GetState() const -{ - return std::max(0.0f, *m_axis / (m_positive ? MOUSE_AXIS_SENSITIVITY : -MOUSE_AXIS_SENSITIVITY)); -} - -ControlState KeyboardMouse::RelativeMouse::GetState() const -{ - return std::max(0.0f, *m_axis / (m_positive ? MOUSE_AXIS_SENSITIVITY : -MOUSE_AXIS_SENSITIVITY)); -} -} // namespace ciface::XInput2 diff --git a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h deleted file mode 100644 index 414480584f..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2013 Max Eliaser -// SPDX-License-Identifier: GPL-2.0-or-later - -// See XInput2.cpp for extensive documentation. - -#pragma once - -#include -#include -#include - -extern "C" { -#include -#include -#include -} - -#include "Common/CommonTypes.h" -#include "Common/Matrix.h" -#include "InputCommon/ControllerInterface/ControllerInterface.h" -#include "InputCommon/ControllerInterface/InputBackend.h" - -namespace ciface::XInput2 -{ -std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface); - -class KeyboardMouse : public Core::Device -{ -private: - struct State - { - std::array keyboard; - u32 buttons; - Common::Vec2 cursor; - Common::Vec3 axis; - Common::Vec3 relative_mouse; - }; - - class Key : public Input - { - friend class KeyboardMouse; - - public: - Key(std::string name, KeyCode keycode, const char* keyboard); - - std::string GetName() const override { return m_keyname; } - ControlState GetState() const override; - - static const char* GetNameForKeyCode(Display* display, KeyCode keycode); - - private: - void AddKeyCode(KeyCode); - bool IsKeyCodePressed(KeyCode) const; - - const std::string m_keyname; - const char* const m_keyboard; - std::vector m_keycodes; - }; - - class Button : public Input - { - public: - std::string GetName() const override { return name; } - Button(unsigned int index, u32* buttons); - ControlState GetState() const override; - - private: - const u32* m_buttons; - const unsigned int m_index; - std::string name; - }; - - class Cursor : public Input - { - public: - std::string GetName() const override { return name; } - bool IsDetectable() const override { return false; } - Cursor(u8 index, bool positive, const float* cursor); - ControlState GetState() const override; - - private: - const float* m_cursor; - const u8 m_index; - const bool m_positive; - std::string name; - }; - - class Axis : public Input - { - public: - std::string GetName() const override { return name; } - bool IsDetectable() const override { return false; } - Axis(u8 index, bool positive, const float* axis); - ControlState GetState() const override; - - private: - const float* m_axis; - const u8 m_index; - const bool m_positive; - std::string name; - }; - - class RelativeMouse : public Input - { - public: - std::string GetName() const override { return name; } - bool IsDetectable() const override { return false; } - RelativeMouse(u8 index, bool positive, const float* axis); - ControlState GetState() const override; - - private: - const float* m_axis; - const u8 m_index; - const bool m_positive; - std::string name; - }; - -private: - void UpdateCursor(bool should_center_mouse); - -public: - Core::DeviceRemoval UpdateInput() override; - - KeyboardMouse(Window window, int opcode, int pointer_deviceid, int keyboard_deviceid, - double scroll_increment); - ~KeyboardMouse(); - - std::string GetName() const override; - std::string GetSource() const override; - int GetSortPriority() const override; - -private: - Window m_window; - Display* m_display; - State m_state{}; - const int xi_opcode; - const int pointer_deviceid; - const int keyboard_deviceid; - const double scroll_increment; - std::string name; -}; -} // namespace ciface::XInput2 diff --git a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp deleted file mode 100644 index 0f6730720f..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp +++ /dev/null @@ -1,878 +0,0 @@ -// Copyright 2015 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "InputCommon/ControllerInterface/evdev/evdev.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include "Common/Assert.h" -#include "Common/Flag.h" -#include "Common/Logging/Log.h" -#include "Common/MathUtil.h" -#include "Common/ScopeGuard.h" -#include "Common/StringUtil.h" -#include "Common/Thread.h" -#include "Common/WorkQueueThread.h" -#include "InputCommon/ControllerInterface/ControllerInterface.h" - -namespace ciface::evdev -{ -class InputBackend final : public ciface::InputBackend -{ -public: - InputBackend(ControllerInterface* controller_interface); - ~InputBackend(); - void PopulateDevices() override; - - void RemoveDevnodeObject(const std::string&); - - // Linux has the strange behavior that closing file descriptors of event devices can be - // surprisingly slow, in the range of 20-70 milliseconds. For modern systems that have maybe 30 - // event devices this can quickly add up, leading to visibly slow startup. So we close FDs on a - // separate thread *shrug* - void CloseDescriptor(int fd) { m_cleanup_thread.Push(fd); } - -private: - std::shared_ptr - FindDeviceWithUniqueIDAndPhysicalLocation(const char* unique_id, const char* physical_location); - - void AddDeviceNode(const char* devnode); - - void StartHotplugThread(); - void StopHotplugThread(); - void HotplugThreadFunc(); - - std::thread m_hotplug_thread; - Common::Flag m_hotplug_thread_running; - int m_wakeup_eventfd; - - // There is no easy way to get the device name from only a dev node - // during a device removed event, since libevdev can't work on removed devices; - // sysfs is not stable, so this is probably the easiest way to get a name for a node. - // This can, and will be modified by different thread, possibly concurrently, - // as devices can be destroyed by any thread at any time. As of now it's protected - // by ControllerInterface::m_devices_population_mutex. - std::map> m_devnode_objects; - - Common::WorkQueueThread m_cleanup_thread; -}; - -std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface) -{ - return std::make_unique(controller_interface); -} - -class Input : public Core::Device::Input -{ -public: - Input(u16 code, libevdev* dev) : m_code(code), m_dev(dev) {} - -protected: - const u16 m_code; - libevdev* const m_dev; -}; - -class Button : public Input -{ -public: - Button(u8 index, u16 code, libevdev* dev) : Input(code, dev), m_index(index) {} - - ControlState GetState() const final override - { - int value = 0; - libevdev_fetch_event_value(m_dev, EV_KEY, m_code, &value); - return value; - } - -protected: - std::optional GetEventCodeName() const - { - if (const char* code_name = libevdev_event_code_get_name(EV_KEY, m_code)) - { - const auto name = StripWhitespace(code_name); - - for (auto remove_prefix : {"BTN_", "KEY_"}) - { - if (name.starts_with(remove_prefix)) - return std::string(name.substr(std::strlen(remove_prefix))); - } - - return std::string(name); - } - else - { - return std::nullopt; - } - } - - std::string GetIndexedName() const { return fmt::format("Button {}", m_index); } - - const u8 m_index; -}; - -class NumberedButton final : public Button -{ -public: - using Button::Button; - - std::string GetName() const override { return GetIndexedName(); } -}; - -class NamedButton final : public Button -{ -public: - using Button::Button; - - bool IsMatchingName(std::string_view name) const final override - { - // Match either the "START" naming provided by evdev or the "Button 0"-like naming. - return name == GetEventCodeName() || name == GetIndexedName(); - } - - std::string GetName() const override { return GetEventCodeName().value_or(GetIndexedName()); } -}; - -class NamedButtonWithNoBackwardsCompat final : public Button -{ -public: - using Button::Button; - - std::string GetName() const override { return GetEventCodeName().value_or(GetIndexedName()); } -}; - -class AnalogInput : public Input -{ -public: - using Input::Input; - - ControlState GetState() const final override - { - int value = 0; - libevdev_fetch_event_value(m_dev, EV_ABS, m_code, &value); - - return (value - m_base) / m_range; - } - -protected: - ControlState m_range; - int m_base; -}; - -class Axis : public AnalogInput -{ -public: - Axis(u8 index, u16 code, bool upper, libevdev* dev) : AnalogInput(code, dev), m_index(index) - { - const int min = libevdev_get_abs_minimum(m_dev, m_code); - const int max = libevdev_get_abs_maximum(m_dev, m_code); - - m_base = (max + min) / 2; - m_range = (upper ? max : min) - m_base; - } - - std::string GetName() const override { return GetIndexedName(); } - -protected: - std::string GetIndexedName() const - { - return fmt::format("Axis {}{}", m_index, m_range < 0 ? '-' : '+'); - } - -private: - const u8 m_index; -}; - -class MotionDataInput final : public AnalogInput -{ -public: - MotionDataInput(u16 code, ControlState resolution_scale, libevdev* dev) : AnalogInput(code, dev) - { - auto* const info = libevdev_get_abs_info(m_dev, m_code); - - // The average of the minimum and maximum value. (neutral value) - m_base = (info->maximum + info->minimum) / 2; - - m_range = info->resolution / resolution_scale; - } - - std::string GetName() const override - { - // Unfortunately there doesn't seem to be a "standard" orientation - // so we can't use "Accel Up"-like names. - constexpr std::array motion_data_names = {{ - "Accel X", - "Accel Y", - "Accel Z", - "Gyro X", - "Gyro Y", - "Gyro Z", - }}; - - // Our name array relies on sane axis codes from 0 to 5. - static_assert(ABS_X == 0, "evdev axis value sanity check"); - static_assert(ABS_RX == 3, "evdev axis value sanity check"); - - return std::string(motion_data_names[m_code]) + (m_range < 0 ? '-' : '+'); - } - - bool IsDetectable() const override { return false; } -}; - -class CursorInput final : public Axis -{ -public: - using Axis::Axis; - - std::string GetName() const final override - { - // "Cursor X-" naming. - return std::string("Cursor ") + char('X' + m_code) + (m_range < 0 ? '-' : '+'); - } - - bool IsDetectable() const override { return false; } -}; - -std::shared_ptr -InputBackend::FindDeviceWithUniqueIDAndPhysicalLocation(const char* unique_id, - const char* physical_location) -{ - if (!unique_id || !physical_location) - return nullptr; - - for (auto& [node, dev] : m_devnode_objects) - { - if (const auto device = dev.lock()) - { - const auto* dev_uniq = device->GetUniqueID(); - const auto* dev_phys = device->GetPhysicalLocation(); - - if (dev_uniq && dev_phys && std::strcmp(dev_uniq, unique_id) == 0 && - std::strcmp(dev_phys, physical_location) == 0) - { - return device; - } - } - } - - return nullptr; -} - -void InputBackend::AddDeviceNode(const char* devnode) -{ - // Unfortunately udev gives us no way to filter out the non event device interfaces. - // So we open it and see if it works with evdev ioctls or not. - - // The device file will be read on one of the main threads, so we open in non-blocking mode. - const int fd = open(devnode, O_RDWR | O_NONBLOCK); - if (fd == -1) - { - return; - } - - libevdev* dev = nullptr; - if (libevdev_new_from_fd(fd, &dev) != 0) - { - // This usually fails because the device node isn't an evdev device, such as /dev/input/js0 - CloseDescriptor(fd); - return; - } - - const auto uniq = libevdev_get_uniq(dev); - const auto phys = libevdev_get_phys(dev); - auto evdev_device = FindDeviceWithUniqueIDAndPhysicalLocation(uniq, phys); - if (evdev_device) - { - NOTICE_LOG_FMT(CONTROLLERINTERFACE, - "evdev combining devices with unique id: {}, physical location: {}", uniq, phys); - - evdev_device->AddNode(devnode, fd, dev); - - // Remove and re-add device as naming and inputs may have changed. - // This will also give it the correct index and invoke device change callbacks. - // Make sure to force the device removal immediately (as they are shared ptrs and - // they could be kept alive, preventing us from re-creating the device) - GetControllerInterface().RemoveDevice( - [&evdev_device](const auto* device) { - return static_cast(device) == evdev_device.get(); - }, - true); - - GetControllerInterface().AddDevice(evdev_device); - } - else - { - evdev_device = std::make_shared(this); - - const bool was_interesting = evdev_device->AddNode(devnode, fd, dev); - - if (was_interesting) - GetControllerInterface().AddDevice(evdev_device); - } - - // If the devices failed to be added to ControllerInterface, it will be added here but then - // immediately removed in its destructor due to the shared ptr not having any references left - m_devnode_objects.emplace(devnode, std::move(evdev_device)); -} - -void InputBackend::HotplugThreadFunc() -{ - Common::SetCurrentThreadName("evdev Hotplug Thread"); - NOTICE_LOG_FMT(CONTROLLERINTERFACE, "evdev hotplug thread started"); - - udev* const udev = udev_new(); - Common::ScopeGuard udev_guard([udev] { udev_unref(udev); }); - - ASSERT_MSG(CONTROLLERINTERFACE, udev != nullptr, "Couldn't initialize libudev."); - - // Set up monitoring - udev_monitor* const monitor = udev_monitor_new_from_netlink(udev, "udev"); - Common::ScopeGuard monitor_guard([monitor] { udev_monitor_unref(monitor); }); - - udev_monitor_filter_add_match_subsystem_devtype(monitor, "input", nullptr); - udev_monitor_enable_receiving(monitor); - const int monitor_fd = udev_monitor_get_fd(monitor); - - while (m_hotplug_thread_running.IsSet()) - { - fd_set fds; - - FD_ZERO(&fds); - FD_SET(monitor_fd, &fds); - FD_SET(m_wakeup_eventfd, &fds); - - const int ret = - select(std::max(monitor_fd, m_wakeup_eventfd) + 1, &fds, nullptr, nullptr, nullptr); - if (ret < 1 || !FD_ISSET(monitor_fd, &fds)) - continue; - - udev_device* const dev = udev_monitor_receive_device(monitor); - Common::ScopeGuard dev_guard([dev] { udev_device_unref(dev); }); - - const char* const action = udev_device_get_action(dev); - const char* const devnode = udev_device_get_devnode(dev); - if (!devnode) - continue; - - // Use GetControllerInterface().PlatformPopulateDevices() to protect access around - // m_devnode_objects. Note that even if we get these events at the same time as a - // a PopulateDevices() request (e.g. on start up, we might get all the add events - // for connected devices), this won't ever cause duplicate devices as AddDeviceNode() - // automatically removes the old one if it already existed - if (strcmp(action, "remove") == 0) - { - GetControllerInterface().PlatformPopulateDevices([&devnode, this] { - std::shared_ptr ptr; - - const auto it = m_devnode_objects.find(devnode); - if (it != m_devnode_objects.end()) - ptr = it->second.lock(); - - // If we don't recognize this device, ptr will be null and no device will be removed. - - GetControllerInterface().RemoveDevice([&ptr](const auto* device) { - return static_cast(device) == ptr.get(); - }); - }); - } - else if (strcmp(action, "add") == 0) - { - GetControllerInterface().PlatformPopulateDevices( - [&devnode, this] { AddDeviceNode(devnode); }); - } - } - NOTICE_LOG_FMT(CONTROLLERINTERFACE, "evdev hotplug thread stopped"); -} - -void InputBackend::StartHotplugThread() -{ - // Mark the thread as running. - if (!m_hotplug_thread_running.TestAndSet()) - { - // It was already running. - return; - } - - m_wakeup_eventfd = eventfd(0, 0); - ASSERT_MSG(CONTROLLERINTERFACE, m_wakeup_eventfd != -1, "Couldn't create eventfd."); - m_hotplug_thread = std::thread(&InputBackend::HotplugThreadFunc, this); -} - -void InputBackend::StopHotplugThread() -{ - // Tell the hotplug thread to stop. - if (!m_hotplug_thread_running.TestAndClear()) - { - // It wasn't running, we're done. - return; - } - - // Write something to efd so that select() stops blocking. - const uint64_t value = 1; - static_cast(!write(m_wakeup_eventfd, &value, sizeof(uint64_t))); - - m_hotplug_thread.join(); - close(m_wakeup_eventfd); -} - -InputBackend::InputBackend(ControllerInterface* controller_interface) - : ciface::InputBackend(controller_interface), m_cleanup_thread("evdev cleanup", close) -{ - StartHotplugThread(); -} - -// Only call this when ControllerInterface::m_devices_population_mutex is locked -void InputBackend::PopulateDevices() -{ - // Don't run if we are not initialized - if (!m_hotplug_thread_running.IsSet()) - { - return; - } - - // We use udev to iterate over all /dev/input/event* devices. - // Note: the Linux kernel is currently limited to just 32 event devices. If - // this ever changes, hopefully udev will take care of this. - - udev* const udev = udev_new(); - ASSERT_MSG(CONTROLLERINTERFACE, udev != nullptr, "Couldn't initialize libudev."); - - // List all input devices - udev_enumerate* const enumerate = udev_enumerate_new(udev); - udev_enumerate_add_match_subsystem(enumerate, "input"); - udev_enumerate_scan_devices(enumerate); - udev_list_entry* const devices = udev_enumerate_get_list_entry(enumerate); - - // Iterate over all input devices - udev_list_entry* dev_list_entry; - udev_list_entry_foreach(dev_list_entry, devices) - { - const char* path = udev_list_entry_get_name(dev_list_entry); - - udev_device* dev = udev_device_new_from_syspath(udev, path); - - if (const char* devnode = udev_device_get_devnode(dev)) - AddDeviceNode(devnode); - - udev_device_unref(dev); - } - udev_enumerate_unref(enumerate); - udev_unref(udev); -} - -InputBackend::~InputBackend() -{ - StopHotplugThread(); -} - -bool evdevDevice::AddNode(std::string devnode, int fd, libevdev* dev) -{ - m_nodes.emplace_back(Node{std::move(devnode), fd, dev}); - - // Take on the alphabetically first name. - const auto potential_new_name = StripWhitespace(libevdev_get_name(dev)); - if (m_name.empty() || potential_new_name < m_name) - m_name = potential_new_name; - - const bool is_motion_device = libevdev_has_property(dev, INPUT_PROP_ACCELEROMETER); - const bool is_pointing_device = libevdev_has_property(dev, INPUT_PROP_BUTTONPAD); - - // If a device has BTN_JOYSTICK it probably uses event codes counting up from 0x120 - // which have very useless and wrong names. - const bool has_btn_joystick = libevdev_has_event_code(dev, EV_KEY, BTN_JOYSTICK); - const bool has_sensible_button_names = !has_btn_joystick; - - // Buttons (and keyboard keys) - int num_buttons = 0; - for (int key = 0; key != KEY_CNT; ++key) - { - if (libevdev_has_event_code(dev, EV_KEY, key)) - { - if (is_pointing_device || is_motion_device) - { - // This node will probably be combined with another with regular buttons. - // We don't want to match "Button 0" names here as it will name clash. - AddInput(new NamedButtonWithNoBackwardsCompat(num_buttons, key, dev)); - } - else if (has_sensible_button_names) - { - AddInput(new NamedButton(num_buttons, key, dev)); - } - else - { - AddInput(new NumberedButton(num_buttons, key, dev)); - } - - ++num_buttons; - } - } - - int num_axis = 0; - - if (is_motion_device) - { - // If INPUT_PROP_ACCELEROMETER is set then X,Y,Z,RX,RY,RZ contain motion data. - - auto add_motion_inputs = [&num_axis, dev, this](int first_code, double scale) { - for (int i = 0; i != 3; ++i) - { - const int code = first_code + i; - if (libevdev_has_event_code(dev, EV_ABS, code)) - { - AddInput(new MotionDataInput(code, scale * -1, dev)); - AddInput(new MotionDataInput(code, scale, dev)); - - ++num_axis; - } - } - }; - - // evdev resolution is specified in "g"s and deg/s. - // Convert these to m/s/s and rad/s. - constexpr ControlState accel_scale = MathUtil::GRAVITY_ACCELERATION; - constexpr ControlState gyro_scale = MathUtil::TAU / 360; - - add_motion_inputs(ABS_X, accel_scale); - add_motion_inputs(ABS_RX, gyro_scale); - - return true; - } - - if (is_pointing_device) - { - auto add_cursor_input = [&num_axis, dev, this](int code) { - if (libevdev_has_event_code(dev, EV_ABS, code)) - { - AddInput(new CursorInput(num_axis, code, false, dev)); - AddInput(new CursorInput(num_axis, code, true, dev)); - - ++num_axis; - } - }; - - add_cursor_input(ABS_X); - add_cursor_input(ABS_Y); - - return true; - } - - // Axes beyond ABS_MISC have strange behavior (for multi-touch) which we do not handle. - const int abs_axis_code_count = ABS_MISC; - - // Absolute axis (thumbsticks) - for (int axis = 0; axis != abs_axis_code_count; ++axis) - { - if (libevdev_has_event_code(dev, EV_ABS, axis)) - { - AddFullAnalogSurfaceInputs(new Axis(num_axis, axis, false, dev), - new Axis(num_axis, axis, true, dev)); - ++num_axis; - } - } - - // Disable autocenter - if (libevdev_has_event_code(dev, EV_FF, FF_AUTOCENTER)) - { - input_event ie = {}; - ie.type = EV_FF; - ie.code = FF_AUTOCENTER; - ie.value = 0; - - static_cast(!write(fd, &ie, sizeof(ie))); - } - - // Constant FF effect - if (libevdev_has_event_code(dev, EV_FF, FF_CONSTANT)) - { - AddOutput(new ConstantEffect(fd)); - } - - // Periodic FF effects - if (libevdev_has_event_code(dev, EV_FF, FF_PERIODIC)) - { - for (auto wave : {FF_SINE, FF_SQUARE, FF_TRIANGLE, FF_SAW_UP, FF_SAW_DOWN}) - { - if (libevdev_has_event_code(dev, EV_FF, wave)) - AddOutput(new PeriodicEffect(fd, wave)); - } - } - - // Rumble (i.e. Left/Right) (i.e. Strong/Weak) effect - if (libevdev_has_event_code(dev, EV_FF, FF_RUMBLE)) - { - AddOutput(new RumbleEffect(fd, RumbleEffect::Motor::Strong)); - AddOutput(new RumbleEffect(fd, RumbleEffect::Motor::Weak)); - } - - // TODO: Add leds as output devices - - // Filter out interesting devices (see description below) - return num_axis >= 2 || num_buttons >= 8; - - // On modern linux systems, there are a lot of event devices that aren't controllers. - // For example, the PC Speaker is an event device. Webcams sometimes show up as - // event devices. The power button is an event device. - // - // We don't want these showing up in the list of controllers, so we use this - // heuristic to filter out anything that doesn't smell like a controller: - // - // More than two analog axis: - // Most controllers have at least one stick. This rule will catch all such - // controllers, while ignoring anything with a single axis (like the mouse - // scroll-wheel) - // - // --- OR --- - // - // More than 8 buttons: - // The user might be using a digital only pad such as a NES controller. - // This rule caches such controllers, while eliminating any device with - // only a few buttons, like the power button. Sometimes laptops have devices - // with 5 or 6 special buttons, which is why the threshold is set to 8 to - // match a NES controller. - // - // This heuristic is quite loose. The user may still see weird devices showing up - // as controllers, but it hopefully shouldn't filter out anything they actually - // want to use. -} - -const char* evdevDevice::GetUniqueID() const -{ - if (m_nodes.empty()) - return nullptr; - - const auto uniq = libevdev_get_uniq(m_nodes.front().device); - - // Some devices (e.g. Mayflash adapter) return an empty string which is not very unique. - if (uniq && std::strlen(uniq) == 0) - return nullptr; - - return uniq; -} - -const char* evdevDevice::GetPhysicalLocation() const -{ - if (m_nodes.empty()) - return nullptr; - - return libevdev_get_phys(m_nodes.front().device); -} - -evdevDevice::evdevDevice(InputBackend* input_backend) : m_input_backend(*input_backend) -{ -} - -evdevDevice::~evdevDevice() -{ - for (auto& node : m_nodes) - { - m_input_backend.RemoveDevnodeObject(node.devnode); - libevdev_free(node.device); - m_input_backend.CloseDescriptor(node.fd); - } -} - -void InputBackend::RemoveDevnodeObject(const std::string& node) -{ - m_devnode_objects.erase(node); -} - -Core::DeviceRemoval evdevDevice::UpdateInput() -{ - // Run through all evdev events - // libevdev will keep track of the actual controller state internally which can be queried - // later with libevdev_fetch_event_value() - for (auto& node : m_nodes) - { - int rc = LIBEVDEV_READ_STATUS_SUCCESS; - while (rc >= 0) - { - input_event ev; - if (LIBEVDEV_READ_STATUS_SYNC == rc) - rc = libevdev_next_event(node.device, LIBEVDEV_READ_FLAG_SYNC, &ev); - else - rc = libevdev_next_event(node.device, LIBEVDEV_READ_FLAG_NORMAL, &ev); - } - } - return Core::DeviceRemoval::Keep; -} - -bool evdevDevice::IsValid() const -{ - for (auto& node : m_nodes) - { - const int current_fd = libevdev_get_fd(node.device); - - if (current_fd == -1) - return false; - - libevdev* device = nullptr; - if (libevdev_new_from_fd(current_fd, &device) != 0) - { - close(current_fd); - return false; - } - - libevdev_free(device); - } - - return true; -} - -evdevDevice::Effect::Effect(int fd) : m_fd(fd) -{ - m_effect.id = -1; - // Left (for wheels): - m_effect.direction = 0x4000; - m_effect.replay.length = RUMBLE_LENGTH_MS; - - // FYI: type is set within UpdateParameters. - m_effect.type = DISABLED_EFFECT_TYPE; -} - -std::string evdevDevice::ConstantEffect::GetName() const -{ - return "Constant"; -} - -std::string evdevDevice::PeriodicEffect::GetName() const -{ - switch (m_effect.u.periodic.waveform) - { - case FF_SQUARE: - return "Square"; - case FF_TRIANGLE: - return "Triangle"; - case FF_SINE: - return "Sine"; - case FF_SAW_UP: - return "Sawtooth Up"; - case FF_SAW_DOWN: - return "Sawtooth Down"; - default: - return "Unknown"; - } -} - -std::string evdevDevice::RumbleEffect::GetName() const -{ - return (Motor::Strong == m_motor) ? "Strong" : "Weak"; -} - -void evdevDevice::Effect::SetState(ControlState state) -{ - if (UpdateParameters(state)) - { - // Update effect if parameters changed. - UpdateEffect(); - } -} - -void evdevDevice::Effect::UpdateEffect() -{ - // libevdev doesn't have nice helpers for forcefeedback - // we will use the file descriptors directly. - - // Note: m_effect.type is set within UpdateParameters - // to determine if effect should be playing or not. - if (m_effect.type != DISABLED_EFFECT_TYPE) - { - if (-1 == m_effect.id) - { - // If effect was not uploaded (previously stopped) - // we upload it and start playback - - ioctl(m_fd, EVIOCSFF, &m_effect); - - input_event play = {}; - play.type = EV_FF; - play.code = m_effect.id; - play.value = 1; - - static_cast(!write(m_fd, &play, sizeof(play))); - } - else - { - // Effect is already playing. Just update parameters. - ioctl(m_fd, EVIOCSFF, &m_effect); - } - } - else - { - // Stop and remove effect. - ioctl(m_fd, EVIOCRMFF, m_effect.id); - m_effect.id = -1; - } -} - -evdevDevice::ConstantEffect::ConstantEffect(int fd) : Effect(fd) -{ - m_effect.u.constant = {}; -} - -evdevDevice::PeriodicEffect::PeriodicEffect(int fd, u16 waveform) : Effect(fd) -{ - m_effect.u.periodic = {}; - m_effect.u.periodic.waveform = waveform; - m_effect.u.periodic.period = RUMBLE_PERIOD_MS; - m_effect.u.periodic.offset = 0; - m_effect.u.periodic.phase = 0; -} - -evdevDevice::RumbleEffect::RumbleEffect(int fd, Motor motor) : Effect(fd), m_motor(motor) -{ - m_effect.u.rumble = {}; -} - -bool evdevDevice::ConstantEffect::UpdateParameters(ControlState state) -{ - s16& value = m_effect.u.constant.level; - const s16 old_value = value; - - constexpr s16 MAX_VALUE = 0x7fff; - value = s16(state * MAX_VALUE); - - m_effect.type = value ? FF_CONSTANT : DISABLED_EFFECT_TYPE; - return value != old_value; -} - -bool evdevDevice::PeriodicEffect::UpdateParameters(ControlState state) -{ - s16& value = m_effect.u.periodic.magnitude; - const s16 old_value = value; - - constexpr s16 MAX_VALUE = 0x7fff; - value = s16(state * MAX_VALUE); - - m_effect.type = value ? FF_PERIODIC : DISABLED_EFFECT_TYPE; - return value != old_value; -} - -bool evdevDevice::RumbleEffect::UpdateParameters(ControlState state) -{ - u16& value = (Motor::Strong == m_motor) ? m_effect.u.rumble.strong_magnitude : - m_effect.u.rumble.weak_magnitude; - const u16 old_value = value; - - constexpr u16 MAX_VALUE = 0xffff; - value = u16(state * MAX_VALUE); - - m_effect.type = value ? FF_RUMBLE : DISABLED_EFFECT_TYPE; - return value != old_value; -} - -evdevDevice::Effect::~Effect() -{ - m_effect.type = DISABLED_EFFECT_TYPE; - UpdateEffect(); -} -} // namespace ciface::evdev diff --git a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h deleted file mode 100644 index f32567af8c..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2015 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include - -#include "InputCommon/ControllerInterface/ControllerInterface.h" - -namespace ciface::evdev -{ -class InputBackend; - -std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface); - -class evdevDevice : public Core::Device -{ -private: - class Effect : public Core::Device::Output - { - public: - Effect(int fd); - ~Effect(); - void SetState(ControlState state) override; - - protected: - virtual bool UpdateParameters(ControlState state) = 0; - - ff_effect m_effect = {}; - - static constexpr int DISABLED_EFFECT_TYPE = 0; - - private: - void UpdateEffect(); - - int const m_fd; - }; - - class ConstantEffect : public Effect - { - public: - ConstantEffect(int fd); - bool UpdateParameters(ControlState state) override; - std::string GetName() const override; - }; - - class PeriodicEffect : public Effect - { - public: - PeriodicEffect(int fd, u16 waveform); - bool UpdateParameters(ControlState state) override; - std::string GetName() const override; - }; - - class RumbleEffect : public Effect - { - public: - enum class Motor : u8 - { - Weak, - Strong, - }; - - RumbleEffect(int fd, Motor motor); - bool UpdateParameters(ControlState state) override; - std::string GetName() const override; - - private: - const Motor m_motor; - }; - -public: - Core::DeviceRemoval UpdateInput() override; - bool IsValid() const override; - - evdevDevice(InputBackend* input_backend); - ~evdevDevice(); - - // Return true if node was "interesting". - bool AddNode(std::string devnode, int fd, libevdev* dev); - - const char* GetUniqueID() const; - const char* GetPhysicalLocation() const; - - std::string GetName() const override { return m_name; } - std::string GetSource() const override { return "evdev"; } - -private: - std::string m_name; - - struct Node - { - std::string devnode; - int fd; - libevdev* device; - }; - - std::vector m_nodes; - - InputBackend& m_input_backend; -}; -} // namespace ciface::evdev