17
17
import android .content .pm .PackageManager ;
18
18
import android .content .res .AssetManager ;
19
19
import android .content .res .Resources ;
20
+ import android .content .res .loader .ResourcesLoader ;
21
+ import android .content .res .loader .ResourcesProvider ;
20
22
import android .os .Build ;
21
23
import android .os .Bundle ;
22
24
import android .os .Handler ;
23
25
import android .os .IBinder ;
24
26
import android .os .Looper ;
25
27
import android .os .Message ;
28
+ import android .os .ParcelFileDescriptor ;
26
29
import android .os .PersistableBundle ;
27
30
import android .os .TestLooperManager ;
28
31
import android .view .KeyEvent ;
29
32
import android .view .MotionEvent ;
30
33
34
+ import androidx .annotation .NonNull ;
31
35
import androidx .annotation .Nullable ;
36
+ import androidx .annotation .RequiresApi ;
32
37
33
38
import java .io .File ;
39
+ import java .io .IOException ;
34
40
import java .lang .reflect .Field ;
35
41
import java .lang .reflect .InvocationHandler ;
36
42
import java .lang .reflect .InvocationTargetException ;
42
48
import cc .ioctl .tmoe .startup .HookEntry ;
43
49
import cc .ioctl .tmoe .util .HostInfo ;
44
50
import cc .ioctl .tmoe .util .Initiator ;
51
+ import cc .ioctl .tmoe .util .Log ;
52
+ import cc .ioctl .tmoe .util .SyncUtils ;
45
53
46
54
/**
47
55
* Inject module Activities into host process and resources injection.
@@ -71,7 +79,6 @@ public static int getActivityStubHookCost() {
71
79
return -1 ;
72
80
}
73
81
74
- @ SuppressLint ("PrivateApi" )
75
82
public static void injectModuleResources (Resources res ) {
76
83
if (res == null ) {
77
84
return ;
@@ -81,48 +88,93 @@ public static void injectModuleResources(Resources res) {
81
88
return ;
82
89
} catch (Resources .NotFoundException ignored ) {
83
90
}
84
- try {
85
- String sModulePath = HookEntry .getModulePath ();
86
- if (sModulePath == null ) {
87
- throw new RuntimeException (
88
- "get module path failed, loader=" + Parasitics .class .getClassLoader ());
91
+ String sModulePath = HookEntry .getModulePath ();
92
+ if (sModulePath == null ) {
93
+ throw new RuntimeException ("get module path failed, loader=" + Parasitics .class .getClassLoader ());
94
+ }
95
+ // AssetsManager.addAssetPath starts to break on Android 12.
96
+ // ResourcesLoader is added since Android 11.
97
+ if (Build .VERSION .SDK_INT >= 30 ) {
98
+ injectResourcesAboveApi30 (res , sModulePath );
99
+ } else {
100
+ injectResourcesBelowApi30 (res , sModulePath );
101
+ }
102
+ }
103
+
104
+ @ RequiresApi (30 )
105
+ private static class ResourcesLoaderHolderApi30 {
106
+
107
+ private ResourcesLoaderHolderApi30 () {
108
+ }
109
+
110
+ public static ResourcesLoader sResourcesLoader = null ;
111
+
112
+ }
113
+
114
+ @ RequiresApi (30 )
115
+ private static void injectResourcesAboveApi30 (@ NonNull Resources res , @ NonNull String path ) {
116
+ if (ResourcesLoaderHolderApi30 .sResourcesLoader == null ) {
117
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor .open (new File (path ),
118
+ ParcelFileDescriptor .MODE_READ_ONLY )) {
119
+ ResourcesProvider provider = ResourcesProvider .loadFromApk (pfd );
120
+ ResourcesLoader loader = new ResourcesLoader ();
121
+ loader .addProvider (provider );
122
+ ResourcesLoaderHolderApi30 .sResourcesLoader = loader ;
123
+ } catch (IOException e ) {
124
+ logForResourceInjectFailure (path , e , 0 );
125
+ return ;
89
126
}
127
+ }
128
+ SyncUtils .runOnUiThread (() -> {
129
+ res .addLoaders (ResourcesLoaderHolderApi30 .sResourcesLoader );
130
+ try {
131
+ res .getString (R .string .res_inject_success );
132
+ if (sResInjectEndTime == 0 ) {
133
+ sResInjectEndTime = System .currentTimeMillis ();
134
+ }
135
+ } catch (Resources .NotFoundException e ) {
136
+ logForResourceInjectFailure (path , e , 0 );
137
+ }
138
+ });
139
+ }
140
+
141
+ @ SuppressWarnings ("JavaReflectionMemberAccess" )
142
+ @ SuppressLint ({"PrivateApi" , "DiscouragedPrivateApi" })
143
+ private static void injectResourcesBelowApi30 (@ NonNull Resources res , @ NonNull String path ) {
144
+ try {
90
145
AssetManager assets = res .getAssets ();
91
- @ SuppressLint ("DiscouragedPrivateApi" )
92
- Method addAssetPath = AssetManager .class
93
- .getDeclaredMethod ("addAssetPath" , String .class );
146
+ Method addAssetPath = AssetManager .class .getDeclaredMethod ("addAssetPath" , String .class );
94
147
addAssetPath .setAccessible (true );
95
- int cookie = (int ) addAssetPath .invoke (assets , sModulePath );
148
+ int cookie = (int ) addAssetPath .invoke (assets , path );
96
149
try {
97
- logd ( "injectModuleResources: " + res .getString (R .string .res_inject_success ) );
150
+ res .getString (R .string .res_inject_success );
98
151
if (sResInjectEndTime == 0 ) {
99
152
sResInjectEndTime = System .currentTimeMillis ();
100
153
}
101
154
} catch (Resources .NotFoundException e ) {
102
- loge ("Fatal: injectModuleResources: test injection failure!" );
103
- loge ("injectModuleResources: cookie=" + cookie + ", path=" + sModulePath
104
- + ", loader=" + Parasitics .class .getClassLoader ());
105
- long length = -1 ;
106
- boolean read = false ;
107
- boolean exist = false ;
108
- boolean isDir = false ;
109
- try {
110
- File f = new File (sModulePath );
111
- exist = f .exists ();
112
- isDir = f .isDirectory ();
113
- length = f .length ();
114
- read = f .canRead ();
115
- } catch (Throwable e2 ) {
116
- loge (e2 );
117
- }
118
- loge ("sModulePath: exists = " + exist + ", isDirectory = " + isDir + ", canRead = "
119
- + read + ", fileLength = " + length );
155
+ logForResourceInjectFailure (path , e , 0 );
120
156
}
121
157
} catch (Exception e ) {
122
- loge (e );
158
+ Log . e (e );
123
159
}
124
160
}
125
161
162
+ private static void logForResourceInjectFailure (@ NonNull String path , @ NonNull Throwable e , int cookie ) {
163
+ Log .e ("Fatal: injectModuleResources: test injection failure!" );
164
+ Log .e ("injectModuleResources: path=" + path + ", cookie=" + cookie +
165
+ ", loader=" + Parasitics .class .getClassLoader ());
166
+ long length = -1 ;
167
+ boolean read = false ;
168
+ boolean exist = false ;
169
+ boolean isDir = false ;
170
+ File f = new File (path );
171
+ exist = f .exists ();
172
+ isDir = f .isDirectory ();
173
+ length = f .length ();
174
+ read = f .canRead ();
175
+ Log .e ("sModulePath: exists = " + exist + ", isDirectory = " + isDir + ", canRead = " + read + ", fileLength = " + length );
176
+ }
177
+
126
178
@ SuppressLint ("PrivateApi" )
127
179
public static void initForStubActivity (Context ctx ) {
128
180
if (__stub_hooked ) {
0 commit comments