This commit is contained in:
isPointer
2026-06-24 09:07:29 +06:00
committed by GitHub
parent 2983f474ed
commit e80e6193ed
9 changed files with 593 additions and 368 deletions
@@ -129,7 +129,7 @@ public class MethodMaker {
public static ImmutableMethod createClinit() {
MethodImplementationBuilder builder = new MethodImplementationBuilder(1);
builder.addInstruction(new BuilderInstruction21c(Opcode.CONST_STRING, 0, new ImmutableStringReference("RePatcher v1.4.14")));
builder.addInstruction(new BuilderInstruction21c(Opcode.CONST_STRING, 0, new ImmutableStringReference("RePatcher v1.5.20")));
builder.addInstruction(new BuilderInstruction35c(Opcode.INVOKE_STATIC, 0, 0, 0, 0, 0, 0, new ImmutableMethodReference(PairipClass.STARTUP_LAUNCHER.type, "launch", null, "V")));
builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
return new ImmutableMethod(PairipClass.APPLICATION.type, "<clinit>", null, "V", AccessFlags.STATIC.getValue() | AccessFlags.CONSTRUCTOR.getValue(), null, null, builder.getMethodImplementation());
@@ -1,343 +1,343 @@
package com.antik.DexPatcher.Translation;
import com.antik.DexPatcher.Translation.antik.PairipClass;
import com.antik.Main;
import com.reandroid.apk.ApkModule;
import com.reandroid.archive.ByteInputSource;
import com.reandroid.archive.InputSource;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.writer.io.MemoryDataStore;
import org.jf.dexlib2.writer.pool.DexPool;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class TranslationPatcher {
private static final byte[] DEX_MAGIC = new byte[]{0x64, 0x65, 0x78, 0x0A};
private static final byte[] PAIRIP_ASSET_HEADER = new byte[]{0x00, 0x49, 0x41, 0x50, 0x02};
public static void patch(ApkModule m, File jsonFile) throws Exception {
System.out.println("[INFO] Loading translation file: " + jsonFile.getName());
TranslationData data = new TranslationData(jsonFile);
TranslationRewriter rewriter = new TranslationRewriter(data);
List<String> dexNames = new ArrayList<>();
for (InputSource s : m.getInputSources()) {
if (s.getName().endsWith(".dex")) {
dexNames.add(s.getName());
}
}
Set<String> existingDexNames = new HashSet<>(dexNames);
Set<String> existingClassTypes = new HashSet<>();
for (String dn : dexNames) {
InputSource s = m.getInputSource(dn);
if (s == null) continue;
try (InputStream input = s.openStream()) {
DexFile dexFile = loadDexFile(readAllBytes(input), "trans_scan");
for (ClassDef classDef : dexFile.getClasses()) {
existingClassTypes.add(classDef.getType());
}
}
}
List<PendingDex> extraDexes = new ArrayList<>();
if (data.hasMethods) {
extraDexes.addAll(collectEmbeddedAssetDexes(m, existingClassTypes));
PendingDex restoreMethodDex = loadRestoreMethodDex(existingClassTypes);
if (restoreMethodDex != null) {
extraDexes.add(restoreMethodDex);
}
}
for (String dn : dexNames) {
InputSource s = m.getInputSource(dn);
if (s == null) continue;
byte[] d_bs;
try (InputStream i = s.openStream()) {
d_bs = readAllBytes(i);
}
DexFile d_f = loadDexFile(d_bs, "trans");
List<ClassDef> cds = new ArrayList<>();
boolean mod = false;
for (ClassDef cd : d_f.getClasses()) {
String type = cd.getType();
if (type.startsWith("Lcom/pairip/")) {
if (PairipClass.contains(type)) {
System.out.println("[INFO] Patching " + type + " in " + dn);
cds.add(rewriter.rewrite(cd));
mod = true;
} else if (!type.equals("Lcom/pairip/PairipLog;") && !type.equals("Lcom/pairip/RestoreMethod;")) {
System.out.println("[INFO] Removing class: " + type);
mod = true;
} else {
cds.add(cd);
}
} else {
cds.add(cd);
}
}
if (mod) {
MemoryDataStore ds = new MemoryDataStore();
DexPool dp = new DexPool(Opcodes.getDefault());
for (ClassDef c : cds) {
dp.internClass(c);
}
dp.writeTo(ds);
byte[] r_bs = Arrays.copyOf(ds.getData(), ds.getSize());
m.add(new ByteInputSource(r_bs, dn));
System.out.println("[BUILD] Patched translation in " + dn);
}
}
int nextDexNumber = nextDexNumber(existingDexNames);
for (PendingDex pendingDex : extraDexes) {
if (!pendingDex.canAddAgainst(existingClassTypes)) {
if (!pendingDex.isFullyPresentIn(existingClassTypes)) {
System.err.println("[WARN] Skipping " + pendingDex.sourceName + " due to partial class overlap");
}
continue;
}
String dexEntryName = nextDexEntryName(existingDexNames, nextDexNumber);
nextDexNumber = dexNumberOf(dexEntryName) + 1;
m.add(new ByteInputSource(pendingDex.dexBytes, dexEntryName));
existingDexNames.add(dexEntryName);
existingClassTypes.addAll(pendingDex.classTypes);
System.out.println("[INFO] Added " + dexEntryName + " from " + pendingDex.sourceName);
}
List<String> libFiles = new ArrayList<>();
for (InputSource source : m.getInputSources()) {
if (source.getName().contains("libpairipcore.so")) {
libFiles.add(source.getName());
}
}
for (String libFile : libFiles) {
try {
m.getZipEntryMap().remove(libFile);
System.out.println("[INFO] Removing lib: " + libFile);
} catch (Exception e) {
System.err.println("[WARN] Could not remove lib: " + libFile);
}
}
}
private static byte[] readAllBytes(InputStream input) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
int len;
while ((len = input.read(buf)) != -1) {
baos.write(buf, 0, len);
}
return baos.toByteArray();
}
private static DexFile loadDexFile(byte[] dexBytes, String prefix) throws Exception {
File tempDex = File.createTempFile(prefix, ".dex");
try {
Files.write(tempDex.toPath(), dexBytes);
return DexFileFactory.loadDexFile(tempDex, Opcodes.getDefault());
} finally {
tempDex.delete();
}
}
private static List<PendingDex> collectEmbeddedAssetDexes(ApkModule module, Set<String> existingClassTypes) throws Exception {
List<PendingDex> pendingDexes = new ArrayList<>();
for (InputSource source : module.getInputSources()) {
if (!isTopLevelAsset(source.getName())) {
continue;
}
byte[] assetBytes;
try (InputStream input = source.openStream()) {
assetBytes = readAllBytes(input);
}
if (!startsWith(assetBytes, PAIRIP_ASSET_HEADER)) {
continue;
}
int dexStart = findEmbeddedDexStart(assetBytes);
if (dexStart < 0) {
continue;
}
try {
byte[] dexBytes = extractEmbeddedDex(assetBytes, dexStart);
PendingDex pendingDex = pendingDexFromBytes(source.getName(), dexBytes, "asset_dex");
if (!pendingDex.isFullyPresentIn(existingClassTypes)) {
pendingDexes.add(pendingDex);
}
} catch (Exception ignored) {
}
}
pendingDexes.sort((left, right) -> {
int bySize = Integer.compare(right.classTypes.size(), left.classTypes.size());
if (bySize != 0) {
return bySize;
}
return left.sourceName.compareTo(right.sourceName);
});
return pendingDexes;
}
private static PendingDex loadRestoreMethodDex(Set<String> existingClassTypes) throws Exception {
try (InputStream input = Main.class.getResourceAsStream("/restoreMethod.dex")) {
if (input == null) {
throw new FileNotFoundException("Missing resource: /restoreMethod.dex");
}
PendingDex pendingDex = pendingDexFromBytes("restoreMethod.dex", readAllBytes(input), "restore_method");
System.out.println("[INFO] Loaded classes from restoreMethod.dex");
return pendingDex.isFullyPresentIn(existingClassTypes) ? null : pendingDex;
}
}
private static PendingDex pendingDexFromBytes(String sourceName, byte[] dexBytes, String prefix) throws Exception {
DexFile dexFile = loadDexFile(dexBytes, prefix);
Set<String> classTypes = new HashSet<>();
for (ClassDef classDef : dexFile.getClasses()) {
classTypes.add(classDef.getType());
}
return new PendingDex(sourceName, dexBytes, classTypes);
}
private static boolean isTopLevelAsset(String entryName) {
if (!entryName.startsWith("assets/")) {
return false;
}
return entryName.indexOf('/', "assets/".length()) < 0;
}
private static boolean startsWith(byte[] bytes, byte[] prefix) {
if (bytes.length < prefix.length) {
return false;
}
for (int i = 0; i < prefix.length; i++) {
if (bytes[i] != prefix[i]) {
return false;
}
}
return true;
}
private static int findEmbeddedDexStart(byte[] bytes) {
for (int i = 0; i <= bytes.length - DEX_MAGIC.length; i++) {
boolean matches = true;
for (int j = 0; j < DEX_MAGIC.length; j++) {
if (bytes[i + j] != DEX_MAGIC[j]) {
matches = false;
break;
}
}
if (matches) {
return i;
}
}
return -1;
}
private static byte[] extractEmbeddedDex(byte[] assetBytes, int dexStart) {
int headerOffset = dexStart + 0x20;
if (headerOffset + 4 > assetBytes.length) {
throw new IllegalArgumentException("Embedded dex header truncated");
}
int declaredFileSize = readLittleEndianInt(assetBytes, headerOffset);
if (declaredFileSize <= 0) {
throw new IllegalArgumentException("Embedded dex declared invalid file size: " + declaredFileSize);
}
int dexEnd = dexStart + declaredFileSize;
if (dexEnd > assetBytes.length) {
throw new IllegalArgumentException("Embedded dex declared size exceeds asset bounds: " + declaredFileSize);
}
return Arrays.copyOfRange(assetBytes, dexStart, dexEnd);
}
private static int readLittleEndianInt(byte[] bytes, int offset) {
return (bytes[offset] & 0xFF) | ((bytes[offset + 1] & 0xFF) << 8) | ((bytes[offset + 2] & 0xFF) << 16) | ((bytes[offset + 3] & 0xFF) << 24);
}
private static int nextDexNumber(Set<String> dexEntryNames) {
int next = 1;
for (String name : dexEntryNames) {
next = Math.max(next, dexNumberOf(name) + 1);
}
return next;
}
private static int dexNumberOf(String dexEntryName) {
if ("classes.dex".equals(dexEntryName)) {
return 1;
}
if (!dexEntryName.startsWith("classes") || !dexEntryName.endsWith(".dex")) {
return -1;
}
String middle = dexEntryName.substring("classes".length(), dexEntryName.length() - ".dex".length());
if (middle.isEmpty()) {
return 1;
}
try {
return Integer.parseInt(middle);
} catch (NumberFormatException ignored) {
return -1;
}
}
private static String nextDexEntryName(Set<String> dexEntryNames, int startNumber) {
int number = Math.max(startNumber, 1);
while (true) {
String candidate = number == 1 ? "classes.dex" : "classes" + number + ".dex";
if (!dexEntryNames.contains(candidate)) {
return candidate;
}
number++;
}
}
private static final class PendingDex {
private final String sourceName;
private final byte[] dexBytes;
private final Set<String> classTypes;
private PendingDex(String sourceName, byte[] dexBytes, Set<String> classTypes) {
this.sourceName = sourceName;
this.dexBytes = dexBytes;
this.classTypes = classTypes;
}
private boolean isFullyPresentIn(Set<String> existingClassTypes) {
return existingClassTypes.containsAll(classTypes);
}
private boolean canAddAgainst(Set<String> existingClassTypes) {
for (String classType : classTypes) {
if (existingClassTypes.contains(classType)) {
return false;
}
}
return true;
}
}
}
package com.antik.DexPatcher.Translation;
import com.antik.DexPatcher.Translation.antik.PairipClass;
import com.antik.Main;
import com.reandroid.apk.ApkModule;
import com.reandroid.archive.ByteInputSource;
import com.reandroid.archive.InputSource;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.writer.io.MemoryDataStore;
import org.jf.dexlib2.writer.pool.DexPool;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class TranslationPatcher {
private static final byte[] DEX_MAGIC = new byte[]{0x64, 0x65, 0x78, 0x0A};
private static final byte[] PAIRIP_ASSET_HEADER = new byte[]{0x00, 0x49, 0x41, 0x50, 0x02};
public static void patch(ApkModule m, File jsonFile) throws Exception {
System.out.println("[INFO] Loading translation file: " + jsonFile.getName());
TranslationData data = new TranslationData(jsonFile);
TranslationRewriter rewriter = new TranslationRewriter(data);
List<String> dexNames = new ArrayList<>();
for (InputSource s : m.getInputSources()) {
if (s.getName().endsWith(".dex")) {
dexNames.add(s.getName());
}
}
Set<String> existingDexNames = new HashSet<>(dexNames);
Set<String> existingClassTypes = new HashSet<>();
for (String dn : dexNames) {
InputSource s = m.getInputSource(dn);
if (s == null) continue;
try (InputStream input = s.openStream()) {
DexFile dexFile = loadDexFile(readAllBytes(input), "trans_scan");
for (ClassDef classDef : dexFile.getClasses()) {
existingClassTypes.add(classDef.getType());
}
}
}
List<PendingDex> extraDexes = new ArrayList<>();
if (data.hasMethods) {
extraDexes.addAll(collectEmbeddedAssetDexes(m, existingClassTypes));
PendingDex restoreMethodDex = loadRestoreMethodDex(existingClassTypes);
if (restoreMethodDex != null) {
extraDexes.add(restoreMethodDex);
}
}
for (String dn : dexNames) {
InputSource s = m.getInputSource(dn);
if (s == null) continue;
byte[] d_bs;
try (InputStream i = s.openStream()) {
d_bs = readAllBytes(i);
}
DexFile d_f = loadDexFile(d_bs, "trans");
List<ClassDef> cds = new ArrayList<>();
boolean mod = false;
for (ClassDef cd : d_f.getClasses()) {
String type = cd.getType();
if (type.startsWith("Lcom/pairip/")) {
if (PairipClass.contains(type)) {
System.out.println("[INFO] Patching " + type + " in " + dn);
cds.add(rewriter.rewrite(cd));
mod = true;
} else if (!type.equals("Lcom/pairip/PairipLog;") && !type.equals("Lcom/pairip/RestoreMethod;")) {
System.out.println("[INFO] Removing class: " + type);
mod = true;
} else {
cds.add(cd);
}
} else {
cds.add(cd);
}
}
if (mod) {
MemoryDataStore ds = new MemoryDataStore();
DexPool dp = new DexPool(Opcodes.getDefault());
for (ClassDef c : cds) {
dp.internClass(c);
}
dp.writeTo(ds);
byte[] r_bs = Arrays.copyOf(ds.getData(), ds.getSize());
m.add(new ByteInputSource(r_bs, dn));
System.out.println("[BUILD] Patched translation in " + dn);
}
}
int nextDexNumber = nextDexNumber(existingDexNames);
for (PendingDex pendingDex : extraDexes) {
if (!pendingDex.canAddAgainst(existingClassTypes)) {
if (!pendingDex.isFullyPresentIn(existingClassTypes)) {
System.err.println("[WARN] Skipping " + pendingDex.sourceName + " due to partial class overlap");
}
continue;
}
String dexEntryName = nextDexEntryName(existingDexNames, nextDexNumber);
nextDexNumber = dexNumberOf(dexEntryName) + 1;
m.add(new ByteInputSource(pendingDex.dexBytes, dexEntryName));
existingDexNames.add(dexEntryName);
existingClassTypes.addAll(pendingDex.classTypes);
System.out.println("[INFO] Added " + dexEntryName + " from " + pendingDex.sourceName);
}
List<String> libFiles = new ArrayList<>();
for (InputSource source : m.getInputSources()) {
if (source.getName().contains("libpairipcore.so")) {
libFiles.add(source.getName());
}
}
for (String libFile : libFiles) {
try {
m.getZipEntryMap().remove(libFile);
System.out.println("[INFO] Removing lib: " + libFile);
} catch (Exception e) {
System.err.println("[WARN] Could not remove lib: " + libFile);
}
}
}
private static byte[] readAllBytes(InputStream input) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
int len;
while ((len = input.read(buf)) != -1) {
baos.write(buf, 0, len);
}
return baos.toByteArray();
}
private static DexFile loadDexFile(byte[] dexBytes, String prefix) throws Exception {
File tempDex = File.createTempFile(prefix, ".dex");
try {
Files.write(tempDex.toPath(), dexBytes);
return DexFileFactory.loadDexFile(tempDex, Opcodes.getDefault());
} finally {
tempDex.delete();
}
}
private static List<PendingDex> collectEmbeddedAssetDexes(ApkModule module, Set<String> existingClassTypes) throws Exception {
List<PendingDex> pendingDexes = new ArrayList<>();
for (InputSource source : module.getInputSources()) {
if (!isTopLevelAsset(source.getName())) {
continue;
}
byte[] assetBytes;
try (InputStream input = source.openStream()) {
assetBytes = readAllBytes(input);
}
if (!startsWith(assetBytes, PAIRIP_ASSET_HEADER)) {
continue;
}
int dexStart = findEmbeddedDexStart(assetBytes);
if (dexStart < 0) {
continue;
}
try {
byte[] dexBytes = extractEmbeddedDex(assetBytes, dexStart);
PendingDex pendingDex = pendingDexFromBytes(source.getName(), dexBytes, "asset_dex");
if (!pendingDex.isFullyPresentIn(existingClassTypes)) {
pendingDexes.add(pendingDex);
}
} catch (Exception ignored) {
}
}
pendingDexes.sort((left, right) -> {
int bySize = Integer.compare(right.classTypes.size(), left.classTypes.size());
if (bySize != 0) {
return bySize;
}
return left.sourceName.compareTo(right.sourceName);
});
return pendingDexes;
}
private static PendingDex loadRestoreMethodDex(Set<String> existingClassTypes) throws Exception {
try (InputStream input = Main.class.getResourceAsStream("/restoreMethod.dex")) {
if (input == null) {
throw new FileNotFoundException("Missing resource: /restoreMethod.dex");
}
PendingDex pendingDex = pendingDexFromBytes("restoreMethod.dex", readAllBytes(input), "restore_method");
System.out.println("[INFO] Loaded classes from restoreMethod.dex");
return pendingDex.isFullyPresentIn(existingClassTypes) ? null : pendingDex;
}
}
private static PendingDex pendingDexFromBytes(String sourceName, byte[] dexBytes, String prefix) throws Exception {
DexFile dexFile = loadDexFile(dexBytes, prefix);
Set<String> classTypes = new HashSet<>();
for (ClassDef classDef : dexFile.getClasses()) {
classTypes.add(classDef.getType());
}
return new PendingDex(sourceName, dexBytes, classTypes);
}
private static boolean isTopLevelAsset(String entryName) {
if (!entryName.startsWith("assets/")) {
return false;
}
return entryName.indexOf('/', "assets/".length()) < 0;
}
private static boolean startsWith(byte[] bytes, byte[] prefix) {
if (bytes.length < prefix.length) {
return false;
}
for (int i = 0; i < prefix.length; i++) {
if (bytes[i] != prefix[i]) {
return false;
}
}
return true;
}
private static int findEmbeddedDexStart(byte[] bytes) {
for (int i = 0; i <= bytes.length - DEX_MAGIC.length; i++) {
boolean matches = true;
for (int j = 0; j < DEX_MAGIC.length; j++) {
if (bytes[i + j] != DEX_MAGIC[j]) {
matches = false;
break;
}
}
if (matches) {
return i;
}
}
return -1;
}
private static byte[] extractEmbeddedDex(byte[] assetBytes, int dexStart) {
int headerOffset = dexStart + 0x20;
if (headerOffset + 4 > assetBytes.length) {
throw new IllegalArgumentException("Embedded dex header truncated");
}
int declaredFileSize = readLittleEndianInt(assetBytes, headerOffset);
if (declaredFileSize <= 0) {
throw new IllegalArgumentException("Embedded dex declared invalid file size: " + declaredFileSize);
}
int dexEnd = dexStart + declaredFileSize;
if (dexEnd > assetBytes.length) {
throw new IllegalArgumentException("Embedded dex declared size exceeds asset bounds: " + declaredFileSize);
}
return Arrays.copyOfRange(assetBytes, dexStart, dexEnd);
}
private static int readLittleEndianInt(byte[] bytes, int offset) {
return (bytes[offset] & 0xFF) | ((bytes[offset + 1] & 0xFF) << 8) | ((bytes[offset + 2] & 0xFF) << 16) | ((bytes[offset + 3] & 0xFF) << 24);
}
private static int nextDexNumber(Set<String> dexEntryNames) {
int next = 1;
for (String name : dexEntryNames) {
next = Math.max(next, dexNumberOf(name) + 1);
}
return next;
}
private static int dexNumberOf(String dexEntryName) {
if ("classes.dex".equals(dexEntryName)) {
return 1;
}
if (!dexEntryName.startsWith("classes") || !dexEntryName.endsWith(".dex")) {
return -1;
}
String middle = dexEntryName.substring("classes".length(), dexEntryName.length() - ".dex".length());
if (middle.isEmpty()) {
return 1;
}
try {
return Integer.parseInt(middle);
} catch (NumberFormatException ignored) {
return -1;
}
}
private static String nextDexEntryName(Set<String> dexEntryNames, int startNumber) {
int number = Math.max(startNumber, 1);
while (true) {
String candidate = number == 1 ? "classes.dex" : "classes" + number + ".dex";
if (!dexEntryNames.contains(candidate)) {
return candidate;
}
number++;
}
}
private static final class PendingDex {
private final String sourceName;
private final byte[] dexBytes;
private final Set<String> classTypes;
private PendingDex(String sourceName, byte[] dexBytes, Set<String> classTypes) {
this.sourceName = sourceName;
this.dexBytes = dexBytes;
this.classTypes = classTypes;
}
private boolean isFullyPresentIn(Set<String> existingClassTypes) {
return existingClassTypes.containsAll(classTypes);
}
private boolean canAddAgainst(Set<String> existingClassTypes) {
for (String classType : classTypes) {
if (existingClassTypes.contains(classType)) {
return false;
}
}
return true;
}
}
}
+69 -23
View File
@@ -4,11 +4,14 @@ import com.antik.DexPatcher.DexPatcher;
import com.antik.DexPatcher.Translation.TranslationPatcher;
import com.antik.crc32.crc32;
import com.antik.manifest.manifestP;
import com.antik.root.*;
import com.antik.root.PackageM.Installer;
import com.antik.root.PackageM.uninstaller;
import com.antik.ui.*;
import com.antik.utils.*;
import com.reandroid.apk.ApkBundle;
import com.reandroid.apk.ApkModule;
import com.reandroid.archive.WriteProgress;
import java.io.*;
import java.nio.file.Files;
@@ -22,12 +25,15 @@ public class Main {
String inputPath = null;
String translatePath = null;
boolean R_mod = false;
for (int i = 0; i < args.length; i++) {
if ("-i".equals(args[i]) && i + 1 < args.length) {
inputPath = args[i + 1];
} else if ("-t".equals(args[i]) && i + 1 < args.length) {
translatePath = args[i + 1];
} else if ("-r".equals(args[i])) {
R_mod = true;
}
}
@@ -39,37 +45,37 @@ public class Main {
File inputApk = new File(inputPath);
if (!inputApk.exists()) {
System.err.println("Input file not found: " + inputPath);
System.err.println("Input file not found : " + inputPath);
return;
}
banner.banner();
File tempDir = null;
File T_dIR = null;
try {
ApkModule module;
File mergedApkFile;
if (inputPath.endsWith(".apk")) {
System.out.println("[INFO] Loading APK...");
System.out.println("[INFO] Loading APK ");
module = ApkModule.loadApkFile(inputApk);
mergedApkFile = inputApk;
} else {
tempDir = Files.createTempDirectory("antik_merge").toFile();
System.out.println("[MERGE] Extracting APKS...");
AntikUtils.ex_apks(inputApk, tempDir);
T_dIR = Files.createTempDirectory("antik_merge").toFile();
System.out.println("[MERGE] Extracting APKS ");
AntikUtils.ex_apks(inputApk, T_dIR);
System.out.println("[MERGE] Merging APK...");
System.out.println("[MERGE] Merging APK ");
ApkBundle bundle = new ApkBundle();
bundle.loadApkDirectory(tempDir);
bundle.loadApkDirectory(T_dIR);
module = bundle.mergeModules();
System.out.println("[INFO] Patching AndroidManifest.xml");
try {
manifestP.patch(module);
} catch (Exception e) {
System.err.println("[ERROR] Manifest patching failed: " + e.getMessage());
System.err.println("[ERROR] Manifest patching failed : " + e.getMessage());
}
String name = inputApk.getName();
@@ -77,7 +83,7 @@ public class Main {
name = (dot > 0 ? name.substring(0, dot) : name) + "_merged.apk";
mergedApkFile = new File(output.get_out(inputApk, name));
System.out.println("[BUILD] Writing merged APK...");
System.out.println("[BUILD] Writing merged APK ");
loading.progress(module, mergedApkFile);
System.out.println("[MERGE] APK merged successfully: " + mergedApkFile.getAbsolutePath());
}
@@ -85,7 +91,7 @@ public class Main {
if (translatePath != null) {
File jsonFile = new File(translatePath);
if (jsonFile.exists()) {
System.out.println("[INFO] Starting Translation Patch...");
System.out.println("[INFO] Starting Translation Patch ");
TranslationPatcher.patch(module, jsonFile);
String tn = mergedApkFile.getName();
@@ -93,19 +99,18 @@ public class Main {
tn = (td > 0 ? tn.substring(0, td) : tn) + "_translated.apk";
File transFile = new File(output.get_out(inputApk, tn));
System.out.println("[BUILD] Building Translated APK...");
System.out.println("[BUILD] Building Translated APK ");
output.write(module, transFile);
System.out.println("[BUILD] Translated APK built at: " + transFile.getAbsolutePath());
System.out.println("[BUILD] Translated APK built at : " + transFile.getAbsolutePath());
} else {
System.err.println("[ERROR] Translation file not found: " + translatePath);
System.err.println("[ERROR] Translation file not found : " + translatePath);
}
} else if (!inputPath.endsWith(".apk")) {
// Default logging patch only if merging and no translation requested
System.out.println("[INFO] Patching classes.dex for logging...");
System.out.println("[INFO] Patching classes.dex for logging ");
try {
DexPatcher.patch(module);
} catch (Exception e) {
System.err.println("[ERROR] Patching failed: " + e.getMessage());
System.err.println("[ERROR] Patching failed : " + e.getMessage());
}
String pn = mergedApkFile.getName();
@@ -113,20 +118,61 @@ public class Main {
pn = (pd > 0 ? pn.substring(0, pd) : pn) + "_pairip.apk";
File paiFile = new File(output.get_out(inputApk, pn));
System.out.println("[BUILD] Building Logging APK...");
System.out.println("[BUILD] Building Logging APK ");
output.write(module, paiFile);
crc32.patch(mergedApkFile, paiFile);
System.out.println("[BUILD] Logging APK built at: " + paiFile.getAbsolutePath());
System.out.println("[BUILD] Logging APK built at : " + paiFile.getAbsolutePath());
if (R_mod) {
String pkg = module.getPackageName();
System.out.println("[ROOT] Starting Root Mode for package : " + pkg);
uninstaller.uninstall(pkg);
Installer.install(paiFile);
if (T_dIR != null) {
File originalBase = new File(T_dIR, "base.apk");
if (originalBase.exists()) {
Base.replaceBase(pkg, originalBase);
launchApp.launchApp(pkg);
File pulledJson = new File(output.get_out(inputApk, "pairip.json"));
if (Pull.waitForFileAndPull(pkg, "dictionary/pairip.json", pulledJson)) {
System.out.println("[ROOT] pairip.json captured!");
Runtime.getRuntime().exec(new String[]{"su", "-c", "am force-stop " + pkg}).waitFor();
uninstaller.uninstall(pkg);
System.out.println("[INFO] Reloading clean merged APK for final patching ");
module = ApkModule.loadApkFile(mergedApkFile);
System.out.println("[INFO] Starting Translation Patch with captured JSON ");
TranslationPatcher.patch(module, pulledJson);
String tn = mergedApkFile.getName();
int td = tn.lastIndexOf('.');
tn = (td > 0 ? tn.substring(0, td) : tn) + "_translated.apk";
File transFile = new File(output.get_out(inputApk, tn));
System.out.println("[BUILD] Building Final Translated APK ");
output.write(module, transFile);
System.out.println("[BUILD] Final APK built at : " + transFile.getAbsolutePath());
}
} else {
System.err.println("[ERROR] Original base.apk not found in APKS for root replacement");
}
} else {
System.out.println("[ROOT] Root mode replacement skipped (Input was not an APKS file)");
}
}
}
System.out.println("[BUILD] Process completed");
} catch (Exception e) {
System.err.println("[ERROR] Process failed: " + e.getMessage());
System.err.println("[ERROR] Process failed : " + e.getMessage());
e.printStackTrace();
} finally {
if (tempDir != null) {
deleteDir.del_dir(tempDir);
if (T_dIR != null) {
deleteDir.del_dir(T_dIR);
}
}
}
@@ -0,0 +1,57 @@
package com.antik.root;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
public class Base {
public static void replaceBase(String _Pname, File Org) {
try {
System.out.println("[ROOT] Finding base.apk path {/}");
Process P_Proc = Runtime.getRuntime().exec( new String[]{
"su",
"-c",
"pm path " + _Pname
}
);
BufferedReader ___Read = new BufferedReader(new InputStreamReader(P_Proc.getInputStream()));
String line = ___Read.readLine();
P_Proc.waitFor();
if (line == null || !line.startsWith("package:")) {
System.err.println("[ERROR] Package path not found : " + _Pname);
return;
}
String installedPath = line.substring(8).trim();
System.out.println("[ROOT] Replacing : " + installedPath);
Runtime.getRuntime().exec(new String[]{
"su",
"-c",
"am force-stop " + _Pname
}
).waitFor();
String cmd = "cp " + Org.getAbsolutePath() + " " + installedPath + " && chmod 644 " + installedPath + " && chown system:system " + installedPath;
Process replaceProcess = Runtime.getRuntime().exec( new String[]{
"su",
"-c",
cmd
}
);
replaceProcess.waitFor();
System.out.println("[ROOT] Finished ");
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,33 @@
package com.antik.root.PackageM;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
public class Installer {
public static void install(File D_TOY) {
try {
System.out.println("[ROOT] Installing : " + D_TOY.getName());
Process p = Runtime.getRuntime().exec(new String[]{
"su", "-c", "pm install -r -t -g " + D_TOY.getAbsolutePath()
});
BufferedReader _TRead = new BufferedReader(new InputStreamReader(p.getInputStream()));
String LN;
while ((LN = _TRead.readLine()) != null) {
System.out.println("[PM] " + LN);
}
p.waitFor();
System.out.println("[ROOT] Finished");
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,15 @@
package com.antik.root.PackageM;
public class uninstaller {
public static void uninstall(String _Pname) {
try {
System.out.println("[ROOT] Uninstalling: " + _Pname);
Process p = Runtime.getRuntime().exec(new String[]{"su", "-c", "pm uninstall " + _Pname});
p.waitFor();
System.out.println("[ROOT] Finished");
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,54 @@
package com.antik.root;
import java.io.File;
public class Pull {
public static boolean waitForFileAndPull(String packageName, String internalPath, File destination) {
String fullPath = "/data/data/" + packageName + "/" + internalPath;
try {
System.out.println("[ROOT] Waiting for: " + fullPath);
long startTime = System.currentTimeMillis();
long timeout = 60000;
while (System.currentTimeMillis() - startTime < timeout) {
Process checkProcess = Runtime.getRuntime().exec(
new String[]{
"su",
"-c",
"ls " + fullPath
}
);
if (checkProcess.waitFor() == 0) {
System.out.println("[ROOT] File found.");
String cmd = "cp " + fullPath + " " + destination.getAbsolutePath() + " && chmod 666 " + destination.getAbsolutePath();
Process C_pad = Runtime.getRuntime().exec(
new String[]{
"su",
"-c",
cmd
}
);
C_pad.waitFor();
System.out.println("[ROOT] Pulled successfully");
return true;
}
Thread.sleep(2000);
}
System.err.println("[ERROR] Timeout ");
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
@@ -0,0 +1,20 @@
package com.antik.root;
public class launchApp {
public static void launchApp(String _Pname) {
try {
System.out.println("[ROOT] Launching " + _Pname);
Runtime.getRuntime().exec(new String[]{
"su",
"-c",
"monkey -p " + _Pname + " -c android.intent.category.LAUNCHER 1"
}
).waitFor();
System.out.println("[ROOT] Finished ");
} catch (Exception e) {
e.printStackTrace();
}
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ public class banner {
System.out.println("░█▀▄░█▀▀░█▀█░█▀█░▀█▀░█▀▄░▀█▀░█▀█░");
System.out.println("░█▀▄░█▀▀░█▀▀░█▀█░░█░░█▀▄░░█░░█▀▀░");
System.out.println("░▀░▀░▀▀▀░▀░░░▀░▀░▀▀▀░▀░▀░▀▀▀░▀░░░");
System.out.println("Version : 1.3.10");
System.out.println("Version : 1.5.20");
System.out.println("--------------------------------------");
System.out.println("Dev : Antik");
System.out.println("Channel : https://t.me/RevDex");