commit 1f4eb18aa832630ee88951cad1d50d259c4228f2
parent 77c5abefb5dc141d451b3e2cb2613aefc1e9e15e
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Sat, 13 Jan 2024 19:23:39 +0100

feat: override camera resolution

Diffstat:
Mcommon/src/main/assets/lang/en_US.json | 22+++++++++-------------
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Camera.kt | 49++++++++++++++++++++++++++++++-------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/tweaks/CameraTweaks.kt | 45+++++++++++++++++----------------------------
Mmapper/src/main/kotlin/me/rhunk/snapenhance/mapper/ClassMapper.kt | 7+++----
Dmapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/ScCameraSettingsMapper.kt | 26--------------------------
5 files changed, 59 insertions(+), 90 deletions(-)

diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -544,21 +544,17 @@ "name": "Immersive Preview", "description": "Prevents Snapchat from Cropping the Camera preview\nThis might cause the camera to flicker on some devices" }, - "override_preview_resolution": { - "name": "Override Preview Resolution", - "description": "Overrides the Camera Preview Resolution" + "override_front_resolution": { + "name": "Override Front Resolution", + "description": "Overrides the camera resolution for the front camera" }, - "override_picture_resolution": { - "name": "Override Picture Resolution", - "description": "Overrides the picture resolution" + "override_back_resolution": { + "name": "Override Back Resolution", + "description": "Overrides the camera resolution for the back camera" }, - "custom_preview_resolution": { - "name": "Custom Preview Resolution", - "description": "Sets a custom camera preview resolution, width x height (e.g. 1920x1080).\nThe custom resolution must be supported by your device" - }, - "custom_picture_resolution": { - "name": "Custom Picture Resolution", - "description": "Sets a custom picture resolution, width x height (e.g. 1920x1080).\nThe custom resolution must be supported by your device" + "custom_resolution": { + "name": "Custom Resolution", + "description": "Sets a custom camera resolution, width x height (e.g. 1920x1080).\nThe custom resolution must be supported by your device" }, "custom_frame_rate": { "name": "Custom Frame Rate", diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Camera.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Camera.kt @@ -15,26 +15,37 @@ class Camera : ConfigContainer() { private val defaultResolutions = listOf("3264x2448", "3264x1840", "3264x1504", "2688x1512", "2560x1920", "2448x2448", "2340x1080", "2160x1080", "1920x1440", "1920x1080", "1600x1200", "1600x960", "1600x900", "1600x736", "1600x720", "1560x720", "1520x720", "1440x1080", "1440x720", "1280x720", "1080x1080", "1080x720", "960x720", "720x720", "720x480", "640x480", "352x288", "320x240", "176x144").toTypedArray() } - private lateinit var _overridePreviewResolution: PropertyValue<String> - private lateinit var _overridePictureResolution: PropertyValue<String> + private lateinit var _overrideFrontResolution: PropertyValue<String> + private lateinit var _overrideBackResolution: PropertyValue<String> override fun lateInit(context: Context) { - val resolutions = runCatching { - if (context.packageName == Constants.SNAPCHAT_PACKAGE_NAME) return@runCatching null // prevent snapchat from crashing - context.getSystemService(CameraManager::class.java).run { - cameraIdList.flatMap { cameraId -> - getCameraCharacteristics(cameraId).get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)?.let { - it.outputFormats.flatMap { format -> it.getOutputSizes(format).toList() } - } ?: emptyList() - }.sortedByDescending { it.width * it.height }.map { "${it.width}x${it.height}" }.distinct().toTypedArray() + val backResolutions = mutableListOf<String>() + val frontResolutions = mutableListOf<String>() + + context.getSystemService(CameraManager::class.java).apply { + if (context.packageName == Constants.SNAPCHAT_PACKAGE_NAME) return@apply // prevent snapchat from crashing + + runCatching { + cameraIdList.forEach { cameraId -> + val characteristics = getCameraCharacteristics(cameraId) + val isSelfie = characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT + + (frontResolutions.takeIf { isSelfie } ?: backResolutions).addAll( + characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)?.let { + it.outputFormats.flatMap { format -> it.getOutputSizes(format).toList() } + }?.sortedByDescending { it.width * it.height }?.map { "${it.width}x${it.height}" }?.distinct() ?: emptyList() + ) + } + }.onFailure { + AbstractLogger.directError("Failed to get camera resolutions", it) + backResolutions.addAll(defaultResolutions) + frontResolutions.addAll(defaultResolutions) } - }.onFailure { - AbstractLogger.directError("Failed to get camera resolutions", it) - }.getOrNull() ?: defaultResolutions + } - _overridePreviewResolution = unique("override_preview_resolution", *resolutions) + _overrideFrontResolution = unique("override_front_resolution", *frontResolutions.toTypedArray()) { addFlags(ConfigFlag.NO_TRANSLATE) } - _overridePictureResolution = unique("override_picture_resolution", *resolutions) + _overrideBackResolution = unique("override_back_resolution", *backResolutions.toTypedArray()) { addFlags(ConfigFlag.NO_TRANSLATE) } } @@ -46,8 +57,8 @@ class Camera : ConfigContainer() { ) { addNotices(FeatureNotice.UNSTABLE); addFlags(ConfigFlag.NO_TRANSLATE) } val hevcRecording = boolean("hevc_recording") { requireRestart(); addNotices(FeatureNotice.UNSTABLE) } val forceCameraSourceEncoding = boolean("force_camera_source_encoding") - val overridePreviewResolution get() = _overridePreviewResolution - val overridePictureResolution get() = _overridePictureResolution - val customPreviewResolution = string("custom_preview_resolution") { addNotices(FeatureNotice.UNSTABLE); inputCheck = { it.matches(Regex("\\d+x\\d+")) } } - val customPictureResolution = string("custom_picture_resolution") { addNotices(FeatureNotice.UNSTABLE); inputCheck = { it.matches(Regex("\\d+x\\d+")) } } + val overrideFrontResolution get() = _overrideFrontResolution + val overrideBackResolution get() = _overrideBackResolution + + val customResolution = string("custom_resolution") { addNotices(FeatureNotice.UNSTABLE); inputCheck = { it.matches(Regex("\\d+x\\d+")) } } } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/tweaks/CameraTweaks.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/tweaks/CameraTweaks.kt @@ -9,15 +9,13 @@ import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraCharacteristics.Key import android.hardware.camera2.CameraManager import android.media.Image +import android.media.ImageReader import android.util.Range import me.rhunk.snapenhance.core.features.Feature import me.rhunk.snapenhance.core.features.FeatureLoadParams import me.rhunk.snapenhance.core.util.hook.HookStage import me.rhunk.snapenhance.core.util.hook.hook -import me.rhunk.snapenhance.core.util.hook.hookConstructor import me.rhunk.snapenhance.core.util.ktx.setObjectField -import me.rhunk.snapenhance.core.wrapper.impl.ScSize -import me.rhunk.snapenhance.mapper.impl.ScCameraSettingsMapper import java.io.ByteArrayOutputStream import java.nio.ByteBuffer @@ -38,15 +36,25 @@ class CameraTweaks : Feature("Camera Tweaks", loadParams = FeatureLoadParams.ACT } } - CameraManager::class.java.hook("openCamera", HookStage.BEFORE) { param -> + } + + var isLastCameraFront = false + + CameraManager::class.java.hook("openCamera", HookStage.BEFORE) { param -> + if (config.disable.get()) { param.setResult(null) + return@hook } + val cameraManager = param.thisObject() as? CameraManager ?: return@hook + isLastCameraFront = cameraManager.getCameraCharacteristics(param.arg(0)).get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT } - val previewResolutionConfig = config.customPreviewResolution.getNullable()?.takeIf { it.isNotEmpty() }?.let { parseResolution(it) } - ?: config.overridePreviewResolution.getNullable()?.let { parseResolution(it) } - val captureResolutionConfig = config.customPictureResolution.getNullable()?.takeIf { it.isNotEmpty() }?.let { parseResolution(it) } - ?: config.overridePictureResolution.getNullable()?.let { parseResolution(it) } + ImageReader::class.java.hook("newInstance", HookStage.BEFORE) { param -> + val captureResolutionConfig = config.customResolution.getNullable()?.takeIf { it.isNotEmpty() }?.let { parseResolution(it) } + ?: (if (isLastCameraFront) config.overrideFrontResolution.getNullable() else config.overrideBackResolution.getNullable())?.let { parseResolution(it) } ?: return@hook + param.setArg(0, captureResolutionConfig[0]) + param.setArg(1, captureResolutionConfig[1]) + } config.customFrameRate.getNullable()?.also { value -> val customFrameRate = value.toInt() @@ -63,25 +71,6 @@ class CameraTweaks : Feature("Camera Tweaks", loadParams = FeatureLoadParams.ACT } } - context.mappings.useMapper(ScCameraSettingsMapper::class) { - classReference.get()?.hookConstructor(HookStage.BEFORE) { param -> - val previewResolution = ScSize(param.argNullable(2)) - val captureResolution = ScSize(param.argNullable(3)) - - if (previewResolution.isPresent() && captureResolution.isPresent()) { - previewResolutionConfig?.let { - previewResolution.first = it[0] - previewResolution.second = it[1] - } - - captureResolutionConfig?.let { - captureResolution.first = it[0] - captureResolution.second = it[1] - } - } - } - } - if (config.blackPhotos.get()) { findClass("android.media.ImageReader\$SurfaceImage").hook("getPlanes", HookStage.AFTER) { param -> val image = param.thisObject() as? Image ?: return@hook @@ -91,7 +80,7 @@ class CameraTweaks : Feature("Camera Tweaks", loadParams = FeatureLoadParams.ACT compress(Bitmap.CompressFormat.JPEG, 100, output) recycle() } - planes.filterNotNull().forEach { plane -> + planes.filterNotNull().forEach { plane -> plane.setObjectField("mBuffer", ByteBuffer.wrap(output.toByteArray())) } } diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/ClassMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/ClassMapper.kt @@ -1,13 +1,13 @@ package me.rhunk.snapenhance.mapper +import com.android.tools.smali.dexlib2.Opcodes +import com.android.tools.smali.dexlib2.dexbacked.DexBackedDexFile +import com.android.tools.smali.dexlib2.iface.ClassDef import com.google.gson.JsonObject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import me.rhunk.snapenhance.mapper.impl.* -import com.android.tools.smali.dexlib2.Opcodes -import com.android.tools.smali.dexlib2.dexbacked.DexBackedDexFile -import com.android.tools.smali.dexlib2.iface.ClassDef import java.io.BufferedInputStream import java.io.InputStream import java.util.zip.ZipFile @@ -26,7 +26,6 @@ class ClassMapper( MediaQualityLevelProviderMapper(), OperaPageViewControllerMapper(), PlusSubscriptionMapper(), - ScCameraSettingsMapper(), StoryBoostStateMapper(), FriendsFeedEventDispatcherMapper(), CompositeConfigurationProviderMapper(), diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/ScCameraSettingsMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/ScCameraSettingsMapper.kt @@ -1,25 +0,0 @@ -package me.rhunk.snapenhance.mapper.impl - -import me.rhunk.snapenhance.mapper.AbstractClassMapper -import me.rhunk.snapenhance.mapper.ext.findConstString -import me.rhunk.snapenhance.mapper.ext.getClassName -import me.rhunk.snapenhance.mapper.ext.getStaticConstructor -import me.rhunk.snapenhance.mapper.ext.isEnum - -class ScCameraSettingsMapper : AbstractClassMapper("ScCameraSettings") { - val classReference = classReference("class") - - init { - mapper { - for (clazz in classes) { - val firstConstructor = clazz.directMethods.firstOrNull { it.name == "<init>" } ?: continue - if (firstConstructor.parameterTypes.size < 27) continue - val firstParameter = getClass(firstConstructor.parameterTypes[0]) ?: continue - if (!firstParameter.isEnum() || firstParameter.getStaticConstructor()?.implementation?.findConstString("CONTINUOUS_PICTURE") != true) continue - - classReference.set(clazz.getClassName()) - return@mapper - } - } - } -}- \ No newline at end of file