summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
45import org.yuzu.yuzu_emu.features.settings.model.Settings 45import org.yuzu.yuzu_emu.features.settings.model.Settings
46import org.yuzu.yuzu_emu.model.EmulationViewModel 46import org.yuzu.yuzu_emu.model.EmulationViewModel
47import org.yuzu.yuzu_emu.model.Game 47import org.yuzu.yuzu_emu.model.Game
48import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
49import org.yuzu.yuzu_emu.utils.ForegroundService 48import org.yuzu.yuzu_emu.utils.ForegroundService
50import org.yuzu.yuzu_emu.utils.InputHandler 49import org.yuzu.yuzu_emu.utils.InputHandler
51import org.yuzu.yuzu_emu.utils.MemoryUtil 50import org.yuzu.yuzu_emu.utils.MemoryUtil
@@ -57,17 +56,16 @@ import kotlin.math.roundToInt
57class EmulationActivity : AppCompatActivity(), SensorEventListener { 56class EmulationActivity : AppCompatActivity(), SensorEventListener {
58 private lateinit var binding: ActivityEmulationBinding 57 private lateinit var binding: ActivityEmulationBinding
59 58
60 private var controllerMappingHelper: ControllerMappingHelper? = null
61
62 var isActivityRecreated = false 59 var isActivityRecreated = false
63 private lateinit var nfcReader: NfcReader 60 private lateinit var nfcReader: NfcReader
64 private lateinit var inputHandler: InputHandler
65 61
66 private val gyro = FloatArray(3) 62 private val gyro = FloatArray(3)
67 private val accel = FloatArray(3) 63 private val accel = FloatArray(3)
68 private var motionTimestamp: Long = 0 64 private var motionTimestamp: Long = 0
69 private var flipMotionOrientation: Boolean = false 65 private var flipMotionOrientation: Boolean = false
70 66
67 private var controllerIds = InputHandler.getGameControllerIds()
68
71 private val actionPause = "ACTION_EMULATOR_PAUSE" 69 private val actionPause = "ACTION_EMULATOR_PAUSE"
72 private val actionPlay = "ACTION_EMULATOR_PLAY" 70 private val actionPlay = "ACTION_EMULATOR_PLAY"
73 private val actionMute = "ACTION_EMULATOR_MUTE" 71 private val actionMute = "ACTION_EMULATOR_MUTE"
@@ -95,8 +93,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
95 93
96 isActivityRecreated = savedInstanceState != null 94 isActivityRecreated = savedInstanceState != null
97 95
98 controllerMappingHelper = ControllerMappingHelper()
99
100 // Set these options now so that the SurfaceView the game renders into is the right size. 96 // Set these options now so that the SurfaceView the game renders into is the right size.
101 enableFullscreenImmersive() 97 enableFullscreenImmersive()
102 98
@@ -105,8 +101,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
105 nfcReader = NfcReader(this) 101 nfcReader = NfcReader(this)
106 nfcReader.initialize() 102 nfcReader.initialize()
107 103
108 inputHandler = InputHandler() 104 InputHandler.initialize()
109 inputHandler.initialize()
110 105
111 val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 106 val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
112 if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) { 107 if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
@@ -162,6 +157,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
162 super.onResume() 157 super.onResume()
163 nfcReader.startScanning() 158 nfcReader.startScanning()
164 startMotionSensorListener() 159 startMotionSensorListener()
160 InputHandler.updateControllerIds()
165 161
166 buildPictureInPictureParams() 162 buildPictureInPictureParams()
167 } 163 }
@@ -195,7 +191,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
195 return super.dispatchKeyEvent(event) 191 return super.dispatchKeyEvent(event)
196 } 192 }
197 193
198 return inputHandler.dispatchKeyEvent(event) 194 return InputHandler.dispatchKeyEvent(event)
199 } 195 }
200 196
201 override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { 197 override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
@@ -210,7 +206,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
210 return true 206 return true
211 } 207 }
212 208
213 return inputHandler.dispatchGenericMotionEvent(event) 209 return InputHandler.dispatchGenericMotionEvent(event)
214 } 210 }
215 211
216 override fun onSensorChanged(event: SensorEvent) { 212 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 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.view.InputDevice
7import android.view.KeyEvent
8import android.view.MotionEvent
9
10/**
11 * Some controllers have incorrect mappings. This class has special-case fixes for them.
12 */
13class ControllerMappingHelper {
14 /**
15 * Some controllers report extra button presses that can be ignored.
16 */
17 fun shouldKeyBeIgnored(inputDevice: InputDevice, keyCode: Int): Boolean {
18 return if (isDualShock4(inputDevice)) {
19 // The two analog triggers generate analog motion events as well as a keycode.
20 // We always prefer to use the analog values, so throw away the button press
21 keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
22 } else {
23 false
24 }
25 }
26
27 /**
28 * Scale an axis to be zero-centered with a proper range.
29 */
30 fun scaleAxis(inputDevice: InputDevice, axis: Int, value: Float): Float {
31 if (isDualShock4(inputDevice)) {
32 // Android doesn't have correct mappings for this controller's triggers. It reports them
33 // as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0]
34 // Scale them to properly zero-centered with a range of [0.0, 1.0].
35 if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY) {
36 return (value + 1) / 2.0f
37 }
38 } else if (isXboxOneWireless(inputDevice)) {
39 // Same as the DualShock 4, the mappings are missing.
40 if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ) {
41 return (value + 1) / 2.0f
42 }
43 if (axis == MotionEvent.AXIS_GENERIC_1) {
44 // This axis is stuck at ~.5. Ignore it.
45 return 0.0f
46 }
47 } else if (isMogaPro2Hid(inputDevice)) {
48 // This controller has a broken axis that reports a constant value. Ignore it.
49 if (axis == MotionEvent.AXIS_GENERIC_1) {
50 return 0.0f
51 }
52 }
53 return value
54 }
55
56 // Sony DualShock 4 controller
57 private fun isDualShock4(inputDevice: InputDevice): Boolean {
58 return inputDevice.vendorId == 0x54c && inputDevice.productId == 0x9cc
59 }
60
61 // Microsoft Xbox One controller
62 private fun isXboxOneWireless(inputDevice: InputDevice): Boolean {
63 return inputDevice.vendorId == 0x45e && inputDevice.productId == 0x2e0
64 }
65
66 // Moga Pro 2 HID
67 private fun isMogaPro2Hid(inputDevice: InputDevice): Boolean {
68 return inputDevice.vendorId == 0x20d6 && inputDevice.productId == 0x6271
69 }
70}
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 @@
3 3
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import android.view.InputDevice
6import android.view.KeyEvent 7import android.view.KeyEvent
7import android.view.MotionEvent 8import android.view.MotionEvent
8import kotlin.math.sqrt 9import kotlin.math.sqrt
9import org.yuzu.yuzu_emu.NativeLibrary 10import org.yuzu.yuzu_emu.NativeLibrary
10 11
11class InputHandler { 12object InputHandler {
13 private var controllerIds = getGameControllerIds()
14
12 fun initialize() { 15 fun initialize() {
13 // Connect first controller 16 // Connect first controller
14 NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device)) 17 NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device))
15 } 18 }
16 19
20 fun updateControllerIds() {
21 controllerIds = getGameControllerIds()
22 }
23
17 fun dispatchKeyEvent(event: KeyEvent): Boolean { 24 fun dispatchKeyEvent(event: KeyEvent): Boolean {
18 val button: Int = when (event.device.vendorId) { 25 val button: Int = when (event.device.vendorId) {
19 0x045E -> getInputXboxButtonKey(event.keyCode) 26 0x045E -> getInputXboxButtonKey(event.keyCode)
@@ -35,7 +42,7 @@ class InputHandler {
35 } 42 }
36 43
37 return NativeLibrary.onGamePadButtonEvent( 44 return NativeLibrary.onGamePadButtonEvent(
38 getPlayerNumber(event.device.controllerNumber), 45 getPlayerNumber(event.device.controllerNumber, event.deviceId),
39 button, 46 button,
40 action 47 action
41 ) 48 )
@@ -58,9 +65,14 @@ class InputHandler {
58 return true 65 return true
59 } 66 }
60 67
61 private fun getPlayerNumber(index: Int): Int { 68 private fun getPlayerNumber(index: Int, deviceId: Int = -1): Int {
69 var deviceIndex = index
70 if (deviceId != -1) {
71 deviceIndex = controllerIds[deviceId]!!
72 }
73
62 // TODO: Joycons are handled as different controllers. Find a way to merge them. 74 // TODO: Joycons are handled as different controllers. Find a way to merge them.
63 return when (index) { 75 return when (deviceIndex) {
64 2 -> NativeLibrary.Player2Device 76 2 -> NativeLibrary.Player2Device
65 3 -> NativeLibrary.Player3Device 77 3 -> NativeLibrary.Player3Device
66 4 -> NativeLibrary.Player4Device 78 4 -> NativeLibrary.Player4Device
@@ -238,7 +250,7 @@ class InputHandler {
238 } 250 }
239 251
240 private fun setGenericAxisInput(event: MotionEvent, axis: Int) { 252 private fun setGenericAxisInput(event: MotionEvent, axis: Int) {
241 val playerNumber = getPlayerNumber(event.device.controllerNumber) 253 val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
242 254
243 when (axis) { 255 when (axis) {
244 MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> 256 MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
@@ -297,7 +309,7 @@ class InputHandler {
297 309
298 private fun setJoyconAxisInput(event: MotionEvent, axis: Int) { 310 private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
299 // Joycon support is half dead. Right joystick doesn't work 311 // Joycon support is half dead. Right joystick doesn't work
300 val playerNumber = getPlayerNumber(event.device.controllerNumber) 312 val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
301 313
302 when (axis) { 314 when (axis) {
303 MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> 315 MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
@@ -325,7 +337,7 @@ class InputHandler {
325 } 337 }
326 338
327 private fun setRazerAxisInput(event: MotionEvent, axis: Int) { 339 private fun setRazerAxisInput(event: MotionEvent, axis: Int) {
328 val playerNumber = getPlayerNumber(event.device.controllerNumber) 340 val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
329 341
330 when (axis) { 342 when (axis) {
331 MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> 343 MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
@@ -362,4 +374,33 @@ class InputHandler {
362 ) 374 )
363 } 375 }
364 } 376 }
377
378 fun getGameControllerIds(): Map<Int, Int> {
379 val gameControllerDeviceIds = mutableMapOf<Int, Int>()
380 val deviceIds = InputDevice.getDeviceIds()
381 var controllerSlot = 1
382 deviceIds.forEach { deviceId ->
383 InputDevice.getDevice(deviceId)?.apply {
384 // Don't over-assign controllers
385 if (controllerSlot >= 8) {
386 return gameControllerDeviceIds
387 }
388
389 // Verify that the device has gamepad buttons, control sticks, or both.
390 if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD ||
391 sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
392 ) {
393 // This device is a game controller. Store its device ID.
394 if (deviceId and id and vendorId and productId != 0) {
395 // Additionally filter out devices that have no ID
396 gameControllerDeviceIds
397 .takeIf { !it.contains(deviceId) }
398 ?.put(deviceId, controllerSlot)
399 controllerSlot++
400 }
401 }
402 }
403 }
404 return gameControllerDeviceIds
405 }
365} 406}
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 @@
32#include "core/hle/service/apm/apm_controller.h" 32#include "core/hle/service/apm/apm_controller.h"
33#include "core/hle/service/apm/apm_interface.h" 33#include "core/hle/service/apm/apm_interface.h"
34#include "core/hle/service/bcat/backend/backend.h" 34#include "core/hle/service/bcat/backend/backend.h"
35#include "core/hle/service/caps/caps_su.h"
35#include "core/hle/service/caps/caps_types.h" 36#include "core/hle/service/caps/caps_types.h"
36#include "core/hle/service/filesystem/filesystem.h" 37#include "core/hle/service/filesystem/filesystem.h"
37#include "core/hle/service/ipc_helpers.h" 38#include "core/hle/service/ipc_helpers.h"
@@ -703,9 +704,17 @@ void ISelfController::SetAlbumImageTakenNotificationEnabled(HLERequestContext& c
703void ISelfController::SaveCurrentScreenshot(HLERequestContext& ctx) { 704void ISelfController::SaveCurrentScreenshot(HLERequestContext& ctx) {
704 IPC::RequestParser rp{ctx}; 705 IPC::RequestParser rp{ctx};
705 706
706 const auto album_report_option = rp.PopEnum<Capture::AlbumReportOption>(); 707 const auto report_option = rp.PopEnum<Capture::AlbumReportOption>();
707 708
708 LOG_WARNING(Service_AM, "(STUBBED) called. album_report_option={}", album_report_option); 709 LOG_INFO(Service_AM, "called, report_option={}", report_option);
710
711 const auto screenshot_service =
712 system.ServiceManager().GetService<Service::Capture::IScreenShotApplicationService>(
713 "caps:su");
714
715 if (screenshot_service) {
716 screenshot_service->CaptureAndSaveScreenshot(report_option);
717 }
709 718
710 IPC::ResponseBuilder rb{ctx, 2}; 719 IPC::ResponseBuilder rb{ctx, 2};
711 rb.Push(ResultSuccess); 720 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(
228 228
229Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry, 229Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
230 const ScreenShotAttribute& attribute, 230 const ScreenShotAttribute& attribute,
231 std::span<const u8> image_data, u64 aruid) { 231 AlbumReportOption report_option, std::span<const u8> image_data,
232 return SaveScreenShot(out_entry, attribute, {}, image_data, aruid); 232 u64 aruid) {
233 return SaveScreenShot(out_entry, attribute, report_option, {}, image_data, aruid);
233} 234}
234 235
235Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry, 236Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
236 const ScreenShotAttribute& attribute, 237 const ScreenShotAttribute& attribute,
238 AlbumReportOption report_option,
237 const ApplicationData& app_data, std::span<const u8> image_data, 239 const ApplicationData& app_data, std::span<const u8> image_data,
238 u64 aruid) { 240 u64 aruid) {
239 const u64 title_id = system.GetApplicationProcessProgramID(); 241 const u64 title_id = system.GetApplicationProcessProgramID();
@@ -407,10 +409,14 @@ Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::p
407 return ResultSuccess; 409 return ResultSuccess;
408} 410}
409 411
410static void PNGToMemory(void* context, void* png, int len) { 412void AlbumManager::FlipVerticallyOnWrite(bool flip) {
413 stbi_flip_vertically_on_write(flip);
414}
415
416static void PNGToMemory(void* context, void* data, int len) {
411 std::vector<u8>* png_image = static_cast<std::vector<u8>*>(context); 417 std::vector<u8>* png_image = static_cast<std::vector<u8>*>(context);
412 png_image->reserve(len); 418 unsigned char* png = static_cast<unsigned char*>(data);
413 std::memcpy(png_image->data(), png, len); 419 png_image->insert(png_image->end(), png, png + len);
414} 420}
415 421
416Result AlbumManager::SaveImage(ApplicationAlbumEntry& out_entry, std::span<const u8> image, 422Result 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:
59 const ScreenShotDecodeOption& decoder_options) const; 59 const ScreenShotDecodeOption& decoder_options) const;
60 60
61 Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute, 61 Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
62 std::span<const u8> image_data, u64 aruid); 62 AlbumReportOption report_option, std::span<const u8> image_data,
63 Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
64 const ApplicationData& app_data, std::span<const u8> image_data,
65 u64 aruid); 63 u64 aruid);
64 Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
65 AlbumReportOption report_option, const ApplicationData& app_data,
66 std::span<const u8> image_data, u64 aruid);
66 Result SaveEditedScreenShot(ApplicationAlbumEntry& out_entry, 67 Result SaveEditedScreenShot(ApplicationAlbumEntry& out_entry,
67 const ScreenShotAttribute& attribute, const AlbumFileId& file_id, 68 const ScreenShotAttribute& attribute, const AlbumFileId& file_id,
68 std::span<const u8> image_data); 69 std::span<const u8> image_data);
69 70
71 void FlipVerticallyOnWrite(bool flip);
72
70private: 73private:
71 static constexpr std::size_t NandAlbumFileLimit = 1000; 74 static constexpr std::size_t NandAlbumFileLimit = 1000;
72 static constexpr std::size_t SdAlbumFileLimit = 10000; 75 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) {
34 IPC::RequestParser rp{ctx}; 34 IPC::RequestParser rp{ctx};
35 struct Parameters { 35 struct Parameters {
36 ScreenShotAttribute attribute{}; 36 ScreenShotAttribute attribute{};
37 u32 report_option{}; 37 AlbumReportOption report_option{};
38 INSERT_PADDING_BYTES(0x4); 38 INSERT_PADDING_BYTES(0x4);
39 u64 applet_resource_user_id{}; 39 u64 applet_resource_user_id{};
40 }; 40 };
@@ -49,13 +49,16 @@ void IScreenShotService::SaveScreenShotEx0(HLERequestContext& ctx) {
49 parameters.applet_resource_user_id); 49 parameters.applet_resource_user_id);
50 50
51 ApplicationAlbumEntry entry{}; 51 ApplicationAlbumEntry entry{};
52 const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer, 52 manager->FlipVerticallyOnWrite(false);
53 parameters.applet_resource_user_id); 53 const auto result =
54 manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option,
55 image_data_buffer, parameters.applet_resource_user_id);
54 56
55 IPC::ResponseBuilder rb{ctx, 10}; 57 IPC::ResponseBuilder rb{ctx, 10};
56 rb.Push(result); 58 rb.Push(result);
57 rb.PushRaw(entry); 59 rb.PushRaw(entry);
58} 60}
61
59void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) { 62void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) {
60 IPC::RequestParser rp{ctx}; 63 IPC::RequestParser rp{ctx};
61 struct Parameters { 64 struct Parameters {
@@ -83,6 +86,7 @@ void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) {
83 image_data_buffer.size(), thumbnail_image_data_buffer.size()); 86 image_data_buffer.size(), thumbnail_image_data_buffer.size());
84 87
85 ApplicationAlbumEntry entry{}; 88 ApplicationAlbumEntry entry{};
89 manager->FlipVerticallyOnWrite(false);
86 const auto result = manager->SaveEditedScreenShot(entry, parameters.attribute, 90 const auto result = manager->SaveEditedScreenShot(entry, parameters.attribute,
87 parameters.file_id, image_data_buffer); 91 parameters.file_id, image_data_buffer);
88 92
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 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/logging/log.h" 4#include "common/logging/log.h"
5#include "core/core.h"
5#include "core/hle/service/caps/caps_manager.h" 6#include "core/hle/service/caps/caps_manager.h"
6#include "core/hle/service/caps/caps_su.h" 7#include "core/hle/service/caps/caps_su.h"
7#include "core/hle/service/caps/caps_types.h" 8#include "core/hle/service/caps/caps_types.h"
8#include "core/hle/service/ipc_helpers.h" 9#include "core/hle/service/ipc_helpers.h"
10#include "video_core/renderer_base.h"
9 11
10namespace Service::Capture { 12namespace Service::Capture {
11 13
@@ -58,8 +60,10 @@ void IScreenShotApplicationService::SaveScreenShotEx0(HLERequestContext& ctx) {
58 parameters.applet_resource_user_id); 60 parameters.applet_resource_user_id);
59 61
60 ApplicationAlbumEntry entry{}; 62 ApplicationAlbumEntry entry{};
61 const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer, 63 manager->FlipVerticallyOnWrite(false);
62 parameters.applet_resource_user_id); 64 const auto result =
65 manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option,
66 image_data_buffer, parameters.applet_resource_user_id);
63 67
64 IPC::ResponseBuilder rb{ctx, 10}; 68 IPC::ResponseBuilder rb{ctx, 10};
65 rb.Push(result); 69 rb.Push(result);
@@ -88,13 +92,43 @@ void IScreenShotApplicationService::SaveScreenShotEx1(HLERequestContext& ctx) {
88 ApplicationAlbumEntry entry{}; 92 ApplicationAlbumEntry entry{};
89 ApplicationData app_data{}; 93 ApplicationData app_data{};
90 std::memcpy(&app_data, app_data_buffer.data(), sizeof(ApplicationData)); 94 std::memcpy(&app_data, app_data_buffer.data(), sizeof(ApplicationData));
95 manager->FlipVerticallyOnWrite(false);
91 const auto result = 96 const auto result =
92 manager->SaveScreenShot(entry, parameters.attribute, app_data, image_data_buffer, 97 manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option, app_data,
93 parameters.applet_resource_user_id); 98 image_data_buffer, parameters.applet_resource_user_id);
94 99
95 IPC::ResponseBuilder rb{ctx, 10}; 100 IPC::ResponseBuilder rb{ctx, 10};
96 rb.Push(result); 101 rb.Push(result);
97 rb.PushRaw(entry); 102 rb.PushRaw(entry);
98} 103}
99 104
105void IScreenShotApplicationService::CaptureAndSaveScreenshot(AlbumReportOption report_option) {
106 auto& renderer = system.Renderer();
107 Layout::FramebufferLayout layout =
108 Layout::DefaultFrameLayout(screenshot_width, screenshot_height);
109
110 const Capture::ScreenShotAttribute attribute{
111 .unknown_0{},
112 .orientation = Capture::AlbumImageOrientation::None,
113 .unknown_1{},
114 .unknown_2{},
115 };
116
117 renderer.RequestScreenshot(
118 image_data.data(),
119 [attribute, report_option, this](bool invert_y) {
120 // Convert from BGRA to RGBA
121 for (std::size_t i = 0; i < image_data.size(); i += bytes_per_pixel) {
122 const u8 temp = image_data[i];
123 image_data[i] = image_data[i + 2];
124 image_data[i + 2] = temp;
125 }
126
127 Capture::ApplicationAlbumEntry entry{};
128 manager->FlipVerticallyOnWrite(invert_y);
129 manager->SaveScreenShot(entry, attribute, report_option, image_data, {});
130 },
131 layout);
132}
133
100} // namespace Service::Capture 134} // 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;
10} 10}
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13enum class AlbumReportOption : s32;
13class AlbumManager; 14class AlbumManager;
14 15
15class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> { 16class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> {
@@ -18,11 +19,19 @@ public:
18 std::shared_ptr<AlbumManager> album_manager); 19 std::shared_ptr<AlbumManager> album_manager);
19 ~IScreenShotApplicationService() override; 20 ~IScreenShotApplicationService() override;
20 21
22 void CaptureAndSaveScreenshot(AlbumReportOption report_option);
23
21private: 24private:
25 static constexpr std::size_t screenshot_width = 1280;
26 static constexpr std::size_t screenshot_height = 720;
27 static constexpr std::size_t bytes_per_pixel = 4;
28
22 void SetShimLibraryVersion(HLERequestContext& ctx); 29 void SetShimLibraryVersion(HLERequestContext& ctx);
23 void SaveScreenShotEx0(HLERequestContext& ctx); 30 void SaveScreenShotEx0(HLERequestContext& ctx);
24 void SaveScreenShotEx1(HLERequestContext& ctx); 31 void SaveScreenShotEx1(HLERequestContext& ctx);
25 32
33 std::array<u8, screenshot_width * screenshot_height * bytes_per_pixel> image_data;
34
26 std::shared_ptr<AlbumManager> manager; 35 std::shared_ptr<AlbumManager> manager;
27}; 36};
28 37