diff --git a/.github/workflows/app.yaml b/.github/workflows/app.yaml index 7a213b4..efa20e2 100644 --- a/.github/workflows/app.yaml +++ b/.github/workflows/app.yaml @@ -2,9 +2,6 @@ name: App on: push: - branches: [ master ] - paths-ignore: - - '.github/ISSUE_TEMPLATE' workflow_dispatch: jobs: @@ -39,7 +36,7 @@ jobs: - name: Write temporary key if: hashFiles('key.jks') == '' && !steps.vars.outputs.HAS_SECRET run: | - keytool -genkey -alias a -dname CN=_ -storepass passwd -keypass passwd -keystore key.jks + keytool -genkey -alias a -dname CN=_ -storepass passwd -keypass passwd -keystore key.jks -keyalg RSA echo KEYSTORE_PASSWORD=passwd >> signing.properties echo KEYSTORE_ALIAS=a >> signing.properties echo KEYSTORE_ALIAS_PASSWORD=passwd >> signing.properties diff --git a/app/src/main/assets/mod.zip b/app/src/main/assets/mod.zip index 5225bf4..73310b8 100644 Binary files a/app/src/main/assets/mod.zip and b/app/src/main/assets/mod.zip differ diff --git a/app/src/main/java/com/itosfish/colorfeatureenhance/MainActivity.kt b/app/src/main/java/com/itosfish/colorfeatureenhance/MainActivity.kt index eea4695..3b3db6f 100644 --- a/app/src/main/java/com/itosfish/colorfeatureenhance/MainActivity.kt +++ b/app/src/main/java/com/itosfish/colorfeatureenhance/MainActivity.kt @@ -97,18 +97,8 @@ class MainActivity : ComponentActivity() { private fun initializeApp() { CSU.checkRoot() - // 如果检测到 Overlayfs,则提示不支持并跳过安装 - if (CSU.isOverlayfs()) { - MaterialAlertDialogBuilder(this) - .setTitle(R.string.ksu_not_supported_title) - .setMessage(R.string.ksu_not_supported_message) - .setPositiveButton(R.string.common_ok) { dialog, _ -> dialog.dismiss() } - .show() - return - } - // 检查并安装模块 - if (!ConfigUtils.isModuleInstalled()) { + if (ConfigUtils.shouldInstallModule()) { val installSuccess = ConfigUtils.installModule() if (installSuccess) { Toast.makeText( diff --git a/app/src/main/java/com/itosfish/colorfeatureenhance/utils/CSU.kt b/app/src/main/java/com/itosfish/colorfeatureenhance/utils/CSU.kt index 02040c4..886b62f 100644 --- a/app/src/main/java/com/itosfish/colorfeatureenhance/utils/CSU.kt +++ b/app/src/main/java/com/itosfish/colorfeatureenhance/utils/CSU.kt @@ -7,6 +7,7 @@ import com.itosfish.colorfeatureenhance.R import java.io.BufferedReader import java.io.DataOutputStream import java.io.InputStreamReader +import kotlin.system.exitProcess data class ShellResult( val output: String, @@ -14,6 +15,64 @@ data class ShellResult( ) object CSU { + const val NONE = -1 + const val MAGISK = 0 + const val KSU = 1 + const val APATCH = 2 + + var suType: Int = NONE + private set + + /** + * 安装模块。 + * + * @param modulePath 模块的路径。 + * @return 如果安装成功则返回 true,否则返回 false。 + */ + fun installModule( + modulePath: String + ): Boolean { + // 构建 Shell 命令: + // 根据不同 Root 实现选择不同 ci 命令 + val command = when (suType) { + MAGISK -> { + // 使用 Magisk 安装模块的逻辑 + "magisk --install-module $modulePath" + } + KSU -> { + // 使用 KSU 安装模块的逻辑 + "ksud module install $modulePath" + } + APATCH -> { + // 使用 APatch 安装模块的逻辑 + "apd module install $modulePath" + } + else -> return false + } + + // 使用 runWithSu 执行命令并获取返回值 + // 根据返回值判断模块安装是否成功 + runWithSu(command).let { result -> + return result.exitCode == 0 + } + } + + /** + * 判断指定命令是否存在。 + * + * @param command 要检查的命令。 + * @return 如果文件存在则返回 true,否则返回 false。 + */ + fun which(command: String): Boolean { + // 构建 Shell 命令: + // 使用 which 来检查命令是否存在 + val command = "which $command" + + // 使用 runWithSu 执行命令并获取返回值 + // 根据返回值判断命令是否存在 + return runWithSu(command).exitCode == 0 + } + /** * 判断指定文件是否存在。 * @@ -41,16 +100,33 @@ object CSU { return runWithSu(command).exitCode == 0 } - /** - * 判断是否使用 Overlayfs(通过检测 /data/adb/ksu/modules.img 是否存在) - */ - fun isOverlayfs(): Boolean { - return fileExists("/data/adb/ksu/modules.img") - } - // 检查是否具有root权限 fun isRooted(): Boolean { - return checkRootMethod() + return if (checkRootMethod()) { + val magisk = which("magisk") + val ksu = which("ksud") + val ap = which("apd") + if (magisk && ksu || magisk && ap || ksu && ap) { + CLog.e("CSU", "不支持多个 Root 实现。 {Magisk: $magisk, KSU: $ksu, APatch: $ap}") + exitProcess(255) + } + suType = when { + magisk -> { + CLog.i("CSU", "检测到 Magisk Root 实现") + MAGISK + } + ksu -> { + CLog.i("CSU", "检测到 KernelSU Root 实现") + KSU + } + ap -> { + CLog.i("CSU", "检测到 APatch Root 实现") + APATCH + } + else -> NONE + } + true + } else false } private fun checkRootMethod(): Boolean { diff --git a/app/src/main/java/com/itosfish/colorfeatureenhance/utils/ConfigUtils.kt b/app/src/main/java/com/itosfish/colorfeatureenhance/utils/ConfigUtils.kt index a079392..3e1d8c4 100644 --- a/app/src/main/java/com/itosfish/colorfeatureenhance/utils/ConfigUtils.kt +++ b/app/src/main/java/com/itosfish/colorfeatureenhance/utils/ConfigUtils.kt @@ -7,14 +7,13 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.io.File import java.io.FileOutputStream -import java.util.zip.ZipEntry -import java.util.zip.ZipInputStream /** * 新架构的配置管理工具类 * 负责配置目录初始化、模块安装等基础功能 */ object ConfigUtils { + const val LATEST_MODULE_VERSION = 19 private const val TAG = "ConfigUtils" @@ -94,7 +93,7 @@ object ConfigUtils { */ fun hasSystemBaseline(): Boolean { return File(systemBaselineDir, APP_FEATURES_FILE).exists() && - File(systemBaselineDir, OPLUS_FEATURES_FILE).exists() + File(systemBaselineDir, OPLUS_FEATURES_FILE).exists() } /** @@ -102,7 +101,7 @@ object ConfigUtils { */ fun hasMergedOutput(): Boolean { return File(mergedOutputDir, APP_FEATURES_FILE).exists() && - File(mergedOutputDir, OPLUS_FEATURES_FILE).exists() + File(mergedOutputDir, OPLUS_FEATURES_FILE).exists() } /** @@ -133,9 +132,8 @@ object ConfigUtils { * 从 assets 解压模块到指定目录 */ fun installModule(): Boolean { - try { + return try { Log.i(TAG, "开始安装模块") - val destDirPath = "/data/adb/modules/ColorOSFeaturesEnhance" // 临时工作目录 val tempDir = File(app.cacheDir, "moduleTemp").apply { @@ -150,33 +148,16 @@ object ConfigUtils { return false } - // 安装到目标目录 - val extractedDir = File(tempDir, "mod") - if (!extractedDir.exists()) { - Log.e(TAG, "解压后的模块目录不存在") - return false - } - - val shellCmd = StringBuilder().apply { - append("mkdir -p \"$destDirPath\" && ") - append("cp -r \"${extractedDir.absolutePath}/.\" \"$destDirPath/\" && ") - append("chmod -R 755 \"$destDirPath\"") - } - - CSU.runWithSu(shellCmd.toString()) - - val success = CSU.fileExists("$destDirPath/module.prop") - if (success) { + if (CSU.installModule(tempZip.absolutePath)) { Log.i(TAG, "模块安装成功") + true } else { Log.e(TAG, "模块安装失败") + false } - - return success - } catch (e: Exception) { Log.e(TAG, "模块安装过程中发生异常", e) - return false + false } } @@ -190,62 +171,65 @@ object ConfigUtils { input.copyTo(output) } } - unzip(tempZip, tempZip.parentFile!!) + true } catch (e: Exception) { Log.e(TAG, "提取模块资源失败", e) false } } - /** - * 解压 zip 文件到指定目录 - */ - private fun unzip(zipFile: File, destDir: File): Boolean { - return try { - ZipInputStream(zipFile.inputStream()).use { zis -> - var entry: ZipEntry? - while (zis.nextEntry.also { entry = it } != null) { - val newFile = File(destDir, entry!!.name) - if (entry!!.isDirectory) { - newFile.mkdirs() - } else { - newFile.parentFile?.mkdirs() - FileOutputStream(newFile).use { fos -> - zis.copyTo(fos) + /* + /** + * 解压 zip 文件到指定目录 + */ + private fun unzip(zipFile: File, destDir: File): Boolean { + return try { + ZipInputStream(zipFile.inputStream()).use { zis -> + var entry: ZipEntry? + while (zis.nextEntry.also { entry = it } != null) { + val newFile = File(destDir, entry!!.name) + if (entry!!.isDirectory) { + newFile.mkdirs() + } else { + newFile.parentFile?.mkdirs() + FileOutputStream(newFile).use { fos -> + zis.copyTo(fos) + } } } } + true + } catch (e: Exception) { + e.printStackTrace() + false } - true - } catch (e: Exception) { - e.printStackTrace() - false } - } + */ /** * 检查模块是否已安装 */ - fun isModuleInstalled(): Boolean { - return CSU.dirExists("/data/adb/modules/ColorOSFeaturesEnhance") && - CSU.fileExists("/data/adb/modules/ColorOSFeaturesEnhance/module.prop") + fun shouldInstallModule(): Boolean { + return moduleVersion < LATEST_MODULE_VERSION } /** * 获取模块版本信息 */ - fun getModuleVersion(): String? { - val modulePropsPath = "/data/adb/modules/ColorOSFeaturesEnhance/module.prop" - if (!CSU.fileExists(modulePropsPath)) return null - - return try { - val output = CSU.runWithSu("grep '^version=' \"$modulePropsPath\" | cut -d'=' -f2").output - output.trim().takeIf { it.isNotEmpty() } - } catch (e: Exception) { - CLog.e(TAG, "获取模块版本失败", e) - null + val moduleVersion: Int + get() { + val modulePropsPath = "/data/adb/modules/ColorOSFeaturesEnhance/module.prop" + if (!CSU.fileExists(modulePropsPath)) return -1 + + return try { + val output = + CSU.runWithSu("grep '^versionCode=' \"$modulePropsPath\" | cut -d'=' -f2").output + output.trim().takeIf { it.isNotEmpty() }?.toInt() ?: -1 + } catch (e: Exception) { + CLog.e(TAG, "获取模块版本失败", e) + -1 + } } - } /** * 将merged_output的配置复制到模块挂载目录 @@ -258,7 +242,7 @@ object ConfigUtils { val configPaths = getConfigPaths() CLog.d(TAG, "配置路径: mergedOutputDir=${configPaths.mergedOutputDir}") - val moduleBase = "/data/adb/modules/ColorOSFeaturesEnhance" + val moduleBase = "/data/adb/cos_feat_e" val moduleDirs = listOf( "$moduleBase/my_product/etc/extension", "$moduleBase/anymount/my_product/etc/extension" @@ -287,7 +271,8 @@ object ConfigUtils { } // 复制oplus-feature.xml - val oplusFeaturesSource = "${configPaths.mergedOutputDir}/${configPaths.oplusFeaturesFile}" + val oplusFeaturesSource = + "${configPaths.mergedOutputDir}/${configPaths.oplusFeaturesFile}" CLog.d(TAG, "检查源文件: $oplusFeaturesSource") if (File(oplusFeaturesSource).exists()) { CLog.i(TAG, "源文件存在,准备复制 oplus-feature.xml") @@ -323,7 +308,10 @@ object ConfigUtils { moduleDirs.forEach { dir -> val appExists = CSU.fileExists("$dir/${configPaths.appFeaturesFile}") val oplusExists = CSU.fileExists("$dir/${configPaths.oplusFeaturesFile}") - CLog.d(TAG, "目录 $dir: app-features存在=$appExists, oplus-feature存在=$oplusExists") + CLog.d( + TAG, + "目录 $dir: app-features存在=$appExists, oplus-feature存在=$oplusExists" + ) if (appExists || oplusExists) { successCount++ } diff --git a/exampleConfig/mod.zip b/exampleConfig/mod.zip index 5225bf4..73310b8 100644 Binary files a/exampleConfig/mod.zip and b/exampleConfig/mod.zip differ diff --git a/exampleConfig/mod/.gitattributes b/exampleConfig/mod/.gitattributes new file mode 100644 index 0000000..a7957d6 --- /dev/null +++ b/exampleConfig/mod/.gitattributes @@ -0,0 +1,9 @@ +# Declare files that will always have LF line endings on checkout. +META-INF/** text eol=lf +*.prop text eol=lf +*.sh text eol=lf +; *.md text eol=lf +; sepolicy.rule text eol=lf + +# Denote all files that are truly binary and should not be modified. +; lib/** binary \ No newline at end of file diff --git a/exampleConfig/mod/META-INF/com/google/android/update-binary b/exampleConfig/mod/META-INF/com/google/android/update-binary new file mode 100644 index 0000000..28b48e5 --- /dev/null +++ b/exampleConfig/mod/META-INF/com/google/android/update-binary @@ -0,0 +1,33 @@ +#!/sbin/sh + +################# +# Initialization +################# + +umask 022 + +# echo before loading util_functions +ui_print() { echo "$1"; } + +require_new_magisk() { + ui_print "*******************************" + ui_print " Please install Magisk v20.4+! " + ui_print "*******************************" + exit 1 +} + +######################### +# Load util_functions.sh +######################### + +OUTFD=$2 +ZIPFILE=$3 + +mount /data 2>/dev/null + +[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk +. /data/adb/magisk/util_functions.sh +[ $MAGISK_VER_CODE -lt 20400 ] && require_new_magisk + +install_module +exit 0 diff --git a/exampleConfig/mod/META-INF/com/google/android/updater-script b/exampleConfig/mod/META-INF/com/google/android/updater-script new file mode 100644 index 0000000..11d5c96 --- /dev/null +++ b/exampleConfig/mod/META-INF/com/google/android/updater-script @@ -0,0 +1 @@ +#MAGISK diff --git a/exampleConfig/mod/customize.sh b/exampleConfig/mod/customize.sh new file mode 100644 index 0000000..d3a5282 --- /dev/null +++ b/exampleConfig/mod/customize.sh @@ -0,0 +1,5 @@ +#!/system/bin/sh +SKIPUNZIP=0 + +mkdir -p /data/adb/cos_feat_e/anymount/my_product/etc/extension +mkdir -p /data/adb/cos_feat_e/my_product/etc/extension diff --git a/exampleConfig/mod/module.prop b/exampleConfig/mod/module.prop index 1dabf5d..6e75dc5 100644 --- a/exampleConfig/mod/module.prop +++ b/exampleConfig/mod/module.prop @@ -1,7 +1,7 @@ id=ColorOSFeaturesEnhance name=ColorOS特性补全 [程序自动创建] -version=0.2 -versionCode=18 +version=0.3 +versionCode=19 author=酷安@ItosEO @yangFenTuoZi @盼干长安月夜 description=程序自动创建,挂载ColorOS特性补全配置 minMagisk=28100 diff --git a/exampleConfig/mod/post-fs-data.sh b/exampleConfig/mod/post-fs-data.sh index e929a80..e03bdda 100644 --- a/exampleConfig/mod/post-fs-data.sh +++ b/exampleConfig/mod/post-fs-data.sh @@ -9,9 +9,9 @@ APP_FEATURES_FILE="com.oplus.app-features.xml" OPLUS_FEATURES_FILE="com.oplus.oplus-feature.xml" # 模块目录 -MODULE_CONFIG_DIR="$MODDIR/my_product/etc/extension" -MODULE_ANYMOUNT_DIR="$MODDIR/anymount/my_product/etc/extension" -MODULE_TEMP_DIR="$MODDIR/temp_configs" +MODULE_CONFIG_DIR="/data/adb/cos_feat_e/my_product/etc/extension" +MODULE_ANYMOUNT_DIR="/data/adb/cos_feat_e/anymount/my_product/etc/extension" +MODULE_TEMP_DIR="/data/adb/cos_feat_e/temp_configs" # 日志函数 log_info() { @@ -129,7 +129,7 @@ log_info "开始挂载配置文件" # mount --bind $MODDIR/my_product/etc/$OPLUS_FEATURES_FILE /my_product/etc/$OPLUS_FEATURES_FILE # 挂载any目录下的其他文件 -TMPDIR=${0%/*}/anymount +TMPDIR=/data/adb/cos_feat_e/anymount if [ -d "$TMPDIR" ]; then for i in `/bin/find $TMPDIR -type f -printf "%P "`; do /bin/mount /$TMPDIR/$i /$i diff --git a/exampleConfig/mod/service.sh b/exampleConfig/mod/service.sh index 8b5a621..c61adaf 100644 --- a/exampleConfig/mod/service.sh +++ b/exampleConfig/mod/service.sh @@ -8,7 +8,7 @@ APP_DATA_DIR="/data/media/0/Android/data/com.itosfish.colorfeatureenhance/files/ SYSTEM_BASELINE_DIR="$APP_DATA_DIR/system_baseline" # 模块临时目录 -MODULE_TEMP_DIR="$MODDIR/temp_configs" +MODULE_TEMP_DIR="/data/adb/cos_feat_e/temp_configs" # 配置文件名 APP_FEATURES_FILE="com.oplus.app-features.xml"