Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .github/workflows/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ name: App

on:
push:
branches: [ master ]
paths-ignore:
- '.github/ISSUE_TEMPLATE'
workflow_dispatch:

jobs:
Expand Down Expand Up @@ -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
Expand Down
Binary file modified app/src/main/assets/mod.zip
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
92 changes: 84 additions & 8 deletions app/src/main/java/com/itosfish/colorfeatureenhance/utils/CSU.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,72 @@ 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,
val exitCode: Int
)

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
}

/**
* 判断指定文件是否存在。
*
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -94,15 +93,15 @@ object ConfigUtils {
*/
fun hasSystemBaseline(): Boolean {
return File(systemBaselineDir, APP_FEATURES_FILE).exists() &&
File(systemBaselineDir, OPLUS_FEATURES_FILE).exists()
File(systemBaselineDir, OPLUS_FEATURES_FILE).exists()
}

/**
* 检查合并输出配置是否存在
*/
fun hasMergedOutput(): Boolean {
return File(mergedOutputDir, APP_FEATURES_FILE).exists() &&
File(mergedOutputDir, OPLUS_FEATURES_FILE).exists()
File(mergedOutputDir, OPLUS_FEATURES_FILE).exists()
}

/**
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
}

Expand All @@ -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的配置复制到模块挂载目录
Expand All @@ -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"
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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++
}
Expand Down
Binary file modified exampleConfig/mod.zip
Binary file not shown.
9 changes: 9 additions & 0 deletions exampleConfig/mod/.gitattributes
Original file line number Diff line number Diff line change
@@ -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
Loading