From 014b02db97671a15327bdfd6e71e2ec116dd4afd Mon Sep 17 00:00:00 2001 From: yellows8 Date: Thu, 2 Apr 2020 18:10:57 -0400 Subject: [PATCH] irs: Major overhaul, irs is now properly supported. Functionality for newer system-versions is now supported. * Updated structs: IrsPackedMomentProcessorConfig, IrsImageTransferProcessorConfig, IrsPackedImageTransferProcessorConfig, IrsImageTransferProcessorState. * The u32 IrCameraHandle was replaced with struct IrsIrCameraHandle. * Added various enums/structs, etc. Improved docs. * irsActivateIrsensor() and irsSuspendImageProcessor() are no longer exposed, these are now used automatically when needed. * Added the following: irsGetIrCameraStatus, irsCheckFirmwareUpdateNecessity, irsGetImageProcessorStatus, irsStopImageProcessorAsync, irsRunMomentProcessor, irsGetMomentProcessorStates, irsCalculateMomentRegionStatistic, irsRunClusteringProcessor, irsGetClusteringProcessorStates, irsRunImageTransferExProcessor, irsRunPointingProcessor, irsGetPointingProcessorMarkerStates, irsGetPointingProcessorStates, irsRunTeraPluginProcessor, irsGetTeraPluginProcessorStates, irsRunIrLedProcessor, irsRunAdaptiveClusteringProcessor, irsRunHandAnalysis, irsGetMomentProcessorDefaultConfig, irsGetClusteringProcessorDefaultConfig, irsGetDefaultImageTransferProcessorExConfig, irsGetIrLedProcessorDefaultConfig. --- nx/include/switch/services/irs.h | 586 ++++++++++++-- nx/source/services/irs.c | 1286 +++++++++++++++++++++++++++--- 2 files changed, 1697 insertions(+), 175 deletions(-) diff --git a/nx/include/switch/services/irs.h b/nx/include/switch/services/irs.h index 64689a91..5a24158d 100644 --- a/nx/include/switch/services/irs.h +++ b/nx/include/switch/services/irs.h @@ -10,44 +10,380 @@ #include "../sf/service.h" #include "../services/hid.h" -typedef struct { - u64 unk_x0; - u8 unk_x8; - u8 unk_x9; - u8 unk_xa; - u8 pad[5]; - u16 unk_x10; - u32 unk_x12; - u16 unk_x16; - u32 unk_constant;//offset 0x18 - u8 unk_x1c; - u8 unk_x1d; - u8 pad2[2]; -} PACKED IrsPackedMomentProcessorConfig; +#define IRS_MAX_CAMERAS 0x9 +/// IrCameraStatus +typedef enum { + IrsIrCameraStatus_Available = 0, ///< Available + IrsIrCameraStatus_Unsupported = 1, ///< Unsupported + IrsIrCameraStatus_Unconnected = 2, ///< Unconnected +} IrsIrCameraStatus; + +/// IrCameraInternalStatus +typedef enum { + IrsIrCameraInternalStatus_Stopped = 0, ///< Stopped + IrsIrCameraInternalStatus_FirmwareUpdateNeeded = 1, ///< FirmwareUpdateNeeded + IrsIrCameraInternalStatus_Unknown2 = 2, ///< Unknown + IrsIrCameraInternalStatus_Unknown3 = 3, ///< Unknown + IrsIrCameraInternalStatus_Unknown4 = 4, ///< Unknown + IrsIrCameraInternalStatus_FirmwareVersionRequested = 5, ///< FirmwareVersionRequested + IrsIrCameraInternalStatus_FirmwareVersionIsInvalid = 6, ///< FirmwareVersionIsInvalid + IrsIrCameraInternalStatus_Ready = 7, ///< [4.0.0+] Ready + IrsIrCameraInternalStatus_Setting = 8, ///< [4.0.0+] Setting +} IrsIrCameraInternalStatus; + +/// IrSensorMode +typedef enum { + IrsIrSensorMode_None = 0, ///< None + IrsIrSensorMode_MomentProcessor = 1, ///< MomentProcessor + IrsIrSensorMode_ClusteringProcessor = 2, ///< ClusteringProcessor + IrsIrSensorMode_ImageTransferProcessor = 3, ///< ImageTransferProcessor + IrsIrSensorMode_PointingProcessor = 4, ///< PointingProcessor + IrsIrSensorMode_TeraPluginProcessor = 5, ///< TeraPluginProcessor + IrsIrSensorMode_IrLedProcessor = 6, ///< IrLedProcessor (doesn't apply to IrsDeviceFormat::ir_sensor_mode) +} IrsIrSensorMode; + +/// ImageProcessorStatus +typedef enum { + IrsImageProcessorStatus_Stopped = 0, ///< Stopped + IrsImageProcessorStatus_Running = 1, ///< Running +} IrsImageProcessorStatus; + +/// ImageTransferProcessorFormat. IR Sensor image resolution. +typedef enum { + IrsImageTransferProcessorFormat_320x240 = 0, ///< 320x240 + IrsImageTransferProcessorFormat_160x120 = 1, ///< 160x120 + IrsImageTransferProcessorFormat_80x60 = 2, ///< 80x60 + IrsImageTransferProcessorFormat_40x30 = 3, ///< [4.0.0+] 40x30 + IrsImageTransferProcessorFormat_20x15 = 4, ///< [4.0.0+] 20x15 +} IrsImageTransferProcessorFormat; + +/// AdaptiveClusteringMode +typedef enum { + IrsAdaptiveClusteringMode_StaticFov = 0, ///< StaticFov + IrsAdaptiveClusteringMode_DynamicFov = 1, ///< DynamicFov +} IrsAdaptiveClusteringMode; + +/// AdaptiveClusteringTargetDistance +typedef enum { + IrsAdaptiveClusteringTargetDistance_Near = 0, ///< Near + IrsAdaptiveClusteringTargetDistance_Middle = 1, ///< Middle + IrsAdaptiveClusteringTargetDistance_Far = 2, ///< Far +} IrsAdaptiveClusteringTargetDistance; + +/// HandAnalysisMode +typedef enum { + IrsHandAnalysisMode_Silhouette = 1, ///< Silhouette + IrsHandAnalysisMode_Image = 2, ///< Image + IrsHandAnalysisMode_SilhouetteAndImage = 3, ///< SilhouetteAndImage + IrsHandAnalysisMode_SilhouetteOnly = 4, ///< [4.0.0+] SilhouetteOnly +} IrsHandAnalysisMode; + +/// Internal validation callblack. +typedef bool (*IrsValidationCb)(void* userdata, void* arg); + +/// IrCameraHandle typedef struct { - u64 exposure; ///< IR Sensor exposure time in nanoseconds. - u32 ir_leds; ///< Controls the IR leds. 0: All leds, 1: Bright group, 2: Dim group, 3: None. - u32 digital_gain; ///< IR sensor signal's digital gain. - u8 color_invert; ///< Inverts the colors of the captured image. 0: Normal image, 1: Negative image. - u8 pad[7]; - u32 sensor_res; ///< IR Sensor resolution. 0: 240x320, 1: 120x160, 2: 60x80. + u8 player_number; ///< PlayerNumber + u8 device_type; ///< DeviceType + u8 reserved[0x2]; ///< Reserved +} IrsIrCameraHandle; + +/// PackedMcuVersion +typedef struct { + u16 major_version; ///< MajorVersion + u16 minor_version; ///< MinorVersion +} IrsPackedMcuVersion; + +/// PackedFunctionLevel +typedef struct { + u8 ir_sensor_function_level; ///< IrSensorFunctionLevel + u8 reserved[0x3]; ///< Reserved +} IrsPackedFunctionLevel; + +/// Rect +typedef struct { + s16 x; ///< X + s16 y; ///< Y + s16 width; ///< Width + s16 height; ///< Height +} IrsRect; + +/// IrsMomentProcessorConfig +typedef struct { + u64 exposure_time; ///< IR Sensor exposure time in nanoseconds. + u32 light_target; ///< Controls the IR leds. 0: All leds, 1: Bright group, 2: Dim group, 3: None. + u32 gain; ///< IR sensor signal's digital gain. + u8 is_negative_image_used; ///< Inverts the colors of the captured image. 0: Normal image, 1: Negative image. + u8 reserved[0x7]; ///< Reserved. + IrsRect window_of_interest; ///< WindowOfInterest + u32 preprocess; ///< Preprocess + u32 preprocess_intensity_threshold; ///< PreprocessIntensityThreshold +} IrsMomentProcessorConfig; + +/// PackedMomentProcessorConfig +typedef struct { + u64 exposure_time; ///< IR Sensor exposure time in nanoseconds. + u8 light_target; ///< Controls the IR leds. 0: All leds, 1: Bright group, 2: Dim group, 3: None. + u8 gain; ///< IR sensor signal's digital gain. + u8 is_negative_image_used; ///< Inverts the colors of the captured image. 0: Normal image, 1: Negative image. + u8 reserved[0x5]; ///< Reserved. + IrsRect window_of_interest; ///< WindowOfInterest + IrsPackedMcuVersion required_mcu_version; ///< RequiredMcuVersion + u8 preprocess; ///< Preprocess + u8 preprocess_intensity_threshold; ///< PreprocessIntensityThreshold + u8 reserved2[0x2]; ///< Reserved. +} IrsPackedMomentProcessorConfig; + +/// ClusteringProcessorConfig +typedef struct { + u64 exposure_time; ///< IR Sensor exposure time in nanoseconds. + u32 light_target; ///< Controls the IR leds. 0: All leds, 1: Bright group, 2: Dim group, 3: None. + u32 gain; ///< IR sensor signal's digital gain. + u8 is_negative_image_used; ///< Inverts the colors of the captured image. 0: Normal image, 1: Negative image. + u8 reserved[0x7]; ///< Reserved. + IrsRect window_of_interest; ///< WindowOfInterest + u32 object_pixel_count_min; ///< ObjectPixelCountMin + u32 object_pixel_count_max; ///< ObjectPixelCountMax + u32 object_intensity_min; ///< ObjectIntensityMin + u8 is_external_light_filter_enabled; ///< IsExternalLightFilterEnabled +} IrsClusteringProcessorConfig; + +/// PackedClusteringProcessorConfig +typedef struct { + u64 exposure_time; ///< IR Sensor exposure time in nanoseconds. + u8 light_target; ///< Controls the IR leds. 0: All leds, 1: Bright group, 2: Dim group, 3: None. + u8 gain; ///< IR sensor signal's digital gain. + u8 is_negative_image_used; ///< Inverts the colors of the captured image. 0: Normal image, 1: Negative image. + u8 reserved[0x5]; ///< Reserved. + IrsRect window_of_interest; ///< WindowOfInterest + IrsPackedMcuVersion required_mcu_version; ///< RequiredMcuVersion + u32 object_pixel_count_min; ///< ObjectPixelCountMin + u32 object_pixel_count_max; ///< ObjectPixelCountMax + u8 object_intensity_min; ///< ObjectIntensityMin + u8 is_external_light_filter_enabled; ///< IsExternalLightFilterEnabled + u8 reserved2[0x2]; ///< Reserved. +} IrsPackedClusteringProcessorConfig; + +/// ImageTransferProcessorConfig +typedef struct { + u64 exposure_time; ///< IR Sensor exposure time in nanoseconds. + u32 light_target; ///< Controls the IR leds. 0: All leds, 1: Bright group, 2: Dim group, 3: None. + u32 gain; ///< IR sensor signal's digital gain. + u8 is_negative_image_used; ///< Inverts the colors of the captured image. 0: Normal image, 1: Negative image. + u8 reserved[0x7]; ///< Reserved. + u32 format; ///< \ref IrsImageTransferProcessorFormat } IrsImageTransferProcessorConfig; +/// ImageTransferProcessorExConfig typedef struct { - u64 exposure; ///< IR Sensor exposure time in nanoseconds. - u8 ir_leds; ///< Controls the IR leds. 0: All leds, 1: Bright group, 2: Dim group, 3: None. - u8 digital_gain; ///< IR sensor signal's digital gain. - u8 color_invert; ///< Inverts the colors of the captured image. 0: Normal image, 1: Negative image. - u8 pad[5]; - u32 unk_constant;//offset 0x10 - u8 sensor_res; ///< IR Sensor resolution. 0: 240x320, 1: 120x160, 2: 60x80. - u8 pad2[3]; + u64 exposure_time; ///< IR Sensor exposure time in nanoseconds. + u32 light_target; ///< Controls the IR leds. 0: All leds, 1: Bright group, 2: Dim group, 3: None. + u32 gain; ///< IR sensor signal's digital gain. + u8 is_negative_image_used; ///< Inverts the colors of the captured image. 0: Normal image, 1: Negative image. + u8 reserved[0x7]; ///< Reserved. + u32 orig_format; ///< OrigFormat \ref IrsImageTransferProcessorFormat + u32 trimming_format; ///< TrimmingFormat \ref IrsImageTransferProcessorFormat + u16 trimming_start_x; ///< TrimmingStartX + u16 trimming_start_y; ///< TrimmingStartY + u8 is_external_light_filter_enabled; ///< IsExternalLightFilterEnabled +} IrsImageTransferProcessorExConfig; + +/// PackedImageTransferProcessorConfig +typedef struct { + u64 exposure_time; ///< IR Sensor exposure time in nanoseconds. + u8 light_target; ///< Controls the IR leds. 0: All leds, 1: Bright group, 2: Dim group, 3: None. + u8 gain; ///< IR sensor signal's digital gain. + u8 is_negative_image_used; ///< Inverts the colors of the captured image. 0: Normal image, 1: Negative image. + u8 reserved[0x5]; ///< Reserved. + IrsPackedMcuVersion required_mcu_version; ///< RequiredMcuVersion + u8 format; ///< \ref IrsImageTransferProcessorFormat + u8 reserved2[0x3]; ///< Reserved. } IrsPackedImageTransferProcessorConfig; +/// PackedImageTransferProcessorExConfig typedef struct { - u8 unk_x0[0x10]; -} PACKED IrsImageTransferProcessorState; + u64 exposure_time; ///< IR Sensor exposure time in nanoseconds. + u8 light_target; ///< Controls the IR leds. 0: All leds, 1: Bright group, 2: Dim group, 3: None. + u8 gain; ///< IR sensor signal's digital gain. + u8 is_negative_image_used; ///< Inverts the colors of the captured image. 0: Normal image, 1: Negative image. + u8 reserved[0x5]; ///< Reserved. + IrsPackedMcuVersion required_mcu_version; ///< RequiredMcuVersion + u8 orig_format; ///< OrigFormat \ref IrsImageTransferProcessorFormat + u8 trimming_format; ///< TrimmingFormat \ref IrsImageTransferProcessorFormat + u16 trimming_start_x; ///< TrimmingStartX + u16 trimming_start_y; ///< TrimmingStartY + u8 is_external_light_filter_enabled; ///< IsExternalLightFilterEnabled + u8 reserved2[0x5]; ///< Reserved. +} IrsPackedImageTransferProcessorExConfig; + +/// ImageTransferProcessorState +typedef struct { + u64 sampling_number; ///< SamplingNumber + u32 ambient_noise_level; ///< AmbientNoiseLevel + u8 reserved[0x4]; ///< Reserved +} IrsImageTransferProcessorState; + +/// PackedPointingProcessorConfig +typedef struct { + IrsRect window_of_interest; ///< WindowOfInterest + IrsPackedMcuVersion required_mcu_version; ///< RequiredMcuVersion +} IrsPackedPointingProcessorConfig; + +/// TeraPluginProcessorConfig +typedef struct { + u8 mode; ///< Mode + u8 unk_x1; ///< [6.0.0+] Unknown + u8 unk_x2; ///< [6.0.0+] Unknown + u8 unk_x3; ///< [6.0.0+] Unknown +} IrsTeraPluginProcessorConfig; + +/// PackedTeraPluginProcessorConfig +typedef struct { + IrsPackedMcuVersion required_mcu_version; ///< RequiredMcuVersion + u8 mode; ///< Mode + u8 unk_x5; ///< [6.0.0+] This is set to 0x2 | (IrsTeraPluginProcessorConfig::unk_x1 << 7). + u8 unk_x6; ///< [6.0.0+] IrsTeraPluginProcessorConfig::unk_x2 + u8 unk_x7; ///< [6.0.0+] IrsTeraPluginProcessorConfig::unk_x3 +} IrsPackedTeraPluginProcessorConfig; + +/// IrLedProcessorConfig +typedef struct { + u32 light_target; ///< Controls the IR leds. 0: All leds, 1: Bright group, 2: Dim group, 3: None. +} IrsIrLedProcessorConfig; + +/// PackedIrLedProcessorConfig +typedef struct { + IrsPackedMcuVersion required_mcu_version; ///< RequiredMcuVersion + u8 light_target; ///< Controls the IR leds. 0: All leds, 1: Bright group, 2: Dim group, 3: None. + u8 pad[0x3]; ///< Padding +} IrsPackedIrLedProcessorConfig; + +/// AdaptiveClusteringProcessorConfig +typedef struct { + u32 mode; ///< \ref IrsAdaptiveClusteringMode + u32 target_distance; ///< [6.0.0+] \ref IrsAdaptiveClusteringTargetDistance +} IrsAdaptiveClusteringProcessorConfig; + +/// HandAnalysisConfig +typedef struct { + u32 mode; ///< \ref IrsHandAnalysisMode +} IrsHandAnalysisConfig; + +/// MomentStatistic +typedef struct { + float average_intensity; ///< AverageIntensity + float centroid_x; ///< CentroidX + float centroid_y; ///< CentroidY +} IrsMomentStatistic; + +/// MomentProcessorState +typedef struct { + s64 sampling_number; ///< SamplingNumber + u64 timestamp; ///< TimeStamp + + u32 ambient_noise_level; ///< AmbientNoiseLevel + u8 reserved[0x4]; ///< Reserved + IrsMomentStatistic statistic[0x30]; ///< \ref IrsMomentStatistic +} IrsMomentProcessorState; + +/// ClusteringData +typedef struct { + float average_intensity; ///< AverageIntensity + float centroid_x; ///< CentroidX + float centroid_y; ///< CentroidY + u32 pixel_count; ///< PixelCount + u16 bound_x; ///< BoundX + u16 bound_y; ///< BoundY + u16 boundt_width; ///< BoundtWidth + u16 bound_height; ///< BoundHeight +} IrsClusteringData; + +/// ClusteringProcessorState +typedef struct { + s64 sampling_number; ///< SamplingNumber + u64 timestamp; ///< TimeStamp + + u8 object_count; ///< ObjectCount + u8 reserved[0x3]; ///< Reserved + u32 ambient_noise_level; ///< AmbientNoiseLevel + IrsClusteringData data[0x10]; ///< \ref IrsClusteringData +} IrsClusteringProcessorState; + +/// PointingProcessorMarkerState +typedef struct { + s64 sampling_number; ///< SamplingNumber + u64 timestamp; ///< TimeStamp + + u8 pointing_status0; ///< PointingStatus + u8 reserved0[0xb]; ///< Reserved + float position0_x; ///< PositionX + float position0_y; ///< PositionY + u8 reserved1[0xc]; ///< Reserved + + u8 pointing_status1; ///< PointingStatus + u8 reserved2[0xb]; ///< Reserved + float position1_x; ///< PositionX + float position1_y; ///< PositionY + u8 reserved3[0xc]; ///< Reserved + + u8 pointing_status2; ///< PointingStatus + u8 reserved4[0xb]; ///< Reserved + float position2_x; ///< PositionX + float position2_y; ///< PositionY + u8 reserved5[0xc]; ///< Reserved +} IrsPointingProcessorMarkerState; + +/// PointingProcessorState +typedef struct { + s64 sampling_number; ///< SamplingNumber + u64 timestamp; ///< TimeStamp + + u32 pointing_status; ///< PointingStatus + float position_x; ///< PositionX + float position_y; ///< PositionY + u8 reserved[0x4]; ///< Reserved +} IrsPointingProcessorState; + +/// TeraPluginProcessorState +typedef struct { + s64 sampling_number; ///< SamplingNumber + u64 timestamp; ///< TimeStamp + u32 ambient_noise_level; ///< AmbientNoiseLevel + u8 plugin_data[0x12c]; ///< PluginData +} IrsTeraPluginProcessorState; + +/// ProcessorState +typedef struct { + s64 start; ///< Start + u32 count; ///< Count + u32 pad; ///< Padding + + u8 data[0xe10]; ///< Contains an array of *ProcessorState, depending on IrsDeviceFormat::ir_sensor_mode. +} IrsProcessorState; + +/// DeviceFormat +typedef struct { + u32 ir_camera_status; ///< \ref IrsIrCameraStatus + u32 ir_camera_internal_status; ///< \ref IrsIrCameraInternalStatus + u32 ir_sensor_mode; ///< \ref IrsIrSensorMode + u32 pad; ///< Padding + + IrsProcessorState processor_state; ///< \ref IrsProcessorState +} IrsDeviceFormat; + +/// AruidFormat +typedef struct { + u64 ir_sensor_aruid; ///< IrSensorAruid + u32 ir_sensor_aruid_status; ///< IrSensorAruidStatus + u32 pad; ///< Padding +} IrsAruidFormat; + +/// StatusManager +typedef struct { + IrsDeviceFormat device_format[IRS_MAX_CAMERAS]; + IrsAruidFormat aruid_format[0x5]; +} IrsStatusManager; /// Initialize irs. Result irsInitialize(void); @@ -58,36 +394,188 @@ void irsExit(void); /// Gets the Service object for the actual irs service session. Service* irsGetServiceSession(void); -/// Gets the address of the SharedMemory. +/// Gets the address of the SharedMemory (\ref IrsStatusManager). void* irsGetSharedmemAddr(void); -/// (De)activate the IR sensor, this is automatically used by \ref irsExit. Must be called after irsInitialize() to activate the IR sensor. -Result irsActivateIrsensor(bool activate); +/// Gets the \ref IrsIrCameraHandle for the specified controller. +Result irsGetIrCameraHandle(IrsIrCameraHandle *handle, HidControllerID id); -/// Gets the IrCameraHandle for the specified controller. -Result irsGetIrCameraHandle(u32 *IrCameraHandle, HidControllerID id); +/// GetIrCameraStatus +Result irsGetIrCameraStatus(IrsIrCameraHandle handle, IrsIrCameraStatus *out); + +/// CheckFirmwareUpdateNecessity +/// When successful where the output flag is set, the user should use \ref hidLaShowControllerFirmwareUpdate. +/// Only available on [4.0.0+]. +Result irsCheckFirmwareUpdateNecessity(IrsIrCameraHandle handle, bool *out); + +/// GetImageProcessorStatus +/// Only available on [4.0.0+]. +Result irsGetImageProcessorStatus(IrsIrCameraHandle handle, IrsImageProcessorStatus *out); + +/// Stop the current Processor. +/// \ref irsExit calls this with all IrCameraHandles which were not already used with \ref irsStopImageProcessor. +Result irsStopImageProcessor(IrsIrCameraHandle handle); + +/// Stop the current Processor, async. +/// Only available on [4.0.0+]. +Result irsStopImageProcessorAsync(IrsIrCameraHandle handle); /** - * @brief Start ImageTransferProcessor. - * @param[in] IrCameraHandle Camera handle. + * @brief Run the MomentProcessor. + * @param[in] handle \ref IrsIrCameraHandle + * @param[in] config Input config. + */ +Result irsRunMomentProcessor(IrsIrCameraHandle handle, const IrsMomentProcessorConfig *config); + +/** + * @brief Gets the states for MomentProcessor or IrLedProcessor. + * @note The official GetIrLedProcessorState is essentially the same as this, except it uses hard-coded count=1 with output-array on stack, without returning that data. Hence we don't implement a seperate func for that. + * @param[in] handle \ref IrsIrCameraHandle + * @param[out] states Output array of \ref IrsMomentProcessorState. + * @param[in] count Size of the states array in entries. Must be 1-5. + * @param[out] total_out Total output entries. + */ +Result irsGetMomentProcessorStates(IrsIrCameraHandle handle, IrsMomentProcessorState *states, s32 count, s32 *total_out); + +/** + * @brief Calculates an \ref IrsMomentStatistic from the specified region in the input \ref IrsMomentProcessorState. + * @param[in] state \ref IrsMomentProcessorState + * @param[in] rect \ref IrsRect, containing the image width and height. + * @param[in] region_x Region x, must be 0-5 (clamped to this range otherwise). region_x = image_x/6. + * @param[in] region_y Region y, must be 0-7 (clamped to this range otherwise). region_y = image_y/8. + * @param[in] region_width Region width. region_x+region_width must be <=6 (clamped to this range otherwise). + * @param[in] region_height Region height. region_y+region_height must be <=8 (clamped to this range otherwise). + */ +IrsMomentStatistic irsCalculateMomentRegionStatistic(const IrsMomentProcessorState *state, IrsRect rect, s32 region_x, s32 region_y, s32 region_width, s32 region_height); + +/** + * @brief Run the ClusteringProcessor. + * @param[in] handle \ref IrsIrCameraHandle + * @param[in] config Input config. + */ +Result irsRunClusteringProcessor(IrsIrCameraHandle handle, const IrsClusteringProcessorConfig *config); + +/** + * @brief Gets the states for ClusteringProcessor. + * @param[in] handle \ref IrsIrCameraHandle + * @param[out] states Output array of \ref IrsClusteringProcessorState. + * @param[in] count Size of the states array in entries. Must be 1-5. + * @param[out] total_out Total output entries. + */ +Result irsGetClusteringProcessorStates(IrsIrCameraHandle handle, IrsClusteringProcessorState *states, s32 count, s32 *total_out); + +/** + * @brief Run the ImageTransferProcessor. + * @param[in] handle \ref IrsIrCameraHandle * @param[in] config Input config. * @param[in] size Work-buffer size, must be 0x1000-byte aligned. - * @note Do not use if already started. */ -Result irsRunImageTransferProcessor(u32 IrCameraHandle, IrsImageTransferProcessorConfig *config, size_t size); +Result irsRunImageTransferProcessor(IrsIrCameraHandle handle, const IrsImageTransferProcessorConfig *config, size_t size); -Result irsGetImageTransferProcessorState(u32 IrCameraHandle, void* buffer, size_t size, IrsImageTransferProcessorState *state); +/** + * @brief Run the ImageTransferExProcessor. + * @note Only available on [4.0.0+]. + * @param[in] handle \ref IrsIrCameraHandle + * @param[in] config Input config. + * @param[in] size Work-buffer size, must be 0x1000-byte aligned. + */ +Result irsRunImageTransferExProcessor(IrsIrCameraHandle handle, const IrsImageTransferProcessorExConfig *config, size_t size); -/// Stop ImageTransferProcessor. Do not use if already stopped. -/// \ref irsExit calls this with all IrCameraHandles which were not already used with \ref irsStopImageProcessor. -Result irsStopImageProcessor(u32 IrCameraHandle); +/// GetImageTransferProcessorState +Result irsGetImageTransferProcessorState(IrsIrCameraHandle handle, void* buffer, size_t size, IrsImageTransferProcessorState *state); -/// "Suspend" ImageTransferProcessor. -/// TODO: What does this really do? -Result irsSuspendImageProcessor(u32 IrCameraHandle); +/** + * @brief Run the PointingProcessor. + * @param[in] handle \ref IrsIrCameraHandle + */ +Result irsRunPointingProcessor(IrsIrCameraHandle handle); -/** - * Gets the default configuration for Image Transfer mode. - * Defaults are exposure 300us, IR LEDs all ON, 8x digital gain, normal image and resolution 240 x 320. +/** + * @brief Gets the states for PointingProcessor. + * @param[in] handle \ref IrsIrCameraHandle + * @param[out] states Output array of \ref IrsPointingProcessorMarkerState. + * @param[in] count Size of the states array in entries. Must be 1-6. + * @param[out] total_out Total output entries. + */ +Result irsGetPointingProcessorMarkerStates(IrsIrCameraHandle handle, IrsPointingProcessorMarkerState *states, s32 count, s32 *total_out); + +/** + * @brief Gets the states for \ref IrsPointingProcessorState. + * @note This uses \ref irsGetPointingProcessorMarkerStates, then converts the output to \ref IrsPointingProcessorState. + * @param[in] handle \ref IrsIrCameraHandle + * @param[out] states Output array of \ref IrsPointingProcessorState. + * @param[in] count Size of the states array in entries. Must be 1-6. + * @param[out] total_out Total output entries. + */ +Result irsGetPointingProcessorStates(IrsIrCameraHandle handle, IrsPointingProcessorState *states, s32 count, s32 *total_out); + +/** + * @brief Run the TeraPluginProcessor. + * @param[in] handle \ref IrsIrCameraHandle + * @param[in] config Input config. + */ +Result irsRunTeraPluginProcessor(IrsIrCameraHandle handle, const IrsTeraPluginProcessorConfig *config); + +/** + * @brief Gets the states for TeraPluginProcessor, filtered using the input params. + * @param[in] handle \ref IrsIrCameraHandle + * @param[out] states Output array of \ref IrsTeraPluginProcessorState. + * @param[in] count Size of the states array in entries. Must be 1-5. + * @param[in] sampling_number Minimum value for IrsTeraPluginProcessorState::sampling_number. + * @param[in] prefix_data Only used when prefix_bitcount is not 0. The first prefix_bitcount bits from prefix_data must match the first prefix_bitcount bits in IrsTeraPluginProcessorState::plugin_data. + * @param[in] prefix_bitcount Total bits for prefix_data. + * @param[out] total_out Total output entries. + */ +Result irsGetTeraPluginProcessorStates(IrsIrCameraHandle handle, IrsTeraPluginProcessorState *states, s32 count, s64 sampling_number, u32 prefix_data, u32 prefix_bitcount, s32 *total_out); + +/** + * @brief Run the IrLedProcessor. + * @note Only available on [4.0.0+]. + * @param[in] handle \ref IrsIrCameraHandle + * @param[in] config Input config. + */ +Result irsRunIrLedProcessor(IrsIrCameraHandle handle, const IrsIrLedProcessorConfig *config); + +/** + * @brief Run the AdaptiveClusteringProcessor. + * @note Only available on [5.0.0+]. + * @param[in] handle \ref IrsIrCameraHandle + * @param[in] config Input config. + */ +Result irsRunAdaptiveClusteringProcessor(IrsIrCameraHandle handle, const IrsAdaptiveClusteringProcessorConfig *config); + +/** + * @brief Run HandAnalysis. + * @param[in] handle \ref IrsIrCameraHandle + * @param[in] config Input config. + */ +Result irsRunHandAnalysis(IrsIrCameraHandle handle, const IrsHandAnalysisConfig *config); + +/** + * Gets the default configuration for MomentProcessor. + */ +void irsGetMomentProcessorDefaultConfig(IrsMomentProcessorConfig *config); + +/** + * Gets the default configuration for ClusteringProcessor. + */ +void irsGetClusteringProcessorDefaultConfig(IrsClusteringProcessorConfig *config); + +/** + * Gets the default configuration for ImageTransferProcessor. + * Defaults are exposure 300us, 8x digital gain, the rest is all-zero. Format is ::IrsImageTransferProcessorFormat_320x240. */ void irsGetDefaultImageTransferProcessorConfig(IrsImageTransferProcessorConfig *config); + +/** + * Gets the default configuration for ImageTransferProcessorEx. + * Defaults are exposure 300us, 8x digital gain, the rest is all-zero. OrigFormat/TrimmingFormat are ::IrsImageTransferProcessorFormat_320x240. + */ +void irsGetDefaultImageTransferProcessorExConfig(IrsImageTransferProcessorExConfig *config); + +/** + * Gets the default configuration for IrLedProcessor. + */ +NX_CONSTEXPR void irsGetIrLedProcessorDefaultConfig(IrsIrLedProcessorConfig *config) { + config->light_target = 0; +} diff --git a/nx/source/services/irs.c b/nx/source/services/irs.c index 1211ed92..729ad638 100644 --- a/nx/source/services/irs.c +++ b/nx/source/services/irs.c @@ -1,39 +1,105 @@ #define NX_SERVICE_ASSUME_NON_DOMAIN -#include "service_guard.h" #include +#include +#include "service_guard.h" +#include "runtime/hosversion.h" #include "kernel/shmem.h" #include "kernel/tmem.h" #include "services/applet.h" #include "services/irs.h" +#include "applets/hid_la.h" typedef struct { - bool initialized; - u32 IrCameraHandle; + IrsIrSensorMode mode; + u32 internal_status; + IrsIrCameraHandle handle; TransferMemory transfermem; + bool version_check; + + u8 is_negative_image_used; + u8 format; + IrsRect window_of_interest; } IrsCameraEntry; +typedef struct { + s64 sampling_number; + u32 prefix_data; + u32 prefix_bitcount; +} IrsTeraFilterArg; + static Service g_irsSrv; static SharedMemory g_irsSharedmem; -static bool g_irsSensorActivated; +static IrsPackedFunctionLevel g_irsFunctionLevel; // In sdknso there's various funcs which get the FunctionLevel, but the only ones which actually use the loaded data is the Run*Processor funcs. -static IrsCameraEntry g_irsCameras[8]; +static IrsPackedMcuVersion g_irsRequiredMcuVersion; + +static IrsCameraEntry g_irsCameras[IRS_MAX_CAMERAS]; + +static const size_t g_irsImageFormatSizes[] = { + [IrsImageTransferProcessorFormat_320x240] = 320*240, + [IrsImageTransferProcessorFormat_160x120] = 160*120, + [IrsImageTransferProcessorFormat_80x60] = 80*60, + [IrsImageTransferProcessorFormat_40x30] = 40*30, + [IrsImageTransferProcessorFormat_20x15] = 20*15, +}; + +static const Result g_irsCameraStatusResults[] = { + [IrsIrCameraStatus_Available] = MAKERESULT(205, 160), + [IrsIrCameraStatus_Unsupported] = MAKERESULT(205, 111), + [IrsIrCameraStatus_Unconnected] = MAKERESULT(205, 110), +}; + +static Result _irsActivateIrsensor(bool activate); static Result _irsGetIrsensorSharedMemoryHandle(Handle* handle_out); +static Result _irsCheckFirmwareVersion(IrsIrCameraHandle handle, IrsPackedMcuVersion version); + +static Result _irsActivateIrsensorWithFunctionLevel(IrsPackedFunctionLevel level); + NX_GENERATE_SERVICE_GUARD(irs); Result _irsInitialize(void) { Result rc=0; Handle sharedmem_handle; - g_irsSensorActivated = 0; memset(g_irsCameras, 0, sizeof(g_irsCameras)); + for (u32 i=0; iinitialized) continue; - irsStopImageProcessor(entry->IrCameraHandle); + if (entry->mode != IrsIrSensorMode_None) irsStopImageProcessor(entry->handle); } - irsActivateIrsensor(0); + _irsActivateIrsensor(0); } serviceClose(&g_irsSrv); shmemClose(&g_irsSharedmem); + + g_irsFunctionLevel.ir_sensor_function_level = 0x0; } Service* irsGetServiceSession(void) { @@ -70,70 +136,299 @@ void* irsGetSharedmemAddr(void) { return shmemGetAddr(&g_irsSharedmem); } -static Result _IrsCameraEntryAlloc(u32 IrCameraHandle, IrsCameraEntry **out_entry) { - int i; - size_t entrycount = sizeof(g_irsCameras)/sizeof(IrsCameraEntry); - int empty_entry = -1; +static inline IrsStatusManager *_irsGetStatusManager(void) { + return (IrsStatusManager*)irsGetSharedmemAddr(); +} + +static Result _irsCameraEntryGet(IrsIrCameraHandle handle, IrsCameraEntry **out_entry) { IrsCameraEntry *entry; + *out_entry = NULL; - if (out_entry) *out_entry = NULL; + if (handle.player_number >= IRS_MAX_CAMERAS) return MAKERESULT(Module_Libnx, LibnxError_BadInput); - for(i=0; iinitialized) { - if (entry->IrCameraHandle == IrCameraHandle) - return MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized); - } - else if (empty_entry == -1) - empty_entry = i; - } - - if (empty_entry == -1) - return MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized); - - entry = &g_irsCameras[empty_entry]; - - entry->initialized = 1; - entry->IrCameraHandle = IrCameraHandle; - - if (out_entry) *out_entry = entry; + entry = &g_irsCameras[handle.player_number]; + *out_entry = entry; return 0; } -static Result _IrsCameraEntryGet(u32 IrCameraHandle, IrsCameraEntry **out_entry) { - int i; - size_t entrycount = sizeof(g_irsCameras)/sizeof(IrsCameraEntry); - IrsCameraEntry *entry; - *out_entry = NULL; +static void _irsCameraEntryFree(IrsCameraEntry *entry) { + tmemClose(&entry->transfermem); +} - for(i=0; iinitialized && entry->IrCameraHandle == IrCameraHandle) { - *out_entry = entry; - return 0; +static bool _irsGetIrSensorAruidStatus(u32 *out) { + u64 aruid = appletGetAppletResourceUserId(); + for (u32 i=0; i<0x5; i++) { + IrsAruidFormat *aruid_format = &_irsGetStatusManager()->aruid_format[i]; + if (atomic_load_explicit(&aruid_format->ir_sensor_aruid, memory_order_acquire) == aruid) { + *out = atomic_load_explicit(&aruid_format->ir_sensor_aruid_status, memory_order_acquire); + return 1; } } - return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + return 0; } -static void _IrsCameraEntryFree(IrsCameraEntry *entry) { - tmemClose(&entry->transfermem); - memset(entry, 0, sizeof(IrsCameraEntry)); +static bool _irsIsAppletForeground(void) { + u32 status=0; + bool flag = _irsGetIrSensorAruidStatus(&status); + return flag==0 || (status & BIT(0)); } -Result irsActivateIrsensor(bool activate) { - if (g_irsSensorActivated==activate) return 0; +static bool _irsIsLibraryAppletCallEnabled(IrsIrCameraHandle handle, IrsIrCameraInternalStatus status) { + if (handle.player_number >= IRS_MAX_CAMERAS) return 0; + + bool ret=0; + if (status) { + for (u32 i=0; i= IRS_MAX_CAMERAS) return; + + g_irsCameras[handle.player_number].internal_status = status; +} + +static bool _irsGetVersionCheckFlag(IrsIrCameraHandle handle) { + if (handle.player_number >= IRS_MAX_CAMERAS) return 0; + + return g_irsCameras[handle.player_number].version_check; +} + +static void _irsSetVersionCheckFlag(IrsIrCameraHandle handle, bool flag) { + if (handle.player_number >= IRS_MAX_CAMERAS) return; + + g_irsCameras[handle.player_number].version_check = flag; +} + +Result irsGetIrCameraStatus(IrsIrCameraHandle handle, IrsIrCameraStatus *out) { + if (handle.player_number >= IRS_MAX_CAMERAS) return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + IrsIrCameraStatus tmp = atomic_load_explicit(&_irsGetStatusManager()->device_format[handle.player_number].ir_camera_status, memory_order_acquire); + if (tmp > IrsIrCameraStatus_Unconnected) return MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen); + *out = tmp; + + return 0; +} + +static Result _irsGetIrCameraInternalStatus(IrsIrCameraHandle handle, IrsIrCameraInternalStatus *out) { + if (handle.player_number >= IRS_MAX_CAMERAS) return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + u32 tmp = atomic_load_explicit(&_irsGetStatusManager()->device_format[handle.player_number].ir_camera_internal_status, memory_order_acquire); + if (tmp > IrsIrCameraInternalStatus_Setting) return MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen); + *out = tmp; + + return 0; +} + +static Result _irsCheckInternalStatus(IrsIrCameraHandle handle) { + Result rc=0; + IrsIrCameraInternalStatus status; + + rc = _irsGetIrCameraInternalStatus(handle, &status); + if (R_FAILED(rc)) return rc; + + bool flag = _irsIsLibraryAppletCallEnabled(handle, status); + + switch (status) { + case IrsIrCameraInternalStatus_Stopped: + case IrsIrCameraInternalStatus_Ready: + break; // Leave rc at value 0 for success. + + case IrsIrCameraInternalStatus_Unknown2: // These are seperate with sdknso. + case IrsIrCameraInternalStatus_Unknown3: + rc = status == IrsIrCameraInternalStatus_Unknown2 ? MAKERESULT(205, 123) : MAKERESULT(205, 124); + // sdknso would use errorResultShow() here with rc when flag is set, however we won't do so. + break; + + case IrsIrCameraInternalStatus_Unknown4: + rc = MAKERESULT(205, 161); + break; + + case IrsIrCameraInternalStatus_FirmwareUpdateNeeded: + if (flag) { + HidLaControllerFirmwareUpdateArg arg; + hidLaCreateControllerFirmwareUpdateArg(&arg); + arg.enable_force_update = 1; + rc = hidLaShowControllerFirmwareUpdate(&arg); + + if (R_FAILED(rc)) { + rc = R_VALUE(rc) == MAKERESULT(Module_Libnx, LibnxError_LibAppletBadExit) ? MAKERESULT(205, 125) : rc; + break; + } + } + // fallthrough + + case IrsIrCameraInternalStatus_FirmwareVersionRequested: + case IrsIrCameraInternalStatus_FirmwareVersionIsInvalid: + case IrsIrCameraInternalStatus_Setting: + rc = MAKERESULT(205, 160); + break; + + default: + rc = MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen); + break; + } + + return rc; +} + +Result irsGetIrCameraHandle(IrsIrCameraHandle *handle, HidControllerID id) { + u32 tmp = hidControllerIDToOfficial(id); + return serviceDispatchInOut(&g_irsSrv, 311, tmp, *handle); +} + +Result irsCheckFirmwareUpdateNecessity(IrsIrCameraHandle handle, bool *out) { + if (hosversionBefore(4,0,0)) // sdknso didn't implement this until 4.x (the RequiredMcuVersion was also updated with that version). + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); Result rc=0; + IrsIrCameraStatus status; + IrsIrCameraInternalStatus internal_status; + + if (!_irsIsAppletForeground()) return MAKERESULT(205, 150); + + rc = irsGetIrCameraStatus(handle, &status); + if (R_FAILED(rc)) return rc; + + if (status == IrsIrCameraStatus_Available) { + if (_irsGetVersionCheckFlag(handle)) { + rc = _irsCheckFirmwareVersion(handle, g_irsRequiredMcuVersion); + if (R_SUCCEEDED(rc)) _irsSetVersionCheckFlag(handle, false); + } + + rc = _irsGetIrCameraInternalStatus(handle, &internal_status); + if (R_SUCCEEDED(rc)) { + bool flag; + if (internal_status == IrsIrCameraInternalStatus_FirmwareVersionIsInvalid) + flag = 1; + else if (internal_status == IrsIrCameraInternalStatus_Ready || internal_status == IrsIrCameraInternalStatus_Setting) + flag = 0; + else if (internal_status == IrsIrCameraInternalStatus_Stopped) + flag = 0; + else + rc = MAKERESULT(205, 150); + + if (R_SUCCEEDED(rc)) { + _irsSetVersionCheckFlag(handle, true); + *out = flag; + } + } + } + else if (status == IrsIrCameraStatus_Unsupported) { + rc = MAKERESULT(205, 111); + } + else if (status == IrsIrCameraStatus_Unconnected) { + rc = MAKERESULT(205, 110); + } + + return rc; +} + +Result irsGetImageProcessorStatus(IrsIrCameraHandle handle, IrsImageProcessorStatus *out) { + if (hosversionBefore(4,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + Result rc=0; + IrsIrCameraInternalStatus tmp; + + rc = _irsGetIrCameraInternalStatus(handle, &tmp); + if (R_SUCCEEDED(rc)) { + if (tmp == IrsIrCameraInternalStatus_FirmwareVersionRequested || tmp == IrsIrCameraInternalStatus_FirmwareVersionIsInvalid || tmp == IrsIrCameraInternalStatus_Ready) + *out = IrsImageProcessorStatus_Stopped; + else + *out = IrsImageProcessorStatus_Running; + } + return rc; +} + +static IrsProcessorState *_irsGetRingLifo(IrsIrCameraHandle handle, IrsIrSensorMode mode) { + if (handle.player_number >= IRS_MAX_CAMERAS) return NULL; + IrsDeviceFormat *device = &_irsGetStatusManager()->device_format[handle.player_number]; + u32 tmp = atomic_load_explicit(&device->ir_sensor_mode, memory_order_acquire); + if (tmp != mode) return NULL; + + return &device->processor_state; +} + +// sdknso has multiple funcs for this (for each *ProcessorState), but we'll just use one func instead. +static s32 _irsRingLifoRead(IrsProcessorState *lifo, void* out, s32 count, IrsValidationCb validation_cb, void* userdata, size_t entrysize, s64 max_entrycount) { + IrsTeraPluginProcessorState tmpdata; // validation_cb is only used with TeraPluginProcessor. + if (validation_cb && entrysize > sizeof(tmpdata)) return 0; + + s64 max_first_entryindex = 0-max_entrycount; // sdknso uses {inparam}-{max_entrycount}, but the inparam is always 0 anyway. + u8 *out8 = (u8*)out; + if (count <= 0) return 0; + if (count > max_entrycount) count = max_entrycount; // sdknso does this in the callers, but we'll do it here instead. + + s32 total_entries=0; + + s64 sampling_number0=0, sampling_number1=0; + s64 prev_samplenum=0; + u32 num_samples=0; + + do { + sampling_number0 = atomic_load_explicit(&lifo->start, memory_order_acquire); + sampling_number1 = atomic_load_explicit(&lifo->start, memory_order_acquire); + + s64 start_samplenum = max_first_entryindex + sampling_number1; + s64 max_prev_samplenum = sampling_number0 - (max_entrycount+1); + s64 timediff = sampling_number1 - (s64)atomic_load_explicit(&lifo->count, memory_order_acquire); + s64 tmp0 = timediff > start_samplenum ? timediff : start_samplenum; + prev_samplenum = max_prev_samplenum > tmp0 ? max_prev_samplenum : tmp0; + + s64 tmp = sampling_number1 - prev_samplenum; + if (tmp <= 0) break; + num_samples = tmp; + + s64 entryindex = prev_samplenum + (s64)num_samples - 1; + total_entries = 0; + s64 entrycount = entryindex - prev_samplenum; + + if (entrycount >= 0) { + entryindex = prev_samplenum + entrycount; + s64 next_samplenum = prev_samplenum+(max_entrycount+1); + + for (s64 i=0; i<=entrycount; i++) { + u8 *data_src = &lifo->data[((entryindex-i) % (max_entrycount+1)) * entrysize]; + + if (validation_cb) memcpy(&tmpdata, data_src, entrysize); + + sampling_number0 = atomic_load_explicit(&lifo->start, memory_order_acquire); + + if (atomic_load_explicit(&lifo->count, memory_order_acquire) < num_samples || sampling_number0 <= prev_samplenum || next_samplenum <= sampling_number0) break; + + bool is_valid=true; + if (validation_cb) is_valid = validation_cb(userdata, &tmpdata); + if (is_valid) { + memcpy(&out8[total_entries*entrysize], data_src, entrysize); + total_entries++; + } + + if (total_entries >= count) break; + } + } + + sampling_number0 = atomic_load_explicit(&lifo->start, memory_order_acquire); + } while (sampling_number0 <= prev_samplenum || atomic_load_explicit(&lifo->count, memory_order_acquire) < num_samples || prev_samplenum+max_entrycount+1 <= sampling_number0); + + return total_entries; +} + +static Result _irsActivateIrsensor(bool activate) { u64 AppletResourceUserId = appletGetAppletResourceUserId(); - rc = serviceDispatchIn(&g_irsSrv, activate ? 302 : 303, AppletResourceUserId, + return serviceDispatchIn(&g_irsSrv, activate ? 302 : 303, AppletResourceUserId, .in_send_pid = true, ); - if (R_SUCCEEDED(rc)) g_irsSensorActivated = activate; - return rc; } static Result _irsGetIrsensorSharedMemoryHandle(Handle* handle_out) { @@ -146,44 +441,51 @@ static Result _irsGetIrsensorSharedMemoryHandle(Handle* handle_out) { ); } -static Result _irsStopImageProcessor(u32 IrCameraHandle) { +static Result _irsStopImageProcessor(IrsIrCameraHandle handle) { const struct { - u32 IrCameraHandle; + IrsIrCameraHandle handle; u64 AppletResourceUserId; - } in = { IrCameraHandle, appletGetAppletResourceUserId() }; + } in = { handle, appletGetAppletResourceUserId() }; return serviceDispatchIn(&g_irsSrv, 305, in, .in_send_pid = true, ); } -Result irsStopImageProcessor(u32 IrCameraHandle) { - Result rc=0; - IrsCameraEntry *entry = NULL; +static Result _irsRunMomentProcessor(IrsIrCameraHandle handle, const IrsPackedMomentProcessorConfig *config) { + const struct { + IrsIrCameraHandle handle; + u32 pad; + u64 AppletResourceUserId; + IrsPackedMomentProcessorConfig config; + } in = { handle, 0, appletGetAppletResourceUserId(), *config }; - if (!serviceIsActive(&g_irsSrv)) - return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); - - rc = _IrsCameraEntryGet(IrCameraHandle, &entry); - if (R_FAILED(rc)) - return rc; - - if (entry->transfermem.handle == INVALID_HANDLE) - return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); - - rc = _irsStopImageProcessor(IrCameraHandle); - _IrsCameraEntryFree(entry); - return rc; + return serviceDispatchIn(&g_irsSrv, 306, in, + .in_send_pid = true, + ); } -static Result _irsRunImageTransferProcessor(u32 IrCameraHandle, IrsPackedImageTransferProcessorConfig *config, TransferMemory *tmem) { +static Result _irsRunClusteringProcessor(IrsIrCameraHandle handle, const IrsPackedClusteringProcessorConfig *config) { const struct { - u32 IrCameraHandle; + IrsIrCameraHandle handle; + u32 pad; + u64 AppletResourceUserId; + IrsPackedClusteringProcessorConfig config; + } in = { handle, 0, appletGetAppletResourceUserId(), *config }; + + return serviceDispatchIn(&g_irsSrv, 307, in, + .in_send_pid = true, + ); +} + +static Result _irsRunImageTransferProcessor(IrsIrCameraHandle handle, const IrsPackedImageTransferProcessorConfig *config, TransferMemory *tmem) { + const struct { + IrsIrCameraHandle handle; u32 pad; u64 AppletResourceUserId; IrsPackedImageTransferProcessorConfig config; u64 TransferMemory_size; - } in = { IrCameraHandle, 0, appletGetAppletResourceUserId(), *config, tmem->size }; + } in = { handle, 0, appletGetAppletResourceUserId(), *config, tmem->size }; return serviceDispatchIn(&g_irsSrv, 308, in, .in_send_pid = true, @@ -192,39 +494,11 @@ static Result _irsRunImageTransferProcessor(u32 IrCameraHandle, IrsPackedImageTr ); } -Result irsRunImageTransferProcessor(u32 IrCameraHandle, IrsImageTransferProcessorConfig *config, size_t size) { - Result rc=0; - IrsCameraEntry *entry = NULL; - IrsPackedImageTransferProcessorConfig packed_config; - - memset(&packed_config, 0, sizeof(packed_config)); - - packed_config.exposure = config->exposure; - packed_config.ir_leds = config->ir_leds; - packed_config.digital_gain = config->digital_gain; - packed_config.color_invert = config->color_invert; - packed_config.unk_constant = 0xa0003; - packed_config.sensor_res = config->sensor_res; - - rc = _IrsCameraEntryAlloc(IrCameraHandle, &entry); - if (R_FAILED(rc)) - return rc; - - rc = tmemCreate(&entry->transfermem, size, Perm_None); - if (R_FAILED(rc)) return rc; - - rc = _irsRunImageTransferProcessor(IrCameraHandle, &packed_config, &entry->transfermem); - - if (R_FAILED(rc)) _IrsCameraEntryFree(entry); - - return rc; -} - -Result irsGetImageTransferProcessorState(u32 IrCameraHandle, void* buffer, size_t size, IrsImageTransferProcessorState *state) { +static Result _irsGetImageTransferProcessorState(IrsIrCameraHandle handle, void* buffer, size_t size, IrsImageTransferProcessorState *state) { const struct { - u32 IrCameraHandle; + IrsIrCameraHandle handle; u64 AppletResourceUserId; - } in = { IrCameraHandle, appletGetAppletResourceUserId() }; + } in = { handle, appletGetAppletResourceUserId() }; return serviceDispatchInOut(&g_irsSrv, 309, in, *state, .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, @@ -233,28 +507,788 @@ Result irsGetImageTransferProcessorState(u32 IrCameraHandle, void* buffer, size_ ); } -void irsGetDefaultImageTransferProcessorConfig(IrsImageTransferProcessorConfig *config) { - memset(config, 0, sizeof(IrsImageTransferProcessorConfig)); - - config->exposure = 300000; - config->ir_leds = 0; - config->digital_gain = 8; - config->color_invert = 0; - config->sensor_res = 0; -} - -Result irsGetIrCameraHandle(u32 *IrCameraHandle, HidControllerID id) { - u32 tmp = hidControllerIDToOfficial(id); - return serviceDispatchInOut(&g_irsSrv, 311, tmp, *IrCameraHandle); -} - -Result irsSuspendImageProcessor(u32 IrCameraHandle) { +static Result _irsRunTeraPluginProcessor(IrsIrCameraHandle handle, const IrsPackedTeraPluginProcessorConfig *config) { const struct { - u32 IrCameraHandle; + IrsIrCameraHandle handle; + IrsPackedTeraPluginProcessorConfig config; u64 AppletResourceUserId; - } in = { IrCameraHandle, appletGetAppletResourceUserId() }; + } in = { handle, *config, appletGetAppletResourceUserId() }; + + return serviceDispatchIn(&g_irsSrv, 310, in, + .in_send_pid = true, + ); +} + +static Result _irsRunPointingProcessor(IrsIrCameraHandle handle, const IrsPackedPointingProcessorConfig *config) { + const struct { + IrsIrCameraHandle handle; + IrsPackedPointingProcessorConfig config; + u64 AppletResourceUserId; + } in = { handle, *config, appletGetAppletResourceUserId() }; + + return serviceDispatchIn(&g_irsSrv, 312, in, + .in_send_pid = true, + ); +} + +static Result _irsSuspendImageProcessor(IrsIrCameraHandle handle) { + const struct { + IrsIrCameraHandle handle; + u64 AppletResourceUserId; + } in = { handle, appletGetAppletResourceUserId() }; return serviceDispatchIn(&g_irsSrv, 313, in, .in_send_pid = true, ); } + +static Result _irsCheckFirmwareVersion(IrsIrCameraHandle handle, IrsPackedMcuVersion version) { + if (hosversionBefore(3,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + const struct { + IrsIrCameraHandle handle; + IrsPackedMcuVersion version; + u32 pad; + u64 AppletResourceUserId; + } in = { handle, version, 0, appletGetAppletResourceUserId() }; + + return serviceDispatchIn(&g_irsSrv, 314, in, + .in_send_pid = true, + ); +} + +static Result _irsRunImageTransferExProcessor(IrsIrCameraHandle handle, const IrsPackedImageTransferProcessorExConfig *config, TransferMemory *tmem) { + const struct { + IrsIrCameraHandle handle; + u32 pad; + u64 AppletResourceUserId; + IrsPackedImageTransferProcessorExConfig config; + u64 TransferMemory_size; + } in = { handle, 0, appletGetAppletResourceUserId(), *config, tmem->size }; + + return serviceDispatchIn(&g_irsSrv, 316, in, + .in_send_pid = true, + .in_num_handles = 1, + .in_handles = { tmem->handle }, + ); +} + +static Result _irsRunIrLedProcessor(IrsIrCameraHandle handle, const IrsPackedIrLedProcessorConfig *config) { + if (hosversionBefore(4,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + const struct { + IrsIrCameraHandle handle; + IrsPackedIrLedProcessorConfig config; + u64 AppletResourceUserId; + } in = { handle, *config, appletGetAppletResourceUserId() }; + + return serviceDispatchIn(&g_irsSrv, 317, in, + .in_send_pid = true, + ); +} + +static Result _irsStopImageProcessorAsync(IrsIrCameraHandle handle) { + if (hosversionBefore(4,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + const struct { + IrsIrCameraHandle handle; + u64 AppletResourceUserId; + } in = { handle, appletGetAppletResourceUserId() }; + + return serviceDispatchIn(&g_irsSrv, 318, in, + .in_send_pid = true, + ); +} + +static Result _irsActivateIrsensorWithFunctionLevel(IrsPackedFunctionLevel level) { + const struct { + IrsPackedFunctionLevel level; + u32 pad; + u64 AppletResourceUserId; + } in = { level, 0, appletGetAppletResourceUserId() }; + + return serviceDispatchIn(&g_irsSrv, 319, in, + .in_send_pid = true, + ); +} + +Result irsStopImageProcessor(IrsIrCameraHandle handle) { + Result rc=0; + IrsCameraEntry *entry = NULL; + + if (!serviceIsActive(&g_irsSrv)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + rc = _irsCameraEntryGet(handle, &entry); + if (R_FAILED(rc)) + return rc; + + _irsSetInternalStatus(handle, IrsIrCameraInternalStatus_Stopped); + _irsSetVersionCheckFlag(handle, true); + + bool old_sysver = hosversionBefore(4,0,0); + + if (old_sysver) + rc = _irsStopImageProcessor(handle); + else + rc = _irsStopImageProcessorAsync(handle); + if (R_SUCCEEDED(rc)) entry->mode = IrsIrSensorMode_None; + + if (R_SUCCEEDED(rc) && !old_sysver) { + for (u32 i=0; i<0x14d; i++) { + IrsImageProcessorStatus status; + irsGetImageProcessorStatus(handle, &status); + if (status == IrsImageProcessorStatus_Stopped) break; + svcSleepThread(15000000); + } + } + + _irsCameraEntryFree(entry); + return rc; +} + +Result irsStopImageProcessorAsync(IrsIrCameraHandle handle) { + Result rc=0; + IrsCameraEntry *entry = NULL; + + if (!serviceIsActive(&g_irsSrv)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + rc = _irsCameraEntryGet(handle, &entry); + if (R_FAILED(rc)) + return rc; + + _irsSetInternalStatus(handle, IrsIrCameraInternalStatus_Stopped); + _irsSetVersionCheckFlag(handle, true); + + rc = _irsStopImageProcessorAsync(handle); + if (R_SUCCEEDED(rc)) entry->mode = IrsIrSensorMode_None; + + _irsCameraEntryFree(entry); + return rc; +} + +Result irsRunMomentProcessor(IrsIrCameraHandle handle, const IrsMomentProcessorConfig *config) { + Result rc=0; + IrsCameraEntry *entry = NULL; + IrsPackedMomentProcessorConfig packed_config; + + memset(&packed_config, 0, sizeof(packed_config)); + + packed_config.exposure_time = config->exposure_time; + packed_config.light_target = config->light_target; + packed_config.gain = config->gain; + packed_config.is_negative_image_used = config->is_negative_image_used; + packed_config.window_of_interest = config->window_of_interest; + packed_config.required_mcu_version = g_irsRequiredMcuVersion; + packed_config.preprocess = config->preprocess; + packed_config.preprocess_intensity_threshold = config->preprocess_intensity_threshold; + + rc = _irsCameraEntryGet(handle, &entry); + if (R_FAILED(rc)) + return rc; + entry->handle = handle; + + if (g_irsFunctionLevel.ir_sensor_function_level >= 0x1 && (entry->mode != IrsIrSensorMode_None && entry->mode != IrsIrSensorMode_MomentProcessor)) { + rc = _irsSuspendImageProcessor(handle); + } + + if (R_SUCCEEDED(rc)) rc = _irsRunMomentProcessor(handle, &packed_config); + + if (R_SUCCEEDED(rc)) { + entry->mode = IrsIrSensorMode_MomentProcessor; + entry->is_negative_image_used = packed_config.is_negative_image_used; + entry->window_of_interest = packed_config.window_of_interest; + } + + return rc; +} + +Result irsGetMomentProcessorStates(IrsIrCameraHandle handle, IrsMomentProcessorState *states, s32 count, s32 *total_out) { + Result rc=0; + IrsCameraEntry *entry = NULL; + + rc = _irsCameraEntryGet(handle, &entry); + if (R_FAILED(rc)) + return rc; + + *total_out = 0; + // sdknso would fill states with default values here, but we won't do that. + + rc = MAKERESULT(205, 160); + if (!_irsIsAppletForeground()) return rc; + + Result rc2 = _irsCheckInternalStatus(handle); + if (R_FAILED(rc2)) + return rc2; + + if (entry->mode != IrsIrSensorMode_MomentProcessor && entry->mode != IrsIrSensorMode_IrLedProcessor) return rc; + IrsProcessorState *lifo = _irsGetRingLifo(handle, IrsIrSensorMode_MomentProcessor); + if (lifo==NULL) return rc; + rc = 0; + + *total_out = _irsRingLifoRead(lifo, states, count, NULL, NULL, sizeof(*states), 5); + + if (!*total_out) { + IrsIrCameraStatus status; + rc = irsGetIrCameraStatus(handle, &status); + if (R_SUCCEEDED(rc)) rc = g_irsCameraStatusResults[status]; // sdknso would verify that status is within bounds first, but that's redundant since irsGetIrCameraStatus() already does so. + return rc; + } + + if (entry->window_of_interest.width != 320 || entry->window_of_interest.height != 240) { // sdknso doesn't check this but we will, since multiplying by 1.0f is pointless (when the width and height are the defaults checked here). + float scale = 76800.0f / (float)(entry->window_of_interest.width * entry->window_of_interest.height); // 76800 == 320*240 + for (s32 statei=0; statei<*total_out; statei++) { + for (s32 stati=0; stati<0x30; stati++) states[statei].statistic[stati].average_intensity *= scale; + } + } + + return rc; +} + +IrsMomentStatistic irsCalculateMomentRegionStatistic(const IrsMomentProcessorState *state, IrsRect rect, s32 region_x, s32 region_y, s32 region_width, s32 region_height) { + s16 width = rect.width / 8; + s16 height = rect.height / 6; + float widthf = (float)width; + float heightf = (float)height; + double sum0=0, sum1=0, sum2=0; + + // sdknso doesn't have this set of validation. + if (region_x < 0) region_x = 0; + if (region_y < 0) region_y = 0; + if (region_x > 5) region_x = 5; + if (region_y > 7) region_y = 7; + if (region_x+region_width > 6) region_width = 6 - region_x; + if (region_y+region_height > 8) region_height = 8 - region_y; + + if (region_width >= 1 && region_height >= 1) { + for (s32 x=region_x; xstatistic[y + x*8]; + float intensity = stat->average_intensity*widthf*heightf; + sum0+= (double)(intensity*stat->centroid_x); + sum1+= (double)intensity; + sum2+= (double)(intensity*stat->centroid_y); + } + } + } + + double tmp = sum1 / (double)(region_width*region_height*width*height); + if (sum1 == 0.0f) { + return (IrsMomentStatistic){.average_intensity = (float)tmp}; + } + return (IrsMomentStatistic){.average_intensity = (float)tmp, .centroid_x = (float)(sum0 / sum1), .centroid_y = (float)(sum2 / sum1)}; +} + +Result irsRunClusteringProcessor(IrsIrCameraHandle handle, const IrsClusteringProcessorConfig *config) { + Result rc=0; + IrsCameraEntry *entry = NULL; + IrsPackedClusteringProcessorConfig packed_config; + + memset(&packed_config, 0, sizeof(packed_config)); + + packed_config.exposure_time = config->exposure_time; + packed_config.light_target = config->light_target; + packed_config.gain = config->gain; + packed_config.is_negative_image_used = config->is_negative_image_used; + packed_config.window_of_interest = config->window_of_interest; + packed_config.required_mcu_version = g_irsRequiredMcuVersion; + packed_config.object_pixel_count_min = config->object_pixel_count_min; + packed_config.object_pixel_count_max = config->object_pixel_count_max; + packed_config.object_intensity_min = config->object_intensity_min; + packed_config.is_external_light_filter_enabled = config->is_external_light_filter_enabled; + + rc = _irsCameraEntryGet(handle, &entry); + if (R_FAILED(rc)) + return rc; + entry->handle = handle; + + if (g_irsFunctionLevel.ir_sensor_function_level >= 0x1 && (entry->mode != IrsIrSensorMode_None && entry->mode != IrsIrSensorMode_ClusteringProcessor)) { + rc = _irsSuspendImageProcessor(handle); + } + + if (R_SUCCEEDED(rc)) rc = _irsRunClusteringProcessor(handle, &packed_config); + + if (R_SUCCEEDED(rc)) { + entry->mode = IrsIrSensorMode_ClusteringProcessor; + entry->is_negative_image_used = packed_config.is_negative_image_used; + entry->window_of_interest = packed_config.window_of_interest; + } + + return rc; +} + +Result irsGetClusteringProcessorStates(IrsIrCameraHandle handle, IrsClusteringProcessorState *states, s32 count, s32 *total_out) { + Result rc=0; + IrsCameraEntry *entry = NULL; + + rc = _irsCameraEntryGet(handle, &entry); + if (R_FAILED(rc)) + return rc; + + *total_out = 0; + // sdknso would fill states with default values here, but we won't do that. + + rc = MAKERESULT(205, 160); + if (!_irsIsAppletForeground()) return rc; + + Result rc2 = _irsCheckInternalStatus(handle); + if (R_FAILED(rc2)) + return rc2; + + if (entry->mode != IrsIrSensorMode_ClusteringProcessor) return rc; + IrsProcessorState *lifo = _irsGetRingLifo(handle, IrsIrSensorMode_ClusteringProcessor); + if (lifo==NULL) return rc; + rc = 0; + + *total_out = _irsRingLifoRead(lifo, states, count, NULL, NULL, sizeof(*states), 5); + + if (!*total_out) { + IrsIrCameraStatus status; + rc = irsGetIrCameraStatus(handle, &status); + if (R_SUCCEEDED(rc)) rc = g_irsCameraStatusResults[status]; // sdknso would verify that status is within bounds first, but that's redundant since irsGetIrCameraStatus() already does so. + } + + return rc; +} + +Result irsRunImageTransferProcessor(IrsIrCameraHandle handle, const IrsImageTransferProcessorConfig *config, size_t size) { + Result rc=0; + IrsCameraEntry *entry = NULL; + IrsPackedImageTransferProcessorConfig packed_config; + + memset(&packed_config, 0, sizeof(packed_config)); + + packed_config.exposure_time = config->exposure_time; + packed_config.light_target = config->light_target; + packed_config.gain = config->gain; + packed_config.is_negative_image_used = config->is_negative_image_used; + packed_config.required_mcu_version = g_irsRequiredMcuVersion; + packed_config.format = config->format; + + rc = _irsCameraEntryGet(handle, &entry); + if (R_FAILED(rc)) + return rc; + entry->handle = handle; + + if (g_irsFunctionLevel.ir_sensor_function_level >= 0x1 && entry->mode != IrsIrSensorMode_None) { + rc = _irsSuspendImageProcessor(handle); + if (R_SUCCEEDED(rc)) _irsCameraEntryFree(entry); + } + + if (R_SUCCEEDED(rc)) { + rc = tmemCreate(&entry->transfermem, size, Perm_None); + if (R_FAILED(rc)) return rc; + + rc = _irsRunImageTransferProcessor(handle, &packed_config, &entry->transfermem); + } + + if (R_SUCCEEDED(rc)) { + entry->mode = IrsIrSensorMode_ImageTransferProcessor; + entry->is_negative_image_used = packed_config.is_negative_image_used; + entry->format = packed_config.format; + } + + if (R_FAILED(rc)) _irsCameraEntryFree(entry); + + return rc; +} + +Result irsRunImageTransferExProcessor(IrsIrCameraHandle handle, const IrsImageTransferProcessorExConfig *config, size_t size) { + if (hosversionBefore(4,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + Result rc=0; + IrsCameraEntry *entry = NULL; + IrsPackedImageTransferProcessorExConfig packed_config; + + memset(&packed_config, 0, sizeof(packed_config)); + + packed_config.exposure_time = config->exposure_time; + packed_config.light_target = config->light_target; + packed_config.gain = config->gain; + packed_config.is_negative_image_used = config->is_negative_image_used; + packed_config.required_mcu_version = g_irsRequiredMcuVersion; + packed_config.orig_format = config->orig_format; + packed_config.trimming_format = config->trimming_format; + packed_config.trimming_start_x = config->trimming_start_x; + packed_config.trimming_start_y = config->trimming_start_y; + packed_config.is_external_light_filter_enabled = config->is_external_light_filter_enabled; + + rc = _irsCameraEntryGet(handle, &entry); + if (R_FAILED(rc)) + return rc; + entry->handle = handle; + + if (g_irsFunctionLevel.ir_sensor_function_level >= 0x1 && entry->mode != IrsIrSensorMode_None) { + rc = _irsSuspendImageProcessor(handle); + if (R_SUCCEEDED(rc)) _irsCameraEntryFree(entry); + } + + if (R_SUCCEEDED(rc)) { + rc = tmemCreate(&entry->transfermem, size, Perm_None); + if (R_FAILED(rc)) return rc; + + rc = _irsRunImageTransferExProcessor(handle, &packed_config, &entry->transfermem); + } + + if (R_SUCCEEDED(rc)) { + entry->mode = IrsIrSensorMode_ImageTransferProcessor; + entry->is_negative_image_used = packed_config.is_negative_image_used; + entry->format = packed_config.trimming_format; + } + + if (R_FAILED(rc)) _irsCameraEntryFree(entry); + + return rc; +} + +Result irsGetImageTransferProcessorState(IrsIrCameraHandle handle, void* buffer, size_t size, IrsImageTransferProcessorState *state) { + Result rc=0; + IrsCameraEntry *entry = NULL; + + if (!_irsIsAppletForeground()) return MAKERESULT(205, 160); + + rc = _irsCheckInternalStatus(handle); + if (R_FAILED(rc)) + return rc; + + rc = _irsCameraEntryGet(handle, &entry); + if (R_FAILED(rc)) + return rc; + + if (entry->mode != IrsIrSensorMode_ImageTransferProcessor) return MAKERESULT(205, 160); + + rc = _irsGetImageTransferProcessorState(handle, buffer, size, state); + + if (R_SUCCEEDED(rc) && entry->is_negative_image_used) { + if (entry->format >= sizeof(g_irsImageFormatSizes)/sizeof(g_irsImageFormatSizes[0])) + rc = MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen); + + if (R_SUCCEEDED(rc)) { + u8 *bufptr = (u8*)buffer; + size_t tmpsize = g_irsImageFormatSizes[entry->format]; + if (tmpsize > size) tmpsize = size; + + for (size_t i=0; ihandle = handle; + + if (g_irsFunctionLevel.ir_sensor_function_level >= 0x1 && (entry->mode != IrsIrSensorMode_None && entry->mode != IrsIrSensorMode_PointingProcessor)) { + rc = _irsSuspendImageProcessor(handle); + } + + if (R_SUCCEEDED(rc)) rc = _irsRunPointingProcessor(handle, &packed_config); + + if (R_SUCCEEDED(rc)) entry->mode = IrsIrSensorMode_PointingProcessor; + + return rc; +} + +Result irsGetPointingProcessorMarkerStates(IrsIrCameraHandle handle, IrsPointingProcessorMarkerState *states, s32 count, s32 *total_out) { + Result rc=0; + IrsCameraEntry *entry = NULL; + + rc = _irsCameraEntryGet(handle, &entry); + if (R_FAILED(rc)) + return rc; + + *total_out = 0; + // sdknso would fill states with default values here, but we won't do that. + + rc = MAKERESULT(205, 160); + if (!_irsIsAppletForeground()) return rc; + + Result rc2 = _irsCheckInternalStatus(handle); + if (R_FAILED(rc2)) + return rc2; + + if (entry->mode != IrsIrSensorMode_PointingProcessor) return rc; + IrsProcessorState *lifo = _irsGetRingLifo(handle, IrsIrSensorMode_PointingProcessor); + if (lifo==NULL) return rc; + rc = 0; + + *total_out = _irsRingLifoRead(lifo, states, count, NULL, NULL, sizeof(*states), 6); + + if (!*total_out) { + IrsIrCameraStatus status; + rc = irsGetIrCameraStatus(handle, &status); + if (R_SUCCEEDED(rc)) rc = g_irsCameraStatusResults[status]; // sdknso would verify that status is within bounds first, but that's redundant since irsGetIrCameraStatus() already does so. + } + + return rc; +} + +Result irsGetPointingProcessorStates(IrsIrCameraHandle handle, IrsPointingProcessorState *states, s32 count, s32 *total_out) { + Result rc=0; + IrsPointingProcessorMarkerState tmp_states[6]; + + *total_out = 0; + rc = irsGetPointingProcessorMarkerStates(handle, tmp_states, count, total_out); + + if (R_SUCCEEDED(rc)) { // sdknso doesn't check this, but we will. + for (s32 i=0; i<*total_out; i++) { + float pos_x = 0.0f, pos_y = 0.0f; + u32 poscount=0; + + states[i].sampling_number = tmp_states[i].sampling_number; + states[i].timestamp = tmp_states[i].timestamp; + + if (tmp_states[i].pointing_status0) { + pos_x+= tmp_states[i].position0_x; + pos_y+= tmp_states[i].position0_y; + poscount++; + } + if (tmp_states[i].pointing_status1) { + pos_x+= tmp_states[i].position1_x; + pos_y+= tmp_states[i].position1_y; + poscount++; + } + if (tmp_states[i].pointing_status2) { + pos_x+= tmp_states[i].position2_x; + pos_y+= tmp_states[i].position2_y; + poscount++; + } + + states[i].pointing_status = poscount < 3; + + if (!poscount) { + states[i].position_x = 0.0f; + states[i].position_y = 0.0f; + } + else { + states[i].position_x = (pos_x / (float)poscount / -160.0f) + 1.0f; + states[i].position_y = (pos_y / (float)poscount / 120.0f) + 1.0f; + } + } + } + + return rc; +} + +Result irsRunTeraPluginProcessor(IrsIrCameraHandle handle, const IrsTeraPluginProcessorConfig *config) { + Result rc=0; + IrsCameraEntry *entry = NULL; + IrsPackedTeraPluginProcessorConfig packed_config; + + memset(&packed_config, 0, sizeof(packed_config)); + + packed_config.required_mcu_version = g_irsRequiredMcuVersion; + packed_config.mode = config->mode; + + if (hosversionAtLeast(6,0,0)) { + packed_config.unk_x5 = 0x2 | (config->unk_x1 << 7); + packed_config.unk_x6 = config->unk_x2; + packed_config.unk_x7 = config->unk_x3; + } + + rc = _irsCameraEntryGet(handle, &entry); + if (R_FAILED(rc)) + return rc; + entry->handle = handle; + + if (g_irsFunctionLevel.ir_sensor_function_level >= 0x1 && entry->mode != IrsIrSensorMode_None) { + rc = _irsSuspendImageProcessor(handle); + } + + // sdknso would assert here when g_irsFunctionLevel.ir_sensor_function_level is >= {certain value} - but that can't happen since it's above the value set during init, so we won't impl that. + + if (R_SUCCEEDED(rc)) rc = _irsRunTeraPluginProcessor(handle, &packed_config); + + if (R_SUCCEEDED(rc)) entry->mode = IrsIrSensorMode_TeraPluginProcessor; + + return rc; +} + +static bool _irsValidateTeraPluginProcessorState(void* userdata, void* arg) { + IrsTeraFilterArg *filter = (IrsTeraFilterArg*)userdata; + IrsTeraPluginProcessorState *state = arg; + + // sdknso would call a parsing func here, but the output from it is unused so we won't impl that. + + for (u32 i=0; iprefix_bitcount; i++) { + u8 data = state->plugin_data[i>>3] >> (i & 0x7); + u8 prefix = filter->prefix_data >> i; + if ((data & 1) != (prefix & 1)) return false; + } + + return state->sampling_number >= filter->sampling_number; +} + +Result irsGetTeraPluginProcessorStates(IrsIrCameraHandle handle, IrsTeraPluginProcessorState *states, s32 count, s64 sampling_number, u32 prefix_data, u32 prefix_bitcount, s32 *total_out) { + Result rc=0; + IrsCameraEntry *entry = NULL; + IrsTeraFilterArg userdata={.sampling_number = sampling_number, .prefix_data = prefix_data, .prefix_bitcount = prefix_bitcount}; + + rc = _irsCameraEntryGet(handle, &entry); + if (R_FAILED(rc)) + return rc; + + *total_out = 0; + // sdknso would fill states with default values here, but we won't do that. + + rc = MAKERESULT(205, 160); + if (!_irsIsAppletForeground()) return rc; + + Result rc2 = _irsCheckInternalStatus(handle); + if (R_FAILED(rc2)) + return rc2; + + if (entry->mode != IrsIrSensorMode_TeraPluginProcessor) return rc; + IrsProcessorState *lifo = _irsGetRingLifo(handle, IrsIrSensorMode_TeraPluginProcessor); + if (lifo==NULL) return rc; + rc = 0; + + *total_out = _irsRingLifoRead(lifo, states, count, _irsValidateTeraPluginProcessorState, &userdata, sizeof(*states), 5); + + if (!*total_out) { + IrsIrCameraStatus status; + rc = irsGetIrCameraStatus(handle, &status); + if (R_SUCCEEDED(rc)) rc = g_irsCameraStatusResults[status]; // sdknso would verify that status is within bounds first, but that's redundant since irsGetIrCameraStatus() already does so. + } + + return rc; +} + +Result irsRunIrLedProcessor(IrsIrCameraHandle handle, const IrsIrLedProcessorConfig *config) { + Result rc=0; + IrsCameraEntry *entry = NULL; + IrsPackedIrLedProcessorConfig packed_config; + + memset(&packed_config, 0, sizeof(packed_config)); + + packed_config.required_mcu_version = g_irsRequiredMcuVersion; + packed_config.light_target = config->light_target; + + rc = _irsCameraEntryGet(handle, &entry); + if (R_FAILED(rc)) + return rc; + entry->handle = handle; + + if (g_irsFunctionLevel.ir_sensor_function_level >= 0x1 && (entry->mode != IrsIrSensorMode_None && entry->mode != IrsIrSensorMode_IrLedProcessor)) { + rc = _irsSuspendImageProcessor(handle); + } + + if (R_SUCCEEDED(rc)) rc = _irsRunIrLedProcessor(handle, &packed_config); + + if (R_SUCCEEDED(rc)) entry->mode = IrsIrSensorMode_IrLedProcessor; + + return rc; +} + +Result irsRunAdaptiveClusteringProcessor(IrsIrCameraHandle handle, const IrsAdaptiveClusteringProcessorConfig *config) { + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + IrsTeraPluginProcessorConfig tmp_config={0}; + + tmp_config.mode = config->mode == 1 ? 0x10 : 0xf; + + if (hosversionAtLeast(6,0,0)) { + IrsAdaptiveClusteringTargetDistance tmp = config->target_distance; + // sdknso would set some tmp_config fields to 0 again in some cases, but we won't do that. + if (tmp == IrsAdaptiveClusteringTargetDistance_Middle) { + tmp_config.unk_x1 = 0x1; + tmp_config.unk_x2 = 0x3; + } + else if (tmp == IrsAdaptiveClusteringTargetDistance_Far) { + tmp_config.unk_x1 = 0x1; + tmp_config.unk_x2 = 0x8; + } + else if (tmp != IrsAdaptiveClusteringTargetDistance_Near) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + } + + return irsRunTeraPluginProcessor(handle, &tmp_config); +} + +// sdknso DecodeMarkerDetectionState (which is called by GetAdaptiveClusteringProcessorStates) uses nerd_gillette_internal* functionality. + +Result irsRunHandAnalysis(IrsIrCameraHandle handle, const IrsHandAnalysisConfig *config) { + IrsTeraPluginProcessorConfig tmp_config={0}; + + u32 mode = config->mode; + + if (mode < IrsHandAnalysisMode_Silhouette || mode > IrsHandAnalysisMode_SilhouetteOnly) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + if (hosversionBefore(4,0,0)) { + if (mode == IrsHandAnalysisMode_SilhouetteOnly) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + tmp_config.mode = mode-0x1; + } + else { + tmp_config.mode = mode != IrsHandAnalysisMode_SilhouetteOnly ? mode+0x4 : 0xa; + } + + return irsRunTeraPluginProcessor(handle, &tmp_config); +} + +// The remaining HandAnalysis funcs in sdknso uses nerd_gillette_internal* functionality. + +void irsGetMomentProcessorDefaultConfig(IrsMomentProcessorConfig *config) { + memset(config, 0, sizeof(*config)); + + config->exposure_time = 300000; + config->gain = 8; + config->window_of_interest.width = 320; + config->window_of_interest.height = 240; + config->preprocess = 1; + config->preprocess_intensity_threshold = 0x50; +} + +void irsGetClusteringProcessorDefaultConfig(IrsClusteringProcessorConfig *config) { + memset(config, 0, sizeof(*config)); + + config->exposure_time = 200000; + config->gain = 2; + config->window_of_interest.width = 320; + config->window_of_interest.height = 240; + config->object_pixel_count_min = 0x3; + config->object_pixel_count_max = 0x12C00; + config->object_intensity_min = 150; + config->is_external_light_filter_enabled = 1; +} + +void irsGetDefaultImageTransferProcessorConfig(IrsImageTransferProcessorConfig *config) { + memset(config, 0, sizeof(*config)); + + config->exposure_time = 300000; + config->gain = 8; +} + +void irsGetDefaultImageTransferProcessorExConfig(IrsImageTransferProcessorExConfig *config) { + memset(config, 0, sizeof(*config)); + + config->exposure_time = 300000; + config->gain = 8; +} +