1 Commits

Author SHA1 Message Date
Ji qiu
13ee77e96f Merge pull request #19 from jiqiu2022/fix_bug
Fix bug
2025-06-27 16:52:25 +08:00
10 changed files with 90 additions and 412 deletions

View File

@@ -140,11 +140,7 @@ public class AppListFragment extends Fragment implements AppListAdapter.OnAppTog
RadioButton radioStandardInjection = dialogView.findViewById(R.id.radioStandardInjection); RadioButton radioStandardInjection = dialogView.findViewById(R.id.radioStandardInjection);
RadioButton radioRiruInjection = dialogView.findViewById(R.id.radioRiruInjection); RadioButton radioRiruInjection = dialogView.findViewById(R.id.radioRiruInjection);
RadioButton radioCustomLinkerInjection = dialogView.findViewById(R.id.radioCustomLinkerInjection); RadioButton radioCustomLinkerInjection = dialogView.findViewById(R.id.radioCustomLinkerInjection);
RadioGroup gadgetConfigGroup = dialogView.findViewById(R.id.gadgetConfigGroup); CheckBox checkboxEnableGadget = dialogView.findViewById(R.id.checkboxEnableGadget);
RadioButton radioNoGadget = dialogView.findViewById(R.id.radioNoGadget);
RadioButton radioUseGlobalGadget = dialogView.findViewById(R.id.radioUseGlobalGadget);
RadioButton radioUseCustomGadget = dialogView.findViewById(R.id.radioUseCustomGadget);
TextView tvGlobalGadgetInfo = dialogView.findViewById(R.id.tvGlobalGadgetInfo);
com.google.android.material.button.MaterialButton btnConfigureGadget = dialogView.findViewById(R.id.btnConfigureGadget); com.google.android.material.button.MaterialButton btnConfigureGadget = dialogView.findViewById(R.id.btnConfigureGadget);
appIcon.setImageDrawable(appInfo.getAppIcon()); appIcon.setImageDrawable(appInfo.getAppIcon());
@@ -162,71 +158,30 @@ public class AppListFragment extends Fragment implements AppListAdapter.OnAppTog
} }
// Load gadget config // Load gadget config
boolean useGlobalGadget = configManager.getAppUseGlobalGadget(appInfo.getPackageName()); ConfigManager.GadgetConfig gadgetConfig = configManager.getAppGadgetConfig(appInfo.getPackageName());
ConfigManager.GadgetConfig appSpecificGadget = configManager.getAppGadgetConfig(appInfo.getPackageName()); checkboxEnableGadget.setChecked(gadgetConfig != null);
ConfigManager.GadgetConfig globalGadget = configManager.getGlobalGadgetConfig(); btnConfigureGadget.setEnabled(gadgetConfig != null);
// Update global gadget info // Setup gadget listeners
if (globalGadget != null) { checkboxEnableGadget.setOnCheckedChangeListener((buttonView, isChecked) -> {
String info = "全局: " + globalGadget.gadgetName; btnConfigureGadget.setEnabled(isChecked);
if (globalGadget.mode.equals("server")) { if (!isChecked) {
info += " (端口: " + globalGadget.port + ")"; // Remove gadget config when unchecked
}
tvGlobalGadgetInfo.setText(info);
} else {
tvGlobalGadgetInfo.setText("未配置全局Gadget");
}
// Set initial radio selection
if (!useGlobalGadget && appSpecificGadget != null) {
radioUseCustomGadget.setChecked(true);
btnConfigureGadget.setVisibility(View.VISIBLE);
btnConfigureGadget.setEnabled(true);
} else if (useGlobalGadget && globalGadget != null) {
radioUseGlobalGadget.setChecked(true);
btnConfigureGadget.setVisibility(View.GONE);
} else {
radioNoGadget.setChecked(true);
btnConfigureGadget.setVisibility(View.GONE);
}
// Setup gadget radio group listener
gadgetConfigGroup.setOnCheckedChangeListener((group, checkedId) -> {
if (checkedId == R.id.radioNoGadget) {
btnConfigureGadget.setVisibility(View.GONE);
configManager.setAppUseGlobalGadget(appInfo.getPackageName(), false);
configManager.setAppGadgetConfig(appInfo.getPackageName(), null); configManager.setAppGadgetConfig(appInfo.getPackageName(), null);
} else if (checkedId == R.id.radioUseGlobalGadget) {
btnConfigureGadget.setVisibility(View.GONE);
configManager.setAppUseGlobalGadget(appInfo.getPackageName(), true);
configManager.setAppGadgetConfig(appInfo.getPackageName(), null);
} else if (checkedId == R.id.radioUseCustomGadget) {
btnConfigureGadget.setVisibility(View.VISIBLE);
btnConfigureGadget.setEnabled(true);
configManager.setAppUseGlobalGadget(appInfo.getPackageName(), false);
} }
}); });
// Configure button listener
btnConfigureGadget.setOnClickListener(v -> { btnConfigureGadget.setOnClickListener(v -> {
ConfigManager.GadgetConfig currentConfig = null; ConfigManager.GadgetConfig currentConfig = configManager.getAppGadgetConfig(appInfo.getPackageName());
if (!useGlobalGadget) {
currentConfig = configManager.getAppGadgetConfig(appInfo.getPackageName());
}
if (currentConfig == null) { if (currentConfig == null) {
currentConfig = new ConfigManager.GadgetConfig(); currentConfig = new ConfigManager.GadgetConfig();
} }
GadgetConfigDialog dialog = new GadgetConfigDialog( GadgetConfigDialog gadgetDialog = GadgetConfigDialog.newInstance(currentConfig);
getContext(), gadgetDialog.setOnGadgetConfigListener(config -> {
"配置" + appInfo.getAppName() + "的Gadget",
currentConfig,
config -> {
configManager.setAppUseGlobalGadget(appInfo.getPackageName(), false);
configManager.setAppGadgetConfig(appInfo.getPackageName(), config); configManager.setAppGadgetConfig(appInfo.getPackageName(), config);
} });
); gadgetDialog.show(getParentFragmentManager(), "gadget_config");
dialog.show();
}); });
// Setup SO list // Setup SO list
@@ -261,6 +216,16 @@ public class AppListFragment extends Fragment implements AppListAdapter.OnAppTog
} }
configManager.setAppInjectionMethod(appInfo.getPackageName(), selectedMethod); configManager.setAppInjectionMethod(appInfo.getPackageName(), selectedMethod);
// Save gadget config if enabled
if (checkboxEnableGadget.isChecked()) {
ConfigManager.GadgetConfig currentGadgetConfig = configManager.getAppGadgetConfig(appInfo.getPackageName());
if (currentGadgetConfig == null) {
// Create default config if not already configured
currentGadgetConfig = new ConfigManager.GadgetConfig();
configManager.setAppGadgetConfig(appInfo.getPackageName(), currentGadgetConfig);
}
}
// Save SO selection // Save SO selection
if (soListRecyclerView.getAdapter() != null) { if (soListRecyclerView.getAdapter() != null) {
SoSelectionAdapter adapter = (SoSelectionAdapter) soListRecyclerView.getAdapter(); SoSelectionAdapter adapter = (SoSelectionAdapter) soListRecyclerView.getAdapter();

View File

@@ -163,44 +163,20 @@ public class ConfigManager {
} }
} }
// Ensure SO storage directory exists
Shell.cmd("mkdir -p " + SO_STORAGE_DIR).exec();
Shell.cmd("chmod 755 " + SO_STORAGE_DIR).exec();
// Copy SO file to our storage // Copy SO file to our storage
Log.i(TAG, "Copying SO file from: " + originalPath + " to: " + storedPath);
Shell.Result result = Shell.cmd("cp \"" + originalPath + "\" \"" + storedPath + "\"").exec(); Shell.Result result = Shell.cmd("cp \"" + originalPath + "\" \"" + storedPath + "\"").exec();
if (result.isSuccess()) { if (result.isSuccess()) {
// Verify the file was actually copied
Shell.Result verifyResult = Shell.cmd("test -f \"" + storedPath + "\" && echo 'exists'").exec();
if (!verifyResult.isSuccess() || verifyResult.getOut().isEmpty()) {
Log.e(TAG, "File copy appeared successful but file not found at: " + storedPath);
return;
}
// Set proper permissions for SO file (readable and executable)
Shell.Result chmodResult = Shell.cmd("chmod 755 \"" + storedPath + "\"").exec();
if (!chmodResult.isSuccess()) {
Log.e(TAG, "Failed to set permissions on SO file: " + String.join("\n", chmodResult.getErr()));
}
SoFile soFile = new SoFile(); SoFile soFile = new SoFile();
soFile.name = fileName; soFile.name = fileName;
soFile.storedPath = storedPath; soFile.storedPath = storedPath;
soFile.originalPath = originalPath; soFile.originalPath = originalPath;
config.globalSoFiles.add(soFile); config.globalSoFiles.add(soFile);
Log.i(TAG, "Successfully added SO file: " + fileName + " to storage");
if (deleteOriginal) { if (deleteOriginal) {
Shell.cmd("rm \"" + originalPath + "\"").exec(); Shell.cmd("rm \"" + originalPath + "\"").exec();
Log.i(TAG, "Deleted original file: " + originalPath);
} }
saveConfig(); saveConfig();
} else {
Log.e(TAG, "Failed to copy SO file: " + String.join("\n", result.getErr()));
} }
} }
@@ -289,46 +265,11 @@ public class ConfigManager {
public GadgetConfig getAppGadgetConfig(String packageName) { public GadgetConfig getAppGadgetConfig(String packageName) {
AppConfig appConfig = config.perAppConfig.get(packageName); AppConfig appConfig = config.perAppConfig.get(packageName);
if (appConfig == null) { if (appConfig == null) {
// If no app config, return global gadget config return null;
return config.globalGadgetConfig;
} }
// If app is set to use global gadget, return global config
if (appConfig.useGlobalGadget) {
return config.globalGadgetConfig;
}
// Otherwise return app-specific gadget config
return appConfig.gadgetConfig; return appConfig.gadgetConfig;
} }
public GadgetConfig getGlobalGadgetConfig() {
return config.globalGadgetConfig;
}
public void setGlobalGadgetConfig(GadgetConfig gadgetConfig) {
config.globalGadgetConfig = gadgetConfig;
saveConfig();
}
public boolean getAppUseGlobalGadget(String packageName) {
AppConfig appConfig = config.perAppConfig.get(packageName);
if (appConfig == null) {
return true; // Default to use global
}
return appConfig.useGlobalGadget;
}
public void setAppUseGlobalGadget(String packageName, boolean useGlobal) {
AppConfig appConfig = config.perAppConfig.get(packageName);
if (appConfig == null) {
appConfig = new AppConfig();
config.perAppConfig.put(packageName, appConfig);
}
appConfig.useGlobalGadget = useGlobal;
saveConfig();
}
public void setAppGadgetConfig(String packageName, GadgetConfig gadgetConfig) { public void setAppGadgetConfig(String packageName, GadgetConfig gadgetConfig) {
AppConfig appConfig = config.perAppConfig.get(packageName); AppConfig appConfig = config.perAppConfig.get(packageName);
if (appConfig == null) { if (appConfig == null) {
@@ -464,36 +405,28 @@ public class ConfigManager {
// Create files directory in app's data dir // Create files directory in app's data dir
String filesDir = "/data/data/" + packageName + "/files"; String filesDir = "/data/data/" + packageName + "/files";
Log.i(TAG, "Deploying SO files to: " + filesDir); // Use su -c for better compatibility
Shell.Result mkdirResult = Shell.cmd("su -c 'mkdir -p " + filesDir + "'").exec();
// Create directory without su -c for better compatibility
Shell.Result mkdirResult = Shell.cmd("mkdir -p " + filesDir).exec();
if (!mkdirResult.isSuccess()) { if (!mkdirResult.isSuccess()) {
Log.e(TAG, "Failed to create directory: " + filesDir); Log.e(TAG, "Failed to create directory: " + filesDir);
Log.e(TAG, "Error: " + String.join("\n", mkdirResult.getErr())); Log.e(TAG, "Error: " + String.join("\n", mkdirResult.getErr()));
// Try without su -c
mkdirResult = Shell.cmd("mkdir -p " + filesDir).exec();
if (!mkdirResult.isSuccess()) {
Log.e(TAG, "Also failed without su -c");
return; return;
} }
}
// Set proper permissions and ownership for the files directory // Set proper permissions and ownership
Shell.cmd("chmod 771 " + filesDir).exec(); Shell.cmd("chmod 755 " + filesDir).exec();
// Get UID and GID for the package // Get UID for the package
Shell.Result uidResult = Shell.cmd("stat -c %u /data/data/" + packageName).exec(); Shell.Result uidResult = Shell.cmd("stat -c %u /data/data/" + packageName).exec();
String uid = ""; String uid = "";
if (uidResult.isSuccess() && !uidResult.getOut().isEmpty()) { if (uidResult.isSuccess() && !uidResult.getOut().isEmpty()) {
uid = uidResult.getOut().get(0).trim(); uid = uidResult.getOut().get(0).trim();
Log.i(TAG, "Package UID: " + uid); Log.i(TAG, "Package UID: " + uid);
// Set ownership of files directory to match app
Shell.Result chownDirResult = Shell.cmd("chown " + uid + ":" + uid + " \"" + filesDir + "\"").exec();
if (!chownDirResult.isSuccess()) {
Log.e(TAG, "Failed to set directory ownership: " + String.join("\n", chownDirResult.getErr()));
}
// Set SELinux context for the directory
Shell.cmd("chcon u:object_r:app_data_file:s0 \"" + filesDir + "\"").exec();
} else {
Log.e(TAG, "Failed to get package UID");
} }
// Copy each SO file configured for this app // Copy each SO file configured for this app
@@ -505,66 +438,39 @@ public class ConfigManager {
Shell.Result checkResult = Shell.cmd("test -f \"" + soFile.storedPath + "\" && echo 'exists'").exec(); Shell.Result checkResult = Shell.cmd("test -f \"" + soFile.storedPath + "\" && echo 'exists'").exec();
if (!checkResult.isSuccess() || checkResult.getOut().isEmpty()) { if (!checkResult.isSuccess() || checkResult.getOut().isEmpty()) {
Log.e(TAG, "Source SO file not found: " + soFile.storedPath); Log.e(TAG, "Source SO file not found: " + soFile.storedPath);
// Log more details about the missing file
Shell.Result lsResult = Shell.cmd("ls -la \"" + SO_STORAGE_DIR + "\"").exec();
Log.e(TAG, "Contents of SO storage dir: " + String.join("\n", lsResult.getOut()));
continue; continue;
} }
Log.i(TAG, "Copying: " + soFile.storedPath + " to " + destPath); Log.i(TAG, "Copying: " + soFile.storedPath + " to " + destPath);
// First, ensure the destination directory exists and has proper permissions // Copy file using cat to avoid permission issues
Shell.cmd("mkdir -p \"" + filesDir + "\"").exec(); String copyCmd = "cat \"" + soFile.storedPath + "\" > \"" + destPath + "\"";
Shell.cmd("chmod 755 \"" + filesDir + "\"").exec(); Shell.Result result = Shell.cmd(copyCmd).exec();
// Copy file using cp with force flag
Shell.Result result = Shell.cmd("cp -f \"" + soFile.storedPath + "\" \"" + destPath + "\"").exec();
if (!result.isSuccess()) { if (!result.isSuccess()) {
Log.e(TAG, "Failed with cp, trying cat method"); Log.e(TAG, "Failed with cat, trying cp");
Log.e(TAG, "cp error: " + String.join("\n", result.getErr())); // Fallback to cp
// Fallback to cat method result = Shell.cmd("cp -f \"" + soFile.storedPath + "\" \"" + destPath + "\"").exec();
result = Shell.cmd("cat \"" + soFile.storedPath + "\" > \"" + destPath + "\"").exec();
if (!result.isSuccess()) {
Log.e(TAG, "Also failed with cat method");
Log.e(TAG, "cat error: " + String.join("\n", result.getErr()));
}
} }
// Set permissions - SO files need to be readable and executable // Set permissions
Shell.Result chmodResult = Shell.cmd("chmod 755 \"" + destPath + "\"").exec(); Shell.cmd("chmod 755 \"" + destPath + "\"").exec();
if (!chmodResult.isSuccess()) {
Log.e(TAG, "Failed to set permissions: " + String.join("\n", chmodResult.getErr()));
}
// Set ownership to match the app's UID // Set ownership if we have the UID
if (!uid.isEmpty()) { if (!uid.isEmpty()) {
Shell.Result chownResult = Shell.cmd("chown " + uid + ":" + uid + " \"" + destPath + "\"").exec(); Shell.cmd("chown " + uid + ":" + uid + " \"" + destPath + "\"").exec();
if (!chownResult.isSuccess()) {
Log.e(TAG, "Failed to set ownership: " + String.join("\n", chownResult.getErr()));
// Try alternative method
Shell.cmd("chown " + uid + ".app_" + uid + " \"" + destPath + "\"").exec();
}
} }
// Set SELinux context to match app's data files // Verify the file was copied
Shell.Result contextResult = Shell.cmd("chcon u:object_r:app_data_file:s0 \"" + destPath + "\"").exec(); Shell.Result verifyResult = Shell.cmd("ls -la \"" + destPath + "\" 2>/dev/null").exec();
if (!contextResult.isSuccess()) {
Log.w(TAG, "Failed to set SELinux context (this may be normal on some devices)");
}
// Verify the file was copied with correct permissions
Shell.Result verifyResult = Shell.cmd("ls -laZ \"" + destPath + "\" 2>/dev/null").exec();
if (verifyResult.isSuccess() && !verifyResult.getOut().isEmpty()) {
Log.i(TAG, "Successfully deployed: " + String.join(" ", verifyResult.getOut()));
} else {
// Fallback verification without SELinux context
verifyResult = Shell.cmd("ls -la \"" + destPath + "\" 2>/dev/null").exec();
if (verifyResult.isSuccess() && !verifyResult.getOut().isEmpty()) { if (verifyResult.isSuccess() && !verifyResult.getOut().isEmpty()) {
Log.i(TAG, "Successfully deployed: " + String.join(" ", verifyResult.getOut())); Log.i(TAG, "Successfully deployed: " + String.join(" ", verifyResult.getOut()));
} else { } else {
Log.e(TAG, "Failed to verify SO file copy: " + destPath); Log.e(TAG, "Failed to verify SO file copy: " + destPath);
// Try another verification method
Shell.Result sizeResult = Shell.cmd("stat -c %s \"" + destPath + "\" 2>/dev/null").exec();
if (sizeResult.isSuccess() && !sizeResult.getOut().isEmpty()) {
Log.i(TAG, "File exists with size: " + sizeResult.getOut().get(0) + " bytes");
} }
} }
} }
@@ -572,9 +478,8 @@ public class ConfigManager {
Log.i(TAG, "Deployment complete for: " + packageName); Log.i(TAG, "Deployment complete for: " + packageName);
// Deploy gadget config if configured // Deploy gadget config if configured
ConfigManager.GadgetConfig gadgetToUse = getAppGadgetConfig(packageName); if (appConfig.gadgetConfig != null) {
if (gadgetToUse != null) { deployGadgetConfigFile(packageName, appConfig.gadgetConfig);
deployGadgetConfigFile(packageName, gadgetToUse);
} }
} }
@@ -622,9 +527,8 @@ public class ConfigManager {
} }
// Clean up gadget config file if exists // Clean up gadget config file if exists
ConfigManager.GadgetConfig gadgetToUse = getAppGadgetConfig(packageName); if (appConfig.gadgetConfig != null) {
if (gadgetToUse != null) { String gadgetConfigName = appConfig.gadgetConfig.gadgetName.replace(".so", ".config.so");
String gadgetConfigName = gadgetToUse.gadgetName.replace(".so", ".config.so");
String configPath = filesDir + "/" + gadgetConfigName; String configPath = filesDir + "/" + gadgetConfigName;
Shell.Result checkConfigResult = Shell.cmd("test -f \"" + configPath + "\" && echo 'exists'").exec(); Shell.Result checkConfigResult = Shell.cmd("test -f \"" + configPath + "\" && echo 'exists'").exec();
@@ -658,7 +562,6 @@ public class ConfigManager {
public int injectionDelay = 2; // Default 2 seconds public int injectionDelay = 2; // Default 2 seconds
public List<SoFile> globalSoFiles = new ArrayList<>(); public List<SoFile> globalSoFiles = new ArrayList<>();
public Map<String, AppConfig> perAppConfig = new HashMap<>(); public Map<String, AppConfig> perAppConfig = new HashMap<>();
public GadgetConfig globalGadgetConfig = null; // Global gadget configuration
} }
public static class AppConfig { public static class AppConfig {
@@ -666,7 +569,6 @@ public class ConfigManager {
public List<SoFile> soFiles = new ArrayList<>(); public List<SoFile> soFiles = new ArrayList<>();
public String injectionMethod = "standard"; // "standard", "riru" or "custom_linker" public String injectionMethod = "standard"; // "standard", "riru" or "custom_linker"
public GadgetConfig gadgetConfig = null; public GadgetConfig gadgetConfig = null;
public boolean useGlobalGadget = true; // Whether to use global gadget settings
} }
public static class SoFile { public static class SoFile {

View File

@@ -115,19 +115,18 @@ public class FileUtils {
// Make file readable // Make file readable
tempFile.setReadable(true, false); tempFile.setReadable(true, false);
// First copy to /data/local/tmp as a temporary location // Use root to copy to a more permanent location
String tempTargetPath = "/data/local/tmp/" + fileName; String targetPath = "/data/local/tmp/" + fileName;
Shell.Result result = Shell.cmd( Shell.Result result = Shell.cmd(
"cp \"" + tempFile.getAbsolutePath() + "\" \"" + tempTargetPath + "\"", "cp \"" + tempFile.getAbsolutePath() + "\" \"" + targetPath + "\"",
"chmod 644 \"" + tempTargetPath + "\"" "chmod 644 \"" + targetPath + "\""
).exec(); ).exec();
// Clean up temp file // Clean up temp file
tempFile.delete(); tempFile.delete();
if (result.isSuccess()) { if (result.isSuccess()) {
// Return the temporary path - it will be moved to the proper location by addGlobalSoFile return targetPath;
return tempTargetPath;
} else { } else {
Log.e(TAG, "Failed to copy file to /data/local/tmp/"); Log.e(TAG, "Failed to copy file to /data/local/tmp/");
return null; return null;

View File

@@ -57,7 +57,6 @@ public class GadgetConfigDialog extends DialogFragment {
// Configuration data // Configuration data
private ConfigManager.GadgetConfig config; private ConfigManager.GadgetConfig config;
private OnGadgetConfigListener listener; private OnGadgetConfigListener listener;
private String customTitle;
// Flag to prevent recursive updates // Flag to prevent recursive updates
private boolean isUpdatingUI = false; private boolean isUpdatingUI = false;
@@ -76,21 +75,6 @@ public class GadgetConfigDialog extends DialogFragment {
return dialog; return dialog;
} }
// Constructor for non-fragment usage
public GadgetConfigDialog(Context context, String title, ConfigManager.GadgetConfig config, OnGadgetConfigListener listener) {
// This constructor is for compatibility with direct dialog creation
// The actual dialog will be created in show() method
this.savedContext = context;
this.customTitle = title;
this.config = config != null ? config : new ConfigManager.GadgetConfig();
this.listener = listener;
}
// Default constructor required for DialogFragment
public GadgetConfigDialog() {
// Empty constructor required
}
public void setOnGadgetConfigListener(OnGadgetConfigListener listener) { public void setOnGadgetConfigListener(OnGadgetConfigListener listener) {
this.listener = listener; this.listener = listener;
} }
@@ -145,10 +129,8 @@ public class GadgetConfigDialog extends DialogFragment {
setupListeners(); setupListeners();
updateJsonPreview(); updateJsonPreview();
String title = customTitle != null ? customTitle : "Gadget 配置";
return new MaterialAlertDialogBuilder(getContext()) return new MaterialAlertDialogBuilder(getContext())
.setTitle(title) .setTitle("Gadget 配置")
.setView(view) .setView(view)
.setPositiveButton("保存", (dialog, which) -> saveConfig()) .setPositiveButton("保存", (dialog, which) -> saveConfig())
.setNegativeButton("取消", null) .setNegativeButton("取消", null)
@@ -585,50 +567,6 @@ public class GadgetConfigDialog extends DialogFragment {
.show(); .show();
} }
// Show method for non-fragment usage
public void show() {
if (getContext() == null) {
throw new IllegalStateException("Context is required for non-fragment usage");
}
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_gadget_config, null);
initViews(view);
// Initialize config if null
if (config == null) {
config = new ConfigManager.GadgetConfig();
}
loadConfig();
setupListeners();
updateJsonPreview();
String title = customTitle != null ? customTitle : "Gadget 配置";
new MaterialAlertDialogBuilder(getContext())
.setTitle(title)
.setView(view)
.setPositiveButton("保存", (dialog, which) -> saveConfig())
.setNegativeButton("取消", null)
.show();
}
private Context savedContext;
@Override
public Context getContext() {
Context context = super.getContext();
if (context == null) {
return savedContext;
}
return context;
}
// Constructor for non-fragment usage needs to save context
public void setContext(Context context) {
this.savedContext = context;
}
private String getPathFromUri(Uri uri) { private String getPathFromUri(Uri uri) {
String path = null; String path = null;

View File

@@ -11,8 +11,6 @@ import android.widget.RadioGroup;
import android.widget.EditText; import android.widget.EditText;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.text.Editable; import android.text.Editable;
import android.widget.TextView;
import android.widget.Button;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -30,8 +28,6 @@ public class SettingsFragment extends Fragment {
private RadioButton radioShowAll; private RadioButton radioShowAll;
private RadioButton radioHideSystem; private RadioButton radioHideSystem;
private EditText editInjectionDelay; private EditText editInjectionDelay;
private TextView tvGlobalGadgetStatus;
private Button btnConfigureGlobalGadget;
private ConfigManager configManager; private ConfigManager configManager;
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
@@ -63,8 +59,6 @@ public class SettingsFragment extends Fragment {
radioShowAll = view.findViewById(R.id.radio_show_all); radioShowAll = view.findViewById(R.id.radio_show_all);
radioHideSystem = view.findViewById(R.id.radio_hide_system); radioHideSystem = view.findViewById(R.id.radio_hide_system);
editInjectionDelay = view.findViewById(R.id.editInjectionDelay); editInjectionDelay = view.findViewById(R.id.editInjectionDelay);
tvGlobalGadgetStatus = view.findViewById(R.id.tvGlobalGadgetStatus);
btnConfigureGlobalGadget = view.findViewById(R.id.btnConfigureGlobalGadget);
configManager = new ConfigManager(getContext()); configManager = new ConfigManager(getContext());
} }
@@ -85,9 +79,6 @@ public class SettingsFragment extends Fragment {
// Load injection delay // Load injection delay
int injectionDelay = configManager.getInjectionDelay(); int injectionDelay = configManager.getInjectionDelay();
editInjectionDelay.setText(String.valueOf(injectionDelay)); editInjectionDelay.setText(String.valueOf(injectionDelay));
// Load global gadget status
updateGlobalGadgetStatus();
} }
private void setupListeners() { private void setupListeners() {
@@ -133,11 +124,6 @@ public class SettingsFragment extends Fragment {
} }
} }
}); });
// Global gadget configuration button
btnConfigureGlobalGadget.setOnClickListener(v -> {
showGlobalGadgetConfigDialog();
});
} }
public void setOnSettingsChangeListener(OnSettingsChangeListener listener) { public void setOnSettingsChangeListener(OnSettingsChangeListener listener) {
@@ -147,34 +133,4 @@ public class SettingsFragment extends Fragment {
public boolean isHideSystemApps() { public boolean isHideSystemApps() {
return sharedPreferences.getBoolean(KEY_HIDE_SYSTEM_APPS, false); return sharedPreferences.getBoolean(KEY_HIDE_SYSTEM_APPS, false);
} }
private void updateGlobalGadgetStatus() {
ConfigManager.GadgetConfig globalGadget = configManager.getGlobalGadgetConfig();
if (globalGadget != null) {
String status = "已配置: " + globalGadget.gadgetName;
if (globalGadget.mode.equals("server")) {
status += " (Server模式, 端口: " + globalGadget.port + ")";
} else {
status += " (Script模式)";
}
tvGlobalGadgetStatus.setText(status);
} else {
tvGlobalGadgetStatus.setText("未配置");
}
}
private void showGlobalGadgetConfigDialog() {
// Use existing GadgetConfigDialog
GadgetConfigDialog dialog = new GadgetConfigDialog(
getContext(),
"全局Gadget配置",
configManager.getGlobalGadgetConfig(),
gadgetConfig -> {
// Save global gadget configuration
configManager.setGlobalGadgetConfig(gadgetConfig);
updateGlobalGadgetStatus();
}
);
dialog.show();
}
} }

View File

@@ -88,67 +88,35 @@
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginBottom="16dp" /> android:layout_marginBottom="16dp" />
<TextView <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Gadget 配置"
android:textSize="14sp"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginBottom="8dp" />
<RadioGroup
android:id="@+id/gadgetConfigGroup"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="8dp"> android:layout_marginBottom="8dp">
<RadioButton <CheckBox
android:id="@+id/radioNoGadget" android:id="@+id/checkboxEnableGadget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="不使用Gadget"
android:checked="true" />
<RadioButton
android:id="@+id/radioUseGlobalGadget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="使用全局Gadget配置" />
<TextView
android:id="@+id/tvGlobalGadgetInfo"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="32dp" android:text="启用 Gadget 配置"
android:layout_marginBottom="4dp" android:layout_marginEnd="8dp" />
android:text="未配置全局Gadget"
android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp" />
<RadioButton
android:id="@+id/radioUseCustomGadget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="自定义Gadget配置" />
</RadioGroup>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnConfigureGadget" android:id="@+id/btnConfigureGadget"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="36dp"
android:text="配置Gadget" android:text="配置"
android:textSize="14sp" android:textSize="12sp"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:enabled="false" android:enabled="false" />
android:visibility="gone"
android:layout_marginBottom="8dp" /> </LinearLayout>
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Gadget可用于Frida调试可配置监听地址和端口" android:text="Gadget可用于Frida调试勾选后可配置监听地址和端口"
android:textSize="12sp" android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:layout_marginBottom="16dp" /> android:layout_marginBottom="16dp" />

View File

@@ -143,53 +143,6 @@
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
<!-- 全局Gadget配置 -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="全局Gadget配置"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="配置全局默认的Gadget设置应用可以选择使用或覆盖"
android:textSize="14sp"
android:textColor="@android:color/darker_gray"
android:layout_marginBottom="12dp" />
<TextView
android:id="@+id/tvGlobalGadgetStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="未配置"
android:textSize="14sp"
android:layout_marginBottom="8dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfigureGlobalGadget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="配置全局Gadget"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- 其他设置可以在这里添加 --> <!-- 其他设置可以在这里添加 -->
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -88,8 +88,12 @@ void load_so_file_custom_linker(const char *game_data_dir, const Config::SoFile
void hack_thread_func(const char *game_data_dir, const char *package_name, JavaVM *vm) { void hack_thread_func(const char *game_data_dir, const char *package_name, JavaVM *vm) {
LOGI("Hack thread started for package: %s", package_name); LOGI("Hack thread started for package: %s", package_name);
// Note: Delay is now handled in main thread before this thread is created // Get injection delay from config
LOGI("Starting injection immediately (delay already applied in main thread)"); int delay = Config::getInjectionDelay();
LOGI("Waiting %d seconds before injection", delay);
// Wait for app to initialize and files to be copied
sleep(delay);
// Get injection method for this app // Get injection method for this app
Config::InjectionMethod method = Config::getAppInjectionMethod(package_name); Config::InjectionMethod method = Config::getAppInjectionMethod(package_name);
@@ -131,5 +135,5 @@ void hack_prepare(const char *game_data_dir, const char *package_name, void *dat
LOGI("hack_prepare called for package: %s, dir: %s", package_name, game_data_dir); LOGI("hack_prepare called for package: %s, dir: %s", package_name, game_data_dir);
std::thread hack_thread(hack_thread_func, game_data_dir, package_name, vm); std::thread hack_thread(hack_thread_func, game_data_dir, package_name, vm);
hack_thread.join(); hack_thread.detach();
} }

View File

@@ -45,13 +45,6 @@ public:
// Get JavaVM // Get JavaVM
JavaVM *vm = nullptr; JavaVM *vm = nullptr;
if (env->GetJavaVM(&vm) == JNI_OK) { if (env->GetJavaVM(&vm) == JNI_OK) {
// Get injection delay from config
int delay = Config::getInjectionDelay();
LOGI("Main thread blocking for %d seconds before injection", delay);
// Block main thread for the delay period
sleep(delay);
// Then start hack thread with JavaVM // Then start hack thread with JavaVM
std::thread hack_thread(hack_prepare, _data_dir, _package_name, data, length, vm); std::thread hack_thread(hack_prepare, _data_dir, _package_name, data, length, vm);
hack_thread.detach(); hack_thread.detach();

View File

@@ -17,8 +17,8 @@ set(SOURCES
find_library(log-lib log) find_library(log-lib log)
# Build as static library to be linked into main module # Build as shared library
add_library(mylinker STATIC ${SOURCES}) add_library(mylinker SHARED ${SOURCES})
target_link_libraries(mylinker ${log-lib}) target_link_libraries(mylinker ${log-lib})