iterator() {
108 | return Iterators.concat(directMethods.iterator(), virtualMethods.iterator());
109 | }
110 |
111 | @Override
112 | public int size() {
113 | return directMethods.size() + virtualMethods.size();
114 | }
115 | };
116 | }
117 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/dex2c/converter/structs/RegisterNativesUtilClassDef.java:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.dex2c.converter.structs;
2 |
3 | import com.android.tools.smali.dexlib2.AccessFlags;
4 | import com.android.tools.smali.dexlib2.HiddenApiRestriction;
5 | import com.android.tools.smali.dexlib2.base.reference.BaseMethodReference;
6 | import com.android.tools.smali.dexlib2.base.reference.BaseTypeReference;
7 | import com.android.tools.smali.dexlib2.iface.*;
8 | import org.jetbrains.annotations.Contract;
9 | import org.jetbrains.annotations.NotNull;
10 | import org.jetbrains.annotations.Nullable;
11 | import org.jetbrains.annotations.Unmodifiable;
12 |
13 | import java.util.ArrayList;
14 | import java.util.Collections;
15 | import java.util.List;
16 | import java.util.Set;
17 |
18 | /**
19 | * 每个类调用的静态初始化方法,一般情况一个classes.dex对应一个注册方法
20 | * 需要把它放在主classes.dex里
21 | *
22 | * 静态初始化方法里增加加载本地库代码
23 | */
24 | public class RegisterNativesUtilClassDef extends BaseTypeReference implements ClassDef {
25 | @NotNull
26 | private final String type;
27 | @NotNull
28 | private final List nativeMethodNames;
29 |
30 | private final String libName;
31 |
32 | public RegisterNativesUtilClassDef(@NotNull String type,
33 | @NotNull List nativeMethodNames,
34 | @NotNull String libName) {
35 | this.type = type;
36 | this.nativeMethodNames = nativeMethodNames;
37 | this.libName = libName;
38 | }
39 |
40 | @NotNull
41 | @Override
42 | public String getType() {
43 | return type;
44 | }
45 |
46 |
47 | @Override
48 | public int getAccessFlags() {
49 | return AccessFlags.PUBLIC.getValue();
50 | }
51 |
52 | @Nullable
53 | @Override
54 | public String getSuperclass() {
55 | return "Ljava/lang/Object;";
56 | }
57 |
58 | @NotNull
59 | @Override
60 | public List getInterfaces() {
61 | return Collections.emptyList();
62 | }
63 |
64 | @Nullable
65 | @Override
66 | public String getSourceFile() {
67 | return null;
68 | }
69 |
70 | @NotNull
71 | @Override
72 | public Set extends Annotation> getAnnotations() {
73 | return Collections.emptySet();
74 | }
75 |
76 | @NotNull
77 | @Override
78 | public Iterable extends Field> getStaticFields() {
79 | return Collections.emptyList();
80 | }
81 |
82 | @NotNull
83 | @Override
84 | public Iterable extends Field> getInstanceFields() {
85 | return Collections.emptyList();
86 | }
87 |
88 | @NotNull
89 | @Override
90 | public Iterable extends Field> getFields() {
91 | return Collections.emptyList();
92 | }
93 |
94 | @NotNull
95 | @Override
96 | public Iterable extends Method> getDirectMethods() {
97 | final ArrayList methods = new ArrayList<>();
98 | //静态初始化方法
99 | methods.add(new LoadLibStaticBlockMethod(null, type, libName));
100 | //空构造函数
101 | methods.add(new EmptyConstructorMethod(type, "Ljava/lang/Object;"));
102 | for (String methodName : nativeMethodNames) {
103 | methods.add(new NativeMethod(type, methodName));
104 | }
105 | return methods;
106 | }
107 |
108 | @NotNull
109 | @Override
110 | public Iterable extends Method> getVirtualMethods() {
111 | return Collections.emptyList();
112 | }
113 |
114 | @NotNull
115 | @Override
116 | public Iterable extends Method> getMethods() {
117 | //virtualMethods为空,总方法只需要返回directMethods就行
118 | return getDirectMethods();
119 | }
120 |
121 | private static class NativeMethod extends BaseMethodReference implements Method {
122 |
123 | @NotNull
124 | private final String type;
125 |
126 | private final String methodName;
127 |
128 | public NativeMethod(@NotNull String type, String methodName) {
129 | this.type = type;
130 | this.methodName = methodName;
131 | }
132 |
133 | @Contract(pure = true)
134 | @NotNull
135 | @Override
136 | public @Unmodifiable List extends MethodParameter> getParameters() {
137 | return Collections.emptyList();
138 | }
139 |
140 | @Override
141 | public int getAccessFlags() {
142 | return AccessFlags.NATIVE.getValue()
143 | | AccessFlags.STATIC.getValue()
144 | | AccessFlags.PUBLIC.getValue();
145 | }
146 |
147 | @Contract(pure = true)
148 | @NotNull
149 | @Override
150 | public @Unmodifiable Set extends Annotation> getAnnotations() {
151 | return Collections.emptySet();
152 | }
153 |
154 | @Contract(pure = true)
155 | @NotNull
156 | @Override
157 | public @Unmodifiable Set getHiddenApiRestrictions() {
158 | return Collections.emptySet();
159 | }
160 |
161 | @Contract(pure = true)
162 | @Override
163 | public @Nullable MethodImplementation getImplementation() {
164 | return null;
165 | }
166 |
167 | @NotNull
168 | @Override
169 | public String getDefiningClass() {
170 | return type;
171 | }
172 |
173 | @NotNull
174 | @Override
175 | public String getName() {
176 | return methodName;
177 | }
178 |
179 | @Contract(value = " -> new", pure = true)
180 | @NotNull
181 | @Override
182 | public @Unmodifiable List extends CharSequence> getParameterTypes() {
183 | return Collections.singletonList("I");
184 | }
185 |
186 | @NotNull
187 | @Override
188 | public String getReturnType() {
189 | return "V";
190 | }
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/dex2c/converter/testbuild/ClassMethodImplCollection.java:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.dex2c.converter.testbuild;
2 |
3 | import com.android.tools.smali.dexlib2.iface.*;
4 | import com.android.tools.smali.dexlib2.util.MethodUtil;
5 | import com.nmmedit.apkprotect.dex2c.converter.MyMethodUtil;
6 | import org.jetbrains.annotations.NotNull;
7 | import org.jetbrains.annotations.Nullable;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 | import java.util.Set;
12 |
13 | /**
14 | * 收集方法具体代码,把它放入全新的dex中
15 | * 当前用于测试,实际使用需要把它转换为c代码,
16 | */
17 | public class ClassMethodImplCollection implements ClassDef {
18 | private final ClassDef classDef;
19 |
20 | private final StringBuilder codeContent;
21 |
22 | public ClassMethodImplCollection(ClassDef classDef, StringBuilder sb) {
23 | this.classDef = classDef;
24 | this.codeContent = sb;
25 | }
26 |
27 | @NotNull
28 | @Override
29 | public String getType() {
30 | return classDef.getType();
31 | }
32 |
33 | @Override
34 | public int compareTo(@NotNull CharSequence o) {
35 | return classDef.compareTo(o);
36 | }
37 |
38 | @Override
39 | public int getAccessFlags() {
40 | return classDef.getAccessFlags();
41 | }
42 |
43 | @Nullable
44 | @Override
45 | public String getSuperclass() {
46 | return classDef.getSuperclass();
47 | }
48 |
49 | @NotNull
50 | @Override
51 | public List getInterfaces() {
52 | return classDef.getInterfaces();
53 | }
54 |
55 | @Nullable
56 | @Override
57 | public String getSourceFile() {
58 | //忽略
59 | return null;
60 | }
61 |
62 | @NotNull
63 | @Override
64 | public Set extends Annotation> getAnnotations() {
65 | return classDef.getAnnotations();
66 | }
67 |
68 | @NotNull
69 | @Override
70 | public Iterable extends Field> getStaticFields() {
71 | return classDef.getStaticFields();
72 | }
73 |
74 | @NotNull
75 | @Override
76 | public Iterable extends Field> getInstanceFields() {
77 | return classDef.getInstanceFields();
78 | }
79 |
80 | @NotNull
81 | @Override
82 | public Iterable extends Field> getFields() {
83 | return classDef.getFields();
84 | }
85 |
86 | @NotNull
87 | @Override
88 | public Iterable extends Method> getDirectMethods() {
89 | Iterable extends Method> directMethods = classDef.getDirectMethods();
90 | return convertMethods(directMethods);
91 | }
92 |
93 | @NotNull
94 | @Override
95 | public Iterable extends Method> getVirtualMethods() {
96 | Iterable extends Method> virtualMethods = classDef.getVirtualMethods();
97 | return convertMethods(virtualMethods);
98 | }
99 |
100 | @NotNull
101 | @Override
102 | public Iterable extends Method> getMethods() {
103 | Iterable extends Method> methods = classDef.getMethods();
104 | return convertMethods(methods);
105 | }
106 |
107 | private @NotNull Iterable extends Method> convertMethods(@NotNull Iterable extends Method> methods) {
108 | ArrayList newMethods = new ArrayList<>();
109 | for (Method method : methods) {
110 | if (MyMethodUtil.isConstructorOrAbstract(method)) {
111 | continue;
112 | }
113 | newMethods.add(method);
114 | methodToC(method);
115 | }
116 | return newMethods;
117 | }
118 |
119 | private void methodToC(@NotNull Method method) {
120 | MethodImplementation implementation = method.getImplementation();
121 | if (implementation == null) {
122 | return;
123 | }
124 | int registerCount = implementation.getRegisterCount();
125 | int parameterRegisterCount = MethodUtil.getParameterRegisterCount(method);
126 | List extends CharSequence> parameterTypes = method.getParameterTypes();
127 |
128 | String code = JniTemp.genJniCode(getType(), method.getName(), parameterTypes, MethodUtil.isStatic(method), registerCount, parameterRegisterCount, method.getReturnType());
129 | codeContent.append(code);
130 | codeContent.append('\n');
131 |
132 | }
133 |
134 | @Override
135 | public int length() {
136 | return classDef.length();
137 | }
138 |
139 | @Override
140 | public char charAt(int index) {
141 | return classDef.charAt(index);
142 | }
143 |
144 | @Override
145 | public @NotNull CharSequence subSequence(int start, int end) {
146 | return classDef.subSequence(start, end);
147 | }
148 |
149 | @Override
150 | public void validateReference() throws InvalidReferenceException {
151 | classDef.validateReference();
152 | }
153 |
154 | @NotNull
155 | @Override
156 | public String toString() {
157 | return classDef.toString();
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/dex2c/converter/testbuild/JniTemp.java:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.dex2c.converter.testbuild;
2 |
3 | import org.jetbrains.annotations.Contract;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * 生成开发测试用的jni代码,依赖libdex从符号dex里提取各种信息
10 | */
11 | public class JniTemp {
12 | public static final String CODE_TEMP_INSNS_TRIES = "\n\n const DexCode *dexCode = findDexCode(\"%s\", \"%s\");\n" +
13 | " const u2 *insns = dexCode->insns;\n" +
14 | " u4 insnsSize = dexCode->insnsSize;\n" +
15 | "\n" +
16 | " size_t size = dexGetTryHandlerSize(dexCode);\n" +
17 | " const DexTry *tries = dexGetTries(dexCode);\n" +
18 | "\n" +
19 | " u1 *tryHandler = (u1 *) malloc(size + 4);\n" +
20 | " u2 *ts = (u2 *) tryHandler;\n" +
21 | " ts[0] = dexCode->triesSize;\n" +
22 | " ts[1] = 0;\n" +
23 | "\n" +
24 | " memcpy(tryHandler + 4, tries, size);\n" +
25 | "\n" +
26 | " jvalue value = dvmInterpret(env, insns, insnsSize, regs, (const u1 *) tryHandler, &dvmResolver);\n" +
27 | " free(tryHandler);\n" +
28 | "\n";
29 |
30 | public static @NotNull String genJniCode(@NotNull String classType, String methodName, List extends CharSequence> parameterTypes, boolean isStatic, int registerCount, int parameterRegisterCount, String returnType) {
31 | StringBuilder params = new StringBuilder();
32 |
33 | //寄存器初始化
34 | StringBuilder regsAss = new StringBuilder(String.format(" u8 regs[%d];\n memset(regs, 0, sizeof(regs));\n", registerCount));
35 |
36 | String clazzName = classType.substring(1, classType.length() - 1);
37 | String jniCode = String.format("JNIEXPORT %s Java_%s_%s(JNIEnv *env, %s ",
38 | getJNIType(returnType),
39 | clazzName.replace('/', '_'),
40 | methodName,
41 | isStatic ? "jclass jcls" : "jobject thiz"
42 | );
43 |
44 | int paramRegStart = registerCount - parameterRegisterCount;
45 | if (!isStatic) {
46 | regsAss.append(String.format(" regs[%d] = (u8) thiz;\n", paramRegStart++));
47 | }
48 |
49 | int size = parameterTypes.size();
50 | for (int i = 0; i < size; i++) {
51 | String type = parameterTypes.get(i).toString();
52 | String jniType = getJNIType(type);
53 | int argNum = isStatic ? i : i + 1;
54 | params.append(jniType)
55 | .append(" p")
56 | .append(argNum);
57 | if (type.startsWith("[") || type.startsWith("L")) {//对象类型,转为u8
58 | regsAss.append(String.format(" regs[%d] = (u8) p%d;\n", paramRegStart++, argNum));
59 | } else if (type.equals("F")) {//先转为u4指针,然后再取值,把float存进4字节里,double同理
60 | regsAss.append(String.format(" regs[%d] = *(u4 *) &p%d;\n", paramRegStart++, argNum));
61 | } else if (type.equals("D")) {
62 | regsAss.append(String.format(" regs[%d] = *(u8 *) &p%d;\n", paramRegStart++, argNum));
63 | } else {
64 | regsAss.append(String.format(" regs[%d] = p%d;\n", paramRegStart++, argNum));
65 | }
66 | if (type.equals("J") || type.equals("D")) {//long和double需要两个寄存器
67 | paramRegStart++;
68 | }
69 | if (i < size - 1) {//最后不用加,
70 | params.append(", ");
71 | }
72 |
73 | }
74 | if (!params.isEmpty()) {
75 | jniCode += ", " + params.toString();
76 | }
77 | jniCode += ") {\n";
78 | jniCode += regsAss;
79 | jniCode += String.format(CODE_TEMP_INSNS_TRIES, classType, methodName);
80 |
81 |
82 | if (!returnType.equals("V")) {
83 | char typeCh = returnType.charAt(0);
84 | jniCode += String.format(" return value.%s;\n", Character.toLowerCase(typeCh == '[' ? 'L' : typeCh));
85 | }
86 | jniCode += "}\n";
87 |
88 | return jniCode;
89 | }
90 |
91 | @Contract(pure = true)
92 | public static String getJNIType(@NotNull String type) {
93 | return switch (type) {
94 | case "Z" -> "jboolean";
95 | case "B" -> "jbyte";
96 | case "S" -> "jshort";
97 | case "C" -> "jchar";
98 | case "I" -> "jint";
99 | case "F" -> "jfloat";
100 | case "J" -> "jlong";
101 | case "D" -> "jdouble";
102 | case "Ljava/lang/String;" -> "jstring";
103 | case "V" -> "void";
104 | default -> "jobject";
105 | };
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/dex2c/filters/BasicKeepConfig.java:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.dex2c.filters;
2 |
3 | import com.android.tools.smali.dexlib2.iface.ClassDef;
4 | import com.android.tools.smali.dexlib2.iface.Method;
5 | import com.nmmedit.apkprotect.dex2c.converter.MyMethodUtil;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | /**
9 | * 基本过滤规则,其他规则必须先通过它, 然后才能处理自定义规则
10 | */
11 |
12 | public class BasicKeepConfig implements ClassAndMethodFilter {
13 | @Override
14 | public boolean acceptClass(@NotNull ClassDef classDef) {
15 | return !classDef.getType().startsWith("Landroidx/core/app/CoreComponentFactory")
16 | && !classDef.getType().startsWith("Landroid/support/v4/app/CoreComponentFactory");
17 | }
18 |
19 | @Override
20 | public boolean acceptMethod(Method method) {
21 | return !MyMethodUtil.isConstructorOrAbstract(method) && !MyMethodUtil.isBridgeOrSynthetic(method);
22 | }
23 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/dex2c/filters/ClassAndMethodFilter.java:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.dex2c.filters;
2 |
3 | import com.android.tools.smali.dexlib2.iface.ClassDef;
4 | import com.android.tools.smali.dexlib2.iface.Method;
5 |
6 | public interface ClassAndMethodFilter {
7 | boolean acceptClass(ClassDef classDef);
8 |
9 | boolean acceptMethod(Method method);
10 | }
11 |
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/dex2c/filters/SimpleConvertConfig.java:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.dex2c.filters;
2 |
3 | import com.android.tools.smali.dexlib2.iface.ClassDef;
4 | import com.android.tools.smali.dexlib2.iface.Method;
5 |
6 | public class SimpleConvertConfig implements ClassAndMethodFilter {
7 | private final ClassAndMethodFilter filter;
8 | private final SimpleRules simpleRule;
9 |
10 | public SimpleConvertConfig(ClassAndMethodFilter filter, SimpleRules simpleRules) {
11 | this.filter = filter;
12 | this.simpleRule = simpleRules;
13 | }
14 |
15 | @Override
16 | public boolean acceptClass(ClassDef classDef) {
17 | if (filter != null && !filter.acceptClass(classDef)) {
18 | return false;
19 | }
20 | return simpleRule != null && simpleRule.matchClass(
21 | classDef.getType(),
22 | classDef.getSuperclass(),
23 | classDef.getInterfaces());
24 | }
25 |
26 | @Override
27 | public boolean acceptMethod(Method method) {
28 | if (filter != null && !filter.acceptMethod(method)) {
29 | return false;
30 | }
31 | return simpleRule != null && simpleRule.matchMethod(method.getName());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/dex2c/filters/SimpleRules.java:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.dex2c.filters;
2 |
3 | import com.google.common.collect.HashMultimap;
4 | import org.jetbrains.annotations.Contract;
5 | import org.jetbrains.annotations.NotNull;
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | import java.io.BufferedReader;
9 | import java.io.IOException;
10 | import java.io.Reader;
11 | import java.rmi.RemoteException;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 | import java.util.Set;
15 |
16 | /**
17 | * class * extends android.app.Activity
18 | * class * implements java.io.Serializable
19 | * class my.package.AClass
20 | * class my.package.* { *; }
21 | * class * extends java.util.ArrayList {
22 | * if*;
23 | * }
24 | * class A {
25 | * }
26 | * class B extends A {
27 | * }
28 | * class C extends B {
29 | * }
30 | * The rule 'class * extends A' only match B
31 | */
32 | public class SimpleRules {
33 | private final HashMultimap convertRules = HashMultimap.create();
34 | private Set methodRules;
35 |
36 | public SimpleRules() {
37 | }
38 |
39 | @Contract(pure = true)
40 | private static @NotNull String classNameToType(@NotNull String className) {
41 | return "L" + className.replace('.', '/') + ";";
42 | }
43 |
44 | @NotNull
45 | private static String toRegex(@NotNull String s) {
46 | final StringBuilder sb = new StringBuilder(s.length() + 3);
47 | for (int i = 0; i < s.length(); i++) {
48 | final char c = s.charAt(i);
49 | if (c == '*') {
50 | sb.append('.');
51 | }
52 | sb.append(c);
53 | }
54 | return sb.toString();
55 | }
56 |
57 | public void parse(Reader ruleReader) throws IOException {
58 | try (BufferedReader reader = new BufferedReader(ruleReader)) {
59 | ClassRule classRule = null;
60 | final ArrayList methodNameList = new ArrayList<>();
61 | boolean methodParsing = false;
62 | int lineNumb = 0;
63 | String line;
64 | while ((line = reader.readLine()) != null) {
65 | line = line.trim();
66 | if (line.isEmpty()) {//empty line
67 | lineNumb++;
68 | continue;
69 | }
70 | if (line.startsWith("class")) {
71 | final String[] split = line.split(" +");
72 | final int length = split.length;
73 | if (length < 2) {
74 | throw new RemoteException("Error rule " + lineNumb + ": " + line);
75 | }
76 | String className = split[1];
77 | String supperName = "";
78 | String interfaceName = "";
79 | if (length >= 4) {
80 | if ("extends".equals(split[2])) {//class * extends A
81 | supperName = split[3];
82 | } else if ("implements".equals(split[2])) {//class * implements I
83 | interfaceName = split[3];
84 | }
85 | }
86 | classRule = new ClassRule(className, supperName, interfaceName);
87 | int mstart;
88 | if ((mstart = line.indexOf('{')) != -1) { // my.pkg.A { methodA;methodB;}
89 | int mend;
90 | if ((mend = line.indexOf('}')) != -1) {
91 | final String[] methodNames = line.substring(mstart + 1, mend).trim().split(";");
92 | if (methodNames.length == 0) {
93 | throw new RemoteException("Error rule " + lineNumb + ": " + line);
94 | }
95 | for (String name : methodNames) {
96 | convertRules.put(classRule, new MethodRule(name));
97 | }
98 | } else {
99 | methodNameList.clear();
100 | methodParsing = true;
101 | }
102 | } else {
103 | //any methods
104 | convertRules.put(classRule, new MethodRule("*"));
105 | }
106 | } else if (methodParsing) {
107 | // my.pkg.A {
108 | // methodA;
109 | // methodB;
110 | // }
111 | if (line.indexOf('}') != -1) {
112 | if (methodNameList.isEmpty()) {
113 | throw new RemoteException("Error rule " + lineNumb + ": " + line);
114 | }
115 | for (String methodName : methodNameList) {
116 | if ("".equals(methodName)) {
117 | continue;
118 | }
119 | convertRules.put(classRule, new MethodRule(methodName));
120 | }
121 | methodParsing = false;
122 | } else {
123 | methodNameList.add(line.replace(";", ""));
124 | }
125 | } else {
126 | throw new RemoteException("Error rule " + lineNumb + ": " + line);
127 | }
128 |
129 | lineNumb++;
130 | }
131 | }
132 | }
133 |
134 | public boolean matchClass(@NotNull String classType, @Nullable String supperType, @NotNull List ifacTypes) {
135 | for (ClassRule rule : convertRules.keySet()) {
136 | final String typeRegex = toRegex(classNameToType(rule.className));
137 | if (classType.matches(typeRegex)) {// match classType
138 | if (!"".equals(rule.supperName)) {//supper name not empty
139 | if (supperType != null) {
140 | final String type = classNameToType(rule.supperName);
141 | if (supperType.equals(type)) {
142 | methodRules = convertRules.get(rule);
143 | return true;
144 | }
145 | }
146 | continue;
147 | }
148 | if (!"".equals(rule.interfaceName)) {//interface name not empty
149 | for (String iface : ifacTypes) {
150 | if (iface.equals(classNameToType(rule.interfaceName))) {
151 | methodRules = convertRules.get(rule);
152 | return true;
153 | }
154 | }
155 | continue;
156 | }
157 | methodRules = convertRules.get(rule);
158 | return true;
159 | }
160 | }
161 | methodRules = null;
162 | return false;
163 | }
164 |
165 | public boolean matchMethod(String methodName) {
166 | if (methodRules == null || methodName == null) {
167 | return false;
168 | }
169 | for (MethodRule methodRule : methodRules) {
170 | if (methodName.matches(toRegex(methodRule.methodName))) {
171 | return true;
172 | }
173 | }
174 | return false;
175 | }
176 |
177 | private static class ClassRule {
178 | @NotNull
179 | private final String className;
180 | //supper class
181 | @NotNull
182 | private final String supperName;
183 | //interface
184 | @NotNull
185 | private final String interfaceName;
186 |
187 | public ClassRule(@NotNull String className) {
188 | this(className, "", "");
189 | }
190 |
191 | public ClassRule(@NotNull String className, @NotNull String supperName, @NotNull String interfaceName) {
192 | this.className = className;
193 | this.supperName = supperName;
194 | this.interfaceName = interfaceName;
195 | }
196 |
197 | @Override
198 | public boolean equals(Object o) {
199 | if (this == o) return true;
200 | if (o == null || getClass() != o.getClass()) return false;
201 |
202 | ClassRule classRule = (ClassRule) o;
203 |
204 | if (!className.equals(classRule.className)) return false;
205 | if (!supperName.equals(classRule.supperName)) return false;
206 | return interfaceName.equals(classRule.interfaceName);
207 | }
208 |
209 | @Override
210 | public int hashCode() {
211 | int result = className.hashCode();
212 | result = 31 * result + supperName.hashCode();
213 | result = 31 * result + interfaceName.hashCode();
214 | return result;
215 | }
216 | }
217 |
218 | private static class MethodRule {
219 | @NotNull
220 | private final String methodName;
221 | // args ?
222 | // private final List args;
223 |
224 | public MethodRule(@NotNull String methodName) {
225 | this.methodName = methodName;
226 | }
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/log/VmpLogger.kt:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.log
2 |
3 | interface VmpLogger {
4 | fun info(msg: String?)
5 | fun error(msg: String?)
6 | fun warning(msg: String?)
7 | }
8 |
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/sign/ApkVerifyCodeGenerator.java:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.sign;
2 |
3 | /**
4 | * 验证公钥,通过公钥生成c代码, 运行时读取apk签名块里公钥, 进行对比从而验证apk是否被重新签名
5 | */
6 | public class ApkVerifyCodeGenerator {
7 | private final String keyStorePath;
8 | private final String alias;
9 | private final String keyStorePassword;
10 |
11 | public ApkVerifyCodeGenerator(String keyStorePath, String alias, String keyStorePassword) {
12 | this.keyStorePath = keyStorePath;
13 | this.alias = alias;
14 | this.keyStorePassword = keyStorePassword;
15 | }
16 |
17 | public String generate() {
18 | final byte[] publicKey = PublicKeyUtils.getPublicKey(keyStorePath, alias, keyStorePassword);
19 | if (publicKey == null) {
20 | throw new RuntimeException("publicKey == null");
21 | }
22 | final StringBuilder sb = new StringBuilder();
23 | for (int i = 0; i < publicKey.length; i++) {
24 | if (i % 10 == 0) {
25 | sb.append(" \\\\\n ");
26 | }
27 | sb.append(String.format("0x%02x, ", publicKey[i] & 0xFF));
28 | }
29 | return sb.toString();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/sign/PublicKeyUtils.java:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.sign;
2 |
3 | import org.jetbrains.annotations.Nullable;
4 |
5 | import java.io.FileInputStream;
6 | import java.security.KeyStore;
7 | import java.security.PublicKey;
8 | import java.security.cert.Certificate;
9 | import java.util.Enumeration;
10 |
11 | public class PublicKeyUtils {
12 | public static byte @Nullable [] getPublicKey(String keyStorePath, String alias, String password) {
13 | try {
14 | final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
15 | ks.load(new FileInputStream(keyStorePath), password.toCharArray());
16 | if (alias == null || alias.isEmpty()) {
17 | final Enumeration aliases = ks.aliases();
18 | if (aliases == null) {
19 | return null;
20 | }
21 | if (aliases.hasMoreElements()) {
22 | alias = aliases.nextElement();//取第一个证书别名
23 | }
24 | }
25 | final Certificate[] chain = ks.getCertificateChain(alias);
26 | if (chain == null || chain.length == 0) {
27 | throw new RuntimeException(
28 | keyStorePath + " entry \"" + alias + "\" does not contain certificates");
29 | }
30 |
31 | final PublicKey publicKey = chain[0].getPublicKey();
32 | return publicKey.getEncoded();
33 | } catch (Exception e) {
34 | throw new RuntimeException(e);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/util/CmakeUtils.java:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.util;
2 |
3 | import com.nmmedit.apkprotect.data.Prefs;
4 | import com.nmmedit.apkprotect.dex2c.converter.instructionrewriter.InstructionRewriter;
5 | import com.nmmedit.apkprotect.sign.ApkVerifyCodeGenerator;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | import java.io.*;
9 | import java.nio.charset.StandardCharsets;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 | import java.util.Random;
13 | import java.util.regex.Matcher;
14 | import java.util.regex.Pattern;
15 | import java.util.stream.Collectors;
16 |
17 | public class CmakeUtils {
18 |
19 | //根据指令重写规则,重新生成新的opcode
20 | public static void writeOpcodeHeaderFile(File source, @NotNull InstructionRewriter instructionRewriter) throws IOException {
21 | final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
22 | new FileInputStream(source), StandardCharsets.UTF_8));
23 |
24 | final String collect = bufferedReader.lines().collect(Collectors.joining("\n"));
25 | final Pattern opcodePattern = Pattern.compile(
26 | "enum Opcode \\{.*?};",
27 | Pattern.MULTILINE | Pattern.DOTALL);
28 | final StringWriter opcodeContent = new StringWriter();
29 | final StringWriter gotoTableContent = new StringWriter();
30 | instructionRewriter.generateConfig(opcodeContent, gotoTableContent);
31 | String headerContent = opcodePattern
32 | .matcher(collect)
33 | .replaceAll(String.format("enum Opcode {\n%s};\n", opcodeContent.toString()));
34 |
35 | //根据opcode生成goto表
36 | final Pattern patternGotoTable = Pattern.compile(
37 | "_name\\[kNumPackedOpcodes] = \\{.*?};",
38 | Pattern.MULTILINE | Pattern.DOTALL);
39 | headerContent = patternGotoTable
40 | .matcher(headerContent)
41 | .replaceAll(String.format("_name[kNumPackedOpcodes] = { \\\\\n%s};\n", gotoTableContent));
42 |
43 | try (FileWriter fileWriter = new FileWriter(source)) {
44 | fileWriter.write(headerContent);
45 | }
46 | }
47 |
48 | //读取证书信息,并把公钥写入签名验证文件里,运行时对apk进行签名校验
49 | private static void writeApkVerifierFile(String packageName, File source, ApkVerifyCodeGenerator apkVerifyCodeGenerator) throws IOException {
50 | if (apkVerifyCodeGenerator == null) {
51 | return;
52 | }
53 | final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
54 | new FileInputStream(source), StandardCharsets.UTF_8));
55 |
56 | final String lines = bufferedReader.lines().collect(Collectors.joining("\n"));
57 | String dataPlaceHolder = "#define publicKeyPlaceHolder";
58 |
59 | String content = lines.replaceAll(dataPlaceHolder, dataPlaceHolder + apkVerifyCodeGenerator.generate());
60 | content = content.replaceAll("(#define PACKAGE_NAME) .*\n", "$1 \"" + packageName + "\"\n");
61 |
62 | try (FileWriter fileWriter = new FileWriter(source)) {
63 | fileWriter.write(content);
64 | }
65 | }
66 |
67 | public static void writeCmakeFile(File cmakeTemp, String libNmmpName, String libVmName, String cxxFlags) throws IOException {
68 | final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
69 | new FileInputStream(cmakeTemp), StandardCharsets.UTF_8));
70 |
71 | String lines = bufferedReader.lines().collect(Collectors.joining("\n"));
72 | //定位cmake里的语句,防止替换错误
73 | String libNameFormat = "set\\(LIBNAME_PLACEHOLDER \"%s\"\\)";
74 |
75 | //替换原本libname
76 | lines = lines.replaceAll(String.format(libNameFormat, "nmmp"), String.format(libNameFormat, libNmmpName));
77 |
78 | libNameFormat = "set\\(LIBNMMVM_NAME \"%s\" CACHE INTERNAL \"lib %s name\"\\)";
79 | lines = lines.replaceAll(String.format(libNameFormat, "nmmvm", "nmmvm"), String.format(libNameFormat, libVmName, libVmName));
80 |
81 | //额外FLAGS
82 | lines = lines.replaceAll("-fvisibility=hidden", "-fvisibility=hidden " + cxxFlags);
83 |
84 | FileHelper.writeToFile(cmakeTemp, lines);
85 | }
86 |
87 |
88 | public static void generateCSources(File srcDir, InstructionRewriter instructionRewriter) throws IOException {
89 | final File vmsrcFile = new File(FileUtils.getHomePath(), "tools/vmsrc.zip");
90 | if (!vmsrcFile.exists()) {
91 | //警告:如果外部源码存在不会复制内部vmsrc.zip出去,需要删除外部源码文件才能保证vmsrc.zip正确更新
92 | vmsrcFile.getParentFile().mkdirs();
93 | //copy vmsrc.zip to external directory
94 | try (
95 | InputStream inputStream = CmakeUtils.class.getResourceAsStream("/vmsrc.zip");
96 | final FileOutputStream outputStream = new FileOutputStream(vmsrcFile);
97 | ) {
98 | FileHelper.copyStream(inputStream, outputStream);
99 | }
100 | }
101 | final List cSources = ApkUtils.extractFiles(vmsrcFile, ".*", srcDir);
102 |
103 | //处理指令及apk验证,生成新的c文件
104 | for (File source : cSources) {
105 | if (source.getName().endsWith("DexOpcodes.h")) {
106 | //根据指令重写规则重新生成DexOpcodes.h文件
107 | writeOpcodeHeaderFile(source, instructionRewriter);
108 | } else if (source.getName().equals("CMakeLists.txt")) {
109 | //处理cmake里配置的本地库名
110 | writeCmakeFile(source, Prefs.getNmmpName(), Prefs.getVmName(), Prefs.getCxxFlags());
111 | } else if (source.getName().endsWith("vm.h")) {
112 | writeRandomResolver(source);
113 | } else if (source.getName().endsWith("JNIWrapper.h")) {
114 | writeRandomJNIWrapper(source);
115 | }
116 | }
117 | }
118 |
119 | private static void writeRandomResolver(File source) throws IOException {
120 | final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
121 | new FileInputStream(source), StandardCharsets.UTF_8));
122 | String lines = bufferedReader.lines().collect(Collectors.joining("\n"));
123 | final Pattern p = Pattern.compile("typedef struct \\{([^}]*?)} vmResolver;", Pattern.MULTILINE | Pattern.DOTALL);
124 | final Matcher matcherResolver = p.matcher(lines);
125 | if (matcherResolver.find()) {
126 | final String body = matcherResolver.group(1);
127 | //match function pointer
128 | final Pattern funcPattern = Pattern.compile("([^();]* \\**\\(\\*[a-zA-z0-9]*\\)\\([^();]*\\);)", Pattern.MULTILINE | Pattern.DOTALL);
129 | final ArrayList funcs = new ArrayList<>();
130 | final Matcher matcher = funcPattern.matcher(body);
131 | while (matcher.find()) {
132 | funcs.add(matcher.group(1));
133 | }
134 |
135 |
136 | try (FileWriter fileWriter = new FileWriter(source)) {
137 | final String doc = matcherResolver.replaceAll("typedef struct {\n" +
138 | randomList(funcs) +
139 | "} vmResolver;");
140 | fileWriter.write(doc);
141 | }
142 | }
143 | }
144 |
145 | private static void writeRandomJNIWrapper(File file) throws IOException {
146 | final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
147 | new FileInputStream(file), StandardCharsets.UTF_8));
148 | String lines = bufferedReader.lines().collect(Collectors.joining("\n"));
149 | final Pattern p = Pattern.compile("typedef struct \\{([^}]*?)} JNIWrapper;", Pattern.MULTILINE | Pattern.DOTALL);
150 | final Matcher matcherWrapper = p.matcher(lines);
151 | if (matcherWrapper.find()) {
152 | final String body = matcherWrapper.group(1);
153 | //match function pointer
154 | final Pattern funcPattern = Pattern.compile("([^();]* \\**\\(\\*[a-zA-z0-9]*\\)\\([^();]*\\);)", Pattern.MULTILINE | Pattern.DOTALL);
155 | final ArrayList funcs = new ArrayList<>();
156 | final Matcher matcher = funcPattern.matcher(body);
157 | while (matcher.find()) {
158 | funcs.add(matcher.group(1));
159 | }
160 | try (FileWriter fileWriter = new FileWriter(file)) {
161 | final String doc = matcherWrapper.replaceAll("typedef struct {\n" +
162 | randomList(funcs) +
163 | "} JNIWrapper;");
164 | fileWriter.write(doc);
165 | }
166 | }
167 | }
168 |
169 | private static @NotNull String randomList(@NotNull List list) {
170 | final StringBuilder sb = new StringBuilder();
171 | final int size = list.size();
172 | for (int i = 0; i < size; i++) {
173 | final Random random = new Random();
174 | final int idx = random.nextInt(list.size());
175 | sb.append(list.get(idx));
176 | sb.append('\n');
177 | list.remove(idx);
178 | }
179 | return sb.toString();
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/util/FileHelper.kt:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.util
2 |
3 | import java.io.File
4 | import java.io.IOException
5 | import java.io.InputStream
6 | import java.io.OutputStream
7 | import java.nio.charset.Charset
8 | import java.nio.charset.StandardCharsets
9 |
10 | object FileHelper {
11 | @JvmStatic
12 | fun bytesToInputStream(byteArray: ByteArray): InputStream {
13 | return byteArray.inputStream()
14 | }
15 |
16 | @JvmStatic
17 | fun readBytes(file: File): ByteArray {
18 | return file.readBytes()
19 | }
20 |
21 | @JvmStatic
22 | fun readFile(file: File, encoding: Charset): String {
23 | return file.inputStream().readBytes().toString(encoding)
24 | }
25 |
26 | @JvmStatic
27 | fun readFile(path: String, encoding: Charset): String {
28 | return File(path).inputStream().readBytes().toString(encoding)
29 | }
30 |
31 | @JvmStatic
32 | fun writeToFile(file: File, content: String) {
33 | file.writeBytes(content.toByteArray(StandardCharsets.UTF_8))
34 | }
35 |
36 | @JvmStatic
37 | fun writeToFile(file: File, inputStream: InputStream) {
38 | file.writeBytes(inputStream.readBytes())
39 | }
40 |
41 | @JvmStatic
42 | fun writeToFile(path: String, content: String) {
43 | File(path).writeBytes(content.toByteArray(StandardCharsets.UTF_8))
44 | }
45 |
46 | @JvmStatic
47 | fun writeToFile(path: String, inputStream: InputStream) {
48 | File(path).writeBytes(inputStream.readBytes())
49 | }
50 |
51 | @JvmStatic
52 | @Throws(IOException::class)
53 | fun copyStream(inputStream: InputStream, out: OutputStream) {
54 | val buf = ByteArray(4 * 1024)
55 | var len: Int
56 | while (inputStream.read(buf).also { len = it } != -1) {
57 | out.write(buf, 0, len)
58 | }
59 | }
60 |
61 | @JvmStatic
62 | fun deleteFile(file: File) {
63 | file.deleteRecursively()
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/util/FileUtils.java:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.util;
2 |
3 | import java.io.File;
4 | import java.net.URISyntaxException;
5 | import java.security.CodeSource;
6 |
7 | public class FileUtils {
8 | public static String getHomePath() {
9 | CodeSource codeSource = FileUtils.class.getProtectionDomain().getCodeSource();
10 | try {
11 | File jarFile = new File(codeSource.getLocation().toURI().getPath());
12 | return jarFile.getParentFile().getPath();
13 | } catch (URISyntaxException e) {
14 | e.printStackTrace();
15 | return getHomePath();
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/util/ModifiedUtf8.java:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.util;
2 |
3 | import java.io.UTFDataFormatException;
4 |
5 | //来自java.nio.charset.ModifiedUtf8
6 | public class ModifiedUtf8 {
7 | private ModifiedUtf8() {
8 | }
9 |
10 | /**
11 | * Decodes a byte array containing modified UTF-8 bytes into a string.
12 | *
13 | * Note that although this method decodes the (supposedly impossible) zero byte to U+0000,
14 | * that's what the RI does too.
15 | */
16 | public static String decode(byte[] in, char[] out, int offset, int utfSize) throws UTFDataFormatException {
17 | int count = 0, s = 0, a;
18 | while (count < utfSize) {
19 | if ((out[s] = (char) in[offset + count++]) < '\u0080') {
20 | s++;
21 | } else if (((a = out[s]) & 0xe0) == 0xc0) {
22 | if (count >= utfSize) {
23 | throw new UTFDataFormatException("bad second byte at " + count);
24 | }
25 | int b = in[offset + count++];
26 | if ((b & 0xC0) != 0x80) {
27 | throw new UTFDataFormatException("bad second byte at " + (count - 1));
28 | }
29 | out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
30 | } else if ((a & 0xf0) == 0xe0) {
31 | if (count + 1 >= utfSize) {
32 | throw new UTFDataFormatException("bad third byte at " + (count + 1));
33 | }
34 | int b = in[offset + count++];
35 | int c = in[offset + count++];
36 | if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
37 | throw new UTFDataFormatException("bad second or third byte at " + (count - 2));
38 | }
39 | out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
40 | } else {
41 | throw new UTFDataFormatException("bad byte at " + (count - 1));
42 | }
43 | }
44 | return new String(out, 0, s);
45 | }
46 |
47 | /**
48 | * Returns the number of bytes the modified UTF8 representation of 's' would take. Note
49 | * that this is just the space for the bytes representing the characters, not the length
50 | * which precedes those bytes, because different callers represent the length differently,
51 | * as two, four, or even eight bytes. If {@code shortLength} is true, we'll throw an
52 | * exception if the string is too long for its length to be represented by a short.
53 | */
54 | public static long countBytes(String s, boolean shortLength) throws UTFDataFormatException {
55 | long result = 0;
56 | final int length = s.length();
57 | for (int i = 0; i < length; ++i) {
58 | char ch = s.charAt(i);
59 | if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
60 | ++result;
61 | } else if (ch <= 2047) {
62 | result += 2;
63 | } else {
64 | result += 3;
65 | }
66 | if (shortLength && result > 65535) {
67 | throw new UTFDataFormatException("String more than 65535 UTF bytes long");
68 | }
69 | }
70 | return result;
71 | }
72 |
73 | /**
74 | * Encodes the modified UTF-8 bytes corresponding to string {@code s} into the
75 | * byte array {@code dst}, starting at the given {@code offset}.
76 | */
77 | public static void encode(byte[] dst, int offset, String s) {
78 | final int length = s.length();
79 | for (int i = 0; i < length; i++) {
80 | char ch = s.charAt(i);
81 | if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
82 | dst[offset++] = (byte) ch;
83 | } else if (ch <= 2047) {
84 | dst[offset++] = (byte) (0xc0 | (0x1f & (ch >> 6)));
85 | dst[offset++] = (byte) (0x80 | (0x3f & ch));
86 | } else {
87 | dst[offset++] = (byte) (0xe0 | (0x0f & (ch >> 12)));
88 | dst[offset++] = (byte) (0x80 | (0x3f & (ch >> 6)));
89 | dst[offset++] = (byte) (0x80 | (0x3f & ch));
90 | }
91 | }
92 | }
93 |
94 | /**
95 | * Returns an array containing the modified UTF-8 form of {@code s}.
96 | * Throws UTFDataFormatException if {@code s} is too long
97 | * for a two-byte length.
98 | */
99 | public static byte[] encode(String s) throws UTFDataFormatException {
100 | int utfCount = (int) ModifiedUtf8.countBytes(s, true);
101 | byte[] result = new byte[utfCount];
102 | ModifiedUtf8.encode(result, 0, s);
103 | return result;
104 | }
105 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/util/OsDetector.kt:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.util
2 |
3 | import java.util.*
4 |
5 | object OsDetector {
6 | @JvmStatic
7 | val isWindows: Boolean
8 | get() {
9 | val osName = System.getProperty("os.name") ?: return false
10 | return osName.lowercase(Locale.getDefault()).contains("windows")
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/util/Pair.kt:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.util
2 |
3 | class Pair(
4 | @JvmField
5 | val first: T,
6 | @JvmField
7 | val second: V
8 | )
9 |
--------------------------------------------------------------------------------
/library/src/main/java/com/nmmedit/apkprotect/util/ZipHelper.kt:
--------------------------------------------------------------------------------
1 | package com.nmmedit.apkprotect.util
2 |
3 | import com.mcal.apkparser.zip.ZipFile
4 | import java.io.*
5 | import java.util.*
6 | import java.util.regex.Pattern
7 |
8 | object ZipHelper {
9 | /**
10 | * Returns true if there exists a file whose name matches `filename` in `apkFile`.
11 | */
12 | @JvmStatic
13 | @Throws(IOException::class)
14 | fun hasFile(apkFile: File, filename: String): Boolean {
15 | ZipFile(apkFile).use { apkZip -> return apkZip.getEntry(filename) != null }
16 | }
17 |
18 | /**
19 | * Возвращает содержимое файла с указанным именем из Zip-архива.
20 | *
21 | * @param zipFile Zip-архив, из которого нужно прочитать файл.
22 | * @param filename Имя файла, который нужно прочитать.
23 | * @return Массив байтов, содержащий содержимое файла.
24 | * @throws IOException Если произошла ошибка при чтении файла из Zip-архива.
25 | */
26 | @JvmStatic
27 | @Throws(IOException::class)
28 | fun getZipFileContent(zipFile: File, filename: String): ByteArray? {
29 | ZipFile(zipFile).use {
30 | it.getInputStream(it.getEntry(filename))?.use { inputStream ->
31 | return inputStream.readBytes()
32 | }
33 | return null
34 | }
35 | }
36 |
37 | /**
38 | * Returns all files in an apk that match a given regular expression.
39 | *
40 | * @param apkFile The file containing the apk zip archive.
41 | * @param regex A regular expression to match the requested filenames.
42 | * @return A mapping of the matched filenames to their byte contents.
43 | * @throws IOException Thrown if a matching file cannot be read from the apk.
44 | */
45 | @JvmStatic
46 | @Throws(IOException::class)
47 | fun getFiles(apkFile: File, regex: String): Map {
48 | return getFiles(apkFile, Pattern.compile(regex))
49 | }
50 |
51 | /**
52 | * Returns all files in an apk that match a given regular expression.
53 | *
54 | * @param apkFile The file containing the apk zip archive.
55 | * @param regex A regular expression to match the requested filenames.
56 | * @return A mapping of the matched filenames to their byte contents.
57 | * @throws IOException Thrown if a matching file cannot be read from the apk.
58 | */
59 | @JvmStatic
60 | @Throws(IOException::class)
61 | fun getFiles(apkFile: File, regex: Pattern): Map {
62 | ZipFile(apkFile).use { apkZip ->
63 | val result = LinkedHashMap()
64 | val entries = apkZip.entries
65 | while (entries.hasMoreElements()) {
66 | val entry = entries.nextElement()
67 | if (regex.matcher(entry.name).matches()) {
68 | apkZip.getInputStream(entry).use { inputStream ->
69 | result.put(
70 | entry.name,
71 | inputStream.readBytes()
72 | )
73 | }
74 | }
75 | }
76 | return result
77 | }
78 | }
79 |
80 | @JvmStatic
81 | @Throws(IOException::class)
82 | fun extractFiles(apkFile: File, regex: String, outDir: File): List {
83 | return extractFiles(apkFile, Pattern.compile(regex), outDir)
84 | }
85 |
86 | @JvmStatic
87 | @Throws(IOException::class)
88 | fun extractFiles(apkFile: File, regex: Pattern, outDir: File): List {
89 | ZipFile(apkFile).use { apkZip ->
90 | val result = LinkedList()
91 | val entries = apkZip.entries
92 | while (entries.hasMoreElements()) {
93 | val entry = entries.nextElement()
94 | if (!entry.isDirectory && regex.matcher(entry.name).matches()) {
95 | val file = File(outDir, entry.name)
96 | if (!file.parentFile.exists()) {
97 | file.parentFile.mkdirs()
98 | }
99 | apkZip.getInputStream(entry).use { inputStream ->
100 | FileOutputStream(file).use { output ->
101 | copyStream(inputStream, output)
102 | result.add(file)
103 | }
104 | }
105 | }
106 | }
107 | return result
108 | }
109 | }
110 |
111 | @JvmStatic
112 | @Throws(IOException::class)
113 | fun copyStream(inputStream: InputStream, out: OutputStream) {
114 | val buf = ByteArray(8 * 1024)
115 | var len: Int
116 | while (inputStream.read(buf).also { len = it } != -1) {
117 | out.write(buf, 0, len)
118 | }
119 | }
120 | }
--------------------------------------------------------------------------------
/library/src/main/proto/Configuration.proto:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | * Copyright (C) 2017 The Android Open Source Project
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | syntax = "proto3";
18 | package aapt.pb;
19 | option java_package = "com.android.aapt";
20 | // A description of the requirements a device must have in order for a
21 | // resource to be matched and selected.
22 | message Configuration {
23 | enum LayoutDirection {
24 | LAYOUT_DIRECTION_UNSET = 0;
25 | LAYOUT_DIRECTION_LTR = 1;
26 | LAYOUT_DIRECTION_RTL = 2;
27 | }
28 | enum ScreenLayoutSize {
29 | SCREEN_LAYOUT_SIZE_UNSET = 0;
30 | SCREEN_LAYOUT_SIZE_SMALL = 1;
31 | SCREEN_LAYOUT_SIZE_NORMAL = 2;
32 | SCREEN_LAYOUT_SIZE_LARGE = 3;
33 | SCREEN_LAYOUT_SIZE_XLARGE = 4;
34 | }
35 | enum ScreenLayoutLong {
36 | SCREEN_LAYOUT_LONG_UNSET = 0;
37 | SCREEN_LAYOUT_LONG_LONG = 1;
38 | SCREEN_LAYOUT_LONG_NOTLONG = 2;
39 | }
40 | enum ScreenRound {
41 | SCREEN_ROUND_UNSET = 0;
42 | SCREEN_ROUND_ROUND = 1;
43 | SCREEN_ROUND_NOTROUND = 2;
44 | }
45 | enum WideColorGamut {
46 | WIDE_COLOR_GAMUT_UNSET = 0;
47 | WIDE_COLOR_GAMUT_WIDECG = 1;
48 | WIDE_COLOR_GAMUT_NOWIDECG = 2;
49 | }
50 | enum Hdr {
51 | HDR_UNSET = 0;
52 | HDR_HIGHDR = 1;
53 | HDR_LOWDR = 2;
54 | }
55 | enum Orientation {
56 | ORIENTATION_UNSET = 0;
57 | ORIENTATION_PORT = 1;
58 | ORIENTATION_LAND = 2;
59 | ORIENTATION_SQUARE = 3;
60 | }
61 | enum UiModeType {
62 | UI_MODE_TYPE_UNSET = 0;
63 | UI_MODE_TYPE_NORMAL = 1;
64 | UI_MODE_TYPE_DESK = 2;
65 | UI_MODE_TYPE_CAR = 3;
66 | UI_MODE_TYPE_TELEVISION = 4;
67 | UI_MODE_TYPE_APPLIANCE = 5;
68 | UI_MODE_TYPE_WATCH = 6;
69 | UI_MODE_TYPE_VRHEADSET = 7;
70 | }
71 | enum UiModeNight {
72 | UI_MODE_NIGHT_UNSET = 0;
73 | UI_MODE_NIGHT_NIGHT = 1;
74 | UI_MODE_NIGHT_NOTNIGHT = 2;
75 | }
76 | enum Touchscreen {
77 | TOUCHSCREEN_UNSET = 0;
78 | TOUCHSCREEN_NOTOUCH = 1;
79 | TOUCHSCREEN_STYLUS = 2;
80 | TOUCHSCREEN_FINGER = 3;
81 | }
82 | enum KeysHidden {
83 | KEYS_HIDDEN_UNSET = 0;
84 | KEYS_HIDDEN_KEYSEXPOSED = 1;
85 | KEYS_HIDDEN_KEYSHIDDEN = 2;
86 | KEYS_HIDDEN_KEYSSOFT = 3;
87 | }
88 | enum Keyboard {
89 | KEYBOARD_UNSET = 0;
90 | KEYBOARD_NOKEYS = 1;
91 | KEYBOARD_QWERTY = 2;
92 | KEYBOARD_TWELVEKEY = 3;
93 | }
94 | enum NavHidden {
95 | NAV_HIDDEN_UNSET = 0;
96 | NAV_HIDDEN_NAVEXPOSED = 1;
97 | NAV_HIDDEN_NAVHIDDEN = 2;
98 | }
99 | enum Navigation {
100 | NAVIGATION_UNSET = 0;
101 | NAVIGATION_NONAV = 1;
102 | NAVIGATION_DPAD = 2;
103 | NAVIGATION_TRACKBALL = 3;
104 | NAVIGATION_WHEEL = 4;
105 | }
106 | //
107 | // Axis/dimensions that are understood by the runtime.
108 | //
109 | // Mobile country code.
110 | uint32 mcc = 1;
111 | // Mobile network code.
112 | uint32 mnc = 2;
113 | // BCP-47 locale tag.
114 | string locale = 3;
115 | // Left-to-right, right-to-left...
116 | LayoutDirection layout_direction = 4;
117 | // Screen width in pixels. Prefer screen_width_dp.
118 | uint32 screen_width = 5;
119 | // Screen height in pixels. Prefer screen_height_dp.
120 | uint32 screen_height = 6;
121 | // Screen width in density independent pixels (dp).
122 | uint32 screen_width_dp = 7;
123 | // Screen height in density independent pixels (dp).
124 | uint32 screen_height_dp = 8;
125 | // The smallest screen dimension, regardless of orientation, in dp.
126 | uint32 smallest_screen_width_dp = 9;
127 | // Whether the device screen is classified as small, normal, large, xlarge.
128 | ScreenLayoutSize screen_layout_size = 10;
129 | // Whether the device screen is long.
130 | ScreenLayoutLong screen_layout_long = 11;
131 | // Whether the screen is round (Android Wear).
132 | ScreenRound screen_round = 12;
133 | // Whether the screen supports wide color gamut.
134 | WideColorGamut wide_color_gamut = 13;
135 | // Whether the screen has high dynamic range.
136 | Hdr hdr = 14;
137 | // Which orientation the device is in (portrait, landscape).
138 | Orientation orientation = 15;
139 | // Which type of UI mode the device is in (television, car, etc.).
140 | UiModeType ui_mode_type = 16;
141 | // Whether the device is in night mode.
142 | UiModeNight ui_mode_night = 17;
143 | // The device's screen density in dots-per-inch (dpi).
144 | uint32 density = 18;
145 | // Whether a touchscreen exists, supports a stylus, or finger.
146 | Touchscreen touchscreen = 19;
147 | // Whether the keyboard hardware keys are currently hidden, exposed, or
148 | // if the keyboard is a software keyboard.
149 | KeysHidden keys_hidden = 20;
150 | // The type of keyboard present (none, QWERTY, 12-key).
151 | Keyboard keyboard = 21;
152 | // Whether the navigation is exposed or hidden.
153 | NavHidden nav_hidden = 22;
154 | // The type of navigation present on the device
155 | // (trackball, wheel, dpad, etc.).
156 | Navigation navigation = 23;
157 | // The minimum SDK version of the device.
158 | uint32 sdk_version = 24;
159 | //
160 | // Build-time only dimensions.
161 | //
162 | string product = 25;
163 | }
--------------------------------------------------------------------------------
/library/src/main/proto/apex_manifest.proto:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // This file should be in sync with
18 | // https://android.googlesource.com/platform/system/apex/+/refs/heads/master/proto/apex_manifest.proto
19 | // But, since bundletool itself doesn't need to read other than name,
20 | // let's keep this as minimal.
21 |
22 | syntax = "proto3";
23 |
24 | package apex.proto;
25 |
26 | option java_package = "com.android.apex";
27 | option java_outer_classname = "ApexManifestProto";
28 |
29 | message ApexManifest {
30 |
31 | // Package Name
32 | string name = 1;
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/library/src/main/proto/app_dependencies.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 |
3 | package android.bundle;
4 |
5 | import "google/protobuf/wrappers.proto";
6 |
7 | option java_package = "com.android.bundle";
8 |
9 | // Lists the dependencies of an application.
10 | message AppDependencies {
11 | // List of all the dependencies, direct and indirect.
12 | repeated Library library = 1;
13 |
14 | // Dependencies of the libraries from the list above.
15 | repeated LibraryDependencies library_dependencies = 2;
16 |
17 | // List of direct dependencies per bundle module.
18 | repeated ModuleDependencies module_dependencies = 3;
19 |
20 | // List of repositories where dependencies were found.
21 | repeated Repository repositories = 4;
22 | }
23 |
24 | // List of dependencies of a given library.
25 | message LibraryDependencies {
26 | // Indices correspond to the pool of libraries defined in AppDependencies.
27 | optional int32 library_index = 1;
28 | repeated int32 library_dep_index = 2;
29 | }
30 |
31 | // Lists the dependencies of a given module.
32 | message ModuleDependencies {
33 | optional string module_name = 1;
34 | // Direct module dependencies.
35 | // Index is from the pool of libraries defined in AppDependencies.
36 | repeated int32 dependency_index = 2;
37 | }
38 |
39 | // Next ID: 5
40 | message Library {
41 | // Type of library dependency.
42 | oneof library_oneof {
43 | // A library downloaded from a Maven repository.
44 | MavenLibrary maven_library = 1;
45 | // A library downloaded from a Unity repository.
46 | UnityLibrary unity_library = 4;
47 | }
48 |
49 | // This message contains various digests of the library contents.
50 | message Digests {
51 | // SHA256 hash value of the file contents.
52 | optional bytes sha256 = 1;
53 | }
54 |
55 | optional Digests digests = 2;
56 |
57 | // Repository from which the artifact was retrieved (if known).
58 | // Index is from pool of repositories defined in AppDependencies.
59 | optional google.protobuf.Int32Value repo_index = 3;
60 | }
61 |
62 | message MavenLibrary {
63 | optional string group_id = 1;
64 | optional string artifact_id = 2;
65 | optional string packaging = 3;
66 | optional string classifier = 4;
67 | optional string version = 5;
68 | }
69 |
70 | message UnityLibrary {
71 | // Corresponds to the "name" field in the package.json, uniquely identifying
72 | // the library.
73 | optional string package_name = 1;
74 |
75 | // Corresponds to the "version" field in the package.json file of the version
76 | // of the library actually compiled in the app.
77 | optional string version = 2;
78 | }
79 |
80 | // A repository for resolving artifacts and metadata.
81 | message Repository {
82 | // The type of the repository, and any type-specific configuration info.
83 | oneof repo_oneof {
84 | MavenRepo maven_repo = 1;
85 | IvyRepo ivy_repo = 2;
86 | UnityRepo unity_repo = 3;
87 | }
88 | }
89 |
90 | message MavenRepo {
91 | // The root url for the repository.
92 | optional string url = 1;
93 | }
94 |
95 | message IvyRepo {
96 | // The root url for the repository.
97 | optional string url = 1;
98 | }
99 |
100 | message UnityRepo {
101 | // The root url for the repository.
102 | optional string url = 1;
103 | }
104 |
--------------------------------------------------------------------------------
/library/src/main/proto/app_integrity_config.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package android.bundle;
4 |
5 | option java_package = "com.android.bundle";
6 |
7 | // Specifies integrity protection options that should be applied to an app
8 | // bundle.
9 | // Next tag: 8.
10 | message AppIntegrityConfig {
11 | bool enabled = 1;
12 | LicenseCheck license_check = 2;
13 | InstallerCheck installer_check = 3;
14 | DebuggerCheck debugger_check_deprecated = 4 [deprecated = true];
15 | EmulatorCheck emulator_check = 5;
16 | // Optional. If present, java/kotlin code will be obfuscated according to the
17 | // config.
18 | DexProtectionConfig dex_protection_config = 6;
19 | string version_label = 7;
20 | }
21 |
22 | // Next tag: 4.
23 | message LicenseCheck {
24 | bool enabled = 1;
25 | bool online_only = 2;
26 | Policy policy = 3;
27 | }
28 |
29 | // Next tag: 4.
30 | message InstallerCheck {
31 | bool enabled = 1;
32 | Policy policy = 2;
33 | repeated string additional_install_source = 3;
34 | }
35 |
36 | // Next tag: 2
37 | message DebuggerCheck {
38 | option deprecated = true;
39 |
40 | bool enabled = 1;
41 | }
42 |
43 | // Next tag: 2
44 | message EmulatorCheck {
45 | bool enabled = 1;
46 | }
47 |
48 | // Next tag: 2
49 | message Policy {
50 | enum Action {
51 | UNSPECIFIED = 0;
52 | WARN = 1;
53 | DISABLE = 2;
54 | WARN_THEN_DISABLE = 3;
55 | }
56 | Action action = 1;
57 | }
58 |
59 | // Configuration of java-related obfuscation.
60 | message DexProtectionConfig {
61 | bool enabled = 1;
62 | // Either fully qualified method reference e.g.
63 | // `java.lang.MyClass#myMethod(int,float,java.lang.String)`, or a partially
64 | // qualified reference e.g. `java.lang.MyClass#myMethod`.
65 | repeated string method_to_obfuscate = 2;
66 |
67 | // Describes how to look for methods to obfuscate.
68 | enum TargetingMode {
69 | TARGETING_MODE_UNSPECIFIED = 0;
70 | // We'll try to automatically find methods for obfuscation, in addition to
71 | // the methods specified in this config.
72 | TARGETING_MODE_AUTOMATIC = 1;
73 | // Only methods spefied in this config will be considered for obfuscation.
74 | TARGETING_MODE_PROPOSED_ONLY = 2;
75 | }
76 | TargetingMode targeting_mode = 3;
77 | }
78 |
--------------------------------------------------------------------------------
/library/src/main/proto/build_stamp.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package android.bundle;
4 |
5 | import "google/protobuf/timestamp.proto";
6 |
7 | option java_package = "com.android.bundle";
8 |
9 | // Build stamp metadata.
10 | message BuildStampFile {
11 | // The build stamp for this bundle.
12 | BuildStamp build_stamp = 1;
13 | }
14 |
15 | // A build stamp.
16 | // Next tag: 8.
17 | message BuildStamp {
18 | // Source revision for the build, e.g. HEAD commit hash.
19 | string source_revision = 1;
20 | // ID of the build job which created this bundle.
21 | string job_id = 2;
22 | // URL to the build job which created this bundle. Does not need to be public,
23 | // and probably will not be.
24 | string job_url = 3;
25 | // ID for the specific build, e.g. a UUID.
26 | string build_id = 4;
27 | // Build label: an arbitrary string set by the build system. May be used to
28 | // embed a release label.
29 | string label = 5;
30 | // Time at which the build was started.
31 | google.protobuf.Timestamp build_start_timestamp = 6;
32 |
33 | // Status of the working tree this bundle was built from.
34 | enum WorktreeStatus {
35 | WORKTREE_STATUS_UNSPECIFIED = 0;
36 | // Clean. No uncommitted modifications or files.
37 | WORKTREE_STATUS_CLEAN = 1;
38 | // Dirty. One or more uncommitted modifications or files.
39 | WORKTREE_STATUS_DIRTY = 2;
40 | }
41 | // Status of the working tree this bundle was built from.
42 | WorktreeStatus worktree_status = 7;
43 | }
44 |
--------------------------------------------------------------------------------
/library/src/main/proto/code_transparency.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package android.bundle;
4 |
5 | option java_package = "com.android.bundle";
6 |
7 | // Code transparency related metadata.
8 | message CodeTransparency {
9 | // List of code-related files in the bundle.
10 | repeated CodeRelatedFile code_related_file = 1;
11 |
12 | // Version of code transparency file format.
13 | // Required.
14 | int32 version = 2;
15 | }
16 |
17 | message CodeRelatedFile {
18 | enum Type {
19 | TYPE_UNSPECIFIED = 0;
20 | NATIVE_LIBRARY = 1;
21 | DEX = 2;
22 | }
23 | // Code related file may come from either Android App Bundle (AAB) or
24 | // bundletool repo. Bundletool repo is used as a source for a DEX file for an
25 | // archived APK which is not the part AAB and is injected when APKs are
26 | // generated from the AAB.
27 | // Required.
28 | oneof source {
29 | // Path to file in the bundle.
30 | string path = 1;
31 | // Path to a file that will be injected at build time.
32 | // Contained in the bundletool source code.
33 | string bundletool_repo_path = 5;
34 | };
35 | // Type of code-related file.
36 | // Required.
37 | Type type = 2;
38 | // Path to file in the APK. Only set for NATIVE_LIBRARY types.
39 | string apk_path = 3;
40 | // SHA256 digest of the code-related file.
41 | string sha256 = 4;
42 | }
43 |
--------------------------------------------------------------------------------
/library/src/main/proto/device_targeting_config.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package android.bundle;
4 |
5 | option java_package = "com.android.bundle";
6 | option java_multiple_files = true;
7 |
8 | // Configuration describing device targeting criteria for the content of an app.
9 | message DeviceTierConfig {
10 | // Definition of device groups for the app.
11 | repeated DeviceGroup device_groups = 1;
12 |
13 | // Definition of the set of device tiers for the app.
14 | DeviceTierSet device_tier_set = 2;
15 |
16 | // Definition of user country sets for the app.
17 | repeated UserCountrySet user_country_sets = 5;
18 | }
19 |
20 | // A group of devices.
21 | //
22 | // A group is defined by a set of device selectors. A device belongs to the
23 | // group if it matches any selector (logical OR).
24 | message DeviceGroup {
25 | // The name of the group.
26 | string name = 1;
27 |
28 | // Device selectors for this group. A device matching any of the selectors
29 | // is included in this group.
30 | repeated DeviceSelector device_selectors = 2;
31 | }
32 |
33 | // A set of device tiers.
34 | //
35 | // A tier set determines what variation of app content gets served to a specific
36 | // device, for device-targeted content.
37 | //
38 | // You should assign a priority level to each tier, which determines the
39 | // ordering by which they are evaluated by Play. See the documentation of
40 | // DeviceTier.level for more details.
41 | message DeviceTierSet {
42 | // Device tiers belonging to the set.
43 | repeated DeviceTier device_tiers = 1;
44 | }
45 |
46 | // A set of user countries.
47 | //
48 | // A country set determines what variation of app content gets served to a
49 | // specific location.
50 | message UserCountrySet {
51 | // Country set name.
52 | string name = 1;
53 |
54 | // List of country codes representing countries.
55 | // A Country code is represented in ISO 3166 alpha-2 format.
56 | // For Example:- "IT" for Italy, "GE" for Georgia.
57 | repeated string country_codes = 2;
58 | }
59 |
60 | // A single device tier.
61 | //
62 | // Devices matching any of the device groups in device_group_names are
63 | // considered to match the tier.
64 | message DeviceTier {
65 | // Groups of devices included in this tier.
66 | // These groups must be defined explicitly under device_groups in this
67 | // configuration.
68 | repeated string device_group_names = 1;
69 |
70 | // The priority level of the tier.
71 | //
72 | // Tiers are evaluated in descending order of level: the highest level tier
73 | // has the highest priority. The highest tier matching a given device is
74 | // selected for that device.
75 | //
76 | // You should use a contiguous range of levels for your tiers in a tier set;
77 | // tier levels in a tier set must be unique.
78 | // For instance, if your tier set has 4 tiers (including the global fallback),
79 | // you should define tiers 1, 2 and 3 in this configuration.
80 | //
81 | // Note: tier 0 is implicitly defined as a global fallback and selected for
82 | // devices that don't match any of the tiers explicitly defined here. You
83 | // mustn't define level 0 explicitly in this configuration.
84 | int32 level = 2;
85 | }
86 |
87 | // Selector for a device group.
88 | // A selector consists of a set of conditions on the device that should all
89 | // match (logical AND) to determine a device group eligibility.
90 | //
91 | // For instance, if a selector specifies RAM conditions, device model inclusion
92 | // and device model exclusion, a device is considered to match if:
93 | // device matches RAM conditions
94 | // AND
95 | // device matches one of the included device models
96 | // AND
97 | // device doesn't match excluded device models
98 | message DeviceSelector {
99 | // Conditions on the device's RAM.
100 | DeviceRam device_ram = 1;
101 |
102 | // Device models included by this selector.
103 | repeated DeviceId included_device_ids = 2;
104 |
105 | // Device models excluded by this selector, even if they match all other
106 | // conditions.
107 | repeated DeviceId excluded_device_ids = 3;
108 |
109 | // A device needs to have all these system features to be
110 | // included by the selector.
111 | repeated SystemFeature required_system_features = 4;
112 |
113 | // A device that has any of these system features is excluded by
114 | // this selector, even if it matches all other conditions.
115 | repeated SystemFeature forbidden_system_features = 5;
116 | }
117 |
118 | // Conditions about a device's RAM capabilities.
119 | message DeviceRam {
120 | // Minimum RAM in bytes (bound included).
121 | int64 min_bytes = 1;
122 |
123 | // Maximum RAM in bytes (bound excluded).
124 | int64 max_bytes = 2;
125 | }
126 |
127 | // Identifier of a device.
128 | message DeviceId {
129 | // Value of Build.BRAND.
130 | string build_brand = 1;
131 |
132 | // Value of Build.DEVICE.
133 | string build_device = 2;
134 | }
135 |
136 | // Representation of a system feature.
137 | message SystemFeature {
138 | // The name of the feature.
139 | string name = 1;
140 | }
141 |
142 | // Properties of a particular device.
143 | message DeviceProperties {
144 | // Device RAM in bytes.
145 | int64 ram = 1;
146 |
147 | // Device ID of device.
148 | DeviceId device_id = 2;
149 |
150 | // System features in device.
151 | repeated SystemFeature system_features = 3;
152 | }
153 |
--------------------------------------------------------------------------------
/library/src/main/proto/devices.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package android.bundle;
4 |
5 | import "google/protobuf/wrappers.proto";
6 |
7 | option java_package = "com.android.bundle";
8 |
9 | message DeviceSpec {
10 | // Supported ABI architectures in the order of preference.
11 | // The values should be the string as reported by the platform, e.g.
12 | // "armeabi-v7a" or "x86_64".
13 | repeated string supported_abis = 1;
14 |
15 | // All installed locales represented as BCP-47 strings.
16 | repeated string supported_locales = 2;
17 |
18 | // List of device features returned by the package manager utility.
19 | repeated string device_features = 3;
20 |
21 | // List of OpenGL extension strings supported by the device.
22 | repeated string gl_extensions = 4;
23 |
24 | // Screen dpi.
25 | uint32 screen_density = 5;
26 |
27 | // getprop ro.build.version.sdk
28 | uint32 sdk_version = 6;
29 |
30 | // getprop ro.build.version.codename
31 | string codename = 7;
32 |
33 | // Device tier.
34 | google.protobuf.Int32Value device_tier = 8;
35 |
36 | // Device groups the device belongs to.
37 | repeated string device_groups = 9;
38 |
39 | // Information about the runtime-enabled SDK capabilities of the device.
40 | SdkRuntime sdk_runtime = 10;
41 |
42 | // Country set.
43 | google.protobuf.StringValue country_set = 11;
44 | }
45 |
46 | message SdkRuntime {
47 | // Whether runtime-enabled SDKs can run on the device
48 | bool supported = 1;
49 |
50 | reserved 2;
51 | }
52 |
--------------------------------------------------------------------------------
/library/src/main/proto/errors.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package android.bundle;
4 |
5 | option java_package = "com.android.bundle";
6 |
7 | // Next ID: 30
8 | message BundleToolError {
9 | // The exception message that the bundle tool outputs.
10 | // NOTE: this may not be safe to show to users since it might
11 | // include server paths and configuration.
12 | string exception_message = 1;
13 |
14 | // The exception message that can be exposed to third party developers.
15 | string user_message = 28;
16 |
17 | // Type of error.
18 | ErrorType error_type = 29;
19 |
20 | reserved 2 to 27;
21 |
22 | enum ErrorType {
23 | UNDEFINED = 0;
24 | // Indicates invalid bundle.
25 | INVALID_BUNDLE_ERROR = 1;
26 | // Indicates bundle with invalid version code in AndroidManifest.xml.
27 | INVALID_VERSION_CODE_ERROR = 2;
28 | // Indicates that command is incompatible with requested device.
29 | INCOMPATIBLE_DEVICE_ERROR = 3;
30 | // Indicates invalid device spec provided to command.
31 | INVALID_DEVICE_SPEC_ERROR = 4;
32 | // Indicates that command is invalid, like options mismatch.
33 | INVALID_COMMAND_ERROR = 5;
34 | // Indicates that something happened during command execution.
35 | COMMAND_EXECUTION_ERROR = 6;
36 | // Indicates invalid signing configuration.
37 | INVALID_SIGNING_CONFIGURATION_ERROR = 7;
38 | }
39 | }
40 |
41 | message ManifestMaxSdkInvalidError {
42 | string max_sdk = 1;
43 | }
44 |
45 | message ManifestMaxSdkLessThanMinInstantSdkError {
46 | int32 max_sdk = 1;
47 | }
48 |
49 | message ManifestMinSdkInvalidError {
50 | string min_sdk = 1;
51 | }
52 |
53 | message ManifestMinSdkGreaterThanMaxSdkError {
54 | int32 min_sdk = 1;
55 | int32 max_sdk = 2;
56 | }
57 |
58 | message ManifestMissingVersionCodeError {}
59 |
60 | message ManifestInvalidVersionCodeError {}
61 |
62 | message ManifestBaseModuleExcludedFromFusingError {}
63 |
64 | message ManifestModuleFusingConfigurationMissingError {
65 | string module_name = 1;
66 | }
67 |
68 | message ManifestFusingMissingIncludeAttributeError {
69 | string module_name = 1;
70 | }
71 |
72 | message ManifestDuplicateAttributeError {
73 | string attribute_name = 1;
74 | string module_name = 2;
75 | }
76 |
77 | message ManifestModulesDifferentVersionCodes {
78 | repeated int32 version_codes = 1;
79 | }
80 |
81 | message FileTypeInvalidFileExtensionError {
82 | string bundle_directory = 1;
83 | string required_extension = 2;
84 | string invalid_file = 3;
85 | }
86 |
87 | message FileTypeInvalidFileNameInDirectoryError {
88 | string bundle_directory = 1;
89 | repeated string allowed_file_name = 2;
90 | string invalid_file = 3;
91 | }
92 |
93 | message FileTypeInvalidNativeLibraryPathError {
94 | string bundle_directory = 1;
95 | string invalid_file = 2;
96 | }
97 |
98 | message FileTypeInvalidApexImagePathError {
99 | string bundle_directory = 1;
100 | string invalid_file = 2;
101 | }
102 |
103 | message FileTypeInvalidNativeArchitectureError {
104 | string invalid_architecture_directory = 1;
105 | }
106 |
107 | message FileTypeFilesInResourceDirectoryRootError {
108 | string resource_directory = 1;
109 | string invalid_file = 2;
110 | }
111 |
112 | message FileTypeUnknownFileOrDirectoryFoundInModuleError {
113 | string invalid_file = 1;
114 | }
115 |
116 | message FileTypeFileUsesReservedNameError {
117 | string invalid_file = 1;
118 | }
119 |
120 | message FileTypeDirectoryInBundleError {
121 | string invalid_directory = 1;
122 | }
123 |
124 | message MandatoryBundleFileMissingError {
125 | string missing_file = 1;
126 | }
127 |
128 | message MandatoryModuleFileMissingError {
129 | string module_name = 1;
130 | string missing_file = 2;
131 | }
132 |
133 | message ResourceTableReferencesFilesOutsideResError {
134 | string module_name = 1;
135 | string file_path = 2;
136 | }
137 |
138 | message ResourceTableReferencesMissingFilesError {
139 | string module_name = 1;
140 | repeated string file_path = 2;
141 | }
142 |
143 | message ResourceTableUnreferencedFilesError {
144 | string module_name = 1;
145 | repeated string file_path = 2;
146 | }
147 |
148 | message ResourceTableMissingError {
149 | string module_name = 1;
150 | }
151 |
--------------------------------------------------------------------------------
/library/src/main/proto/files.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package android.bundle;
4 |
5 | import "targeting.proto";
6 |
7 | option java_package = "com.android.bundle";
8 |
9 | // Describes the assets in the App Bundle.
10 | message Assets {
11 | // List of all the directories (and subdirectories recursively) under
12 | // "assets/" that contain at least one file. They may be explicitly targeted
13 | // or not.
14 | repeated TargetedAssetsDirectory directory = 1;
15 | }
16 |
17 | // Describes the native libraries in the App Bundle.
18 | message NativeLibraries {
19 | // List of all the directories in under "lib/" that contain at least one file.
20 | repeated TargetedNativeDirectory directory = 1;
21 | }
22 |
23 | // Describes the APEX images in the App Bundle.
24 | message ApexImages {
25 | reserved 2;
26 |
27 | // List of all the image files under "apex/".
28 | repeated TargetedApexImage image = 1;
29 | }
30 |
31 | // An assets directory in the module that contains targeting information.
32 | // This information will be used when generating the APKs to determine how to
33 | // best split them.
34 | message TargetedAssetsDirectory {
35 | // Path of the directory relative to the root of the module.
36 | // Required.
37 | string path = 1;
38 |
39 | // Targeting of the directory.
40 | // Required.
41 | AssetsDirectoryTargeting targeting = 2;
42 | }
43 |
44 | // A native directory in the module that contains targeting information.
45 | // This information will be used when generating the APKs to determine how to
46 | // best split them.
47 | message TargetedNativeDirectory {
48 | // Path of the directory relative to the root of the module.
49 | // Required.
50 | string path = 1;
51 |
52 | // Targeting of the directory.
53 | // Required.
54 | NativeDirectoryTargeting targeting = 2;
55 | }
56 |
57 | // An APEX image file in the module that contains targeting information.
58 | // This information will be used when generating the APK to determine which
59 | // image it contains.
60 | message TargetedApexImage {
61 | string path = 1;
62 |
63 | // Path to binary protocol buffer file containing apex.proto.ApexBuildInfo
64 | // with metadata about how the individual APEX filesystem image was built.
65 | // May be empty if APEX build info was not provided for this image.
66 | string build_info_path = 3;
67 |
68 | ApexImageTargeting targeting = 2;
69 | }
70 |
--------------------------------------------------------------------------------
/library/src/main/proto/rotation_config.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package android.bundle;
4 |
5 | option java_package = "com.android.bundle";
6 |
7 | // Specifies the config that gets applied to the rotation aspect of the signing
8 | // process of the App Bundle.
9 | // Next tag: 3
10 | message RotationConfig {
11 | // The SHA256 fingerprint of the expected certificate to sign the APKs
12 | // generated from the Bundle.
13 | // Example:
14 | // FE:C0:E6:5B:F3:76:5D:A1:C2:56:13:C7:A3:60:35:A9:26:BC:3B:3A:39:9B:C8:55:40:F1:6D:55:17:3F:F5:9B
15 | string signing_certificate_sha256_fingerprint = 1;
16 |
17 | // The minimum API level for which an APK's rotated signing key should be used
18 | // to produce the APK's signature. The original signing key for the APK will
19 | // be used for all previous platform versions.
20 | int32 rotation_min_sdk_version = 2;
21 | }
22 |
--------------------------------------------------------------------------------
/library/src/main/proto/runtime_enabled_sdk_config.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package android.bundle;
4 |
5 | option java_package = "com.android.bundle";
6 | option java_outer_classname = "RuntimeEnabledSdkConfigProto";
7 |
8 | // Information about runtime-enabled SDK dependencies of a specific module
9 | // inside the App Bundle.
10 | message RuntimeEnabledSdkConfig {
11 | // Runtime-enabled SDKs in this config.
12 | repeated RuntimeEnabledSdk runtime_enabled_sdk = 1;
13 | }
14 |
15 | message RuntimeEnabledSdk {
16 | // Package name of the runtime-enabled SDK.
17 | string package_name = 1;
18 | // Major version of the runtime-enabled SDK.
19 | int32 version_major = 2;
20 | // Minor version of the runtime-enabled SDK.
21 | int32 version_minor = 3;
22 | // Patch version of the runtime-enabled SDK.
23 | // The dependency on a specific patch version is a build-time soft dependency,
24 | // that ensures reproducibility of local builds; it does not imply that all
25 | // app stores will honour it when delivering apps to end-users. For instance,
26 | // some stores may just honour the dependency on a specific major and minor,
27 | // while serve the latest available patch for the given major.minor version of
28 | // the SDK.
29 | int32 build_time_version_patch = 6;
30 | // SHA-256 hash of the runtime-enabled SDK's signing certificate, represented
31 | // as a string of bytes in hexadecimal form, with ':' separating the bytes.
32 | string certificate_digest = 4;
33 | // Package ID that the resource IDs of this runtime-enabled SDK must be
34 | // remapped to.
35 | int32 resources_package_id = 5;
36 | }
37 |
38 | // Options for overriding parts of runtime-enabled SDK dependency configuration
39 | // for local deployment and testing.
40 | message LocalDeploymentRuntimeEnabledSdkConfig {
41 | // Options for overriding SDK certificate digests in RuntimeEnabledSdkConfig
42 | // of the app bundle.
43 | CertificateOverrides certificate_overrides = 1;
44 | }
45 |
46 | // Contains information on certificate digests that should be overridden in
47 | // runtime-enabled SDK dependency config for local deployment.
48 | message CertificateOverrides {
49 | // Certificate digest override for specific SDKs.
50 | repeated CertificateOverride per_sdk_certificate_override = 1;
51 | // Default certificate override. If set, certificate digests of SDKs that are
52 | // not present in per_sdk_certificate_override field will be overriden to this
53 | // value.
54 | optional string default_certificate_override = 2;
55 | }
56 |
57 | // Contains information on certificate digest overrides for a specific SDK.
58 | message CertificateOverride {
59 | // Package name of the SDK that we want to override the certificate digest
60 | // for.
61 | string sdk_package_name = 1;
62 | // Certificate digest that we should override to.
63 | string certificate_digest = 2;
64 | }
65 |
66 | // Properties that backwards-compatible SDK split inherits from the app.
67 | message SdkSplitPropertiesInheritedFromApp {
68 | // Package name of the app.
69 | string package_name = 1;
70 | // Version code of the app.
71 | int32 version_code = 2;
72 | // minSdkVersion of the app.
73 | int32 min_sdk_version = 3;
74 | // Package ID that the SDK resources should be remapped to so that they do not
75 | // conflict with the app's resources.
76 | int32 resources_package_id = 4;
77 | }
78 |
--------------------------------------------------------------------------------
/library/src/main/proto/sdk_bundle_config.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package android.bundle;
4 |
5 | option java_package = "com.android.bundle";
6 | option java_outer_classname = "SdkBundleConfigProto";
7 |
8 | message SdkBundleConfig {
9 | // Runtime-enabled SDKs this SDK depends on.
10 | repeated SdkBundle sdk_dependencies = 1;
11 | }
12 |
13 | message SdkBundle {
14 | // Package name of the SDK bundle.
15 | string package_name = 1;
16 |
17 | // Major version of the SDK bundle.
18 | int32 version_major = 2;
19 |
20 | // Minor version of the SDK bundle.
21 | int32 version_minor = 3;
22 |
23 | // Patch version of the SDK bundle.
24 | // The dependency on a specific patch version is a build-time soft dependency,
25 | // that ensures reproducibility of local builds; it does not imply that all
26 | // app stores will honour it when delivering apps to end-users. For instance,
27 | // some stores may just honour the dependency on a specific major and minor,
28 | // while serve the latest available patch for the given major.minor version of
29 | // the SDK.
30 | int32 build_time_version_patch = 4;
31 |
32 | // SHA-256 hash of the SDK's signing certificate, represented as a string of
33 | // bytes in hexadecimal form, with ':' separating the bytes.
34 | string certificate_digest = 5;
35 |
36 | // Whether the dependency is optional or required. Only required dependencies
37 | // will be included in the final POM file.
38 | // Unspecified dependency types will be treated as required.
39 | SdkDependencyType dependency_type = 6;
40 | }
41 |
42 | enum SdkDependencyType {
43 | SDK_DEPENDENCY_TYPE_UNSPECIFIED = 0;
44 | // The dependency should be installed automatically.
45 | SDK_DEPENDENCY_TYPE_REQUIRED = 1;
46 | // The dependency is only needed at compile time.
47 | SDK_DEPENDENCY_TYPE_OPTIONAL = 2;
48 | }
49 |
--------------------------------------------------------------------------------
/library/src/main/proto/sdk_metadata.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package android.bundle;
4 |
5 | import "sdk_modules_config.proto";
6 |
7 | option java_package = "com.android.bundle";
8 |
9 | // Information that uniquely identifies this runtime-enabled SDK version.
10 | // This will be stored inside the ASAR for an SDK.
11 | message SdkMetadata {
12 | // Package name of the runtime-enabled SDK.
13 | string package_name = 1;
14 |
15 | // Version information for the runtime-enabled SDK.
16 | RuntimeEnabledSdkVersion sdk_version = 2;
17 |
18 | // SHA-256 hash of the runtime-enabled SDK's signing certificate, represented
19 | // as a string of bytes in hexadecimal form, with ':' separating the bytes.
20 | string certificate_digest = 3;
21 | }
22 |
--------------------------------------------------------------------------------
/library/src/main/proto/sdk_modules_config.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package android.bundle;
4 |
5 | import "config.proto";
6 |
7 | option java_package = "com.android.bundle";
8 |
9 | // Contains the package name and versioning information of the Android SDK
10 | // Bundle. These are required to generate the installable APKs.
11 | message SdkModulesConfig {
12 | // Version of BundleTool used to generate the Android SDK Bundle.
13 | Bundletool bundletool = 1;
14 |
15 | // Package name of the runtime-enabled SDK.
16 | string sdk_package_name = 2;
17 |
18 | // Version of the runtime-enabled SDK.
19 | RuntimeEnabledSdkVersion sdk_version = 3;
20 |
21 | // The fully qualified name for the platform SDK provider entrypoint class.
22 | string sdk_provider_class_name = 4;
23 |
24 | // The fully qualified name for the compatibility SDK provider entrypoint
25 | // class.
26 | // Expected to be set for SDKs that support devices without the privacy
27 | // sandbox.
28 | string compat_sdk_provider_class_name = 5;
29 | }
30 |
31 | message RuntimeEnabledSdkVersion {
32 | // The major version of the RE SDK.
33 | int32 major = 1;
34 |
35 | // The minor version of the RE SDK.
36 | int32 minor = 2;
37 |
38 | // The patch version of the RE SDK.
39 | int32 patch = 3;
40 | }
41 |
--------------------------------------------------------------------------------
/library/src/main/proto/sizes.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package android.bundle;
4 |
5 | option java_package = "com.android.bundle";
6 |
7 | // Sizes of the breakdown item: bytes in the APK, and bytes over the wire.
8 | message Sizes {
9 | int64 disk_size = 1;
10 | int64 download_size = 2;
11 | }
12 |
13 | // Breakdown of APK, or an aggregate.
14 | message Breakdown {
15 | // Total size of entries and ZIP format overheads.
16 | Sizes total = 1;
17 | // Only dex code.
18 | Sizes dex = 2;
19 | // Resources, resource table and Android Manifest.
20 | Sizes resources = 3;
21 | // Only assets.
22 | Sizes assets = 4;
23 | // Only native libraries.
24 | Sizes native_libs = 5;
25 | // Other entries e.g. META-INF/ and ZIP format overheads.
26 | Sizes other = 6;
27 | }
28 |
--------------------------------------------------------------------------------
/library/src/main/proto/targeting.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package android.bundle;
4 |
5 | import "google/protobuf/wrappers.proto";
6 |
7 | option java_package = "com.android.bundle";
8 |
9 | // Targeting on the level of variants.
10 | message VariantTargeting {
11 | SdkVersionTargeting sdk_version_targeting = 1;
12 | AbiTargeting abi_targeting = 2;
13 | ScreenDensityTargeting screen_density_targeting = 3;
14 | MultiAbiTargeting multi_abi_targeting = 4;
15 | TextureCompressionFormatTargeting texture_compression_format_targeting = 5;
16 | SdkRuntimeTargeting sdk_runtime_targeting = 6;
17 | }
18 |
19 | // Targeting on the level of individual APKs.
20 | message ApkTargeting {
21 | AbiTargeting abi_targeting = 1;
22 | reserved 2; // was GraphicsApiTargeting
23 | LanguageTargeting language_targeting = 3;
24 | ScreenDensityTargeting screen_density_targeting = 4;
25 | SdkVersionTargeting sdk_version_targeting = 5;
26 | TextureCompressionFormatTargeting texture_compression_format_targeting = 6;
27 | MultiAbiTargeting multi_abi_targeting = 7;
28 | SanitizerTargeting sanitizer_targeting = 8;
29 | DeviceTierTargeting device_tier_targeting = 9;
30 | CountrySetTargeting country_set_targeting = 10;
31 | }
32 |
33 | // Targeting on the module level.
34 | // The semantic of the targeting is the "AND" rule on all immediate values.
35 | message ModuleTargeting {
36 | SdkVersionTargeting sdk_version_targeting = 1;
37 | repeated DeviceFeatureTargeting device_feature_targeting = 2;
38 | UserCountriesTargeting user_countries_targeting = 3;
39 | DeviceGroupModuleTargeting device_group_targeting = 5;
40 |
41 | reserved 4;
42 | }
43 |
44 | // User Countries targeting describing an inclusive/exclusive list of country
45 | // codes that module targets.
46 | message UserCountriesTargeting {
47 | // List of country codes in the two-letter CLDR territory format.
48 | repeated string country_codes = 1;
49 |
50 | // Indicates if the list above is exclusive.
51 | bool exclude = 2;
52 | }
53 |
54 | message ScreenDensity {
55 | enum DensityAlias {
56 | DENSITY_UNSPECIFIED = 0;
57 | NODPI = 1;
58 | LDPI = 2;
59 | MDPI = 3;
60 | TVDPI = 4;
61 | HDPI = 5;
62 | XHDPI = 6;
63 | XXHDPI = 7;
64 | XXXHDPI = 8;
65 | }
66 |
67 | oneof density_oneof {
68 | DensityAlias density_alias = 1;
69 | int32 density_dpi = 2;
70 | }
71 | }
72 |
73 | message SdkVersion {
74 | // Inclusive.
75 | google.protobuf.Int32Value min = 1;
76 | }
77 |
78 | message TextureCompressionFormat {
79 | enum TextureCompressionFormatAlias {
80 | UNSPECIFIED_TEXTURE_COMPRESSION_FORMAT = 0;
81 | ETC1_RGB8 = 1;
82 | PALETTED = 2;
83 | THREE_DC = 3;
84 | ATC = 4;
85 | LATC = 5;
86 | DXT1 = 6;
87 | S3TC = 7;
88 | PVRTC = 8;
89 | ASTC = 9;
90 | ETC2 = 10;
91 | }
92 | TextureCompressionFormatAlias alias = 1;
93 | }
94 |
95 | message Abi {
96 | enum AbiAlias {
97 | UNSPECIFIED_CPU_ARCHITECTURE = 0;
98 | ARMEABI = 1;
99 | ARMEABI_V7A = 2;
100 | ARM64_V8A = 3;
101 | X86 = 4;
102 | X86_64 = 5;
103 | MIPS = 6;
104 | MIPS64 = 7;
105 | }
106 | AbiAlias alias = 1;
107 | }
108 |
109 | message MultiAbi {
110 | repeated Abi abi = 1;
111 | }
112 |
113 | message Sanitizer {
114 | enum SanitizerAlias {
115 | NONE = 0;
116 | HWADDRESS = 1;
117 | }
118 | SanitizerAlias alias = 1;
119 | }
120 |
121 | message DeviceFeature {
122 | string feature_name = 1;
123 | // Equivalent of android:glEsVersion or android:version in .
124 | int32 feature_version = 2;
125 | }
126 |
127 | // Targeting specific for directories under assets/.
128 | message AssetsDirectoryTargeting {
129 | AbiTargeting abi = 1;
130 | reserved 2; // was GraphicsApiTargeting
131 | TextureCompressionFormatTargeting texture_compression_format = 3;
132 | LanguageTargeting language = 4;
133 | DeviceTierTargeting device_tier = 5;
134 | CountrySetTargeting country_set = 6;
135 | }
136 |
137 | // Targeting specific for directories under lib/.
138 | message NativeDirectoryTargeting {
139 | Abi abi = 1;
140 | reserved 2; // was GraphicsApi
141 | TextureCompressionFormat texture_compression_format = 3;
142 | Sanitizer sanitizer = 4;
143 | }
144 |
145 | // Targeting specific for image files under apex/.
146 | message ApexImageTargeting {
147 | MultiAbiTargeting multi_abi = 1;
148 | }
149 |
150 | message AbiTargeting {
151 | repeated Abi value = 1;
152 | // Targeting of other sibling directories that were in the Bundle.
153 | // For master splits this is targeting of other master splits.
154 | repeated Abi alternatives = 2;
155 | }
156 |
157 | message MultiAbiTargeting {
158 | repeated MultiAbi value = 1;
159 | // Targeting of other sibling directories that were in the Bundle.
160 | // For master splits this is targeting of other master splits.
161 | repeated MultiAbi alternatives = 2;
162 | }
163 |
164 | message ScreenDensityTargeting {
165 | repeated ScreenDensity value = 1;
166 | // Targeting of other sibling directories that were in the Bundle.
167 | // For master splits this is targeting of other master splits.
168 | repeated ScreenDensity alternatives = 2;
169 | }
170 |
171 | message LanguageTargeting {
172 | // ISO-639: 2 or 3 letter language code.
173 | repeated string value = 1;
174 | // Targeting of other sibling directories that were in the Bundle.
175 | // For master splits this is targeting of other master splits.
176 | repeated string alternatives = 2;
177 | }
178 |
179 | message SdkVersionTargeting {
180 | repeated SdkVersion value = 1;
181 | // Targeting of other sibling directories that were in the Bundle.
182 | // For master splits this is targeting of other master splits.
183 | repeated SdkVersion alternatives = 2;
184 | }
185 |
186 | message TextureCompressionFormatTargeting {
187 | repeated TextureCompressionFormat value = 1;
188 | // Targeting of other sibling directories that were in the Bundle.
189 | // For master splits this is targeting of other master splits.
190 | repeated TextureCompressionFormat alternatives = 2;
191 | }
192 |
193 | message SanitizerTargeting {
194 | repeated Sanitizer value = 1;
195 | }
196 |
197 | // Since other atom targeting messages have the "OR" semantic on values
198 | // the DeviceFeatureTargeting represents only one device feature to retain
199 | // that convention.
200 | message DeviceFeatureTargeting {
201 | DeviceFeature required_feature = 1;
202 | }
203 |
204 | // Targets assets and APKs to a concrete device tier.
205 | message DeviceTierTargeting {
206 | repeated google.protobuf.Int32Value value = 3;
207 | repeated google.protobuf.Int32Value alternatives = 4;
208 |
209 | reserved 1, 2;
210 | }
211 |
212 | // Targets assets and APKs to a specific country set.
213 | // For Example:-
214 | // The values and alternatives for the following files in assets directory
215 | // targeting would be as follows:
216 | // assetpack1/assets/foo#countries_latam/bar.txt ->
217 | // { value: [latam], alternatives: [sea] }
218 | // assetpack1/assets/foo#countries_sea/bar.txt ->
219 | // { value: [sea], alternatives: [latam] }
220 | // assetpack1/assets/foo/bar.txt ->
221 | // { value: [], alternatives: [sea, latam] }
222 | // The values and alternatives for the following targeted split apks would be as
223 | // follows:
224 | // splits/base-countries_latam.apk ->
225 | // { value: [latam], alternatives: [sea] }
226 | // splits/base-countries_sea.apk ->
227 | // { value: [sea], alternatives: [latam] }
228 | // splits/base-other_countries.apk ->
229 | // { value: [], alternatives: [sea, latam] }
230 | message CountrySetTargeting {
231 | // Country set name defined in device tier config.
232 | repeated string value = 1;
233 | // Targeting of other sibling directories that were in the Bundle.
234 | repeated string alternatives = 2;
235 | }
236 |
237 | // Targets conditional modules to a set of device groups.
238 | message DeviceGroupModuleTargeting {
239 | repeated string value = 1;
240 | }
241 |
242 | // Variant targeting based on SDK Runtime availability on device.
243 | message SdkRuntimeTargeting {
244 | // Whether the variant requires SDK Runtime to be available on the device.
245 | bool requires_sdk_runtime = 1;
246 | }
247 |
--------------------------------------------------------------------------------
/library/src/main/resources/vmsrc.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timscriptov/nmmp/ceb3417f756a4193dfe2ae24da27cf2a04ee1470/library/src/main/resources/vmsrc.zip
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "nmmp"
2 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
3 |
4 | pluginManagement {
5 | repositories {
6 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
7 | google()
8 | gradlePluginPortal()
9 | mavenCentral()
10 | }
11 | }
12 |
13 | dependencyResolutionManagement {
14 | repositories {
15 | google()
16 | mavenCentral()
17 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
18 | maven("https://www.jitpack.io")
19 | }
20 | }
21 |
22 | include(":composeApp")
23 | include(":library")
24 |
--------------------------------------------------------------------------------