aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt70
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt55
-rw-r--r--src/core/hle/service/am/am.cpp13
-rw-r--r--src/core/hle/service/caps/caps_manager.cpp16
-rw-r--r--src/core/hle/service/caps/caps_manager.h9
-rw-r--r--src/core/hle/service/caps/caps_ss.cpp10
-rw-r--r--src/core/hle/service/caps/caps_su.cpp42
-rw-r--r--src/core/hle/service/caps/caps_su.h9
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;
};