feat: camera settings override (#51)
* feat: camera tweaks * refact: force media source quality --------- Co-authored-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
"spying_privacy": "Spying & Privacy",
|
||||
"media_manager": "Media Manager",
|
||||
"ui_tweaks": "UI & Tweaks",
|
||||
"camera": "Camera",
|
||||
"updates": "Updates",
|
||||
"experimental_debugging": "Experimental"
|
||||
},
|
||||
@@ -41,7 +42,7 @@
|
||||
"snapchat_plus": "Snapchat Plus",
|
||||
"disable_snap_splitting": "Disable Snap Splitting",
|
||||
"disable_video_length_restriction": "Disable Video Length Restriction",
|
||||
"override_media_quality": "Override Media Quality",
|
||||
"force_media_source_quality": "Force Media Source Quality",
|
||||
"media_quality_level": "Media Quality Level",
|
||||
"remove_voice_record_button": "Remove Voice Record Button",
|
||||
"remove_stickers_button": "Remove Stickers Button",
|
||||
@@ -61,7 +62,11 @@
|
||||
"auto_updater": "Auto Updater",
|
||||
"infinite_story_boost": "Infinite Story Boost",
|
||||
"enable_app_appearance": "Enable App Appearance Settings",
|
||||
"disable_spotlight": "Disable Spotlight"
|
||||
"disable_spotlight": "Disable Spotlight",
|
||||
"preview_resolution": "Override Preview Resolution",
|
||||
"picture_resolution": "Override Picture Resolution",
|
||||
"force_highest_frame_rate": "Force Highest Frame Rate",
|
||||
"force_camera_source_encoding": "Force Camera Source Encoding"
|
||||
},
|
||||
|
||||
"option": {
|
||||
|
||||
@@ -7,5 +7,6 @@ enum class ConfigCategory(
|
||||
MEDIA_MANAGEMENT("category.media_manager"),
|
||||
UI_TWEAKS("category.ui_tweaks"),
|
||||
UPDATES("category.updates"),
|
||||
CAMERA("category.camera"),
|
||||
EXPERIMENTAL_DEBUGGING("category.experimental_debugging");
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import me.rhunk.snapenhance.config.impl.ConfigStateListValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateSelection
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStateValue
|
||||
import me.rhunk.snapenhance.config.impl.ConfigStringValue
|
||||
import me.rhunk.snapenhance.features.impl.tweaks.CameraTweaks
|
||||
import java.io.File
|
||||
|
||||
enum class ConfigProperty(
|
||||
@@ -13,7 +14,8 @@ enum class ConfigProperty(
|
||||
val descriptionKey: String,
|
||||
val category: ConfigCategory,
|
||||
val valueContainer: ConfigValue<*>,
|
||||
val shouldAppearInSettings: Boolean = true
|
||||
val shouldAppearInSettings: Boolean = true,
|
||||
val disableValueLocalization: Boolean = false
|
||||
) {
|
||||
|
||||
//SPYING AND PRIVACY
|
||||
@@ -166,22 +168,13 @@ enum class ConfigProperty(
|
||||
ConfigCategory.MEDIA_MANAGEMENT,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
|
||||
OVERRIDE_MEDIA_QUALITY(
|
||||
"property.override_media_quality",
|
||||
"description.override_media_quality",
|
||||
|
||||
FORCE_MEDIA_SOURCE_QUALITY(
|
||||
"property.force_media_source_quality",
|
||||
"description.force_media_source_quality",
|
||||
ConfigCategory.MEDIA_MANAGEMENT,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
MEDIA_QUALITY_LEVEL(
|
||||
"property.media_quality_level",
|
||||
"description.media_quality_level",
|
||||
ConfigCategory.MEDIA_MANAGEMENT,
|
||||
ConfigStateSelection(
|
||||
listOf("LEVEL_NONE", "LEVEL_1", "LEVEL_2", "LEVEL_3", "LEVEL_4", "LEVEL_5", "LEVEL_6", "LEVEL_7", "LEVEL_MAX"),
|
||||
"LEVEL_NONE"
|
||||
)
|
||||
),
|
||||
|
||||
//UI AND TWEAKS
|
||||
HIDE_UI_ELEMENTS(
|
||||
@@ -272,6 +265,40 @@ enum class ConfigProperty(
|
||||
),
|
||||
|
||||
|
||||
//CAMERA
|
||||
OVERRIDE_PREVIEW_RESOLUTION(
|
||||
"property.preview_resolution",
|
||||
"description.preview_resolution",
|
||||
ConfigCategory.CAMERA,
|
||||
ConfigStateSelection(
|
||||
CameraTweaks.resolutions,
|
||||
"OFF"
|
||||
),
|
||||
disableValueLocalization = true
|
||||
),
|
||||
OVERRIDE_PICTURE_RESOLUTION(
|
||||
"property.picture_resolution",
|
||||
"description.picture_resolution",
|
||||
ConfigCategory.CAMERA,
|
||||
ConfigStateSelection(
|
||||
CameraTweaks.resolutions,
|
||||
"OFF"
|
||||
),
|
||||
disableValueLocalization = true
|
||||
),
|
||||
FORCE_HIGHEST_FRAME_RATE(
|
||||
"property.force_highest_frame_rate",
|
||||
"description.force_highest_frame_rate",
|
||||
ConfigCategory.CAMERA,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
FORCE_CAMERA_SOURCE_ENCODING(
|
||||
"property.force_camera_source_encoding",
|
||||
"description.force_camera_source_encoding",
|
||||
ConfigCategory.CAMERA,
|
||||
ConfigStateValue(false)
|
||||
),
|
||||
|
||||
// UPDATES
|
||||
AUTO_UPDATER(
|
||||
"property.auto_updater",
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package me.rhunk.snapenhance.data.wrapper.impl
|
||||
|
||||
import me.rhunk.snapenhance.data.wrapper.AbstractWrapper
|
||||
|
||||
class ScSize(
|
||||
obj: Any?
|
||||
) : AbstractWrapper(obj) {
|
||||
private val firstField by lazy {
|
||||
instanceNonNull().javaClass.declaredFields.first { it.type == Int::class.javaPrimitiveType }.also { it.isAccessible = true }
|
||||
}
|
||||
|
||||
private val secondField by lazy {
|
||||
instanceNonNull().javaClass.declaredFields.last { it.type == Int::class.javaPrimitiveType }.also { it.isAccessible = true }
|
||||
}
|
||||
|
||||
|
||||
var first: Int get() = firstField.getInt(instanceNonNull())
|
||||
set(value) {
|
||||
firstField.setInt(instanceNonNull(), value)
|
||||
}
|
||||
|
||||
var second: Int get() = secondField.getInt(instanceNonNull())
|
||||
set(value) {
|
||||
secondField.setInt(instanceNonNull(), value)
|
||||
}
|
||||
}
|
||||
@@ -6,31 +6,48 @@ import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.hook
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.setObjectField
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Modifier
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class ConfigEnumKeys : Feature("Config enum keys", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||
private fun hookAllEnums(enumClass: Class<*>, callback: (String, (Any) -> Unit) -> Unit) {
|
||||
//Enum(String, int, ?)
|
||||
//or Enum(?)
|
||||
val enumDataClass = enumClass.constructors[0].parameterTypes.first { clazz: Class<*> -> clazz != String::class.java && !clazz.isPrimitive }
|
||||
|
||||
//get the field which contains the enum data class
|
||||
val enumDataField = enumClass.declaredFields.first { field: Field -> field.type == enumDataClass }
|
||||
data class HookEnumContext(
|
||||
val key: String,
|
||||
val type: Type?,
|
||||
val value: Any?,
|
||||
val set: (Any) -> Unit
|
||||
)
|
||||
|
||||
//get the field value of the enum data class (the first field of the class with the desc Object)
|
||||
val objectDataField = enumDataField.type.fields.first { field: Field ->
|
||||
field.type == Any::class.java && Modifier.isPublic(
|
||||
field.modifiers
|
||||
) && Modifier.isFinal(field.modifiers)
|
||||
}
|
||||
companion object {
|
||||
fun hookAllEnums(enumClass: Class<*>, callback: HookEnumContext.() -> Unit) {
|
||||
//Enum(String, int, ?)
|
||||
//or Enum(?)
|
||||
val enumDataClass = enumClass.constructors[0].parameterTypes.first { clazz: Class<*> -> clazz != String::class.java && !clazz.isPrimitive }
|
||||
|
||||
enumClass.enumConstants.forEach { enum ->
|
||||
enumDataField.get(enum)?.let { enumData ->
|
||||
val key = enum.toString()
|
||||
callback(key) { newValue ->
|
||||
enumData.setObjectField(objectDataField.name, newValue)
|
||||
//get the field which contains the enum data class
|
||||
val enumDataField = enumClass.declaredFields.first { field: Field -> field.type == enumDataClass }
|
||||
|
||||
val typeField = enumDataClass.declaredFields.first { field: Field -> field.type == Type::class.java }
|
||||
|
||||
//get the field value of the enum data class (the first field of the class with the desc Object)
|
||||
val objectDataField = enumDataField.type.fields.first { field: Field ->
|
||||
field.type == Any::class.java && Modifier.isPublic(
|
||||
field.modifiers
|
||||
) && Modifier.isFinal(field.modifiers)
|
||||
}
|
||||
|
||||
enumClass.enumConstants.forEach { enum ->
|
||||
enumDataField.get(enum)?.let { enumData ->
|
||||
val key = enum.toString()
|
||||
val type = typeField.get(enumData) as Type?
|
||||
val value = enumData.getObjectField(objectDataField.name)
|
||||
val set = { newValue: Any ->
|
||||
enumData.setObjectField(objectDataField.name, newValue)
|
||||
}
|
||||
callback(HookEnumContext(key, type, value, set))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,25 +56,25 @@ class ConfigEnumKeys : Feature("Config enum keys", loadParams = FeatureLoadParam
|
||||
@SuppressLint("PrivateApi")
|
||||
override fun onActivityCreate() {
|
||||
if (context.config.bool(ConfigProperty.NEW_MAP_UI)) {
|
||||
hookAllEnums(context.mappings.getMappedClass("enums", "PLUS")) { key, set ->
|
||||
hookAllEnums(context.mappings.getMappedClass("enums", "PLUS")) {
|
||||
if (key == "REDUCE_MY_PROFILE_UI_COMPLEXITY") set(true)
|
||||
}
|
||||
}
|
||||
|
||||
hookAllEnums(context.mappings.getMappedClass("enums", "ARROYO")) { key, set ->
|
||||
hookAllEnums(context.mappings.getMappedClass("enums", "ARROYO")) {
|
||||
if (key == "ENABLE_LONG_SNAP_SENDING") {
|
||||
if (context.config.bool(ConfigProperty.DISABLE_SNAP_SPLITTING)) set(true)
|
||||
}
|
||||
}
|
||||
|
||||
if (context.config.bool(ConfigProperty.STREAK_EXPIRATION_INFO)) {
|
||||
hookAllEnums(context.mappings.getMappedClass("enums", "FRIENDS_FEED")) { key, set ->
|
||||
hookAllEnums(context.mappings.getMappedClass("enums", "FRIENDS_FEED")) {
|
||||
if (key == "STREAK_EXPIRATION_INFO") set(true)
|
||||
}
|
||||
}
|
||||
|
||||
if (context.config.bool(ConfigProperty.BLOCK_ADS)) {
|
||||
hookAllEnums(context.mappings.getMappedClass("enums", "SNAPADS")) { key, set ->
|
||||
hookAllEnums(context.mappings.getMappedClass("enums", "SNAPADS")) {
|
||||
if (key == "BYPASS_AD_FEATURE_GATE") {
|
||||
set(true)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package me.rhunk.snapenhance.features.impl.tweaks
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import me.rhunk.snapenhance.config.ConfigProperty
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.ScSize
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.features.impl.ConfigEnumKeys
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.hookConstructor
|
||||
|
||||
class CameraTweaks : Feature("Camera Tweaks", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||
companion object {
|
||||
val resolutions = listOf("OFF", "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")
|
||||
}
|
||||
|
||||
private fun parseResolution(resolution: String): IntArray? {
|
||||
return resolution.takeIf { resolution != "OFF" }?.split("x")?.map { it.toInt() }?.toIntArray()
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission", "DiscouragedApi")
|
||||
override fun onActivityCreate() {
|
||||
ConfigEnumKeys.hookAllEnums(context.mappings.getMappedClass("enums", "CAMERA")) {
|
||||
if (key == "FORCE_CAMERA_HIGHEST_FPS" && context.config.bool(ConfigProperty.FORCE_HIGHEST_FRAME_RATE)) {
|
||||
set(true)
|
||||
}
|
||||
if (key == "MEDIA_RECORDER_MAX_QUALITY_LEVEL" && context.config.bool(ConfigProperty.FORCE_CAMERA_SOURCE_ENCODING)) {
|
||||
value!!.javaClass.enumConstants?.let { enumData -> set(enumData.filter { it.toString() == "LEVEL_MAX" }) }
|
||||
}
|
||||
}
|
||||
|
||||
val previewResolutionConfig = parseResolution(context.config.state(ConfigProperty.OVERRIDE_PREVIEW_RESOLUTION))
|
||||
val captureResolutionConfig = parseResolution(context.config.state(ConfigProperty.OVERRIDE_PICTURE_RESOLUTION))
|
||||
|
||||
context.mappings.getMappedClass("ScCameraSettings").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]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,14 +13,9 @@ class MediaQualityLevelOverride : Feature("MediaQualityLevelOverride", loadParam
|
||||
Hooker.hook(context.mappings.getMappedClass("MediaQualityLevelProvider"),
|
||||
context.mappings.getMappedValue("MediaQualityLevelProviderMethod"),
|
||||
HookStage.BEFORE,
|
||||
{context.config.bool(ConfigProperty.OVERRIDE_MEDIA_QUALITY)}
|
||||
{ context.config.bool(ConfigProperty.FORCE_MEDIA_SOURCE_QUALITY) }
|
||||
) { param ->
|
||||
val currentCompressionLevel = enumQualityLevel.enumConstants
|
||||
.firstOrNull { it.toString() == context.config.state(ConfigProperty.MEDIA_QUALITY_LEVEL)} ?: run {
|
||||
context.longToast("Invalid media quality level: ${context.config.state(ConfigProperty.MEDIA_QUALITY_LEVEL)}")
|
||||
return@hook
|
||||
}
|
||||
param.setResult(currentCompressionLevel)
|
||||
param.setResult(enumQualityLevel.enumConstants.firstOrNull { it.toString() == "LEVEL_MAX" } )
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,17 +36,29 @@ class SettingsMenu : AbstractMenu() {
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun createPropertyView(viewModel: View, property: ConfigProperty): View {
|
||||
val propertyName = context.translation.get(property.nameKey)
|
||||
val updateButtonText: (TextView, String) -> Unit = { textView, text ->
|
||||
textView.text = "${context.translation.get(property.nameKey)}${if (text.isEmpty()) "" else ": $text"}"
|
||||
textView.text = "$propertyName${if (text.isEmpty()) "" else ": $text"}"
|
||||
}
|
||||
|
||||
val updateLocalizedText: (TextView, String) -> Unit = { textView, value ->
|
||||
updateButtonText(textView, value.let { if (it.isEmpty()) "(empty)" else context.translation.get("option." + property.nameKey + "." + it) })
|
||||
updateButtonText(textView, value.let {
|
||||
if (it.isEmpty()) {
|
||||
"(empty)"
|
||||
}
|
||||
else {
|
||||
if (property.disableValueLocalization) {
|
||||
it
|
||||
} else {
|
||||
context.translation.get("option." + property.nameKey + "." + it)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val textEditor: ((String) -> Unit) -> Unit = { updateValue ->
|
||||
val builder = AlertDialog.Builder(viewModel.context)
|
||||
builder.setTitle(context.translation.get(property.nameKey))
|
||||
builder.setTitle(propertyName)
|
||||
|
||||
val input = EditText(viewModel.context)
|
||||
input.inputType = InputType.TYPE_CLASS_TEXT
|
||||
@@ -98,7 +110,7 @@ class SettingsMenu : AbstractMenu() {
|
||||
}
|
||||
is ConfigStateValue -> {
|
||||
val switch = Switch(viewModel.context)
|
||||
switch.text = context.translation.get(property.nameKey)
|
||||
switch.text = propertyName
|
||||
switch.isChecked = property.valueContainer.value()
|
||||
switch.setOnCheckedChangeListener { _, isChecked ->
|
||||
property.valueContainer.writeFrom(isChecked.toString())
|
||||
@@ -112,10 +124,13 @@ class SettingsMenu : AbstractMenu() {
|
||||
|
||||
button.setOnClickListener {_ ->
|
||||
val builder = AlertDialog.Builder(viewModel.context)
|
||||
builder.setTitle(context.translation.get(property.nameKey))
|
||||
builder.setTitle(propertyName)
|
||||
|
||||
builder.setSingleChoiceItems(
|
||||
property.valueContainer.keys().toTypedArray().map { context.translation.get("option." + property.nameKey + "." + it) }.toTypedArray(),
|
||||
property.valueContainer.keys().toTypedArray().map {
|
||||
if (property.disableValueLocalization) it
|
||||
else context.translation.get("option." + property.nameKey + "." + it)
|
||||
}.toTypedArray(),
|
||||
property.valueContainer.keys().indexOf(property.valueContainer.value())
|
||||
) { _, which ->
|
||||
property.valueContainer.writeFrom(property.valueContainer.keys()[which])
|
||||
@@ -136,12 +151,15 @@ class SettingsMenu : AbstractMenu() {
|
||||
|
||||
button.setOnClickListener {_ ->
|
||||
val builder = AlertDialog.Builder(viewModel.context)
|
||||
builder.setTitle(context.translation.get(property.nameKey))
|
||||
builder.setTitle(propertyName)
|
||||
|
||||
val sortedStates = property.valueContainer.value().toSortedMap()
|
||||
|
||||
builder.setMultiChoiceItems(
|
||||
sortedStates.toSortedMap().map { context.translation.get("option." + property.nameKey + "." +it.key) }.toTypedArray(),
|
||||
sortedStates.toSortedMap().map {
|
||||
if (property.disableValueLocalization) it.key
|
||||
else context.translation.get("option." + property.nameKey + "." + it.key)
|
||||
}.toTypedArray(),
|
||||
sortedStates.map { it.value }.toBooleanArray()
|
||||
) { _, which, isChecked ->
|
||||
sortedStates.keys.toList()[which].let { key ->
|
||||
|
||||
@@ -27,6 +27,7 @@ import me.rhunk.snapenhance.features.impl.spying.AnonymousStoryViewing
|
||||
import me.rhunk.snapenhance.features.impl.spying.MessageLogger
|
||||
import me.rhunk.snapenhance.features.impl.spying.PreventReadReceipts
|
||||
import me.rhunk.snapenhance.features.impl.spying.StealthMode
|
||||
import me.rhunk.snapenhance.features.impl.tweaks.CameraTweaks
|
||||
import me.rhunk.snapenhance.features.impl.ui.UITweaks
|
||||
import me.rhunk.snapenhance.features.impl.ui.menus.MenuViewInjector
|
||||
import me.rhunk.snapenhance.manager.Manager
|
||||
@@ -78,6 +79,7 @@ class FeatureManager(private val context: ModContext) : Manager {
|
||||
register(AppPasscode::class)
|
||||
register(LocationSpoofer::class)
|
||||
register(AutoUpdater::class)
|
||||
register(CameraTweaks::class)
|
||||
register(InfiniteStoryBoost::class)
|
||||
|
||||
initializeFeatures()
|
||||
|
||||
@@ -16,15 +16,17 @@ import me.rhunk.snapenhance.manager.Manager
|
||||
import me.rhunk.snapenhance.mapping.Mapper
|
||||
import me.rhunk.snapenhance.mapping.impl.BCryptClassMapper
|
||||
import me.rhunk.snapenhance.mapping.impl.CallbackMapper
|
||||
import me.rhunk.snapenhance.mapping.impl.DefaultMediaItemMapper
|
||||
import me.rhunk.snapenhance.mapping.impl.EnumMapper
|
||||
import me.rhunk.snapenhance.mapping.impl.DefaultMediaItemMapper
|
||||
import me.rhunk.snapenhance.mapping.impl.OperaPageViewControllerMapper
|
||||
import me.rhunk.snapenhance.mapping.impl.PlatformAnalyticsCreatorMapper
|
||||
import me.rhunk.snapenhance.mapping.impl.PlusSubscriptionMapper
|
||||
import me.rhunk.snapenhance.mapping.impl.ScCameraSettingsMapper
|
||||
import me.rhunk.snapenhance.mapping.impl.StoryBoostStateMapper
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class MappingManager(private val context: ModContext) : Manager {
|
||||
@@ -36,6 +38,7 @@ class MappingManager(private val context: ModContext) : Manager {
|
||||
add(DefaultMediaItemMapper())
|
||||
add(BCryptClassMapper())
|
||||
add(PlatformAnalyticsCreatorMapper())
|
||||
add(ScCameraSettingsMapper())
|
||||
add(StoryBoostStateMapper())
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package me.rhunk.snapenhance.mapping.impl
|
||||
|
||||
import me.rhunk.snapenhance.mapping.Mapper
|
||||
|
||||
class ScCameraSettingsMapper : Mapper() {
|
||||
override fun useClasses(
|
||||
classLoader: ClassLoader,
|
||||
classes: List<Class<*>>,
|
||||
mappings: MutableMap<String, Any>
|
||||
) {
|
||||
for (clazz in classes) {
|
||||
if (clazz.constructors.isEmpty()) continue
|
||||
val parameters = clazz.constructors.first().parameterTypes
|
||||
if (parameters.size < 27) continue
|
||||
val firstParameter = parameters[0]
|
||||
if (!firstParameter.isEnum || firstParameter.enumConstants.find { it.toString() == "CONTINUOUS_PICTURE" } == null) continue
|
||||
mappings["ScCameraSettings"] = clazz.name
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user