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:
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