diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt | 16 | ||||
-rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt | 70 | ||||
-rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt | 55 | ||||
-rw-r--r-- | src/core/hle/service/am/am.cpp | 13 | ||||
-rw-r--r-- | src/core/hle/service/caps/caps_manager.cpp | 16 | ||||
-rw-r--r-- | src/core/hle/service/caps/caps_manager.h | 9 | ||||
-rw-r--r-- | src/core/hle/service/caps/caps_ss.cpp | 10 | ||||
-rw-r--r-- | src/core/hle/service/caps/caps_su.cpp | 42 | ||||
-rw-r--r-- | src/core/hle/service/caps/caps_su.h | 9 |
9 files changed, 136 insertions, 104 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index e96a2059b..f37875ffe 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -45,7 +45,6 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.model.EmulationViewModel import org.yuzu.yuzu_emu.model.Game -import org.yuzu.yuzu_emu.utils.ControllerMappingHelper import org.yuzu.yuzu_emu.utils.ForegroundService import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.MemoryUtil @@ -57,17 +56,16 @@ import kotlin.math.roundToInt class EmulationActivity : AppCompatActivity(), SensorEventListener { private lateinit var binding: ActivityEmulationBinding - private var controllerMappingHelper: ControllerMappingHelper? = null - var isActivityRecreated = false private lateinit var nfcReader: NfcReader - private lateinit var inputHandler: InputHandler private val gyro = FloatArray(3) private val accel = FloatArray(3) private var motionTimestamp: Long = 0 private var flipMotionOrientation: Boolean = false + private var controllerIds = InputHandler.getGameControllerIds() + private val actionPause = "ACTION_EMULATOR_PAUSE" private val actionPlay = "ACTION_EMULATOR_PLAY" private val actionMute = "ACTION_EMULATOR_MUTE" @@ -95,8 +93,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { isActivityRecreated = savedInstanceState != null - controllerMappingHelper = ControllerMappingHelper() - // Set these options now so that the SurfaceView the game renders into is the right size. enableFullscreenImmersive() @@ -105,8 +101,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { nfcReader = NfcReader(this) nfcReader.initialize() - inputHandler = InputHandler() - inputHandler.initialize() + InputHandler.initialize() val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) { @@ -162,6 +157,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { super.onResume() nfcReader.startScanning() startMotionSensorListener() + InputHandler.updateControllerIds() buildPictureInPictureParams() } @@ -195,7 +191,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { return super.dispatchKeyEvent(event) } - return inputHandler.dispatchKeyEvent(event) + return InputHandler.dispatchKeyEvent(event) } override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { @@ -210,7 +206,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { return true } - return inputHandler.dispatchGenericMotionEvent(event) + return InputHandler.dispatchGenericMotionEvent(event) } override fun onSensorChanged(event: SensorEvent) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt deleted file mode 100644 index eeefcdf20..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.yuzu.yuzu_emu.utils - -import android.view.InputDevice -import android.view.KeyEvent -import android.view.MotionEvent - -/** - * Some controllers have incorrect mappings. This class has special-case fixes for them. - */ -class ControllerMappingHelper { - /** - * Some controllers report extra button presses that can be ignored. - */ - fun shouldKeyBeIgnored(inputDevice: InputDevice, keyCode: Int): Boolean { - return if (isDualShock4(inputDevice)) { - // The two analog triggers generate analog motion events as well as a keycode. - // We always prefer to use the analog values, so throw away the button press - keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2 - } else { - false - } - } - - /** - * Scale an axis to be zero-centered with a proper range. - */ - fun scaleAxis(inputDevice: InputDevice, axis: Int, value: Float): Float { - if (isDualShock4(inputDevice)) { - // Android doesn't have correct mappings for this controller's triggers. It reports them - // as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0] - // Scale them to properly zero-centered with a range of [0.0, 1.0]. - if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY) { - return (value + 1) / 2.0f - } - } else if (isXboxOneWireless(inputDevice)) { - // Same as the DualShock 4, the mappings are missing. - if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ) { - return (value + 1) / 2.0f - } - if (axis == MotionEvent.AXIS_GENERIC_1) { - // This axis is stuck at ~.5. Ignore it. - return 0.0f - } - } else if (isMogaPro2Hid(inputDevice)) { - // This controller has a broken axis that reports a constant value. Ignore it. - if (axis == MotionEvent.AXIS_GENERIC_1) { - return 0.0f - } - } - return value - } - - // Sony DualShock 4 controller - private fun isDualShock4(inputDevice: InputDevice): Boolean { - return inputDevice.vendorId == 0x54c && inputDevice.productId == 0x9cc - } - - // Microsoft Xbox One controller - private fun isXboxOneWireless(inputDevice: InputDevice): Boolean { - return inputDevice.vendorId == 0x45e && inputDevice.productId == 0x2e0 - } - - // Moga Pro 2 HID - private fun isMogaPro2Hid(inputDevice: InputDevice): Boolean { - return inputDevice.vendorId == 0x20d6 && inputDevice.productId == 0x6271 - } -} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt index e963dfbc1..fc6a8b5cb 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt @@ -3,17 +3,24 @@ package org.yuzu.yuzu_emu.utils +import android.view.InputDevice import android.view.KeyEvent import android.view.MotionEvent import kotlin.math.sqrt import org.yuzu.yuzu_emu.NativeLibrary -class InputHandler { +object InputHandler { + private var controllerIds = getGameControllerIds() + fun initialize() { // Connect first controller NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device)) } + fun updateControllerIds() { + controllerIds = getGameControllerIds() + } + fun dispatchKeyEvent(event: KeyEvent): Boolean { val button: Int = when (event.device.vendorId) { 0x045E -> getInputXboxButtonKey(event.keyCode) @@ -35,7 +42,7 @@ class InputHandler { } return NativeLibrary.onGamePadButtonEvent( - getPlayerNumber(event.device.controllerNumber), + getPlayerNumber(event.device.controllerNumber, event.deviceId), button, action ) @@ -58,9 +65,14 @@ class InputHandler { return true } - private fun getPlayerNumber(index: Int): Int { + private fun getPlayerNumber(index: Int, deviceId: Int = -1): Int { + var deviceIndex = index + if (deviceId != -1) { + deviceIndex = controllerIds[deviceId]!! + } + // TODO: Joycons are handled as different controllers. Find a way to merge them. - return when (index) { + return when (deviceIndex) { 2 -> NativeLibrary.Player2Device 3 -> NativeLibrary.Player3Device 4 -> NativeLibrary.Player4Device @@ -238,7 +250,7 @@ class InputHandler { } private fun setGenericAxisInput(event: MotionEvent, axis: Int) { - val playerNumber = getPlayerNumber(event.device.controllerNumber) + val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId) when (axis) { MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> @@ -297,7 +309,7 @@ class InputHandler { private fun setJoyconAxisInput(event: MotionEvent, axis: Int) { // Joycon support is half dead. Right joystick doesn't work - val playerNumber = getPlayerNumber(event.device.controllerNumber) + val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId) when (axis) { MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> @@ -325,7 +337,7 @@ class InputHandler { } private fun setRazerAxisInput(event: MotionEvent, axis: Int) { - val playerNumber = getPlayerNumber(event.device.controllerNumber) + val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId) when (axis) { MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> @@ -362,4 +374,33 @@ class InputHandler { ) } } + + fun getGameControllerIds(): Map<Int, Int> { + val gameControllerDeviceIds = mutableMapOf<Int, Int>() + val deviceIds = InputDevice.getDeviceIds() + var controllerSlot = 1 + deviceIds.forEach { deviceId -> + InputDevice.getDevice(deviceId)?.apply { + // Don't over-assign controllers + if (controllerSlot >= 8) { + return gameControllerDeviceIds + } + + // Verify that the device has gamepad buttons, control sticks, or both. + if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD || + sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK + ) { + // This device is a game controller. Store its device ID. + if (deviceId and id and vendorId and productId != 0) { + // Additionally filter out devices that have no ID + gameControllerDeviceIds + .takeIf { !it.contains(deviceId) } + ?.put(deviceId, controllerSlot) + controllerSlot++ + } + } + } + } + return gameControllerDeviceIds + } } diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index c80b29928..cc643ea09 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -32,6 +32,7 @@ #include "core/hle/service/apm/apm_controller.h" #include "core/hle/service/apm/apm_interface.h" #include "core/hle/service/bcat/backend/backend.h" +#include "core/hle/service/caps/caps_su.h" #include "core/hle/service/caps/caps_types.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/ipc_helpers.h" @@ -703,9 +704,17 @@ void ISelfController::SetAlbumImageTakenNotificationEnabled(HLERequestContext& c void ISelfController::SaveCurrentScreenshot(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto album_report_option = rp.PopEnum<Capture::AlbumReportOption>(); + const auto report_option = rp.PopEnum<Capture::AlbumReportOption>(); - LOG_WARNING(Service_AM, "(STUBBED) called. album_report_option={}", album_report_option); + LOG_INFO(Service_AM, "called, report_option={}", report_option); + + const auto screenshot_service = + system.ServiceManager().GetService<Service::Capture::IScreenShotApplicationService>( + "caps:su"); + + if (screenshot_service) { + screenshot_service->CaptureAndSaveScreenshot(report_option); + } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp index 7d733eb54..96b225d5f 100644 --- a/src/core/hle/service/caps/caps_manager.cpp +++ b/src/core/hle/service/caps/caps_manager.cpp @@ -228,12 +228,14 @@ Result AlbumManager::LoadAlbumScreenShotThumbnail( Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute, - std::span<const u8> image_data, u64 aruid) { - return SaveScreenShot(out_entry, attribute, {}, image_data, aruid); + AlbumReportOption report_option, std::span<const u8> image_data, + u64 aruid) { + return SaveScreenShot(out_entry, attribute, report_option, {}, image_data, aruid); } Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute, + AlbumReportOption report_option, const ApplicationData& app_data, std::span<const u8> image_data, u64 aruid) { const u64 title_id = system.GetApplicationProcessProgramID(); @@ -407,10 +409,14 @@ Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::p return ResultSuccess; } -static void PNGToMemory(void* context, void* png, int len) { +void AlbumManager::FlipVerticallyOnWrite(bool flip) { + stbi_flip_vertically_on_write(flip); +} + +static void PNGToMemory(void* context, void* data, int len) { std::vector<u8>* png_image = static_cast<std::vector<u8>*>(context); - png_image->reserve(len); - std::memcpy(png_image->data(), png, len); + unsigned char* png = static_cast<unsigned char*>(data); + png_image->insert(png_image->end(), png, png + len); } Result AlbumManager::SaveImage(ApplicationAlbumEntry& out_entry, std::span<const u8> image, diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h index 44d85117f..e20c70c7b 100644 --- a/src/core/hle/service/caps/caps_manager.h +++ b/src/core/hle/service/caps/caps_manager.h @@ -59,14 +59,17 @@ public: const ScreenShotDecodeOption& decoder_options) const; Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute, - std::span<const u8> image_data, u64 aruid); - Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute, - const ApplicationData& app_data, std::span<const u8> image_data, + AlbumReportOption report_option, std::span<const u8> image_data, u64 aruid); + Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute, + AlbumReportOption report_option, const ApplicationData& app_data, + std::span<const u8> image_data, u64 aruid); Result SaveEditedScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute, const AlbumFileId& file_id, std::span<const u8> image_data); + void FlipVerticallyOnWrite(bool flip); + private: static constexpr std::size_t NandAlbumFileLimit = 1000; static constexpr std::size_t SdAlbumFileLimit = 10000; diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp index 1ba2b7972..eab023568 100644 --- a/src/core/hle/service/caps/caps_ss.cpp +++ b/src/core/hle/service/caps/caps_ss.cpp @@ -34,7 +34,7 @@ void IScreenShotService::SaveScreenShotEx0(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { ScreenShotAttribute attribute{}; - u32 report_option{}; + AlbumReportOption report_option{}; INSERT_PADDING_BYTES(0x4); u64 applet_resource_user_id{}; }; @@ -49,13 +49,16 @@ void IScreenShotService::SaveScreenShotEx0(HLERequestContext& ctx) { parameters.applet_resource_user_id); ApplicationAlbumEntry entry{}; - const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer, - parameters.applet_resource_user_id); + manager->FlipVerticallyOnWrite(false); + const auto result = + manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option, + image_data_buffer, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 10}; rb.Push(result); rb.PushRaw(entry); } + void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { @@ -83,6 +86,7 @@ void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) { image_data_buffer.size(), thumbnail_image_data_buffer.size()); ApplicationAlbumEntry entry{}; + manager->FlipVerticallyOnWrite(false); const auto result = manager->SaveEditedScreenShot(entry, parameters.attribute, parameters.file_id, image_data_buffer); diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp index e85625ee4..296b07b00 100644 --- a/src/core/hle/service/caps/caps_su.cpp +++ b/src/core/hle/service/caps/caps_su.cpp @@ -2,10 +2,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/service/caps/caps_manager.h" #include "core/hle/service/caps/caps_su.h" #include "core/hle/service/caps/caps_types.h" #include "core/hle/service/ipc_helpers.h" +#include "video_core/renderer_base.h" namespace Service::Capture { @@ -58,8 +60,10 @@ void IScreenShotApplicationService::SaveScreenShotEx0(HLERequestContext& ctx) { parameters.applet_resource_user_id); ApplicationAlbumEntry entry{}; - const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer, - parameters.applet_resource_user_id); + manager->FlipVerticallyOnWrite(false); + const auto result = + manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option, + image_data_buffer, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 10}; rb.Push(result); @@ -88,13 +92,43 @@ void IScreenShotApplicationService::SaveScreenShotEx1(HLERequestContext& ctx) { ApplicationAlbumEntry entry{}; ApplicationData app_data{}; std::memcpy(&app_data, app_data_buffer.data(), sizeof(ApplicationData)); + manager->FlipVerticallyOnWrite(false); const auto result = - manager->SaveScreenShot(entry, parameters.attribute, app_data, image_data_buffer, - parameters.applet_resource_user_id); + manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option, app_data, + image_data_buffer, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 10}; rb.Push(result); rb.PushRaw(entry); } +void IScreenShotApplicationService::CaptureAndSaveScreenshot(AlbumReportOption report_option) { + auto& renderer = system.Renderer(); + Layout::FramebufferLayout layout = + Layout::DefaultFrameLayout(screenshot_width, screenshot_height); + + const Capture::ScreenShotAttribute attribute{ + .unknown_0{}, + .orientation = Capture::AlbumImageOrientation::None, + .unknown_1{}, + .unknown_2{}, + }; + + renderer.RequestScreenshot( + image_data.data(), + [attribute, report_option, this](bool invert_y) { + // Convert from BGRA to RGBA + for (std::size_t i = 0; i < image_data.size(); i += bytes_per_pixel) { + const u8 temp = image_data[i]; + image_data[i] = image_data[i + 2]; + image_data[i + 2] = temp; + } + + Capture::ApplicationAlbumEntry entry{}; + manager->FlipVerticallyOnWrite(invert_y); + manager->SaveScreenShot(entry, attribute, report_option, image_data, {}); + }, + layout); +} + } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h index 89e71f506..21912e95f 100644 --- a/src/core/hle/service/caps/caps_su.h +++ b/src/core/hle/service/caps/caps_su.h @@ -10,6 +10,7 @@ class System; } namespace Service::Capture { +enum class AlbumReportOption : s32; class AlbumManager; class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> { @@ -18,11 +19,19 @@ public: std::shared_ptr<AlbumManager> album_manager); ~IScreenShotApplicationService() override; + void CaptureAndSaveScreenshot(AlbumReportOption report_option); + private: + static constexpr std::size_t screenshot_width = 1280; + static constexpr std::size_t screenshot_height = 720; + static constexpr std::size_t bytes_per_pixel = 4; + void SetShimLibraryVersion(HLERequestContext& ctx); void SaveScreenShotEx0(HLERequestContext& ctx); void SaveScreenShotEx1(HLERequestContext& ctx); + std::array<u8, screenshot_width * screenshot_height * bytes_per_pixel> image_data; + std::shared_ptr<AlbumManager> manager; }; |