diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/antik/build.gradle.kts b/antik/build.gradle.kts new file mode 100644 index 0000000..28f445e --- /dev/null +++ b/antik/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + java + alias(libs.plugins.shadow) +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +tasks.jar { + enabled = false +} + +tasks.shadowJar { + archiveFileName.set("RePairip.jar") + manifest { + attributes["Main-Class"] = "com.antik.Main" + } +} + +// Make the 'build' and 'assemble' tasks produce the shadow jar +tasks.assemble { + dependsOn(tasks.shadowJar) +} + +dependencies { + implementation(libs.dexlib2) + implementation(libs.reandroid.arsclib) + implementation(libs.guava) +} diff --git a/antik/src/main/java/com/antik/AntikUtils.java b/antik/src/main/java/com/antik/AntikUtils.java new file mode 100644 index 0000000..90ab46e --- /dev/null +++ b/antik/src/main/java/com/antik/AntikUtils.java @@ -0,0 +1,89 @@ +package com.antik; + +import java.io.*; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class AntikUtils { + public static String get_out(File i, String n) { + File p = i.getParentFile(); + if (p == null) { + return n; + } + return new File(p, n).getPath(); + } + + public static void ex_apks(File zf, File dd) throws Exception { + try (ZipFile z = new ZipFile(zf)) { + Enumeration es = z.entries(); + while (es.hasMoreElements()) { + ZipEntry e = es.nextElement(); + if (e.getName().endsWith(".apk")) { + File of = new File(dd, new File(e.getName()).getName()); + try (InputStream i = z.getInputStream(e); + FileOutputStream fo = new FileOutputStream(of)) { + byte[] b = new byte[8192]; + int l; + while ((l = i.read(b)) > 0) { + fo.write(b, 0, l); + } + } + } + } + } + } + + public static void del_dir(File d) { + File[] fs = d.listFiles(); + if (fs != null) { + for (File f : fs) { + if (f.isDirectory()) { + del_dir(f); + } else { + f.delete(); + } + } + } + d.delete(); + } + + public static void banner() { + System.out.println("░░░░░█▀▄░█▀▀░█░█░█▀▄░█▀▀░█░█░░░░░"); + System.out.println("░░░░░█▀▄░█▀▀░▀▄▀░█░█░█▀▀░▄▀▄░░░░░"); + System.out.println("░░░░░▀░▀░▀▀▀░░▀░░▀▀░░▀▀▀░▀░▀░░░░░"); + System.out.println("░█▀▄░█▀▀░█▀█░█▀█░▀█▀░█▀▄░▀█▀░█▀█░"); + System.out.println("░█▀▄░█▀▀░█▀▀░█▀█░░█░░█▀▄░░█░░█▀▀░"); + System.out.println("░▀░▀░▀▀▀░▀░░░▀░▀░▀▀▀░▀░▀░▀▀▀░▀░░░"); + System.out.println("Version : 1.3.10"); + System.out.println("--------------------------------------"); + System.out.println("Dev : Antik (Fox)"); + System.out.println("Channel : https://t.me/RevDex"); + System.out.println("GitHub : https://github.com/ispointer/RePairip"); + System.out.println("--------------------------------------"); + } + + public static void help() { + System.out.println("java -jar antik.jar -i "); + } + + public static void progress(int c, int t) { + if (t <= 0) t = 1; + int w = 30; + float p = (float) c / t; + if (p > 1.0f) p = 1.0f; + int f = (int) (p * w); + + StringBuilder s = new StringBuilder("\r["); + for (int i = 0; i < f; i++) { + s.append("#"); + } + for (int i = f; i < w; i++) { + s.append("-"); + } + s.append("] "); + s.append(String.format("%d%%", (int) (p * 100))); + System.out.print(s.toString()); + System.out.flush(); + } +} diff --git a/antik/src/main/java/com/antik/CrcPatcher.java b/antik/src/main/java/com/antik/CrcPatcher.java new file mode 100644 index 0000000..8f59481 --- /dev/null +++ b/antik/src/main/java/com/antik/CrcPatcher.java @@ -0,0 +1,130 @@ +package com.antik; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +public class CrcPatcher { + public static void patch(File s_a, File t_a) throws IOException { + + Map s_es = new LinkedHashMap(); + Map t_es = new LinkedHashMap(); + + File[] f_ls = {s_a, t_a}; + + for (int i = 0; i < 2; i++) { + File f = f_ls[i]; + Map cur = (i == 0) ? s_es : t_es; + + try (RandomAccessFile rf = new RandomAccessFile(f, "r")) { + FileChannel ch = rf.getChannel(); + long sz = ch.size(); + int t_sz = (int) Math.min(65558L, sz); + ByteBuffer t_bf = ByteBuffer.allocate(t_sz).order(ByteOrder.LITTLE_ENDIAN); + ch.read(t_bf, sz - t_sz); + t_bf.flip(); + + Long eo = null; + for (int j = t_bf.limit() - 4; j >= 0; j--) { + if (t_bf.getInt(j) == 0x06054b50) { + eo = (sz - t_sz) + (long)j; + break; + } + } + + if (eo != null) { + ByteBuffer eb = ByteBuffer.allocate(22).order(ByteOrder.LITTLE_ENDIAN); + ch.read(eb, eo); + eb.flip(); + long cds = eb.getInt(12) & 0xFFFFFFFFL; + long cdo = eb.getInt(16) & 0xFFFFFFFFL; + + ByteBuffer hb = ByteBuffer.allocate(46).order(ByteOrder.LITTLE_ENDIAN); + long co = cdo; + long e_o = cdo + cds; + while (co < e_o) { + hb.clear(); + ch.read(hb, co); + hb.flip(); + if (hb.remaining() < 46 || hb.getInt() != 0x02014b50) { + break; + } + + long co_c = co + 16; + hb.position(16); + int c = hb.getInt(); + + hb.position(28); + int nl = hb.getShort() & 0xFFFF; + int el = hb.getShort() & 0xFFFF; + int cl = hb.getShort() & 0xFFFF; + + hb.position(42); + long lho = hb.getInt() & 0xFFFFFFFFL; + + byte[] nb = new byte[nl]; + ByteBuffer nbf = ByteBuffer.wrap(nb); + ch.read(nbf, co + 46); + String n = new String(nb, StandardCharsets.UTF_8); + + if (n.startsWith("classes") && n.endsWith(".dex")) { + ByteBuffer lhb = ByteBuffer.allocate(30).order(ByteOrder.LITTLE_ENDIAN); + ch.read(lhb, lho); + lhb.flip(); + if (lhb.remaining() >= 30 && lhb.getInt() == 0x04034b50) { + cur.put(n, new long[]{co_c, lho + 14, (long) c}); + } + } + + co += 46L + nl + el + cl; + } + } + } + } + + if (s_es.isEmpty() || t_es.isEmpty()) { + System.out.println("[CRC] No dex files found in apk"); + return; + } + + boolean mod = false; + try (RandomAccessFile rf = new RandomAccessFile(t_a, "rw"); + FileChannel ch = rf.getChannel()) { + + ByteBuffer b = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + + Iterator> it = s_es.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry e = it.next(); + + long[] se = e.getValue(); + long[] te = t_es.get(e.getKey()); + if (te != null && te[2] != se[2]) { + int n_c = (int) se[2]; + b.clear(); + b.putInt(n_c); + b.flip(); + ch.write(b, te[0]); + b.clear(); + b.putInt(n_c); + b.flip(); + ch.write(b, te[1]); + + mod = true; + } + } + + if (mod) { + ch.force(true); + System.out.println("[CRC] Patched dex CRC values from merged APK"); + } + } + } +} diff --git a/antik/src/main/java/com/antik/DexPatcher.java b/antik/src/main/java/com/antik/DexPatcher.java new file mode 100644 index 0000000..6f62bf1 --- /dev/null +++ b/antik/src/main/java/com/antik/DexPatcher.java @@ -0,0 +1,377 @@ +package com.antik; + +import com.reandroid.apk.ApkModule; +import com.reandroid.archive.ByteInputSource; +import com.reandroid.archive.InputSource; +import org.jf.dexlib2.DexFileFactory; +import org.jf.dexlib2.Opcode; +import org.jf.dexlib2.Opcodes; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.DexFile; +import org.jf.dexlib2.iface.Method; +import org.jf.dexlib2.iface.MethodImplementation; +import org.jf.dexlib2.iface.instruction.Instruction; +import org.jf.dexlib2.iface.instruction.ReferenceInstruction; +import org.jf.dexlib2.iface.reference.MethodReference; +import org.jf.dexlib2.immutable.ImmutableClassDef; +import org.jf.dexlib2.immutable.ImmutableMethod; +import org.jf.dexlib2.immutable.ImmutableMethodImplementation; +import org.jf.dexlib2.immutable.instruction.ImmutableInstruction10x; +import org.jf.dexlib2.immutable.instruction.ImmutableInstruction11n; +import org.jf.dexlib2.immutable.instruction.ImmutableInstruction11x; +import org.jf.dexlib2.immutable.instruction.ImmutableInstruction21c; +import org.jf.dexlib2.immutable.instruction.ImmutableInstruction35c; +import org.jf.dexlib2.immutable.reference.ImmutableMethodReference; +import org.jf.dexlib2.immutable.reference.ImmutableTypeReference; +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.InputStream; +import java.nio.file.Files; +import java.util.*; + +public class DexPatcher { + public static void patch(ApkModule m) throws Exception { + List dx_ns = new ArrayList(); + for (InputSource s : m.getInputSources()) { + if (s.getName().endsWith(".dex")) { + dx_ns.add(s.getName()); + } + } + + List l_cds = new ArrayList(); + Set a_ts = new HashSet(); + String pkg = "unknown"; + try { + pkg = m.getPackageName(); + } catch (Exception ignored) {} + + try (InputStream i = Main.class.getResourceAsStream("/log.dex")) { + if (i != null) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buf = new byte[8192]; + int len; + while ((len = i.read(buf)) != -1) { + baos.write(buf, 0, len); + } + byte[] l_bs = baos.toByteArray(); + File t_l_dx = File.createTempFile("log", ".dex"); + Files.write(t_l_dx.toPath(), l_bs); + DexFile l_df = DexFileFactory.loadDexFile(t_l_dx, Opcodes.getDefault()); + t_l_dx.delete(); + + for (ClassDef c : l_df.getClasses()) { + if ("Lcom/pairip/PairipLog;".equals(c.getType())) { + List s_fs = new ArrayList(); + for (org.jf.dexlib2.iface.Field f : c.getStaticFields()) { + if ("DIR_PATH".equals(f.getName())) { + String n_v = "/data/data/" + pkg + "/dictionary"; + s_fs.add(new org.jf.dexlib2.immutable.ImmutableField( + f.getDefiningClass(), + f.getName(), + f.getType(), + f.getAccessFlags(), + new org.jf.dexlib2.immutable.value.ImmutableStringEncodedValue(n_v), + f.getAnnotations(), + f.getHiddenApiRestrictions())); + } else { + s_fs.add(f); + } + } + l_cds.add(new ImmutableClassDef( + c.getType(), c.getAccessFlags(), c.getSuperclass(), + c.getInterfaces(), c.getSourceFile(), c.getAnnotations(), + s_fs, c.getInstanceFields(), + c.getDirectMethods(), c.getVirtualMethods())); + } else { + l_cds.add(c); + } + a_ts.add(c.getType()); + } + System.out.println("[INFO] Added classes from log.dex (Package: " + pkg + ")"); + } + } + + List j_ts = new ArrayList(); + for (String dn : dx_ns) { + InputSource s = m.getInputSource(dn); + if (s == null) continue; + byte[] d_bs; + try (InputStream i = s.openStream()) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buf = new byte[8192]; + int len; + while ((len = i.read(buf)) != -1) { + baos.write(buf, 0, len); + } + d_bs = baos.toByteArray(); + } + File t_dx = File.createTempFile("temp", ".dex"); + Files.write(t_dx.toPath(), d_bs); + DexFile d_f = DexFileFactory.loadDexFile(t_dx, Opcodes.getDefault()); + t_dx.delete(); + + for (ClassDef cd : d_f.getClasses()) { + if (cd.getMethods().iterator().hasNext()) continue; + if (!cd.getFields().iterator().hasNext()) continue; + if (!"Ljava/lang/Object;".equals(cd.getSuperclass())) continue; + if (cd.getAccessFlags() != 1) continue; + + boolean o_t = true; + for (org.jf.dexlib2.iface.Field f : cd.getFields()) { + if (f.getAccessFlags() != 9) { + o_t = false; + break; + } + String t = f.getType(); + if (!t.equals("Ljava/lang/String;") && !t.equals("Ljava/lang/reflect/Method;")) { + o_t = false; + break; + } + if (f.getInitialValue() != null) { + o_t = false; + break; + } + } + if (o_t) { + j_ts.add(cd.getType()); + } + } + } + System.out.println("[INFO] Found " + j_ts.size() + " junk classes"); + + for (String dn : dx_ns) { + InputSource s = m.getInputSource(dn); + if (s == null) continue; + byte[] d_bs; + try (InputStream i = s.openStream()) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buf = new byte[8192]; + int len; + while ((len = i.read(buf)) != -1) { + baos.write(buf, 0, len); + } + d_bs = baos.toByteArray(); + } + + File t_dx = File.createTempFile("temp", ".dex"); + Files.write(t_dx.toPath(), d_bs); + DexFile d_f = DexFileFactory.loadDexFile(t_dx, Opcodes.getDefault()); + t_dx.delete(); + + List cds = new ArrayList(); + boolean mod = false; + + for (ClassDef cd : d_f.getClasses()) { + if (a_ts.contains(cd.getType())) { + continue; + } + if ("Lcom/pairip/StartupLauncher;".equals(cd.getType())) { + mod = true; + cds.add(patchStartupLauncher(cd, j_ts)); + } else if ("Lcom/pairip/SignatureCheck;".equals(cd.getType()) || + "Lcom/pairip/licensecheck/LicenseClient;".equals(cd.getType()) || + "Lcom/pairip/licensecheck3/LicenseClientV3;".equals(cd.getType())) { + mod = true; + + List d_ms = new ArrayList(); + for (Method method : cd.getDirectMethods()) { + d_ms.add(patchMethodIfTarget(method)); + } + + List v_ms = new ArrayList(); + for (Method method : cd.getVirtualMethods()) { + v_ms.add(patchMethodIfTarget(method)); + } + + cds.add(new ImmutableClassDef( + cd.getType(), cd.getAccessFlags(), cd.getSuperclass(), + cd.getInterfaces(), cd.getSourceFile(), cd.getAnnotations(), + cd.getStaticFields(), cd.getInstanceFields(), + d_ms, v_ms)); + } else { + cds.add(cd); + } + } + + if (dn.equals("classes.dex")) { + if (!l_cds.isEmpty()) { + cds.addAll(l_cds); + mod = true; + } + } + + 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 " + dn); + } + } + } + + private static Method patchMethodIfTarget(Method m) { + String n = m.getName(); + if ("verifySignatureMatches".equals(n)) { + System.out.println("[INFO] Bypassing verifySignatureMatches"); + List ins = Arrays.asList( + new ImmutableInstruction11n(Opcode.CONST_4, 0, 1), + new ImmutableInstruction11x(Opcode.RETURN, 0) + ); + return new ImmutableMethod( + m.getDefiningClass(), n, m.getParameters(), + m.getReturnType(), m.getAccessFlags(), + m.getAnnotations(), m.getHiddenApiRestrictions(), + new ImmutableMethodImplementation(1, ins, null, null)); + } else if ("verifyIntegrity".equals(n)) { + System.out.println("[INFO] Bypassing " + n); + String rt = m.getReturnType(); + List ins; + if ("V".equals(rt)) { + ins = Collections.singletonList(new ImmutableInstruction10x(Opcode.RETURN_VOID)); + } else if (rt.startsWith("L") || rt.startsWith("[")) { + ins = Arrays.asList( + new ImmutableInstruction11n(Opcode.CONST_4, 0, 0), + new ImmutableInstruction11x(Opcode.RETURN_OBJECT, 0) + ); + } else { + ins = Arrays.asList( + new ImmutableInstruction11n(Opcode.CONST_4, 0, 1), + new ImmutableInstruction11x(Opcode.RETURN, 0) + ); + } + return new ImmutableMethod( + m.getDefiningClass(), n, m.getParameters(), + rt, m.getAccessFlags(), + m.getAnnotations(), m.getHiddenApiRestrictions(), + new ImmutableMethodImplementation(1, ins, null, null)); + } + return m; + } + + private static ClassDef patchStartupLauncher(ClassDef cd, List j_ts) { + System.out.println("[INFO] Patching StartupLauncher"); + List d_ms = new ArrayList(); + for (Method m : cd.getDirectMethods()) { + if ("launch".equals(m.getName())) { + d_ms.add(patchLaunchMethod(m)); + } else if (!"pairip".equals(m.getName())) { + d_ms.add(m); + } + } + d_ms.add(createPairipMethod(cd.getType(), j_ts)); + + return new ImmutableClassDef( + cd.getType(), cd.getAccessFlags(), cd.getSuperclass(), + cd.getInterfaces(), cd.getSourceFile(), cd.getAnnotations(), + cd.getStaticFields(), cd.getInstanceFields(), + d_ms, cd.getVirtualMethods()); + } + + private static Method patchLaunchMethod(Method m) { + MethodImplementation im = m.getImplementation(); + if (im == null) return m; + + List o_ins = new ArrayList(); + for (Instruction i : im.getInstructions()) { + o_ins.add(i); + } + + List n_ins = new ArrayList(); + int p_off = -1; + int c_off = 0; + + for (int i = 0; i < o_ins.size(); i++) { + Instruction inst = o_ins.get(i); + n_ins.add(inst); + int u = inst.getCodeUnits(); + if (inst instanceof ReferenceInstruction) { + ReferenceInstruction r_i = (ReferenceInstruction) inst; + if (r_i.getReference() instanceof MethodReference) { + MethodReference m_r = (MethodReference) r_i.getReference(); + if ("Lcom/pairip/VMRunner;".equals(m_r.getDefiningClass()) && "invoke".equals(m_r.getName())) { + + boolean a_p = false; + if (i + 1 < o_ins.size()) { + Instruction nx = o_ins.get(i + 1); + if (nx instanceof ReferenceInstruction) { + ReferenceInstruction n_r = (ReferenceInstruction) nx; + if (n_r.getReference() instanceof MethodReference) { + MethodReference n_m = (MethodReference) n_r.getReference(); + if ("pairip".equals(n_m.getName())) { + a_p = true; + } + } + } + } + + if (!a_p) { + p_off = c_off + u; + n_ins.add(new ImmutableInstruction35c(Opcode.INVOKE_STATIC, 0, 0, 0, 0, 0, 0, + new ImmutableMethodReference("Lcom/pairip/StartupLauncher;", "pairip", null, "V"))); + } + } + } + } + c_off += u; + } + + if (p_off == -1) return m; + + List> o_tbs = im.getTryBlocks(); + List n_tbs = new ArrayList(); + + for (org.jf.dexlib2.iface.TryBlock t : o_tbs) { + int s = t.getStartCodeAddress(); + int c = t.getCodeUnitCount(); + int e = s + c; + + List h = new ArrayList(); + for (org.jf.dexlib2.iface.ExceptionHandler ha : t.getExceptionHandlers()) { + int h_a = ha.getHandlerCodeAddress(); + if (h_a >= p_off) { + h.add(new org.jf.dexlib2.immutable.ImmutableExceptionHandler(ha.getExceptionType(), h_a + 3)); + } else { + h.add(org.jf.dexlib2.immutable.ImmutableExceptionHandler.of(ha)); + } + } + + if (s < p_off && e >= p_off) { + n_tbs.add(new org.jf.dexlib2.immutable.ImmutableTryBlock(s, c + 3, h)); + } else if (s >= p_off) { + n_tbs.add(new org.jf.dexlib2.immutable.ImmutableTryBlock(s + 3, c, h)); + } else { + n_tbs.add(new org.jf.dexlib2.immutable.ImmutableTryBlock(s, c, h)); + } + } + + return new ImmutableMethod( + m.getDefiningClass(), m.getName(), m.getParameters(), + m.getReturnType(), m.getAccessFlags(), + m.getAnnotations(), m.getHiddenApiRestrictions(), + new ImmutableMethodImplementation(im.getRegisterCount(), n_ins, n_tbs, im.getDebugItems())); + } + + private static Method createPairipMethod(String dc, List j_ts) { + List ins = new ArrayList(); + for (String t : j_ts) { + ins.add(new ImmutableInstruction21c(Opcode.CONST_CLASS, 0, new ImmutableTypeReference(t))); + ins.add(new ImmutableInstruction35c(Opcode.INVOKE_STATIC, 1, 0, 0, 0, 0, 0, + new ImmutableMethodReference("Lcom/pairip/PairipLog;", "put", Collections.singletonList("Ljava/lang/Class;"), "V"))); + } + ins.add(new ImmutableInstruction10x(Opcode.RETURN_VOID)); + + return new ImmutableMethod( + dc, "pairip", null, "V", 0x08, + null, null, + new ImmutableMethodImplementation(1, ins, null, null)); + } +} diff --git a/antik/src/main/java/com/antik/Main.java b/antik/src/main/java/com/antik/Main.java new file mode 100644 index 0000000..7a65c98 --- /dev/null +++ b/antik/src/main/java/com/antik/Main.java @@ -0,0 +1,153 @@ +package com.antik; + +import com.reandroid.apk.ApkBundle; +import com.reandroid.apk.ApkModule; +import java.io.*; +import java.nio.file.Files; + +/* + * Created by aantik + * 3/20/2026 6:20 PM + * + * ⋆ ႔ ႔ + * ᠸ^ ^ ⸝⸝ + * |、˜〵 + * じしˍ,)⁐̤ᐷ + * + * Fox Mode 🍺 + */ + +/** + * + * @life is butterfly flyfly + * I am a professional Android Developer + * + */ + +public class Main { + public static void main(String[] a) { + + if (a.length < 2 || !"-i".equals(a[0])) + { + AntikUtils.help(); + return; + } + + String P_ta = a[1]; + File I_pa = new File(P_ta); + + if (!I_pa.exists()) { + System.err.println("Input file not found : " + P_ta); + return; + } + + AntikUtils.banner(); + + File T_Dir = null; + + try { + + T_Dir = Files.createTempDirectory("antik_merge").toFile(); + + + System.out.println("[MERGE] Extracting APKs..."); + AntikUtils.ex_apks(I_pa, T_Dir); + + System.out.println("[MERGE] Merging APK..."); + ApkBundle bd = new ApkBundle(); + bd.loadApkDirectory(T_Dir); + + + ApkModule Sp_T = bd.mergeModules(); + + + System.out.println("[INFO] Patching AndroidManifest.xml"); + + try { + ManifestPatcher.patch(Sp_T); + } catch (Exception e) { + System.err.println("[ERROR] Manifest patching failed: " + e.getMessage()); + e.printStackTrace(); + } + + + + String o = I_pa.getName(); + + int d = o.lastIndexOf('.'); + + if (d > 0) { + o = o.substring(0, d) + "_merged.apk"; + } else { + o = o + "_merged.apk"; + } + + + File o_f = new File(AntikUtils.get_out(I_pa, o)); + + int T_tl = Sp_T.getZipEntryMap().size(); + + if (Sp_T.hasTableBlock()) { + T_tl = T_tl - 1; + } + + int[] c = {0}; + final int T_l = T_tl; + Sp_T.writeApk(o_f, new com.reandroid.archive.WriteProgress() { + @Override + public void onCompressFile(String p, int m, long w) { + c[0]++; + AntikUtils.progress(c[0], T_l); + } + }); + + AntikUtils.progress(T_l, T_l); + + System.out.println(); + System.out.println("[MERGE] APK merged successfully.."); + + String pkg = "unknown"; + try { + pkg = Sp_T.getPackageName(); + System.out.println("[INFO] Package Name: " + pkg); + } catch (Exception ignored) {} + + String p_n = I_pa.getName(); + int d2 = p_n.lastIndexOf('.'); + if (d2 > 0) { + p_n = p_n.substring(0, d2) + "_pairip.apk"; + } else { + p_n = p_n + "_pairip.apk"; + } + File Pai_Dir = new File(AntikUtils.get_out(I_pa, p_n)); + + System.out.println("[INFO] Patching classes.dex"); + try { + DexPatcher.patch(Sp_T); + } catch (Exception e) { + System.err.println("[ERROR] Patching failed: " + e.getMessage()); + } + + + System.out.println("[BUILD] Building APK..."); + Sp_T.writeApk(Pai_Dir); + CrcPatcher.patch(o_f, Pai_Dir); + + System.out.println("[BUILD] APK built successfully at " + Pai_Dir.getAbsolutePath()); + System.out.println("[BUILD] Process completed"); + + System.out.println("--------------------------------------"); + System.out.println("⚠️ DO NOT use the pairip app (" + Pai_Dir.getAbsolutePath() + ") for translation."); + System.out.println("It is incomplete and intended for logging only."); + System.out.println("Please use the merged app instead (" + o_f.getAbsolutePath() + ")."); + System.out.println("--------------------------------------"); + + } catch (Exception e) { + System.err.println("[ERROR] Merge failed: " + e.getMessage()); + } finally { + if (T_Dir != null) { + AntikUtils.del_dir(T_Dir); + } + } + } +} diff --git a/antik/src/main/java/com/antik/ManifestPatcher.java b/antik/src/main/java/com/antik/ManifestPatcher.java new file mode 100644 index 0000000..831dc3b --- /dev/null +++ b/antik/src/main/java/com/antik/ManifestPatcher.java @@ -0,0 +1,108 @@ +package com.antik; + +import com.reandroid.apk.ApkModule; +import com.reandroid.arsc.chunk.xml.AndroidManifestBlock; +import com.reandroid.arsc.chunk.xml.ResXmlElement; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +@SuppressWarnings({"unchecked", "rawtypes"}) +public class ManifestPatcher { + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void patch(ApkModule m) throws Exception { + + AndroidManifestBlock mf = m.getAndroidManifest(); + + if (mf == null) + { + return; + } + Boolean e = mf.isExtractNativeLibs(); + if (e != null && e.booleanValue() == false) { + mf.setExtractNativeLibs(true); + } + + ResXmlElement m_e = mf.getManifestElement(); + + if (m_e != null) + { + m_e.removeAttributesWithName("requiredSplitTypes"); + m_e.removeAttributesWithName("splitTypes"); + } + + List md_r = Arrays.asList(new String[]{"com.android.stamp.source", "com.android.stamp.type", "com.android.vending.splits", "com.android.vending.derived.apk.id", "com.android.dynamic.apk.fused.modules", "com.android.vending.splits.required"}); + + List act_r = Arrays.asList(new String[]{"com.pairip.licensecheck.LicenseActivity"}); + + List pv_r = Arrays.asList(new String[]{"com.pairip.licensecheck.LicenseContentProvider"}); + + Iterator i = mf.getApplicationElementsByTag("meta-data"); + + List rm_ls = new ArrayList(); + + while (i.hasNext()) { + + ResXmlElement el = (ResXmlElement) i.next(); + + String n = AndroidManifestBlock.getAndroidNameValue(el); + + if (md_r.contains(n)) { + rm_ls.add(el); + } + } + + int x = 0; + for (; x < rm_ls.size(); x = x + 1) { + ResXmlElement el = (ResXmlElement) rm_ls.get(x); + el.removeSelf(); + } + + rm_ls.clear(); + + i = mf.getApplicationElementsByTag("activity"); + + while (i.hasNext()) { + + ResXmlElement el = (ResXmlElement) i.next(); + + String n = AndroidManifestBlock.getAndroidNameValue(el); + + if (act_r.contains(n)) { + rm_ls.add(el); + } + } + + for (x = 0; x < rm_ls.size(); x = x + 1) { + ResXmlElement el = (ResXmlElement) rm_ls.get(x); + el.removeSelf(); + } + + rm_ls.clear(); + + i = mf.getApplicationElementsByTag("provider"); + + while (i.hasNext()) { + + ResXmlElement el = (ResXmlElement) i.next(); + + String n = AndroidManifestBlock.getAndroidNameValue(el); + + if (pv_r.contains(n)) { + rm_ls.add(el); + } + } + + for (x = 0; x < rm_ls.size(); x++) { + + ResXmlElement el = (ResXmlElement) rm_ls.get(x); + el.removeSelf(); + + } + + m.setManifest(mf); + } +} \ No newline at end of file diff --git a/antik/src/main/resources/info.txt b/antik/src/main/resources/info.txt new file mode 100644 index 0000000..0a122cc --- /dev/null +++ b/antik/src/main/resources/info.txt @@ -0,0 +1,3 @@ +This is a resource file for the antik project. +Project: RePairip +Subproject: antik diff --git a/antik/src/main/resources/log.dex b/antik/src/main/resources/log.dex new file mode 100644 index 0000000..cd4be8f Binary files /dev/null and b/antik/src/main/resources/log.dex differ diff --git a/antik/src/main/resources/restoreMethod.dex b/antik/src/main/resources/restoreMethod.dex new file mode 100644 index 0000000..8187362 Binary files /dev/null and b/antik/src/main/resources/restoreMethod.dex differ diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..4f298e6 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,2 @@ +plugins { +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..73cc41b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,17 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# org.gradle.parallel=true +# When enabled, the Configuration Cache allows Gradle to skip the configuration +# phase entirely if nothing that affects the build configuration (such as build scripts) +# has changed. Additionally, Gradle applies performance optimizations to task execution. +org.gradle.configuration-cache=true \ No newline at end of file diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 0000000..6c1139e --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,12 @@ +#This file is generated by updateDaemonJvm +toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect +toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect +toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect +toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect +toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73bcfb608d1fde9fb62e462f834a3299/redirect +toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/846ee0d876d26a26f37aa1ce8de73224/redirect +toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect +toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect +toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/9482ddec596298c84656d31d16652665/redirect +toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/39701d92e1756bb2f141eb67cd4c660e/redirect +toolchainVersion=21 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..c730470 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,15 @@ +[versions] +junit = "4.13.2" +dexlib2 = "2.5.2" +reandroid-arsclib = "V1.3.8" +guava = "33.2.1-jre" +shadow = "8.3.5" + +[libraries] +junit = { group = "junit", name = "junit", version.ref = "junit" } +dexlib2 = { group = "org.smali", name = "dexlib2", version.ref = "dexlib2" } +reandroid-arsclib = { group = "com.github.REAndroid", name = "ARSCLib", version.ref = "reandroid-arsclib" } +guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } + +[plugins] +shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..8bdaf60 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..7a20022 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,8 @@ +#Tue Jun 09 03:26:12 BDT 2026 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..ef07e01 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..5eed7ee --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..1479afd --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,28 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { url = uri("https://jitpack.io") } + } +} + +rootProject.name = "RePairip" +include(":antik") +