Merge branch 'master' of github.com:ElderDrivers/EdXposed

This commit is contained in:
kotori0 2021-01-07 22:13:51 +08:00
commit f78355a189
No known key found for this signature in database
GPG Key ID: 3FEE57ED0385A6B2
2 changed files with 192 additions and 15 deletions

View File

@ -17,6 +17,8 @@ import com.elderdrivers.riru.edxp.util.Utils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import de.robv.android.xposed.XC_MethodHook;
@ -62,6 +64,7 @@ public class HandleBindApp extends XC_MethodHook {
boolean isModule = false;
int xposedminversion = -1;
boolean xposedsharedprefs = false;
boolean xposedmigrateprefs = false;
try {
Map<String, Object> metaData = MetaDataReader.getMetaData(new File(appInfo.sourceDir));
isModule = metaData.containsKey("xposedmodule");
@ -73,6 +76,7 @@ public class HandleBindApp extends XC_MethodHook {
xposedminversion = MetaDataReader.extractIntPart((String) minVersionRaw);
}
xposedsharedprefs = metaData.containsKey("xposedsharedprefs");
xposedmigrateprefs = metaData.containsKey("xposedmigrateprefs");
}
} catch (NumberFormatException | IOException e) {
Hookers.logE("ApkParser fails", e);
@ -90,11 +94,32 @@ public class HandleBindApp extends XC_MethodHook {
}
}
});
final boolean migratePrefs = xposedmigrateprefs;
XposedHelpers.findAndHookMethod(ContextImpl.class, "getPreferencesDir", new XC_MethodHook() {
@SuppressLint({"SetWorldReadable", "WorldReadableFiles"})
@Override
protected void afterHookedMethod(MethodHookParam param) {
param.setResult(new File(ConfigManager.getPrefsPath(appInfo.packageName)));
File newDir = new File(ConfigManager.getPrefsPath(appInfo.packageName));
if (migratePrefs) {
File oldDir = (File) param.getResult();
for (File oldFile : oldDir.listFiles()) {
Path oldPath = oldFile.toPath();
if (!Files.isSymbolicLink(oldPath)) {
Utils.logD("Migrating prefs file: " + oldFile.getAbsolutePath());
Path newPath = new File(newDir, oldFile.getName()).toPath();
try {
Files.move(oldPath, newPath);
try {
Files.createSymbolicLink(oldPath, newPath);
} catch (IOException e) {
Utils.logD("Symlink creation failed", e);
}
} catch (IOException e) {
Utils.logD("File move operation failed", e);
}
}
}
}
param.setResult(newDir);
}
});
}

View File

@ -15,6 +15,14 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@ -28,6 +36,11 @@ import de.robv.android.xposed.services.FileResult;
*/
public final class XSharedPreferences implements SharedPreferences {
private static final String TAG = "XSharedPreferences";
private static final HashMap<Path, PrefsData> mInstances = new HashMap<>();
private static final Object mContent = new Object();
private static Thread mDaemon = null;
private static WatchService mWatcher;
private final HashMap<OnSharedPreferenceChangeListener, Object> mListeners = new HashMap<>();
private final File mFile;
private final String mFilename;
private Map<String, Object> mMap;
@ -35,6 +48,15 @@ public final class XSharedPreferences implements SharedPreferences {
private long mLastModified;
private long mFileSize;
static {
try {
mWatcher = new File(XposedInit.prefsBasePath).toPath().getFileSystem().newWatchService();
Log.d(TAG, "Created WatchService instance");
} catch (IOException e) {
Log.e(TAG, "Failed to create WatchService", e);
}
}
/**
* Read settings from the specified file.
*
@ -42,8 +64,8 @@ public final class XSharedPreferences implements SharedPreferences {
*/
public XSharedPreferences(File prefFile) {
mFile = prefFile;
mFilename = mFile.getAbsolutePath();
startLoadFromDisk();
mFilename = prefFile.getAbsolutePath();
init();
}
/**
@ -95,9 +117,103 @@ public final class XSharedPreferences implements SharedPreferences {
mFile = new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/" + prefFileName + ".xml");
}
mFilename = mFile.getAbsolutePath();
init();
}
private void tryRegisterWatcher() {
Path path = mFile.toPath();
if (mInstances.containsKey(path)) {
return;
}
try {
path.getParent().register(mWatcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
mInstances.put(path, new PrefsData(this));
Log.d(TAG, "Registered file watcher for " + path);
} catch (Exception e) {
Log.d(TAG, "Failed to register file watcher", e);
}
}
private void init() {
if (mDaemon == null || !mDaemon.isAlive()) {
mDaemon = new Thread() {
@Override
public void run() {
Log.d(TAG, "Daemon thread started");
while (true) {
WatchKey key;
try {
key = mWatcher.take();
} catch (InterruptedException ignored) {
return;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}
Path dir = (Path)key.watchable();
Path path = dir.resolve((Path)event.context());
String pathStr = path.toString();
Log.v(TAG, "File " + path.toString() + " event: " + kind.name());
// We react to both real and backup files due to rare race conditions
if (pathStr.endsWith(".bak")) {
if (kind != StandardWatchEventKinds.ENTRY_DELETE) {
continue;
} else {
pathStr = path.getFileName().toString();
path = dir.resolve(pathStr.substring(0, pathStr.length() - 4));
}
} else if (SELinuxHelper.getAppDataFileService().checkFileExists(pathStr + ".bak")) {
continue;
}
PrefsData data = mInstances.get(path);
if (data != null && data.hasChanged()) {
for (OnSharedPreferenceChangeListener l : data.mPrefs.mListeners.keySet()) {
try {
l.onSharedPreferenceChanged(data.mPrefs, null);
} catch (Throwable t) {
Log.e(TAG, "Fail in preference change listener", t);
}
}
}
}
key.reset();
}
}
};
mDaemon.setName(TAG + "-Daemon");
mDaemon.setDaemon(true);
mDaemon.start();
}
tryRegisterWatcher();
startLoadFromDisk();
}
private static long tryGetFileSize(String filename) {
try {
return SELinuxHelper.getAppDataFileService().getFileSize(filename);
} catch (IOException ignored) {
return 0;
}
}
private static byte[] tryGetFileHash(String filename) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
try (InputStream is = SELinuxHelper.getAppDataFileService().getFileInputStream(filename)) {
byte[] buf = new byte[4096];
int read;
while ((read = is.read(buf)) != -1) {
md.update(buf, 0, read);
}
}
return md.digest();
} catch (Exception ignored) {
return new byte[0];
}
}
/**
* Tries to make the preferences file world-readable.
*
@ -117,7 +233,11 @@ public final class XSharedPreferences implements SharedPreferences {
if (!mFile.exists()) // Just in case - the file should never be created if it doesn't exist.
return false;
return mFile.setReadable(true, false);
if (!mFile.setReadable(true, false))
return false;
tryRegisterWatcher();
return true;
}
/**
@ -194,8 +314,9 @@ public final class XSharedPreferences implements SharedPreferences {
* <p><strong>Warning:</strong> With enforcing SELinux, this call might be quite expensive.
*/
public synchronized void reload() {
if (hasFileChanged())
startLoadFromDisk();
if (hasFileChanged()) {
init();
}
}
/**
@ -329,22 +450,53 @@ public final class XSharedPreferences implements SharedPreferences {
throw new UnsupportedOperationException("read-only implementation");
}
/**
* @deprecated Not supported by this implementation.
*/
@Deprecated
@Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
throw new UnsupportedOperationException("listeners are not supported in this implementation");
synchronized(this) {
mListeners.put(listener, mContent);
}
}
/**
* @deprecated Not supported by this implementation.
*/
@Deprecated
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
throw new UnsupportedOperationException("listeners are not supported in this implementation");
synchronized(this) {
mListeners.remove(listener);
}
}
private static class PrefsData {
public final XSharedPreferences mPrefs;
private long mSize;
private byte[] mHash;
public PrefsData(XSharedPreferences prefs) {
mPrefs = prefs;
mSize = tryGetFileSize(prefs.mFilename);
mHash = tryGetFileHash(prefs.mFilename);
}
public boolean hasChanged() {
long size = tryGetFileSize(mPrefs.mFilename);
if (size < 1) {
Log.d(TAG, "Ignoring empty prefs file");
return false;
}
if (size != mSize) {
mSize = size;
mHash = tryGetFileHash(mPrefs.mFilename);
Log.d(TAG, "Prefs file size changed");
return true;
}
byte[] hash = tryGetFileHash(mPrefs.mFilename);
if (!Arrays.equals(hash, mHash)) {
mHash = hash;
Log.d(TAG, "Prefs file hash changed");
return true;
}
Log.d(TAG, "Prefs file not changed");
return false;
}
}
}