Skip to content

Commit ea20b5e

Browse files
committed
refactor: use ResourcesLoader to inject module resources
1 parent ff20f83 commit ea20b5e

File tree

1 file changed

+82
-30
lines changed

1 file changed

+82
-30
lines changed

app/src/main/java/cc/ioctl/tmoe/lifecycle/Parasitics.java

+82-30
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,26 @@
1717
import android.content.pm.PackageManager;
1818
import android.content.res.AssetManager;
1919
import android.content.res.Resources;
20+
import android.content.res.loader.ResourcesLoader;
21+
import android.content.res.loader.ResourcesProvider;
2022
import android.os.Build;
2123
import android.os.Bundle;
2224
import android.os.Handler;
2325
import android.os.IBinder;
2426
import android.os.Looper;
2527
import android.os.Message;
28+
import android.os.ParcelFileDescriptor;
2629
import android.os.PersistableBundle;
2730
import android.os.TestLooperManager;
2831
import android.view.KeyEvent;
2932
import android.view.MotionEvent;
3033

34+
import androidx.annotation.NonNull;
3135
import androidx.annotation.Nullable;
36+
import androidx.annotation.RequiresApi;
3237

3338
import java.io.File;
39+
import java.io.IOException;
3440
import java.lang.reflect.Field;
3541
import java.lang.reflect.InvocationHandler;
3642
import java.lang.reflect.InvocationTargetException;
@@ -42,6 +48,8 @@
4248
import cc.ioctl.tmoe.startup.HookEntry;
4349
import cc.ioctl.tmoe.util.HostInfo;
4450
import cc.ioctl.tmoe.util.Initiator;
51+
import cc.ioctl.tmoe.util.Log;
52+
import cc.ioctl.tmoe.util.SyncUtils;
4553

4654
/**
4755
* Inject module Activities into host process and resources injection.
@@ -71,7 +79,6 @@ public static int getActivityStubHookCost() {
7179
return -1;
7280
}
7381

74-
@SuppressLint("PrivateApi")
7582
public static void injectModuleResources(Resources res) {
7683
if (res == null) {
7784
return;
@@ -81,48 +88,93 @@ public static void injectModuleResources(Resources res) {
8188
return;
8289
} catch (Resources.NotFoundException ignored) {
8390
}
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;
89126
}
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 {
90145
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);
94147
addAssetPath.setAccessible(true);
95-
int cookie = (int) addAssetPath.invoke(assets, sModulePath);
148+
int cookie = (int) addAssetPath.invoke(assets, path);
96149
try {
97-
logd("injectModuleResources: " + res.getString(R.string.res_inject_success));
150+
res.getString(R.string.res_inject_success);
98151
if (sResInjectEndTime == 0) {
99152
sResInjectEndTime = System.currentTimeMillis();
100153
}
101154
} 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);
120156
}
121157
} catch (Exception e) {
122-
loge(e);
158+
Log.e(e);
123159
}
124160
}
125161

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+
126178
@SuppressLint("PrivateApi")
127179
public static void initForStubActivity(Context ctx) {
128180
if (__stub_hooked) {

0 commit comments

Comments
 (0)