feat: override camera resolution

This commit is contained in:
rhunk
2024-01-13 19:23:39 +01:00
parent 77c5abefb5
commit 1f4eb18aa8
5 changed files with 62 additions and 92 deletions

View File

@@ -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",

View File

@@ -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()
}
}.onFailure {
AbstractLogger.directError("Failed to get camera resolutions", it)
}.getOrNull() ?: defaultResolutions
val backResolutions = mutableListOf<String>()
val frontResolutions = mutableListOf<String>()
_overridePreviewResolution = unique("override_preview_resolution", *resolutions)
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)
}
}
_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+")) } }
}

View File

@@ -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 ->
param.setResult(null)
}
}
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) }
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
}
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()))
}
}

View File

@@ -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(),

View File

@@ -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
}
}
}
}