feat: override camera resolution
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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+")) } }
|
||||
}
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user