diff options
| -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 | 92 | ||||
| -rw-r--r-- | src/core/hle/service/am/am.h | 3 | ||||
| -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 | 
10 files changed, 216 insertions, 106 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 ff067c8d9..cc643ea09 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -23,6 +23,7 @@  #include "core/hle/service/am/applets/applet_cabinet.h"  #include "core/hle/service/am/applets/applet_mii_edit_types.h"  #include "core/hle/service/am/applets/applet_profile_select.h" +#include "core/hle/service/am/applets/applet_software_keyboard_types.h"  #include "core/hle/service/am/applets/applet_web_browser.h"  #include "core/hle/service/am/applets/applets.h"  #include "core/hle/service/am/idle.h" @@ -31,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" @@ -702,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); @@ -1562,7 +1572,7 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)          {16, nullptr, "GetMainAppletStorageId"},          {17, nullptr, "GetCallerAppletIdentityInfoStack"},          {18, nullptr, "GetNextReturnDestinationAppletIdentityInfo"}, -        {19, nullptr, "GetDesirableKeyboardLayout"}, +        {19, &ILibraryAppletSelfAccessor::GetDesirableKeyboardLayout, "GetDesirableKeyboardLayout"},          {20, nullptr, "PopExtraStorage"},          {25, nullptr, "GetPopExtraStorageEvent"},          {30, nullptr, "UnpopInData"}, @@ -1581,7 +1591,7 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)          {120, nullptr, "GetLaunchStorageInfoForDebug"},          {130, nullptr, "GetGpuErrorDetectedSystemEvent"},          {140, nullptr, "SetApplicationMemoryReservation"}, -        {150, nullptr, "ShouldSetGpuTimeSliceManually"}, +        {150, &ILibraryAppletSelfAccessor::ShouldSetGpuTimeSliceManually, "ShouldSetGpuTimeSliceManually"},      };      // clang-format on      RegisterHandlers(functions); @@ -1596,6 +1606,9 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)      case Applets::AppletId::PhotoViewer:          PushInShowAlbum();          break; +    case Applets::AppletId::SoftwareKeyboard: +        PushInShowSoftwareKeyboard(); +        break;      default:          break;      } @@ -1672,6 +1685,14 @@ void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext&      rb.PushRaw(applet_info);  } +void ILibraryAppletSelfAccessor::GetDesirableKeyboardLayout(HLERequestContext& ctx) { +    LOG_WARNING(Service_AM, "(STUBBED) called"); + +    IPC::ResponseBuilder rb{ctx, 3}; +    rb.Push(ResultSuccess); +    rb.Push<u32>(0); +} +  void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext& ctx) {      const Service::Account::ProfileManager manager{};      bool is_empty{true}; @@ -1691,6 +1712,14 @@ void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext&      rb.Push(user_count);  } +void ILibraryAppletSelfAccessor::ShouldSetGpuTimeSliceManually(HLERequestContext& ctx) { +    LOG_WARNING(Service_AM, "(STUBBED) called"); + +    IPC::ResponseBuilder rb{ctx, 2}; +    rb.Push(ResultSuccess); +    rb.Push<u8>(0); +} +  void ILibraryAppletSelfAccessor::PushInShowAlbum() {      const Applets::CommonArguments arguments{          .arguments_version = Applets::CommonArgumentVersion::Version3, @@ -1759,6 +1788,61 @@ void ILibraryAppletSelfAccessor::PushInShowMiiEditData() {      queue_data.emplace_back(std::move(argument_data));  } +void ILibraryAppletSelfAccessor::PushInShowSoftwareKeyboard() { +    const Applets::CommonArguments arguments{ +        .arguments_version = Applets::CommonArgumentVersion::Version3, +        .size = Applets::CommonArgumentSize::Version3, +        .library_version = static_cast<u32>(Applets::SwkbdAppletVersion::Version524301), +        .theme_color = Applets::ThemeColor::BasicBlack, +        .play_startup_sound = true, +        .system_tick = system.CoreTiming().GetClockTicks(), +    }; + +    std::vector<char16_t> initial_string(0); + +    const Applets::SwkbdConfigCommon swkbd_config{ +        .type = Applets::SwkbdType::Qwerty, +        .ok_text{}, +        .left_optional_symbol_key{}, +        .right_optional_symbol_key{}, +        .use_prediction = false, +        .key_disable_flags{}, +        .initial_cursor_position = Applets::SwkbdInitialCursorPosition::Start, +        .header_text{}, +        .sub_text{}, +        .guide_text{}, +        .max_text_length = 500, +        .min_text_length = 0, +        .password_mode = Applets::SwkbdPasswordMode::Disabled, +        .text_draw_type = Applets::SwkbdTextDrawType::Box, +        .enable_return_button = true, +        .use_utf8 = false, +        .use_blur_background = true, +        .initial_string_offset{}, +        .initial_string_length = static_cast<u32>(initial_string.size()), +        .user_dictionary_offset{}, +        .user_dictionary_entries{}, +        .use_text_check = false, +    }; + +    Applets::SwkbdConfigNew swkbd_config_new{}; + +    std::vector<u8> argument_data(sizeof(arguments)); +    std::vector<u8> swkbd_data(sizeof(swkbd_config) + sizeof(swkbd_config_new)); +    std::vector<u8> work_buffer(swkbd_config.initial_string_length * sizeof(char16_t)); + +    std::memcpy(argument_data.data(), &arguments, sizeof(arguments)); +    std::memcpy(swkbd_data.data(), &swkbd_config, sizeof(swkbd_config)); +    std::memcpy(swkbd_data.data() + sizeof(swkbd_config), &swkbd_config_new, +                sizeof(Applets::SwkbdConfigNew)); +    std::memcpy(work_buffer.data(), initial_string.data(), +                swkbd_config.initial_string_length * sizeof(char16_t)); + +    queue_data.emplace_back(std::move(argument_data)); +    queue_data.emplace_back(std::move(swkbd_data)); +    queue_data.emplace_back(std::move(work_buffer)); +} +  IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_)      : ServiceFramework{system_, "IAppletCommonFunctions"} {      // clang-format off diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 64b3f3fe2..8f8cb8a9e 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -347,11 +347,14 @@ private:      void GetLibraryAppletInfo(HLERequestContext& ctx);      void ExitProcessAndReturn(HLERequestContext& ctx);      void GetCallerAppletIdentityInfo(HLERequestContext& ctx); +    void GetDesirableKeyboardLayout(HLERequestContext& ctx);      void GetMainAppletAvailableUsers(HLERequestContext& ctx); +    void ShouldSetGpuTimeSliceManually(HLERequestContext& ctx);      void PushInShowAlbum();      void PushInShowCabinetData();      void PushInShowMiiEditData(); +    void PushInShowSoftwareKeyboard();      std::deque<std::vector<u8>> queue_data;  }; 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;  }; | 
