From 90d9fdd4af0f31ee8189a07a87ca58364f7d9d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9C=9F=E8=8A=99=E8=BE=A3?= <140885477+yangFenTuoZi@users.noreply.github.com> Date: Tue, 15 Jul 2025 23:03:10 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E6=A8=A1=E5=9D=97=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 支持 overlayfs - 支持检测版本号并自动更新模块 - 移动配置文件至 /data/adb/cos_feat_e --- app/src/main/assets/mod.zip | Bin 5436 -> 5431 bytes .../colorfeatureenhance/MainActivity.kt | 12 +- .../itosfish/colorfeatureenhance/utils/CSU.kt | 82 ++++++++++-- .../colorfeatureenhance/utils/ConfigUtils.kt | 122 ++++++++---------- exampleConfig/mod/.gitattributes | 9 ++ .../META-INF/com/google/android/update-binary | 33 +++++ .../com/google/android/updater-script | 1 + exampleConfig/mod/customize.sh | 5 + exampleConfig/mod/module.prop | 4 +- exampleConfig/mod/post-fs-data.sh | 8 +- 10 files changed, 184 insertions(+), 92 deletions(-) create mode 100644 exampleConfig/mod/.gitattributes create mode 100644 exampleConfig/mod/META-INF/com/google/android/update-binary create mode 100644 exampleConfig/mod/META-INF/com/google/android/updater-script create mode 100644 exampleConfig/mod/customize.sh diff --git a/app/src/main/assets/mod.zip b/app/src/main/assets/mod.zip index 5225bf4dac3d128e1c187f77cdcd24070bf3eedb..fea4df5eb3642a53f2e39b7157233abbd35d7448 100644 GIT binary patch literal 5431 zcmb7I1yodB*B*N4?vRvjrG^+JM}|g8C5My_X^`#^Bn9biqy_1aknWIfkPsx4Z~WfE z|E>Q!Ywf$vz3c4fIeVXTPdq9LNXSG0000PZ&OX#B_Ll{lz~5fM4GV6@&W=vD))wwC zE=O~(en|{}9z2v`FD1k6#_OjUXfY4)!zcv}p|V&nLxmWU<~$I+uFgHd#S~@{kc{dp zpSiLdZ0mD3P-0?I?M972hzW8S!vj?b-ej|waXlB_wVkQbPBF4+#IHOgiZTZ`&L9Mp zUwZ@iZ*Slo_`RURYl<=&lAI7FS?<4YhJCx4v90w#H2o3v?}$H|n%UZ#J%jy&2i4zr z7}}UP*jkwU?)ghDxf~wr(IWr=CWrt4>%Vw9+nE?T!8na9Yz!UTUd237Zs#EiI{a8J z6cP9pe=BL892()P9RR*=98SusKxB4GE90aHyg2|w;*(@;R=F*0o{Fv$@Jo#jKoLI7 zA;BWf!?-FCD*bAmFIUe{tmdk$0edMh*`FgpxhJeK95Ugs0$s zl)~1xM2@9iBkBZ+qOf@9lcu_;vgGY_jMGCKP-)(scBy+?(hDwQ5(x5Yi9X?7?HE`e zwKz7Rpj-e=rO4ttetg^+S)w`Z5aoKUPi1Na-!flwA9-vhjtmmNx79xh_b!fn1mne` zkc^3Ae?`yd8M*Ll7=H|SN`ILq9+);pKzRL!vBB-H2KBcg`X$j0oQ}p0 z7Isbyijr~=bp?#y<`WRmT70OJKJ2AG0mnIp8z$VWZB3k?!MN-kZ0)S0nv2?b0Dremy2r&SF3yfcg)h6VURr`GK@yzo@5DPxf}1UM2G6ity%{MQI7aC*u|mTFeElD8v(k7~ zxqd5q#HAHDHR(^eacXt#8DVEpWMjvgrVtiX|J7-T!&8?{tRwEZz4y?i&f73LU7b;jP(3wM_N9Zv@elUXbT@c6wg+V z>(2Y!PS&C=aOWeB#g_Q!JG=tZKm1@P$Q;Gu{*n21(3fH#p`&Xgc+C~X_eQpa6s_PJG` zu$jirn~HSvsQt^%9J`Gczge6Ei4gX*yn4V)-V+J;!7Fd@R{G{sBRx@?;0=rpT=CVp zF@;=Dt+WMt(UL<>0}Jvm;DGPIJrsrDmnc&d!k)TbIE|A*9d%2aN1CY(p5^sa*z8J= zZ6YPjbu+y&1QI>hr|P4sHcikUm5cE+i$DH=Ihk#-!Yiv*WB8(0yiElKbYZ3aIG>O! z^3(I@vuC^s_GFO3cjUbKN)zg8h-SJLw~U9M30s0gGc0Z*P|o!lg@zSh%1Sid&pXC%6oeD| z?dw9NHPcyZm84~@Z$(nM4HsoG&V3u|Lf_8@VRDU)J2xGs)3Xat800Ld1KAH6Uld&D zihzhsd7U6S=308r^ERJCEs{HP#!9jnf$kOFNXF`BC{VP@_j>I(TPZ}9{_jTD%yqkQ zc6{`EsRJ$7bN5=yp755l8CmHMMd{p>WKeIHDm3aO(H&*##MyC14W?X_52f>aRu6Uv zCO1hT#*jEmRWMg3*|(V|W$y5|y(X&+bK5<8E$onM@24Hb#nXliEAP5qfjN7LtE1gF zxz81ma)GA6bVa%3z3tK?PwU7gAwH~9dCF16i8{Ia0Jbbl?I31s%SrO)F=m_wS+dK~ zT=7OYI`_MwB!wv7oCSh?b$STNp{r)9(-hKrW_{a*!=k%{@^}t|W3%?FW8|6Ks@;uQ zu$j4Fa9a?zk3d7I=}uv`@eQ!RCEzI2PkVUQLTq@YbS6|Tjo?Y9GH7SG*aN+^w;X4j z!o61JDyz$6(OuI^j6%rBr^!fh2^~bhr7j^baF!GUWW-R!!?)(#pEY#RuCxBtSI7vI45DoiVbrqxO9^m#D}`RGc-; z>OSG3yc7GvGo`w~Ci0yO_Wf{q18cSSrTJ^!3dtL(q_ZE|8oVyB|5)R7%=UG_6`c*l zXaE3|1_1c6k2=B}Tr7-#9P$>le_7t6<8LaGkSSDFNbqoF5Qu@nolJuru`QGd+rI z>9S(F-sWx5Id(t0GvDBNxLk2)l>g@G^O;@M*DEu0U8^1FZgwS?&#*p9oVvT?P>1dB z36B!0%c?v1#gd71+#SVQfSi90eI29J+EE{ap35W&&Fj)&DFZ_gaY$5#`|kCGnUKAK zz9C>OXLjdsXRMN_ZDyvJsXGTF-~F7tIi^Mg>I>mW+|b5ok5?!^Ix+M`H!QM?ItcF! z>5xic0LT+wJ%grdR0oguMqje8L-eSSBbw;NY=-4#ERiQ6gj3kBvf1xC-d- zr8fDg>>WN2w`7mxk$l#zQ-z+^2QMuL8qP~7^X+}HYcJGFQO2M~a2nZ_TnmoON0z46 zw8*dr8-rDfZ_w9vY5Pn^X1{zlmh`#1rA&L1Zgcb9#>dGTaM8%})|owP6)M^_x zf}dyK$r`8#@+vcr*b_l%KWtE>P5b2#^-DD>9t=bgp^&o*Bj;&{vh?yQ4GBKWCEU|a z`RGX17%-F@LX$z&&lpoxTbB_4z~NA5SW{p0f6fOb zYS8fx?Xg*HAy6^0C{sz+r&R-9vPq=G>XRmHpuS6YgzAJb1?uhP(8}{xtyNR8S9WLd zOq-rzKkdVE$RxuE1cW8{t}?1q=WDU15>|z5Jt=5&qz7j{GL&|X9ld8NzosP+Ew^62NsXCi)T6`yI6C<5hb-&+`1-45*50P=EEqu(H#sZtqWp+?R6*x?L3C z$a6~xmkN>}Ve3u}wgI-csGEh|M-0T@*M7~B-xC=}1a%p6A2V4Kz6{py)4wx$09|g1 zE+9@5yUDx7D_B%-GOJEKc)kQvccVBRQw!FR1l3rhIq8m?!j!pRk|r*T-6Fch%`HDzIb?HZEXmY z@Oa^|37l?>#m`e^@gO<~pQw+E;lx<-M88L0El?UMX0DQ&$7IRSnE>NM8Lq(dq^8dE z8F3#c29jlC!zwx^56o>PrdpaE{USwHw0kU(b(l<0oQ5!-Od%n~6UVePJh%^C*>JC% z5%6GgNldMvsf%lr+~abRZClNkk5FGGoXi|4&R zc!i&1x_Y9T7Ku0GAf@Pg8Dxk&BrkIxE&Il0GB8-ln*ITZ{?=_QPvG@5+p|#WRuHuW zz&Et3%&tCeH9=lO@O_xfeV5>%VS}7H9WL|pqGP0su8`6eo}A3IGqbX{Jor$NzdmDI*$)|Vu{Tc)nY%s6MmE@;TyOZOfUTApqQivm!uKcEu64?qMf^+ zbCx=Lc(TUFlB6Jl%kjgHGuQ;0@mZRiV%6V!9n)Gk`a2=8=tDc{&S7Nzp z6g^p@%He$#au9G3cfDRC%4ab2mO-t^`9x)({Chhu0)>1%xCL!c4S^J0FEXVjKDDlx z67fdUWAtFTu$<%bYtV6NliK8@fN0>-V?(}@ffW-4?$13oIz9CRbu7a`K#6B8-@TV5 z4~{KJm(uLU@4W5ay+Bt{KtPlL{GCd|2;V?|6x#njf7VPee*KC7AOm#qSHXJ~A|ZRncY&_BeG+weWPI{z~~(NFN7{xAHm zTFY$~IXT_`nT6^n76N!~f6vb?Md!E6|82I!Hq(D$`zsaax0KPsDkp91|^<+#o5OlKXZSBU{aIqT4UUBT zC&A%1fw7s- zeya%qfZ)3YBKsQSWRcwL!MrDOK>lv6HopqoqXu{DAL!!SB6C+&=jP;5;c@TlQR-FI znD18YRS)fwT%X!6Gu?-Us%F+z+zIz@&cQ5K9MPuNlD&!Nv0J$@^Y) zc-{*9bx?nmlJaa^Ju@3+1^?L9p0QQUaW^r~ggKhV11=FAwuSu0{Dsgb3yR=tA`+R; z7nl3h`3l$YuX)`poL!C+HRLRA?~+f(Wo5d1-NE-o;o?2bVc2sYIC2AQx4!)xjdT&% z_N5#afYmreaTAjeBLR0*@d=poiYi{76eRlO{`;EwQ=k7wsZjnQ_P=5OU4NzCUt>I6+-`ETKFWrmm*EQ0pHRN5@HF%zZ76K79wL;a{kZ)=Zos zl-6PNcX!J<)QGijucTUIfLEv@{bYIQ-#uc;3RcF|Qp&cM#{vyvm5#{>J7iPlo2$zn zt5u}?&+vVk99LeQ@CvH7*(XQTe`%5&5M9jhX(Zj~$TQ914!HM=mwE7VaC}P6{6ocT zMncP5VZ=(sl)=}SQCZc`1FbSwHKeM0HQ!?FMPXCs8@)$Jb361*YWah_? zNlHQ%>fmHb7MA6skqG!Ew0O|<_-&9*95upTX837SEV*lXk7qWUqXl)s#mg6I869|0 z{^N2*IGgcU(zz~ew2Vr)Q?S7|AkSsObXqYf5s6!Z7pN#ly2=vt`gXT)5@WNtP4zTk zDJij)v?JsDx=jL)*AvSN(UCbu%00FU=(V6!;utatcES66NE_6AU?Py_%9#3|JY)%S zC_yve;!HSWbubg+w>c;)Nsb18+;c|BOQ+p#TI%%S$Um%MlnP-y8wg(6z)DqPyJ9Hj zxyIvPwQJKzu6tWR?%}lTIsI;HM$ZwvF&QE0x=??+kqePeuQ^6aaesUqpve}lqF?f` zJL3UaA@7p*dADb&HRGe&5P^MRUly1Bx(&K{1+X6&gjl(kS{5W%+pFL*9(E-9z zR<-r<{E<13uUvjuZ#Y)=YNPIwTR>;0nz^rO``&v&-lJ(nT@F0RBl6X?!P%w<6`Quk zTBr+#nK9IIy@V7T)sLcs^5k)`$e{_v?#g$y`dGB?1VWfuB0e(>wZO9KO%I^Aa^A!H?<{`AcK3xNU6*Wv-Hflt!`E}#sN4>bhMeU(9vP_iL{HmP98lt> zC{j^7cSnPxN z`ThKa(IDS~TD%%M8msa)`uf2*r$f}!aR&3C-eTXqDDC#4#?C>UiJtcE{&UAWgP@eR zJUluut`XW~_f53;SWmv#^Zq1NN5B461(}OMS5;TN^)g+RJ>0=X?@_+>;Nu4QsXUe% zsWO%1iHV##Y^VdvSc%fC7!jt9JkDKs_n6L7HeWODCXk z(PH%K#mkJqUF|I8Mj1iR_ms2dH}(xktzy@OT^^-?NY9(04W{eXbj&ui*bcX33`CKY ze8<0aMYmjB=O$bLfSLM_u6Vj@__jHae(gI@u#3BmIrty?LS8?_divgBV6T#yIhyEC(X0YAp`F`TU$W)^rZfs+;rk1K7ru zrFMSn3>E3vou}^y65{R&F zfo`&tFmbbc-XoU=$i3>F&tv+1z zl-2G^XBxk~n9l9Q^JEdWp#fY-*mI?ej#`_#*78LY)rSRLuyWH^x=Ah-6p$!fPLN@cRmRKjzYmydoQ`VD<2VrvO%=<@`8m$7vVTB*sR&7L@L)*HQ zy6*^bzJ9t(3P}epX;M)T-RDcW_e|T7RpNd=hGGMe3xJ_IwT?prrKmSv2Oht6JWh5D zm{Y>vaD$^>5w;LE#gL2MSkvO#kghHIf@sjk(`wxiU?a5*+eKMt3H!RB!UmQd9oid) z6(GP%M4_KXv9@7FcmVJIF*xwTcHSlZ2k_@C$>9nyQVz2m5(+OlHPgA`)7p7sH2FQ= z6Qe8Nw@sZB33%pfCC_;IiUaDI1t;qIGz_>v zZf|#`|Dmb-k!FuPYYn=b)5B5i@{#>xaf}4vCbd9L&1;-F((4+G_YQ9wb;{ zkrVPL=SrVaKC2d`-KWaCJ(h24BR-+)efeo$k@J6oaok#!%}MCzunBbKwywE^zAtS+ zP-SE(7LF{6<0xnq-q9Di;G{oxwHY`;<)yvL8Jwxj3)8wwCB*v3SR2*aXBuMXIHo2Z zp3;#gn^nD6VMq_n%@zoC%x!d@lF+VWb%-K?*i-SAz4Q=Pwe#RXV+MOlhfohSSfZ%4fQYR<9Cx$5JiX9MuzXsrq*S=> z)%&VCD!&6n^I?%o0QD`;UiYf^L-NoJwN$<1^1X$UIrb|PyR;4KhzJCtl)#XanG=f- zn7eDP6j)t-VWrFke_Yh-n#&Ln4NQ}z0$q8X?8>x9?!?X2KMrZKRUz|ZaZ2wsR0*=I zs?VG>{`dF-082Q2x{+4iiysNxB6vB zrg`0(KUDD0p;RZW)Lyf7k?-RM8x|;47OAgE-iOVnT*4dqjGz|XB3{YwRu&SgszIkp z6QNQw;^|hU>#c;ZG=rIZ7m6ObUf)=S zn`6XF-Xui_4M$T=_&kepKK6b`A;%?cj~BvbD9FUIBk*$GtiOSTnsJ5Ciq6G08X{P? zq9syvsR%(_A1GRS?PfW6>pmUc4jQB@--Ss)NGC}$>R8X+B=nt2xC_Ey5Aci2a`fBf z&L7fpc1AKqG^MGhre}f9S(B|MsuAJbpmMHNtw&^1_81t^l{$e%w-a>dC}zGRv~-C33x+ z9Xr~FRM!g4W^WF^<-m3DL3)O~3`_0Z@U18S`cxiEmJX=JoG%E@Tidzmm=x9M_h|>2 zj}0A$_=XITFS~|6HlQ3wNPgPbhuLBIbbyzV?*v){u3Y zm{XaR8!L_=F>$ZLyPvd&Aw7x{y&JfX*=VIu2ep_kn4BI&1KRIApuq^-_~$C0+8Ae5KHa09-Mya8y)LNXYlwfcGfqS~wKKj2pM6J3&xleNG*0-tC_mW> X|H;I~JE>g2xs%V-NjfU$Pk;Rjd^3vH 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..229fcdc 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,23 @@ 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) { + exitProcess(255) + } + suType = when { + magisk -> MAGISK + ksu -> KSU + ap -> 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 b210316..7a08848 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" @@ -84,8 +83,8 @@ object ConfigUtils { // 验证目录是否创建成功 return CSU.dirExists("$mediaConfigsDir/system_baseline") && - CSU.dirExists("$mediaConfigsDir/user_patches") && - CSU.dirExists("$mediaConfigsDir/merged_output") + CSU.dirExists("$mediaConfigsDir/user_patches") && + CSU.dirExists("$mediaConfigsDir/merged_output") } /** @@ -110,7 +109,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() } /** @@ -118,7 +117,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() } /** @@ -149,9 +148,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 { @@ -166,33 +164,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 } } @@ -206,62 +187,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的配置复制到模块挂载目录 @@ -274,7 +258,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" @@ -303,7 +287,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") @@ -339,7 +324,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/.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 From 5b29f1dd61d944f40f5b14ec394be004c0b9e119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9C=9F=E8=8A=99=E8=BE=A3?= <140885477+yangFenTuoZi@users.noreply.github.com> Date: Tue, 15 Jul 2025 23:13:45 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=B8=8D=E5=90=8C?= =?UTF-8?q?=E5=88=86=E6=94=AF=E8=A7=A6=E5=8F=91=20Action=20=E6=9E=84?= =?UTF-8?q?=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/app.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/app.yaml b/.github/workflows/app.yaml index 7a213b4..8969561 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: From e15ae0a25653267c69acd81a1d6a9a5725db3657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9C=9F=E8=8A=99=E8=BE=A3?= <140885477+yangFenTuoZi@users.noreply.github.com> Date: Tue, 15 Jul 2025 23:17:16 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=B4=E6=97=B6?= =?UTF-8?q?=E5=AF=86=E9=92=A5=E7=94=9F=E6=88=90=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/app.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/app.yaml b/.github/workflows/app.yaml index 8969561..efa20e2 100644 --- a/.github/workflows/app.yaml +++ b/.github/workflows/app.yaml @@ -36,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 From 1a01cac7f8586ff3eddf9c0fdad759e66910844d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9C=9F=E8=8A=99=E8=BE=A3?= <140885477+yangFenTuoZi@users.noreply.github.com> Date: Thu, 17 Jul 2025 19:16:05 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E5=A4=8D=E5=88=B6=E6=A8=A1=E5=9D=97=E4=B8=B4=E6=97=B6=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- exampleConfig/mod/service.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From 03b56717ff3cc084eabaeea4af285254e98f7ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9C=9F=E8=8A=99=E8=BE=A3?= <140885477+yangFenTuoZi@users.noreply.github.com> Date: Thu, 17 Jul 2025 19:36:18 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=A3=80=E6=B5=8B=20Root?= =?UTF-8?q?=20=E5=AE=9E=E7=8E=B0=E5=87=BD=E6=95=B0=E7=9A=84=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../itosfish/colorfeatureenhance/utils/CSU.kt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) 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 229fcdc..886b62f 100644 --- a/app/src/main/java/com/itosfish/colorfeatureenhance/utils/CSU.kt +++ b/app/src/main/java/com/itosfish/colorfeatureenhance/utils/CSU.kt @@ -107,12 +107,22 @@ object CSU { 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 -> MAGISK - ksu -> KSU - ap -> APATCH + magisk -> { + CLog.i("CSU", "检测到 Magisk Root 实现") + MAGISK + } + ksu -> { + CLog.i("CSU", "检测到 KernelSU Root 实现") + KSU + } + ap -> { + CLog.i("CSU", "检测到 APatch Root 实现") + APATCH + } else -> NONE } true From b4a8d805853c5c3699999f7d4d48a6161c1e41e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9C=9F=E8=8A=99=E8=BE=A3?= <140885477+yangFenTuoZi@users.noreply.github.com> Date: Thu, 17 Jul 2025 21:55:38 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=A8=A1=E5=9D=97=20zip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/mod.zip | Bin 5431 -> 5413 bytes exampleConfig/mod.zip | Bin 5436 -> 5413 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/app/src/main/assets/mod.zip b/app/src/main/assets/mod.zip index fea4df5eb3642a53f2e39b7157233abbd35d7448..73310b8bfef2b400de8e5f1b47984d0ccd70f499 100644 GIT binary patch delta 2588 zcmZ8j2{hDeAO4Rq6C>M@WsD_d-^xx5pBkFRGRZK;ersgk6=NBZtt>GWV{5Y3jUrj1 z7<))ewk`^dltNiDm(u-k@BQxWJMa0O=e*DRd!F~4-+9k@p9bYJWnLT>%qa{202i=M zU&-RV3f_hM!+X%KxCcubyFir&>Fr{zBghl9N3?TTQFaKT^1frM13MO+DouAW4uyR4i2ql3j&9OZcG zXi71}$>Gfd=KLn@_vX7si*XrfDz#-LW?Qahs;m{6T*U4<^3eX$MUb%>_73{QzH&_r z9uoM|tcWO4&5Mu*2msjEA~=H~lqKKto8$Pj!nf;+mc0qRxwf@336%_`_rGY8Bv^2c z^ZR|dZ0D5aR8*iCwwFnD1&8FMM{&`Ni!MqQys2Gb_ZksdhVT)|Qk*P}&9v!F*V7&t zKl127Lg;}tiJx!2d1fJvXCDA0O9B9FXW*fvkPF_Fvn2IUuh)*h1q_=C$FLpt%U^tUy}AMYQ{7g1C~S3;UkxIUN6NH9hodLLTA z`HR^VK?;HI@_!2Y3$eA%OWj(ebs+?C+lY&@?vRW<j*Vc2Kzq+t zr9;F0+s8+{Rz4EM-zc}WAEpkp=Wq9qW528kMO2+Y8a;_(|0rQH%3jFS&B87T%zm$i zvAt}?5f4z($yYN|iVj(Pcx~K6_6k?T$cfQzYf_wL2XKXUnb)4BE^x*^yFxoj6e&AL zJcTCQK*P_>904iQ<_)-cg|Zih;h0XF&de^pm&c;s)_}k9cO8jk7#2!NH05OJqtt>* zKNj1P!ER~pv9HiKdRD+k5w|64$i0WjOh0&4(|dGH6gM)7V4yf)hP3jI}LILda;EJ-1?!-5Z)si)0o9YGxE~cIQn>O){&03V9Gp1x-7@Iu=CHp$;*I2FWc26h zFwsFTeSc0-adgS)Z!a%(aX*p)$ktso6Ai11y~fJr#O18&UK7xX>gfbHN5&Dl z2-zUc9;~w#TeYLm6~jeGALg1+-DYRtk2j*507IoD=9xbaehN`WvHo`lXw zd9`88Q498!V#^Yp!gmHVt$VE$SEBv7wA3#*%#OlSy@bOi6i!DQT8D<{DtLn6bt$}h zg0gLSiZabOL%-mxh>ZUD0Z&yVwYdV%<0hAOV7h5v#2t#cQ4cduu}?PLl{}Qd6LV(_ zaoYo#J(M;UKhc@&v*}r)8CG7+{1AZDyCpaI(2p6Aau- zbf6dO2~x4d{9J-##C&sc`2IP8Y*SE}H$RT%OW{p7)$jWRN-H;_#dYzUYr~WyW(`ya z?`1tU#7X=CI^Ti_+*j!Muw%5=d*q_Ajdi*&O(k=ML0{sYhZLWQ4vyLM7NmuHVZN?* z7ryG>x>=m|#J*f=beZyk5tZ31u%O}M`l94n{ZgycbbL{y3M4es`)#wg*9)tpyz5*= z1*8c2{cTP-{fUglu@#=XROni{rs8CbEw>{0CG158EglgRO4R)%#H7@kDot7(l62MnP4!7DL82VwMicy^8trO5~-__N}e^hNPh5G75Z(KXFcoB%zU|+vR*#bXL+Was@@n}oVgqwgkYrVuZ*t|t4QE4%N2lQ4P z^R%0!VLr$6usQcPKi_;j@qS+BS53fUS@pxCV{?3!391y-_^gh_#K%W!fOEqK@~k60 zqd0ilWoAmd>3XgO*7?_uU1d|P-|2wD{1?^H^Lk(6k{U#etR+e_#^SzoO(hdH_4n1> zP0IzG8vMM$9W;D0E4Ohxiz6y$lt zqNN8SfkX(+Ju*XZ)v->fv@UMKzjaPirT|M$TYtq}In1*2K|T)|DnughJ$-zJrlvCe zz#iL>x}PedJ&0s#S1s`$9&_m!<*u0yp*St;xmsoFpjcVfx^YVO7SoQ=L0{moxr7o+F=>c6Q)LH+-yuy7M72mB{t4cr1MYVa3@gzQBrsZKYe1{yZ zo3W^QLim4m^Y3;4lcA_G!m3d7~@NdO5^^34LG;H)Qfv_+Hy6XqqS=(R$fD7*K{tG7un8pAA delta 2604 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 S1D{$kob2zk0Dvg#>FGa%Nxjwp diff --git a/exampleConfig/mod.zip b/exampleConfig/mod.zip index 5225bf4dac3d128e1c187f77cdcd24070bf3eedb..73310b8bfef2b400de8e5f1b47984d0ccd70f499 100644 GIT binary patch literal 5413 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 VMCWkT#_f*D0LQrL0cn5U{R`k8ZruO? literal 5436 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 X|H;I~JE>g2xs%V-NjfU$Pk;Rjd^3vH