Showing preview only (442K chars total). Download the full file or copy to clipboard to get everything.
Repository: shwenzhang/AndResGuard
Branch: master
Commit: e4df245d82f2
Files: 146
Total size: 399.6 KB
Directory structure:
gitextract_al65wnr7/
├── .gitignore
├── .travis.yml
├── AndResGuard-cli/
│ ├── build.gradle
│ └── src/
│ └── main/
│ └── java/
│ └── com/
│ └── tencent/
│ └── mm/
│ └── resourceproguard/
│ └── cli/
│ └── CliMain.java
├── AndResGuard-core/
│ ├── build.gradle
│ ├── gradle.properties
│ └── src/
│ └── main/
│ └── java/
│ ├── apksigner/
│ │ ├── ApkSignerTool.java
│ │ ├── HexEncoding.java
│ │ ├── OptionsParser.java
│ │ ├── PasswordRetriever.java
│ │ ├── help.txt
│ │ ├── help_sign.txt
│ │ └── help_verify.txt
│ └── com/
│ ├── mindprod/
│ │ └── ledatastream/
│ │ ├── LEDataInputStream.java
│ │ ├── LEDataOutputStream.java
│ │ └── LittleEndianDataOutputStream.java
│ └── tencent/
│ └── mm/
│ ├── androlib/
│ │ ├── AndrolibException.java
│ │ ├── ApkDecoder.java
│ │ ├── ResourceApkBuilder.java
│ │ ├── ResourceRepackage.java
│ │ └── res/
│ │ ├── data/
│ │ │ ├── ResID.java
│ │ │ ├── ResPackage.java
│ │ │ └── ResType.java
│ │ ├── decoder/
│ │ │ ├── ARSCDecoder.java
│ │ │ ├── RawARSCDecoder.java
│ │ │ └── StringBlock.java
│ │ └── util/
│ │ ├── ExtFile.java
│ │ └── StringUtil.java
│ ├── directory/
│ │ ├── AbstractDirectory.java
│ │ ├── Directory.java
│ │ ├── DirectoryException.java
│ │ ├── FileDirectory.java
│ │ ├── PathAlreadyExists.java
│ │ ├── PathNotExist.java
│ │ └── ZipRODirectory.java
│ ├── resourceproguard/
│ │ ├── Configuration.java
│ │ ├── InputParam.java
│ │ └── Main.java
│ └── util/
│ ├── DataInputDelegate.java
│ ├── DataOutputDelegate.java
│ ├── ExtDataInput.java
│ ├── ExtDataOutput.java
│ ├── FileOperation.java
│ ├── Md5Util.java
│ ├── TypedValue.java
│ └── Utils.java
├── AndResGuard-example/
│ ├── .gitignore
│ ├── app/
│ │ ├── .gitignore
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ ├── resource_mapping.txt
│ │ └── src/
│ │ ├── androidTest/
│ │ │ └── java/
│ │ │ └── andresguard/
│ │ │ └── tencent/
│ │ │ └── com/
│ │ │ └── andresguard_example/
│ │ │ └── ApplicationTest.java
│ │ ├── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java/
│ │ │ │ └── andresguard/
│ │ │ │ └── tencent/
│ │ │ │ └── com/
│ │ │ │ └── andresguard_example/
│ │ │ │ └── MainActivity.java
│ │ │ └── res/
│ │ │ └── .keep
│ │ └── test/
│ │ └── java/
│ │ └── andresguard/
│ │ └── tencent/
│ │ └── com/
│ │ └── andresguard_example/
│ │ └── ExampleUnitTest.java
│ ├── app1/
│ │ ├── .gitignore
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ ├── resource_mapping.txt
│ │ └── src/
│ │ ├── androidTest/
│ │ │ └── java/
│ │ │ └── andresguard/
│ │ │ └── tencent/
│ │ │ └── com/
│ │ │ └── andresguard_example/
│ │ │ └── ApplicationTest.java
│ │ ├── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java/
│ │ │ │ └── andresguard/
│ │ │ │ └── tencent/
│ │ │ │ └── com/
│ │ │ │ └── andresguard_example/
│ │ │ │ └── MainActivity.java
│ │ │ └── res/
│ │ │ └── .keep
│ │ └── test/
│ │ └── java/
│ │ └── andresguard/
│ │ └── tencent/
│ │ └── com/
│ │ └── andresguard_example/
│ │ └── ExampleUnitTest.java
│ ├── app2/
│ │ ├── .gitignore
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ ├── resource_mapping.txt
│ │ └── src/
│ │ ├── androidTest/
│ │ │ └── java/
│ │ │ └── andresguard/
│ │ │ └── tencent/
│ │ │ └── com/
│ │ │ └── andresguard_example/
│ │ │ └── ApplicationTest.java
│ │ ├── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java/
│ │ │ │ └── andresguard/
│ │ │ │ └── tencent/
│ │ │ │ └── com/
│ │ │ │ └── andresguard_example/
│ │ │ │ └── MainActivity.java
│ │ │ └── res/
│ │ │ └── .keep
│ │ └── test/
│ │ └── java/
│ │ └── andresguard/
│ │ └── tencent/
│ │ └── com/
│ │ └── andresguard_example/
│ │ └── ExampleUnitTest.java
│ ├── build.gradle
│ ├── gradle/
│ │ └── wrapper/
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ ├── gradlew
│ ├── gradlew.bat
│ ├── keystore test/
│ │ ├── debug.keystore
│ │ ├── release.keystore
│ │ └── testKey.jks
│ ├── libres/
│ │ ├── .gitignore
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ └── src/
│ │ ├── androidTest/
│ │ │ └── java/
│ │ │ └── com/
│ │ │ └── tinkerpatch/
│ │ │ └── libres/
│ │ │ └── ExampleInstrumentedTest.java
│ │ ├── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ └── res/
│ │ │ ├── drawable/
│ │ │ │ └── side_nav_bar.xml
│ │ │ ├── drawable-v21/
│ │ │ │ ├── ic_menu_camera.xml
│ │ │ │ ├── ic_menu_gallery.xml
│ │ │ │ ├── ic_menu_manage.xml
│ │ │ │ ├── ic_menu_send.xml
│ │ │ │ ├── ic_menu_share.xml
│ │ │ │ └── ic_menu_slideshow.xml
│ │ │ ├── layout/
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── app_bar_main.xml
│ │ │ │ ├── content_main.xml
│ │ │ │ └── nav_header_main.xml
│ │ │ ├── menu/
│ │ │ │ ├── activity_main_drawer.xml
│ │ │ │ └── main.xml
│ │ │ ├── values/
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── drawables.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-v21/
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-w820dp/
│ │ │ │ ├── dimens.xml
│ │ │ │ └── strings.xml
│ │ │ └── values-xxhdpi/
│ │ │ └── strings.xml
│ │ └── test/
│ │ └── java/
│ │ └── com/
│ │ └── tinkerpatch/
│ │ └── libres/
│ │ └── ExampleUnitTest.java
│ └── settings.gradle
├── AndResGuard-gradle-plugin/
│ ├── build.gradle
│ ├── gradle.properties
│ └── src/
│ └── main/
│ ├── groovy/
│ │ └── com/
│ │ └── tencent/
│ │ └── gradle/
│ │ ├── AndResGuardExtension.groovy
│ │ ├── AndResGuardPlugin.groovy
│ │ ├── AndResGuardTask.groovy
│ │ ├── BuildInfo.groovy
│ │ └── ExecutorExtension.groovy
│ └── resources/
│ └── META-INF/
│ └── gradle-plugins/
│ └── AndResGuard.properties
├── LICENSE
├── README.md
├── README.zh-cn.md
├── SECURITY.md
├── SevenZip/
│ ├── build.gradle
│ └── gradle.properties
├── appveyol.yml
├── build.gradle
├── doc/
│ ├── how_to_work.md
│ ├── how_to_work.zh-cn.md
│ └── white_list.md
├── gradle/
│ ├── gradle-mvn-push.gradle
│ ├── java-artifacts.gradle
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── package.json
├── settings.gradle
└── tool_output/
├── AndResGuard-cli-1.2.15.jar
├── build_apk.bat
├── build_apk.sh
├── config.xml
└── release.keystore
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Built application files
*.apk
*.ap_
# Files for the Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
# Gradle files
.gradle/
build/
/*/build/
*.iml
/*/*.iml
.idea
/*/.idea/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
.DS_Store
work/outapk/*
local_repo
tool_output/outapk
node_modules/
classes
AndResGuard-example/true
final.apk
================================================
FILE: .travis.yml
================================================
language: java
dist: trusty
script: "./gradlew check"
jdk:
- oraclejdk8
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/cbb5207fafff92a021b7
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: never # options: [always|never|change] default: always
================================================
FILE: AndResGuard-cli/build.gradle
================================================
apply plugin: 'java'
[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8'
version rootProject.ext.VERSION_NAME
group rootProject.ext.GROUP
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
//compile group: 'com.tencent.mm', name: 'AndResGuard-core', version: version
compile project(':AndResGuard-core')
}
sourceSets {
main {
java {
srcDir 'src'
}
}
}
jar {
manifest {
attributes 'Main-Class': 'com.tencent.mm.resourceproguard.cli.CliMain'
attributes 'Manifest-Version': version
attributes "Jar-Version": "${ANDRESGUARD_VESSION}"
attributes "Build-Time": releaseTime()
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
// copy the jar to work directory
task buildJar(type: Copy, dependsOn: [build, jar]) {
from('build/libs') {
include '*' + version + '*.jar'
}
into('../tool_output')
}
def releaseTime() {
return new Date().format("yyyy-MM-dd HH:mm ZZZ", TimeZone.getDefault())
}
defaultTasks 'buildJar'
================================================
FILE: AndResGuard-cli/src/main/java/com/tencent/mm/resourceproguard/cli/CliMain.java
================================================
package com.tencent.mm.resourceproguard.cli;
import com.tencent.mm.androlib.ResourceRepackage;
import com.tencent.mm.resourceproguard.Configuration;
import com.tencent.mm.resourceproguard.InputParam;
import com.tencent.mm.resourceproguard.Main;
import com.tencent.mm.util.TypedValue;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
/**
* Created by simsun on 1/9/16.
*/
public class CliMain extends Main {
private static final String ARG_HELP = "--help";
private static final String ARG_OUT = "-out";
private static final String ARG_FINAL_APK_PATH = "-finalApkPath";
private static final String ARG_CONFIG = "-config";
private static final String ARG_7ZIP = "-7zip";
private static final String ARG_ZIPALIGN = "-zipalign";
private static final String ARG_SIGNATURE = "-signature";
private static final String ARG_KEEPMAPPING = "-mapping";
private static final String ARG_REPACKAGE = "-repackage";
private static final String ARG_SIGNATURE_TYPE = "-signatureType";
private static final String VALUE_SIGNATURE_TYPE_V1 = "v1";
private static final String VALUE_SIGNATURE_TYPE_V2 = "v2";
public static void main(String[] args) {
mBeginTime = System.currentTimeMillis();
CliMain m = new CliMain();
setRunningLocation(m);
m.run(args);
}
private static void setRunningLocation(CliMain m) {
mRunningLocation = m.getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
try {
mRunningLocation = URLDecoder.decode(mRunningLocation, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (mRunningLocation.endsWith(".jar")) {
mRunningLocation = mRunningLocation.substring(0, mRunningLocation.lastIndexOf(File.separator) + 1);
}
File f = new File(mRunningLocation);
mRunningLocation = f.getAbsolutePath();
}
private static void printUsage(PrintStream out) {
// TODO: Look up launcher script name!
String command = "resousceproguard.jar"; //$NON-NLS-1$
out.println();
out.println();
out.println("Usage: java -jar " + command + " input.apk");
out.println("if you want to special the output path or config file path, you can input:");
out.println("Such as: java -jar "
+ command
+ " "
+ "input.apk "
+ ARG_CONFIG
+ " yourconfig.xml "
+ ARG_OUT
+ " output_directory");
out.println("if you want to special the sign or mapping data, you can input:");
out.println("Such as: java -jar "
+ command
+ " "
+ "input.apk "
+ ARG_CONFIG
+ " yourconfig.xml "
+ ARG_OUT
+ " output_directory "
+ ARG_SIGNATURE
+ " signature_file_path storepass keypass storealias "
+ ARG_KEEPMAPPING
+ " mapping_file_path");
out.println("if you want to special the signature type, you can input:");
out.printf("Such as: java -jar %s input.apk %s %s/%s\n",
command,
ARG_SIGNATURE_TYPE,
VALUE_SIGNATURE_TYPE_V1,
VALUE_SIGNATURE_TYPE_V2
);
out.println("if you want to special 7za or zipalign path, you can input:");
out.println("Such as: java -jar "
+ command
+ " "
+ "input.apk "
+ ARG_7ZIP
+ " /home/shwenzhang/tools/7za "
+ ARG_ZIPALIGN
+ " /home/shwenzhang/sdk/tools/zipalign");
out.println("if you just want to repackage an apk compress with 7z:");
out.println("Such as: java -jar " + command + " " + ARG_REPACKAGE + " input.apk");
out.println("if you want to special the output path, 7za or zipalign path, you can input:");
out.println("Such as: java -jar "
+ command
+ " "
+ ARG_REPACKAGE
+ " input.apk"
+ ARG_OUT
+ " output_directory "
+ ARG_7ZIP
+ " /home/shwenzhang/tools/7za "
+ ARG_ZIPALIGN
+ "/home/shwenzhang/sdk/tools/zipalign");
out.println("if you want to special the final apk path, you can input:");
out.printf("Such as: java -jar %s input.apk %s final_apk_path\n", command, ARG_FINAL_APK_PATH);
out.println();
out.println("Flags:\n");
printUsage(out, new String[] {
ARG_HELP, "This message.", "-h", "short for -help", ARG_OUT,
"set the output directory yourself, if not, the default directory is the running location with name of the input file",
ARG_CONFIG,
"set the config file yourself, if not, the default path is the running location with name config.xml",
ARG_SIGNATURE, "set sign property, following by parameters: signature_file_path storepass keypass storealias",
" ", "if you set these, the sign data in the config file will be overlayed", ARG_KEEPMAPPING,
"set keep mapping property, following by parameters: mapping_file_path", " ",
"if you set these, the mapping data in the config file will be overlayed", ARG_7ZIP,
"set the 7zip path, such as /home/shwenzhang/tools/7za, window will be end of 7za.exe", ARG_ZIPALIGN,
"set the zipalign, such as /home/shwenzhang/sdk/tools/zipalign, window will be end of zipalign.exe",
ARG_REPACKAGE, "usually, when we build the channeles apk, it may destroy the 7zip.", " ",
"so you may need to use 7zip to repackage the apk",
});
out.println();
out.println("if you donot know how to write the config file, look at the comment in the default config.xml");
out.println("if you want to use 7z, you must install the 7z command line version in window;");
out.println("sudo apt-get install p7zip-full in linux");
}
private static void printUsage(PrintStream out, String[] args) {
int argWidth = 0;
for (int i = 0; i < args.length; i += 2) {
String arg = args[i];
argWidth = Math.max(argWidth, arg.length());
}
argWidth += 2;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < argWidth; i++) {
sb.append(' ');
}
String indent = sb.toString();
String formatString = "%1$-" + argWidth + "s%2$s"; //$NON-NLS-1$
for (int i = 0; i < args.length; i += 2) {
String arg = args[i];
String description = args[i + 1];
if (arg.length() == 0) {
out.println(description);
} else {
out.print(wrap(String.format(formatString, arg, description), 300, indent));
}
}
}
private static String wrap(String explanation, int lineWidth, String hangingIndent) {
int explanationLength = explanation.length();
StringBuilder sb = new StringBuilder(explanationLength * 2);
int index = 0;
while (index < explanationLength) {
int lineEnd = explanation.indexOf('\n', index);
int next;
if (lineEnd != -1 && (lineEnd - index) < lineWidth) {
next = lineEnd + 1;
} else {
// Line is longer than available width; grab as much as we can
lineEnd = Math.min(index + lineWidth, explanationLength);
if (lineEnd - index < lineWidth) {
next = explanationLength;
} else {
// then back up to the last space
int lastSpace = explanation.lastIndexOf(' ', lineEnd);
if (lastSpace > index) {
lineEnd = lastSpace;
next = lastSpace + 1;
} else {
// No space anywhere on the line: it contains something wider than
// can fit (like a long URL) so just hard break it
next = lineEnd + 1;
}
}
}
if (sb.length() > 0) {
sb.append(hangingIndent);
} else {
lineWidth -= hangingIndent.length();
}
sb.append(explanation.substring(index, lineEnd));
sb.append('\n');
index = next;
}
return sb.toString();
}
private void run(String[] args) {
synchronized (CliMain.class) {
if (args.length < 1) {
goToError();
}
final ReadArgs readArgs = new ReadArgs(args).invoke();
final File configFile = readArgs.getConfigFile();
final File signatureFile = readArgs.getSignatureFile();
final File mappingFile = readArgs.getMappingFile();
final String keypass = readArgs.getKeypass();
final String storealias = readArgs.getStorealias();
final String storepass = readArgs.getStorepass();
final String signedFile = readArgs.getSignedFile();
final File outputFile = readArgs.getOutputFile();
final File finalApkFile = readArgs.getFinalApkFile();
final String apkFileName = readArgs.getApkFileName();
final InputParam.SignatureType signatureType = readArgs.getSignatureType();
loadConfigFromXml(configFile, signatureFile, mappingFile, keypass, storealias, storepass);
//对于repackage模式,不管之前的东东,直接return
if (signedFile != null) {
ResourceRepackage repackage = new ResourceRepackage(config.mZipalignPath,
config.m7zipPath,
new File(signedFile)
);
try {
if (outputFile != null) {
repackage.setOutDir(outputFile);
}
repackage.repackageApk();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
return;
}
System.out.printf("[AndResGuard] begin: %s, %s, %s\n", outputFile, finalApkFile, apkFileName);
resourceProguard(outputFile, finalApkFile, apkFileName, signatureType);
System.out.printf("[AndResGuard] done, total time cost: %fs\n", diffTimeFromBegin());
System.out.printf("[AndResGuard] done, you can go to file to find the output %s\n", mOutDir.getAbsolutePath());
clean();
}
}
private void loadConfigFromXml(
File configFile, File signatureFile, File mappingFile, String keypass, String storealias, String storepass) {
if (configFile == null) {
configFile = new File(mRunningLocation + File.separator + TypedValue.CONFIG_FILE);
if (!configFile.exists()) {
System.err.printf("the config file %s does not exit", configFile.getAbsolutePath());
printUsage(System.err);
System.exit(ERRNO_USAGE);
}
}
try {
//不需要检查命令行的设置
if (!mSetSignThroughCmd) {
signatureFile = null;
}
if (!mSetMappingThroughCmd) {
mappingFile = null;
}
config = new Configuration(configFile,
m7zipPath,
mZipalignPath,
mappingFile,
signatureFile,
keypass,
storealias,
storepass
);
} catch (IOException | ParserConfigurationException | SAXException e) {
e.printStackTrace();
goToError();
}
}
public double diffTimeFromBegin() {
long end = System.currentTimeMillis();
return (end - mBeginTime) / 1000.0;
}
protected void goToError() {
printUsage(System.err);
System.exit(ERRNO_USAGE);
}
private class ReadArgs {
private String[] args;
private File configFile;
private File outputFile;
private File finalApkFile;
private String apkFileName;
private File signatureFile;
private File mappingFile;
private String keypass;
private String storealias;
private String storepass;
private InputParam.SignatureType signatureType = InputParam.SignatureType.SchemaV1;
private String signedFile;
public ReadArgs(String[] args) {
this.args = args;
}
public File getConfigFile() {
return configFile;
}
public File getOutputFile() {
return outputFile;
}
public File getFinalApkFile() {
return finalApkFile;
}
public String getApkFileName() {
return apkFileName;
}
public File getSignatureFile() {
return signatureFile;
}
public File getMappingFile() {
return mappingFile;
}
public String getKeypass() {
return keypass;
}
public String getStorealias() {
return storealias;
}
public String getStorepass() {
return storepass;
}
public InputParam.SignatureType getSignatureType() {
return signatureType;
}
public String getSignedFile() {
return signedFile;
}
public ReadArgs invoke() {
for (int index = 0; index < args.length; index++) {
String arg = args[index];
if (arg.equals(ARG_HELP) || arg.equals("-h")) {
goToError();
} else if (arg.equals(ARG_CONFIG)) {
if (index == args.length - 1 || !args[index + 1].endsWith(TypedValue.XML_FILE)) {
System.err.println("Missing XML configuration file argument");
goToError();
}
configFile = new File(args[++index]);
if (!configFile.exists()) {
System.err.println(configFile.getAbsolutePath() + " does not exist");
goToError();
}
System.out.printf("special configFile file path: %s\n", configFile.getAbsolutePath());
} else if (arg.equals(ARG_OUT)) {
if (index == args.length - 1) {
System.err.println("Missing output file argument");
goToError();
}
outputFile = new File(args[++index]);
File parent = outputFile.getParentFile();
if (parent != null && (!parent.exists())) {
parent.mkdirs();
}
System.out.printf("special output directory path: %s\n", outputFile.getAbsolutePath());
} else if (arg.equals(ARG_FINAL_APK_PATH)) {
if (index == args.length - 1) {
System.err.println("Missing output file argument");
goToError();
}
finalApkFile = new File(args[++index]);
File parent = finalApkFile.getParentFile();
if (parent != null && (!parent.exists())) {
parent.mkdirs();
}
System.out.printf("special final apk file path: %s\n", finalApkFile.getAbsolutePath());
} else if (arg.equals(ARG_SIGNATURE)) {
//需要检查是否有四个参数
if (index == args.length - 1) {
System.err.println("Missing signature data argument, should be "
+ ARG_SIGNATURE
+ " signature_file_path storepass keypass storealias");
goToError();
}
//在后面设置的时候会检查文件是否存在
signatureFile = new File(args[++index]);
if (index == args.length - 1) {
System.err.println("Missing signature data argument, should be "
+ ARG_SIGNATURE
+ " signature_file_path storepass keypass storealias");
goToError();
}
storepass = args[++index];
if (index == args.length - 1) {
System.err.println("Missing signature data argument, should be "
+ ARG_SIGNATURE
+ " signature_file_path storepass keypass storealias");
goToError();
}
keypass = args[++index];
if (index == args.length - 1) {
System.err.println("Missing signature data argument, should be "
+ ARG_SIGNATURE
+ " signature_file_path storepass keypass storealias");
goToError();
}
storealias = args[++index];
mSetSignThroughCmd = true;
} else if (arg.equals(ARG_SIGNATURE_TYPE)) {
if (index == args.length - 1) {
System.err.println("Missing signature type argument");
goToError();
}
if (VALUE_SIGNATURE_TYPE_V2.equalsIgnoreCase(args[++index])) {
signatureType = InputParam.SignatureType.SchemaV2;
} else {
signatureType = InputParam.SignatureType.SchemaV1;
}
} else if (arg.equals(ARG_KEEPMAPPING)) {
if (index == args.length - 1) {
System.err.println("Missing mapping file argument");
goToError();
}
//在后面设置的时候会检查文件是否存在
mappingFile = new File(args[++index]);
mSetMappingThroughCmd = true;
} else if (arg.equals(ARG_7ZIP)) {
if (index == args.length - 1) {
System.err.println("Missing 7zip path argument");
goToError();
}
m7zipPath = args[++index];
} else if (arg.equals(ARG_ZIPALIGN)) {
if (index == args.length - 1) {
System.err.println("Missing zipalign path argument");
goToError();
}
mZipalignPath = args[++index];
} else if (arg.equals(ARG_REPACKAGE)) {
//这个模式的话就直接干活了,不会再理其他命令!
if (index == args.length - 1) {
System.err.println("Missing the signed apk file argument");
goToError();
}
signedFile = args[++index];
} else {
apkFileName = arg;
}
}
return this;
}
}
}
================================================
FILE: AndResGuard-core/build.gradle
================================================
apply plugin: 'java'
version rootProject.ext.VERSION_NAME
group rootProject.ext.GROUP
[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.tools.build:gradle:4.1.2'
compile 'commons-io:commons-io:2.6'
}
sourceSets {
main {
java {
srcDir 'src'
}
}
}
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
apply from: rootProject.file('gradle/java-artifacts.gradle')
apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
================================================
FILE: AndResGuard-core/gradle.properties
================================================
POM_ARTIFACT_ID=AndResGuard-core
POM_NAME=AndResGuard Core
POM_PACKAGING=jar
================================================
FILE: AndResGuard-core/src/main/java/apksigner/ApkSignerTool.java
================================================
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apksigner;
import com.android.apksig.ApkSigner;
import com.android.apksig.ApkVerifier;
import com.android.apksig.apk.MinSdkVersionException;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.DSAKey;
import java.security.interfaces.DSAParams;
import java.security.interfaces.ECKey;
import java.security.interfaces.RSAKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
/**
* Command-line tool for signing APKs and for checking whether an APK's signature are expected to
* verify on Android devices.
*/
public class ApkSignerTool {
private static final String VERSION = "0.5";
private static final String HELP_PAGE_GENERAL = "help.txt";
private static final String HELP_PAGE_SIGN = "help_sign.txt";
private static final String HELP_PAGE_VERIFY = "help_verify.txt";
public static void main(String[] params) throws Exception {
if ((params.length == 0) || ("--help".equals(params[0])) || ("-h".equals(params[0]))) {
printUsage(HELP_PAGE_GENERAL);
return;
} else if ("--version".equals(params[0])) {
System.out.println(VERSION);
return;
}
String cmd = params[0];
try {
if ("sign".equals(cmd)) {
sign(Arrays.copyOfRange(params, 1, params.length));
return;
} else if ("verify".equals(cmd)) {
verify(Arrays.copyOfRange(params, 1, params.length));
return;
} else if ("help".equals(cmd)) {
printUsage(HELP_PAGE_GENERAL);
return;
} else if ("version".equals(cmd)) {
System.out.println(VERSION);
return;
} else {
throw new ParameterException("Unsupported command: " + cmd + ". See --help for supported commands");
}
} catch (ParameterException | OptionsParser.OptionsException e) {
System.err.println(e.getMessage());
System.exit(1);
return;
}
}
private static void sign(String[] params) throws Exception {
if (params.length == 0) {
printUsage(HELP_PAGE_SIGN);
return;
}
File outputApk = null;
File inputApk = null;
boolean verbose = false;
boolean v1SigningEnabled = true;
boolean v2SigningEnabled = true;
boolean v3SigningEnabled = false;
int minSdkVersion = 1;
boolean minSdkVersionSpecified = false;
int maxSdkVersion = Integer.MAX_VALUE;
List<SignerParams> signers = new ArrayList<>(1);
SignerParams signerParams = new SignerParams();
OptionsParser optionsParser = new OptionsParser(params);
String optionName;
String optionOriginalForm = null;
while ((optionName = optionsParser.nextOption()) != null) {
optionOriginalForm = optionsParser.getOptionOriginalForm();
if (("help".equals(optionName)) || ("h".equals(optionName))) {
printUsage(HELP_PAGE_SIGN);
return;
} else if ("out".equals(optionName)) {
outputApk = new File(optionsParser.getRequiredValue("Output file name"));
} else if ("in".equals(optionName)) {
inputApk = new File(optionsParser.getRequiredValue("Input file name"));
} else if ("min-sdk-version".equals(optionName)) {
minSdkVersion = optionsParser.getRequiredIntValue("Mininimum API Level");
minSdkVersionSpecified = true;
} else if ("max-sdk-version".equals(optionName)) {
maxSdkVersion = optionsParser.getRequiredIntValue("Maximum API Level");
} else if ("v1-signing-enabled".equals(optionName)) {
v1SigningEnabled = optionsParser.getOptionalBooleanValue(true);
} else if ("v2-signing-enabled".equals(optionName)) {
v2SigningEnabled = optionsParser.getOptionalBooleanValue(true);
} else if ("v3-signing-enabled".equals(optionName)) {
v3SigningEnabled = optionsParser.getOptionalBooleanValue(false);
} else if ("next-signer".equals(optionName)) {
if (!signerParams.isEmpty()) {
signers.add(signerParams);
signerParams = new SignerParams();
}
} else if ("ks".equals(optionName)) {
signerParams.keystoreFile = optionsParser.getRequiredValue("KeyStore file");
} else if ("ks-key-alias".equals(optionName)) {
signerParams.keystoreKeyAlias = optionsParser.getRequiredValue("KeyStore key alias");
} else if ("ks-pass".equals(optionName)) {
signerParams.keystorePasswordSpec = optionsParser.getRequiredValue("KeyStore password");
} else if ("key-pass".equals(optionName)) {
signerParams.keyPasswordSpec = optionsParser.getRequiredValue("Key password");
} else if ("v1-signer-name".equals(optionName)) {
signerParams.v1SigFileBasename = optionsParser.getRequiredValue("JAR signature file basename");
} else if ("ks-type".equals(optionName)) {
signerParams.keystoreType = optionsParser.getRequiredValue("KeyStore type");
} else if ("ks-provider-name".equals(optionName)) {
signerParams.keystoreProviderName = optionsParser.getRequiredValue("JCA KeyStore Provider name");
} else if ("ks-provider-class".equals(optionName)) {
signerParams.keystoreProviderClass = optionsParser.getRequiredValue("JCA KeyStore Provider class name");
} else if ("ks-provider-arg".equals(optionName)) {
signerParams.keystoreProviderArg = optionsParser.getRequiredValue("JCA KeyStore Provider constructor argument");
} else if ("key".equals(optionName)) {
signerParams.keyFile = optionsParser.getRequiredValue("Private key file");
} else if ("cert".equals(optionName)) {
signerParams.certFile = optionsParser.getRequiredValue("Certificate file");
} else if (("v".equals(optionName)) || ("verbose".equals(optionName))) {
verbose = optionsParser.getOptionalBooleanValue(true);
} else {
throw new ParameterException("Unsupported option: "
+ optionOriginalForm
+ ". See --help for supported"
+ " options.");
}
}
if (!signerParams.isEmpty()) {
signers.add(signerParams);
}
signerParams = null;
if (signers.isEmpty()) {
throw new ParameterException("At least one signer must be specified");
}
params = optionsParser.getRemainingParams();
if (inputApk != null) {
// Input APK has been specified via preceding parameters. We don't expect any more
// parameters.
if (params.length > 0) {
throw new ParameterException("Unexpected parameter(s) after " + optionOriginalForm + ": " + params[0]);
}
} else {
// Input APK has not been specified via preceding parameters. The next parameter is
// supposed to be the path to input APK.
if (params.length < 1) {
throw new ParameterException("Missing input APK");
} else if (params.length > 1) {
throw new ParameterException("Unexpected parameter(s) after input APK (" + params[1] + ")");
}
inputApk = new File(params[0]);
}
if ((minSdkVersionSpecified) && (minSdkVersion > maxSdkVersion)) {
throw new ParameterException("Min API Level (" + minSdkVersion + ") > max API Level (" + maxSdkVersion + ")");
}
List<ApkSigner.SignerConfig> signerConfigs = new ArrayList<>(signers.size());
int signerNumber = 0;
try (PasswordRetriever passwordRetriever = new PasswordRetriever()) {
for (SignerParams signer : signers) {
signerNumber++;
signer.name = "signer #" + signerNumber;
try {
signer.loadPrivateKeyAndCerts(passwordRetriever);
} catch (ParameterException e) {
System.err.println("Failed to load signer \"" + signer.name + "\": " + e.getMessage());
System.exit(2);
return;
} catch (Exception e) {
System.err.println("Failed to load signer \"" + signer.name + "\"");
e.printStackTrace();
System.exit(2);
return;
}
String v1SigBasename;
if (signer.v1SigFileBasename != null) {
v1SigBasename = signer.v1SigFileBasename;
} else if (signer.keystoreKeyAlias != null) {
v1SigBasename = signer.keystoreKeyAlias;
} else if (signer.keyFile != null) {
String keyFileName = new File(signer.keyFile).getName();
int delimiterIndex = keyFileName.indexOf('.');
if (delimiterIndex == -1) {
v1SigBasename = keyFileName;
} else {
v1SigBasename = keyFileName.substring(0, delimiterIndex);
}
} else {
throw new RuntimeException("Neither KeyStore key alias nor private key file available");
}
ApkSigner.SignerConfig signerConfig = new ApkSigner.SignerConfig.Builder(v1SigBasename,
signer.privateKey,
signer.certs
).build();
signerConfigs.add(signerConfig);
}
}
if (outputApk == null) {
outputApk = inputApk;
}
File tmpOutputApk;
if (inputApk.getCanonicalPath().equals(outputApk.getCanonicalPath())) {
tmpOutputApk = File.createTempFile("apksigner", ".apk");
tmpOutputApk.deleteOnExit();
} else {
tmpOutputApk = outputApk;
}
ApkSigner.Builder apkSignerBuilder = new ApkSigner.Builder(signerConfigs).setInputApk(inputApk)
.setOutputApk(tmpOutputApk)
.setOtherSignersSignaturesPreserved(false)
.setV1SigningEnabled(v1SigningEnabled)
.setV2SigningEnabled(v2SigningEnabled)
.setV3SigningEnabled(v3SigningEnabled);
if (minSdkVersionSpecified) {
apkSignerBuilder.setMinSdkVersion(minSdkVersion);
}
ApkSigner apkSigner = apkSignerBuilder.build();
try {
apkSigner.sign();
} catch (MinSdkVersionException e) {
String msg = e.getMessage();
if (!msg.endsWith(".")) {
msg += '.';
}
throw new MinSdkVersionException("Failed to determine APK's minimum supported platform version"
+ ". Use --min-sdk-version to override", e);
}
if (!tmpOutputApk.getCanonicalPath().equals(outputApk.getCanonicalPath())) {
Files.move(tmpOutputApk.toPath(), outputApk.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
if (verbose) {
System.out.println("Signed");
}
}
private static void verify(String[] params) throws Exception {
if (params.length == 0) {
printUsage(HELP_PAGE_VERIFY);
return;
}
File inputApk = null;
int minSdkVersion = 1;
boolean minSdkVersionSpecified = false;
int maxSdkVersion = Integer.MAX_VALUE;
boolean maxSdkVersionSpecified = false;
boolean printCerts = false;
boolean verbose = false;
boolean warningsTreatedAsErrors = false;
OptionsParser optionsParser = new OptionsParser(params);
String optionName;
String optionOriginalForm = null;
while ((optionName = optionsParser.nextOption()) != null) {
optionOriginalForm = optionsParser.getOptionOriginalForm();
if ("min-sdk-version".equals(optionName)) {
minSdkVersion = optionsParser.getRequiredIntValue("Mininimum API Level");
minSdkVersionSpecified = true;
} else if ("max-sdk-version".equals(optionName)) {
maxSdkVersion = optionsParser.getRequiredIntValue("Maximum API Level");
maxSdkVersionSpecified = true;
} else if ("print-certs".equals(optionName)) {
printCerts = optionsParser.getOptionalBooleanValue(true);
} else if (("v".equals(optionName)) || ("verbose".equals(optionName))) {
verbose = optionsParser.getOptionalBooleanValue(true);
} else if ("Werr".equals(optionName)) {
warningsTreatedAsErrors = optionsParser.getOptionalBooleanValue(true);
} else if (("help".equals(optionName)) || ("h".equals(optionName))) {
printUsage(HELP_PAGE_VERIFY);
return;
} else if ("in".equals(optionName)) {
inputApk = new File(optionsParser.getRequiredValue("Input APK file"));
} else {
throw new ParameterException("Unsupported option: "
+ optionOriginalForm
+ ". See --help for supported"
+ " options.");
}
}
params = optionsParser.getRemainingParams();
if (inputApk != null) {
// Input APK has been specified in preceding parameters. We don't expect any more
// parameters.
if (params.length > 0) {
throw new ParameterException("Unexpected parameter(s) after " + optionOriginalForm + ": " + params[0]);
}
} else {
// Input APK has not been specified in preceding parameters. The next parameter is
// supposed to be the input APK.
if (params.length < 1) {
throw new ParameterException("Missing APK");
} else if (params.length > 1) {
throw new ParameterException("Unexpected parameter(s) after APK (" + params[1] + ")");
}
inputApk = new File(params[0]);
}
if ((minSdkVersionSpecified) && (maxSdkVersionSpecified) && (minSdkVersion > maxSdkVersion)) {
throw new ParameterException("Min API Level (" + minSdkVersion + ") > max API Level (" + maxSdkVersion + ")");
}
ApkVerifier.Builder apkVerifierBuilder = new ApkVerifier.Builder(inputApk);
if (minSdkVersionSpecified) {
apkVerifierBuilder.setMinCheckedPlatformVersion(minSdkVersion);
}
if (maxSdkVersionSpecified) {
apkVerifierBuilder.setMaxCheckedPlatformVersion(maxSdkVersion);
}
ApkVerifier apkVerifier = apkVerifierBuilder.build();
ApkVerifier.Result result;
try {
result = apkVerifier.verify();
} catch (MinSdkVersionException e) {
String msg = e.getMessage();
if (!msg.endsWith(".")) {
msg += '.';
}
throw new MinSdkVersionException("Failed to determine APK's minimum supported platform version"
+ ". Use --min-sdk-version to override", e);
}
boolean verified = result.isVerified();
boolean warningsEncountered = false;
if (verified) {
List<X509Certificate> signerCerts = result.getSignerCertificates();
if (verbose) {
System.out.println("Verifies");
System.out.println("Verified using v1 scheme (JAR signing): " + result.isVerifiedUsingV1Scheme());
System.out.println("Verified using v2 scheme (APK Signature Scheme v2): " + result.isVerifiedUsingV2Scheme());
System.out.println("Number of signers: " + signerCerts.size());
}
if (printCerts) {
int signerNumber = 0;
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
MessageDigest md5 = MessageDigest.getInstance("MD5");
for (X509Certificate signerCert : signerCerts) {
signerNumber++;
System.out.println("Signer #" + signerNumber + " certificate DN" + ": " + signerCert.getSubjectDN());
byte[] encodedCert = signerCert.getEncoded();
System.out.println("Signer #"
+ signerNumber
+ " certificate SHA-256 digest: "
+ HexEncoding.encode(sha256.digest(encodedCert)));
System.out.println("Signer #" + signerNumber + " certificate SHA-1 digest: " + HexEncoding.encode(sha1.digest(
encodedCert)));
System.out.println("Signer #" + signerNumber + " certificate MD5 digest: " + HexEncoding.encode(md5.digest(
encodedCert)));
if (verbose) {
PublicKey publicKey = signerCert.getPublicKey();
System.out.println("Signer #" + signerNumber + " key algorithm: " + publicKey.getAlgorithm());
int keySize = -1;
if (publicKey instanceof RSAKey) {
keySize = ((RSAKey) publicKey).getModulus().bitLength();
} else if (publicKey instanceof ECKey) {
keySize = ((ECKey) publicKey).getParams().getOrder().bitLength();
} else if (publicKey instanceof DSAKey) {
// DSA parameters may be inherited from the certificate. We
// don't handle this case at the moment.
DSAParams dsaParams = ((DSAKey) publicKey).getParams();
if (dsaParams != null) {
keySize = dsaParams.getP().bitLength();
}
}
System.out.println("Signer #" + signerNumber + " key size (bits): " + ((keySize != -1) ? String.valueOf(
keySize) : "n/a"));
byte[] encodedKey = publicKey.getEncoded();
System.out.println("Signer #"
+ signerNumber
+ " public key SHA-256 digest: "
+ HexEncoding.encode(sha256.digest(encodedKey)));
System.out.println("Signer #"
+ signerNumber
+ " public key SHA-1 digest: "
+ HexEncoding.encode(sha1.digest(encodedKey)));
System.out.println("Signer #" + signerNumber + " public key MD5 digest: " + HexEncoding.encode(md5.digest(
encodedKey)));
}
}
}
} else {
System.err.println("DOES NOT VERIFY");
}
for (ApkVerifier.IssueWithParams error : result.getErrors()) {
System.err.println("ERROR: " + error);
}
@SuppressWarnings("resource") // false positive -- this resource is not opened here
PrintStream warningsOut = (warningsTreatedAsErrors) ? System.err : System.out;
for (ApkVerifier.IssueWithParams warning : result.getWarnings()) {
warningsEncountered = true;
warningsOut.println("WARNING: " + warning);
}
for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) {
String signerName = signer.getName();
for (ApkVerifier.IssueWithParams error : signer.getErrors()) {
System.err.println("ERROR: JAR signer " + signerName + ": " + error);
}
for (ApkVerifier.IssueWithParams warning : signer.getWarnings()) {
warningsEncountered = true;
warningsOut.println("WARNING: JAR signer " + signerName + ": " + warning);
}
}
for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) {
String signerName = "signer #" + (signer.getIndex() + 1);
for (ApkVerifier.IssueWithParams error : signer.getErrors()) {
System.err.println("ERROR: APK Signature Scheme v2 " + signerName + ": " + error);
}
for (ApkVerifier.IssueWithParams warning : signer.getWarnings()) {
warningsEncountered = true;
warningsOut.println("WARNING: APK Signature Scheme v2 " + signerName + ": " + warning);
}
}
if (!verified) {
System.exit(1);
return;
}
if ((warningsTreatedAsErrors) && (warningsEncountered)) {
System.exit(1);
return;
}
}
private static void printUsage(String page) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(
ApkSignerTool.class.getResourceAsStream(page),
StandardCharsets.UTF_8
))) {
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
throw new RuntimeException("Failed to read " + page + " resource");
}
}
private static byte[] readFully(File file) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
try (FileInputStream in = new FileInputStream(file)) {
drain(in, result);
}
return result.toByteArray();
}
private static void drain(InputStream in, OutputStream out) throws IOException {
byte[] buf = new byte[65536];
int chunkSize;
while ((chunkSize = in.read(buf)) != -1) {
out.write(buf, 0, chunkSize);
}
}
private static class SignerParams {
String name;
String keystoreFile;
String keystoreKeyAlias;
String keystorePasswordSpec;
String keyPasswordSpec;
String keystoreType;
String keystoreProviderName;
String keystoreProviderClass;
String keystoreProviderArg;
String keyFile;
String certFile;
String v1SigFileBasename;
PrivateKey privateKey;
List<X509Certificate> certs;
private static void loadKeyStoreFromFile(KeyStore ks, String file, List<char[]> passwords) throws Exception {
Exception lastFailure = null;
for (char[] password : passwords) {
try {
try (FileInputStream in = new FileInputStream(file)) {
ks.load(in, password);
}
return;
} catch (Exception e) {
lastFailure = e;
}
}
if (lastFailure == null) {
throw new RuntimeException("No keystore passwords");
} else {
throw lastFailure;
}
}
private static Key getKeyStoreKey(KeyStore ks, String keyAlias, List<char[]> passwords)
throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException {
UnrecoverableKeyException lastFailure = null;
for (char[] password : passwords) {
try {
return ks.getKey(keyAlias, password);
} catch (UnrecoverableKeyException e) {
lastFailure = e;
}
}
if (lastFailure == null) {
throw new RuntimeException("No key passwords");
} else {
throw lastFailure;
}
}
private static PKCS8EncodedKeySpec decryptPkcs8EncodedKey(
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo, List<char[]> passwords)
throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
InvalidKeySpecException lastKeySpecException = null;
InvalidKeyException lastKeyException = null;
for (char[] password : passwords) {
PBEKeySpec decryptionKeySpec = new PBEKeySpec(password);
try {
SecretKey decryptionKey = keyFactory.generateSecret(decryptionKeySpec);
return encryptedPrivateKeyInfo.getKeySpec(decryptionKey);
} catch (InvalidKeySpecException e) {
lastKeySpecException = e;
} catch (InvalidKeyException e) {
lastKeyException = e;
}
}
if ((lastKeyException == null) && (lastKeySpecException == null)) {
throw new RuntimeException("No passwords");
} else if (lastKeyException != null) {
throw lastKeyException;
} else {
throw lastKeySpecException;
}
}
private static PrivateKey loadPkcs8EncodedPrivateKey(PKCS8EncodedKeySpec spec)
throws InvalidKeySpecException, NoSuchAlgorithmException {
try {
return KeyFactory.getInstance("RSA").generatePrivate(spec);
} catch (InvalidKeySpecException expected) {
}
try {
return KeyFactory.getInstance("EC").generatePrivate(spec);
} catch (InvalidKeySpecException expected) {
}
try {
return KeyFactory.getInstance("DSA").generatePrivate(spec);
} catch (InvalidKeySpecException expected) {
}
throw new InvalidKeySpecException("Not an RSA, EC, or DSA private key");
}
private boolean isEmpty() {
return (name == null)
&& (keystoreFile == null)
&& (keystoreKeyAlias == null)
&& (keystorePasswordSpec == null)
&& (keyPasswordSpec == null)
&& (keystoreType == null)
&& (keystoreProviderName == null)
&& (keystoreProviderClass == null)
&& (keystoreProviderArg == null)
&& (keyFile == null)
&& (certFile == null)
&& (v1SigFileBasename == null)
&& (privateKey == null)
&& (certs == null);
}
private void loadPrivateKeyAndCerts(PasswordRetriever passwordRetriever) throws Exception {
if (keystoreFile != null) {
if (keyFile != null) {
throw new ParameterException("--ks and --key may not be specified at the same time");
} else if (certFile != null) {
throw new ParameterException("--ks and --cert may not be specified at the same time");
}
loadPrivateKeyAndCertsFromKeyStore(passwordRetriever);
} else if (keyFile != null) {
loadPrivateKeyAndCertsFromFiles(passwordRetriever);
} else {
throw new ParameterException("KeyStore (--ks) or private key file (--key) must be specified");
}
}
private void loadPrivateKeyAndCertsFromKeyStore(PasswordRetriever passwordRetriever) throws Exception {
if (keystoreFile == null) {
throw new ParameterException("KeyStore (--ks) must be specified");
}
// 1. Obtain a KeyStore implementation
String ksType = (keystoreType != null) ? keystoreType : KeyStore.getDefaultType();
KeyStore ks;
if (keystoreProviderName != null) {
// Use a named Provider (assumes the provider is already installed)
ks = KeyStore.getInstance(ksType, keystoreProviderName);
} else if (keystoreProviderClass != null) {
// Use a new Provider instance (does not require the provider to be installed)
Class<?> ksProviderClass = Class.forName(keystoreProviderClass);
if (!Provider.class.isAssignableFrom(ksProviderClass)) {
throw new ParameterException("Keystore Provider class "
+ keystoreProviderClass
+ " not subclass of "
+ Provider.class.getName());
}
Provider ksProvider;
if (keystoreProviderArg != null) {
// Single-arg Provider constructor
ksProvider = (Provider) ksProviderClass.getConstructor(String.class).newInstance(keystoreProviderArg);
} else {
// No-arg Provider constructor
ksProvider = (Provider) ksProviderClass.getConstructor().newInstance();
}
ks = KeyStore.getInstance(ksType, ksProvider);
} else {
// Use the highest-priority Provider which offers the requested KeyStore type
ks = KeyStore.getInstance(ksType);
}
// 2. Load the KeyStore
List<char[]> keystorePasswords = null;
if ("NONE".equals(keystoreFile)) {
ks.load(null);
} else {
String keystorePasswordSpec =
(this.keystorePasswordSpec != null) ? this.keystorePasswordSpec : PasswordRetriever.SPEC_STDIN;
keystorePasswords = passwordRetriever.getPasswords(keystorePasswordSpec, "Keystore password for " + name);
loadKeyStoreFromFile(ks, keystoreFile, keystorePasswords);
}
// 3. Load the PrivateKey and cert chain from KeyStore
String keyAlias = null;
PrivateKey key = null;
try {
if (keystoreKeyAlias == null) {
// Private key entry alias not specified. Find the key entry contained in this
// KeyStore. If the KeyStore contains multiple key entries, return an error.
Enumeration<String> aliases = ks.aliases();
if (aliases != null) {
while (aliases.hasMoreElements()) {
String entryAlias = aliases.nextElement();
if (ks.isKeyEntry(entryAlias)) {
keyAlias = entryAlias;
if (keystoreKeyAlias != null) {
throw new ParameterException(keystoreFile
+ " contains multiple key entries"
+ ". --ks-key-alias option must be used to specify"
+ " which entry to use.");
}
keystoreKeyAlias = keyAlias;
}
}
}
if (keystoreKeyAlias == null) {
throw new ParameterException(keystoreFile + " does not contain key entries");
}
}
// Private key entry alias known. Load that entry's private key.
keyAlias = keystoreKeyAlias;
if (!ks.isKeyEntry(keyAlias)) {
throw new ParameterException(keystoreFile + " entry \"" + keyAlias + "\" does not contain a key");
}
Key entryKey;
if (keyPasswordSpec != null) {
// Key password spec is explicitly specified. Use this spec to obtain the
// password and then load the key using that password.
List<char[]> keyPasswords = passwordRetriever.getPasswords(keyPasswordSpec,
"Key \"" + keyAlias + "\" password for " + name
);
entryKey = getKeyStoreKey(ks, keyAlias, keyPasswords);
} else {
// Key password spec is not specified. This means we should assume that key
// password is the same as the keystore password and that, if this assumption is
// wrong, we should prompt for key password and retry loading the key using that
// password.
try {
entryKey = getKeyStoreKey(ks, keyAlias, keystorePasswords);
} catch (UnrecoverableKeyException expected) {
List<char[]> keyPasswords = passwordRetriever.getPasswords(PasswordRetriever.SPEC_STDIN,
"Key \"" + keyAlias + "\" password for " + name
);
entryKey = getKeyStoreKey(ks, keyAlias, keyPasswords);
}
}
if (entryKey == null) {
throw new ParameterException(keystoreFile + " entry \"" + keyAlias + "\" does not contain a key");
} else if (!(entryKey instanceof PrivateKey)) {
throw new ParameterException(keystoreFile
+ " entry \""
+ keyAlias
+ "\" does not contain a private"
+ " key. It contains a key of algorithm: "
+ entryKey.getAlgorithm());
}
key = (PrivateKey) entryKey;
} catch (UnrecoverableKeyException e) {
throw new IOException("Failed to obtain key with alias \""
+ keyAlias
+ "\" from "
+ keystoreFile
+ ". Wrong password?", e);
}
this.privateKey = key;
Certificate[] certChain = ks.getCertificateChain(keyAlias);
if ((certChain == null) || (certChain.length == 0)) {
throw new ParameterException(keystoreFile + " entry \"" + keyAlias + "\" does not contain certificates");
}
this.certs = new ArrayList<>(certChain.length);
for (Certificate cert : certChain) {
this.certs.add((X509Certificate) cert);
}
}
private void loadPrivateKeyAndCertsFromFiles(PasswordRetriever passwordRetriver) throws Exception {
if (keyFile == null) {
throw new ParameterException("Private key file (--key) must be specified");
}
if (certFile == null) {
throw new ParameterException("Certificate file (--cert) must be specified");
}
byte[] privateKeyBlob = readFully(new File(keyFile));
PKCS8EncodedKeySpec keySpec;
// Potentially encrypted key blob
try {
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(privateKeyBlob);
// The blob is indeed an encrypted private key blob
String passwordSpec = (keyPasswordSpec != null) ? keyPasswordSpec : PasswordRetriever.SPEC_STDIN;
List<char[]> keyPasswords = passwordRetriver.getPasswords(passwordSpec, "Private key password for " + name);
keySpec = decryptPkcs8EncodedKey(encryptedPrivateKeyInfo, keyPasswords);
} catch (IOException e) {
// The blob is not an encrypted private key blob
if (keyPasswordSpec == null) {
// Given that no password was specified, assume the blob is an unencrypted
// private key blob
keySpec = new PKCS8EncodedKeySpec(privateKeyBlob);
} else {
throw new InvalidKeySpecException("Failed to parse encrypted private key blob " + keyFile, e);
}
}
// Load the private key from its PKCS #8 encoded form.
try {
privateKey = loadPkcs8EncodedPrivateKey(keySpec);
} catch (InvalidKeySpecException e) {
throw new InvalidKeySpecException("Failed to load PKCS #8 encoded private key from " + keyFile, e);
}
// Load certificates
Collection<? extends Certificate> certs;
try (FileInputStream in = new FileInputStream(certFile)) {
certs = CertificateFactory.getInstance("X.509").generateCertificates(in);
}
List<X509Certificate> certList = new ArrayList<>(certs.size());
for (Certificate cert : certs) {
certList.add((X509Certificate) cert);
}
this.certs = certList;
}
}
/**
* Indicates that there is an issue with command-line parameters provided to this tool.
*/
private static class ParameterException extends Exception {
private static final long serialVersionUID = 1L;
ParameterException(String message) {
super(message);
}
}
}
================================================
FILE: AndResGuard-core/src/main/java/apksigner/HexEncoding.java
================================================
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apksigner;
import java.nio.ByteBuffer;
/**
* Hexadecimal encoding where each byte is represented by two hexadecimal digits.
*/
class HexEncoding {
private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
/**
* Hidden constructor to prevent instantiation.
*/
private HexEncoding() {
}
/**
* Encodes the provided data as a hexadecimal string.
*/
public static String encode(byte[] data, int offset, int length) {
StringBuilder result = new StringBuilder(length * 2);
for (int i = 0; i < length; i++) {
byte b = data[offset + i];
result.append(HEX_DIGITS[(b >>> 4) & 0x0f]);
result.append(HEX_DIGITS[b & 0x0f]);
}
return result.toString();
}
/**
* Encodes the provided data as a hexadecimal string.
*/
public static String encode(byte[] data) {
return encode(data, 0, data.length);
}
/**
* Encodes the remaining bytes of the provided {@link ByteBuffer} as a hexadecimal string.
*/
public static String encodeRemaining(ByteBuffer data) {
return encode(data.array(), data.arrayOffset() + data.position(), data.remaining());
}
}
================================================
FILE: AndResGuard-core/src/main/java/apksigner/OptionsParser.java
================================================
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apksigner;
import java.util.Arrays;
/**
* Parser of command-line options/switches/flags.
*
* <p>Supported option formats:
* <ul>
* <li>{@code --name value}</li>
* <li>{@code --name=value}</li>
* <li>{@code -name value}</li>
* <li>{@code --name} (boolean options only)</li>
* </ul>
*
* <p>To use the parser, create an instance, providing it with the command-line parameters, then
* iterate over options by invoking {@link #nextOption()} until it returns {@code null}.
*/
class OptionsParser {
private final String[] mParams;
private int mIndex;
private String mLastOptionValue;
private String mLastOptionOriginalForm;
/**
* Constructs a new {@code OptionsParser} initialized with the provided command-line.
*/
public OptionsParser(String[] params) {
mParams = params.clone();
}
/**
* Returns the name (without leading dashes) of the next option (starting with the very first
* option) or {@code null} if there are no options left.
*
* <p>The value of this option can be obtained via {@link #getRequiredValue(String)},
* {@link #getRequiredIntValue(String)}, and {@link #getOptionalBooleanValue(boolean)}.
*/
public String nextOption() {
if (mIndex >= mParams.length) {
// No more parameters left
return null;
}
String param = mParams[mIndex];
if (!param.startsWith("-")) {
// Not an option
return null;
}
mIndex++;
mLastOptionOriginalForm = param;
mLastOptionValue = null;
if (param.startsWith("--")) {
// FORMAT: --name value OR --name=value
if ("--".equals(param)) {
// End of options marker
return null;
}
int valueDelimiterIndex = param.indexOf('=');
if (valueDelimiterIndex != -1) {
mLastOptionValue = param.substring(valueDelimiterIndex + 1);
mLastOptionOriginalForm = param.substring(0, valueDelimiterIndex);
return param.substring("--".length(), valueDelimiterIndex);
} else {
return param.substring("--".length());
}
} else {
// FORMAT: -name value
return param.substring("-".length());
}
}
/**
* Returns the original form of the current option. The original form includes the leading dash
* or dashes. This is intended to be used for referencing the option in error messages.
*/
public String getOptionOriginalForm() {
return mLastOptionOriginalForm;
}
/**
* Returns the value of the current option, throwing an exception if the value is missing.
*/
public String getRequiredValue(String valueDescription) throws OptionsException {
if (mLastOptionValue != null) {
String result = mLastOptionValue;
mLastOptionValue = null;
return result;
}
if (mIndex >= mParams.length) {
// No more parameters left
throw new OptionsException(valueDescription + " missing after " + mLastOptionOriginalForm);
}
String param = mParams[mIndex];
if ("--".equals(param)) {
// End of options marker
throw new OptionsException(valueDescription + " missing after " + mLastOptionOriginalForm);
}
mIndex++;
return param;
}
/**
* Returns the value of the current numeric option, throwing an exception if the value is
* missing or is not numeric.
*/
public int getRequiredIntValue(String valueDescription) throws OptionsException {
String value = getRequiredValue(valueDescription);
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new OptionsException(valueDescription
+ " ("
+ mLastOptionOriginalForm
+ ") must be a decimal number: "
+ value);
}
}
/**
* Gets the value of the current boolean option. Boolean options are not required to have
* explicitly specified values.
*/
public boolean getOptionalBooleanValue(boolean defaultValue) throws OptionsException {
if (mLastOptionValue != null) {
// --option=value form
String stringValue = mLastOptionValue;
mLastOptionValue = null;
if ("true".equals(stringValue)) {
return true;
} else if ("false".equals(stringValue)) {
return false;
}
throw new OptionsException("Unsupported value for "
+ mLastOptionOriginalForm
+ ": "
+ stringValue
+ ". Only true or false supported.");
}
// --option (true|false) form OR just --option
if (mIndex >= mParams.length) {
return defaultValue;
}
String stringValue = mParams[mIndex];
if ("true".equals(stringValue)) {
mIndex++;
return true;
} else if ("false".equals(stringValue)) {
mIndex++;
return false;
} else {
return defaultValue;
}
}
/**
* Returns the remaining command-line parameters. This is intended to be invoked once
* {@link #nextOption()} returns {@code null}.
*/
public String[] getRemainingParams() {
if (mIndex >= mParams.length) {
return new String[0];
}
String param = mParams[mIndex];
if ("--".equals(param)) {
// Skip end of options marker
return Arrays.copyOfRange(mParams, mIndex + 1, mParams.length);
} else {
return Arrays.copyOfRange(mParams, mIndex, mParams.length);
}
}
/**
* Indicates that an error was encountered while parsing command-line options.
*/
public static class OptionsException extends Exception {
private static final long serialVersionUID = 1L;
public OptionsException(String message) {
super(message);
}
}
}
================================================
FILE: AndResGuard-core/src/main/java/apksigner/PasswordRetriever.java
================================================
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apksigner;
import java.io.ByteArrayOutputStream;
import java.io.Console;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Retriever of passwords based on password specs supported by {@code apksigner} tool.
*
* <p>apksigner supports retrieving multiple passwords from the same source (e.g., file, standard
* input) which adds the need to keep some sources open across password retrievals. This class
* addresses the need.
*
* <p>To use this retriever, construct a new instance, use {@link #getPasswords(String, String)} to
* retrieve passwords, and then invoke {@link #close()} on the instance when done, enabling the
* instance to release any held resources.
*/
class PasswordRetriever implements AutoCloseable {
public static final String SPEC_STDIN = "stdin";
private static final Charset CONSOLE_CHARSET = getConsoleEncoding();
private final Map<File, InputStream> mFileInputStreams = new HashMap<>();
private boolean mClosed;
/**
* Returns the provided password and all password variants derived from the password. The
* resulting list is guaranteed to contain at least one element.
*/
private static List<char[]> getPasswords(char[] pwd) {
List<char[]> passwords = new ArrayList<>(3);
addPasswords(passwords, pwd);
return passwords;
}
/**
* Returns the provided password and all password variants derived from the password. The
* resulting list is guaranteed to contain at least one element.
*
* @param encodedPwd password encoded using the provided character encoding.
* @param encodings character encodings in which the password is encoded in {@code encodedPwd}.
*/
private static List<char[]> getPasswords(byte[] encodedPwd, Charset... encodings) {
List<char[]> passwords = new ArrayList<>(4);
for (Charset encoding : encodings) {
// Decode password and add it and its variants to the list
try {
char[] pwd = decodePassword(encodedPwd, encoding);
addPasswords(passwords, pwd);
} catch (IOException ignored) {
}
}
// Add the original encoded form
addPassword(passwords, castBytesToChars(encodedPwd));
return passwords;
}
/**
* Adds the provided password and its variants to the provided list of passwords.
*
* <p>NOTE: This method adds only the passwords/variants which are not yet in the list.
*/
private static void addPasswords(List<char[]> passwords, char[] pwd) {
// Verbatim password
addPassword(passwords, pwd);
// Password encoded using the JVM default character encoding and upcast into char[]
try {
char[] encodedPwd = castBytesToChars(encodePassword(pwd, Charset.defaultCharset()));
addPassword(passwords, encodedPwd);
} catch (IOException ignored) {
}
// Password encoded using console character encoding and upcast into char[]
if (!CONSOLE_CHARSET.equals(Charset.defaultCharset())) {
try {
char[] encodedPwd = castBytesToChars(encodePassword(pwd, CONSOLE_CHARSET));
addPassword(passwords, encodedPwd);
} catch (IOException ignored) {
}
}
}
/**
* Adds the provided password to the provided list. Does nothing if the password is already in
* the list.
*/
private static void addPassword(List<char[]> passwords, char[] password) {
for (char[] existingPassword : passwords) {
if (Arrays.equals(password, existingPassword)) {
return;
}
}
passwords.add(password);
}
private static byte[] encodePassword(char[] pwd, Charset cs) throws IOException {
ByteBuffer pwdBytes = cs.newEncoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(
CodingErrorAction.REPLACE).encode(CharBuffer.wrap(pwd));
byte[] encoded = new byte[pwdBytes.remaining()];
pwdBytes.get(encoded);
return encoded;
}
private static char[] decodePassword(byte[] pwdBytes, Charset encoding) throws IOException {
CharBuffer pwdChars = encoding.newDecoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(
CodingErrorAction.REPLACE).decode(ByteBuffer.wrap(pwdBytes));
char[] result = new char[pwdChars.remaining()];
pwdChars.get(result);
return result;
}
/**
* Upcasts each {@code byte} in the provided array of bytes to a {@code char} and returns the
* resulting array of characters.
*/
private static char[] castBytesToChars(byte[] bytes) {
if (bytes == null) {
return null;
}
char[] chars = new char[bytes.length];
for (int i = 0; i < bytes.length; i++) {
chars[i] = (char) (bytes[i] & 0xff);
}
return chars;
}
/**
* Returns the character encoding used by the console.
*/
private static Charset getConsoleEncoding() {
// IMPLEMENTATION NOTE: There is no public API for obtaining the console's character
// encoding. We thus cheat by using implementation details of the most popular JVMs.
String consoleCharsetName;
try {
Method encodingMethod = Console.class.getDeclaredMethod("encoding");
encodingMethod.setAccessible(true);
consoleCharsetName = (String) encodingMethod.invoke(null);
if (consoleCharsetName == null) {
return Charset.defaultCharset();
}
} catch (ReflectiveOperationException e) {
Charset defaultCharset = Charset.defaultCharset();
System.err.println("warning: Failed to obtain console character encoding name. Assuming " + defaultCharset);
return defaultCharset;
}
try {
return Charset.forName(consoleCharsetName);
} catch (IllegalArgumentException e) {
// On Windows 10, cp65001 is the UTF-8 code page. For some reason, popular JVMs don't
// have a mapping for cp65001...
if ("cp65001".equals(consoleCharsetName)) {
return StandardCharsets.UTF_8;
}
Charset defaultCharset = Charset.defaultCharset();
System.err.println("warning: Console uses unknown character encoding: "
+ consoleCharsetName
+ ". Using "
+ defaultCharset
+ " instead");
return defaultCharset;
}
}
private static byte[] readEncodedPassword(InputStream in) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
int b;
while ((b = in.read()) != -1) {
if (b == '\n') {
break;
} else if (b == '\r') {
int next = in.read();
if ((next == -1) || (next == '\n')) {
break;
}
if (!(in instanceof PushbackInputStream)) {
in = new PushbackInputStream(in);
}
((PushbackInputStream) in).unread(next);
}
result.write(b);
}
return result.toByteArray();
}
/**
* Returns the passwords described by the provided spec. The reason there may be more than one
* password is compatibility with {@code keytool} and {@code jarsigner} which in certain cases
* use the form of passwords encoded using the console's character encoding.
*
* <p>Supported specs:
* <ul>
* <li><em>stdin</em> -- read password as a line from console, if available, or standard
* input if console is not available</li>
* <li><em>pass:password</em> -- password specified inside the spec, starting after
* {@code pass:}</li>
* <li><em>file:path</em> -- read password as a line from the specified file</li>
* <li><em>env:name</em> -- password is in the specified environment variable</li>
* </ul>
*
* <p>When the same file (including standard input) is used for providing multiple passwords,
* the passwords are read from the file one line at a time.
*/
public List<char[]> getPasswords(String spec, String description) throws IOException {
// IMPLEMENTATION NOTE: Java KeyStore and PBEKeySpec APIs take passwords as arrays of
// Unicode characters (char[]). Unfortunately, it appears that Sun/Oracle keytool and
// jarsigner in some cases use passwords which are the encoded form obtained using the
// console's character encoding. For example, if the encoding is UTF-8, keytool and
// jarsigner will use the password which is obtained by upcasting each byte of the UTF-8
// encoded form to char. This occurs only when the password is read from stdin/console, and
// does not occur when the password is read from a command-line parameter.
// There are other tools which use the Java KeyStore API correctly.
// Thus, for each password spec, there may be up to three passwords:
// * Unicode characters,
// * characters (upcast bytes) obtained from encoding the password using the console's
// character encoding,
// * characters (upcast bytes) obtained from encoding the password using the JVM's default
// character encoding.
//
// For a sample password "\u0061\u0062\u00a1\u00e4\u044e\u0031":
// On Windows 10 with English US as the UI language, IBM437 is used as console encoding and
// windows-1252 is used as the JVM default encoding:
// * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000
// -alias test
// generates a keystore and key which decrypt only with
// "\u0061\u0062\u00ad\u0084\u003f\u0031"
// * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000
// -alias test -storepass <pass here>
// generates a keystore and key which decrypt only with
// "\u0061\u0062\u00a1\u00e4\u003f\u0031"
// On modern OSX/Linux UTF-8 is used as the console and JVM default encoding:
// * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000
// -alias test
// generates a keystore and key which decrypt only with
// "\u0061\u0062\u00c2\u00a1\u00c3\u00a4\u00d1\u008e\u0031"
// * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000
// -alias test
// generates a keystore and key which decrypt only with
// "\u0061\u0062\u00a1\u00e4\u044e\u0031"
assertNotClosed();
if (spec.startsWith("pass:")) {
char[] pwd = spec.substring("pass:".length()).toCharArray();
return getPasswords(pwd);
} else if (SPEC_STDIN.equals(spec)) {
Console console = System.console();
if (console != null) {
// Reading from console
char[] pwd = console.readPassword(description + ": ");
if (pwd == null) {
throw new IOException("Failed to read " + description + ": console closed");
}
return getPasswords(pwd);
} else {
// Console not available -- reading from redirected input
System.out.println(description + ": ");
byte[] encodedPwd = readEncodedPassword(System.in);
if (encodedPwd.length == 0) {
throw new IOException("Failed to read " + description + ": standard input closed");
}
// By default, textual input obtained via standard input is supposed to be decoded
// using the in JVM default character encoding but we also try the console's
// encoding just in case.
return getPasswords(encodedPwd, Charset.defaultCharset(), CONSOLE_CHARSET);
}
} else if (spec.startsWith("file:")) {
String name = spec.substring("file:".length());
File file = new File(name).getCanonicalFile();
InputStream in = mFileInputStreams.get(file);
if (in == null) {
in = new FileInputStream(file);
mFileInputStreams.put(file, in);
}
byte[] encodedPwd = readEncodedPassword(in);
if (encodedPwd.length == 0) {
throw new IOException("Failed to read " + description + " : end of file reached in " + file);
}
// By default, textual input from files is supposed to be treated as encoded using JVM's
// default character encoding.
return getPasswords(encodedPwd, Charset.defaultCharset());
} else if (spec.startsWith("env:")) {
String name = spec.substring("env:".length());
String value = System.getenv(name);
if (value == null) {
throw new IOException("Failed to read " + description + ": environment variable " + value + " not specified");
}
return getPasswords(value.toCharArray());
} else {
throw new IOException("Unsupported password spec for " + description + ": " + spec);
}
}
private void assertNotClosed() {
if (mClosed) {
throw new IllegalStateException("Closed");
}
}
@Override
public void close() {
for (InputStream in : mFileInputStreams.values()) {
try {
in.close();
} catch (IOException ignored) {
}
}
mFileInputStreams.clear();
mClosed = true;
}
}
================================================
FILE: AndResGuard-core/src/main/java/apksigner/help.txt
================================================
USAGE: apksigner <command> [options]
apksigner --version
apksigner --help
EXAMPLE:
apksigner sign --ks release.jks app.apk
apksigner verify --verbose app.apk
apksigner is a tool for signing Android APK files and for checking whether
signatures of APK files will verify on Android devices.
COMMANDS
sign Sign the provided APK
verify Check whether the provided APK is expected to verify on
Android
version Show this tool's version number and exit
help Show this usage page and exit
================================================
FILE: AndResGuard-core/src/main/java/apksigner/help_sign.txt
================================================
USAGE: apksigner sign [options] apk
This signs the provided APK, stripping out any pre-existing signatures. Signing
is performed using one or more signers, each represented by an asymmetric key
pair and a corresponding certificate. Typically, an APK is signed by just one
signer. For each signer, you need to provide the signer's private key and
certificate.
GENERAL OPTIONS
--in Input APK file to sign. This is an alternative to
specifying the APK as the very last parameter, after all
options. Unless --out is specified, this file will be
overwritten with the resulting signed APK.
--out File into which to output the signed APK. By default, the
APK is signed in-place, overwriting the input file.
-v, --verbose Verbose output mode
--v1-signing-enabled Whether to enable signing using JAR signing scheme (aka v1
signing scheme) used in Android since day one. By default,
signing using this scheme is enabled based on min and max
SDK version (see --min-sdk-version and --max-sdk-version).
--v2-signing-enabled Whether to enable signing using APK Signature Scheme v2
(aka v2 signing scheme) introduced in Android Nougat,
API Level 24. By default, signing using this scheme is
enabled based on min and max SDK version (see
--min-sdk-version and --max-sdk-version).
--min-sdk-version Lowest API Level on which this APK's signatures will be
verified. By default, the value from AndroidManifest.xml
is used. The higher the value, the stronger security
parameters are used when signing.
--max-sdk-version Highest API Level on which this APK's signatures will be
verified. By default, the highest possible value is used.
-h, --help Show help about this command and exit
PER-SIGNER OPTIONS
These options specify the configuration of a particular signer. To delimit
options of different signers, use --next-signer.
--next-signer Delimits options of two different signers. There is no
need to use this option when only one signer is used.
--v1-signer-name Basename for files comprising the JAR signature scheme
(aka v1 scheme) signature of this signer. By default,
KeyStore key alias or basename of key file is used.
PER-SIGNER SIGNING KEY & CERTIFICATE OPTIONS
There are two ways to provide the signer's private key and certificate: (1) Java
KeyStore (see --ks), or (2) private key file in PKCS #8 format and certificate
file in X.509 format (see --key and --cert).
--ks Load private key and certificate chain from the Java
KeyStore initialized from the specified file. NONE means
no file is needed by KeyStore, which is the case for some
PKCS #11 KeyStores.
--ks-key-alias Alias under which the private key and certificate are
stored in the KeyStore. This must be specified if the
KeyStore contains multiple keys.
--ks-pass KeyStore password (see --ks). The following formats are
supported:
pass:<password> password provided inline
env:<name> password provided in the named
environment variable
file:<file> password provided in the named
file, as a single line
stdin password provided on standard input,
as a single line
A password is required to open a KeyStore.
By default, the tool will prompt for password via console
or standard input.
When the same file (including standard input) is used for
providing multiple passwords, the passwords are read from
the file one line at a time. Passwords are read in the
order in which signers are specified and, within each
signer, KeyStore password is read before the key password
is read.
--key-pass Password with which the private key is protected.
The following formats are supported:
pass:<password> password provided inline
env:<name> password provided in the named
environment variable
file:<file> password provided in the named
file, as a single line
stdin password provided on standard input,
as a single line
If --key-pass is not specified for a KeyStore key, this
tool will attempt to load the key using the KeyStore
password and, if that fails, will prompt for key password
and attempt to load the key using that password.
If --key-pass is not specified for a private key file key,
this tool will prompt for key password only if a password
is required.
When the same file (including standard input) is used for
providing multiple passwords, the passwords are read from
the file one line at a time. Passwords are read in the
order in which signers are specified and, within each
signer, KeyStore password is read before the key password
is read.
--ks-type Type/algorithm of KeyStore to use. By default, the default
type is used.
--ks-provider-name Name of the JCA Provider from which to request the
KeyStore implementation. By default, the highest priority
provider is used. See --ks-provider-class for the
alternative way to specify a provider.
--ks-provider-class Fully-qualified class name of the JCA Provider from which
to request the KeyStore implementation. By default, the
provider is chosen based on --ks-provider-name.
--ks-provider-arg Value to pass into the constructor of the JCA Provider
class specified by --ks-provider-class. The value is
passed into the constructor as java.lang.String. By
default, the no-arg provider's constructor is used.
--key Load private key from the specified file. If the key is
password-protected, the password will be prompted via
standard input unless specified otherwise using
--key-pass. The file must be in PKCS #8 DER format.
--cert Load certificate chain from the specified file. The file
must be in X.509 PEM or DER format.
EXAMPLES
1. Sign an APK, in-place, using the one and only key in keystore release.jks:
$ apksigner sign --ks release.jks app.apk
1. Sign an APK, without overwriting, using the one and only key in keystore
release.jks:
$ apksigner sign --ks release.jks --in app.apk --out app-signed.apk
3. Sign an APK using a private key and certificate stored as individual files:
$ apksigner sign --key release.pk8 --cert release.x509.pem app.apk
4. Sign an APK using two keys:
$ apksigner sign --ks release.jks --next-signer --ks magic.jks app.apk
================================================
FILE: AndResGuard-core/src/main/java/apksigner/help_verify.txt
================================================
USAGE: apksigner verify [options] apk
This checks whether the provided APK will verify on Android. By default, this
checks whether the APK will verify on all Android platform versions supported
by the APK (as declared using minSdkVersion in AndroidManifest.xml). Use
--min-sdk-version and/or --max-sdk-version to verify the APK against a custom
range of API Levels.
OPTIONS
--print-certs Show information about the APK's signing certificates
-v, --verbose Verbose output mode
--min-sdk-version Lowest API Level on which this APK's signatures will be
verified. By default, the value from AndroidManifest.xml
is used.
--max-sdk-version Highest API Level on which this APK's signatures will be
verified. By default, the highest possible value is used.
-Werr Treat warnings as errors
--in APK file to verify. This is an alternative to specifying
the APK as the very last parameter, after all options.
-h, --help Show help about this command and exit
EXAMPLES
1. Check whether the APK's signatures are expected to verify on all Android
platforms declared as supported by this APK:
$ apksigner verify app.apk
2. Check whether the APK's signatures are expected to verify on Android
platforms with API Level 15 and higher:
$ apksigner verify --min-sdk-version 15 app.apk
================================================
FILE: AndResGuard-core/src/main/java/com/mindprod/ledatastream/LEDataInputStream.java
================================================
/*
* @(#)LEDataInputStream.java
*
* Summary: Little-Endian version of DataInputStream.
*
* Copyright: (c) 1998-2010 Roedy Green, Canadian Mind Products, http://mindprod.com
*
* Licence: This software may be copied and used freely for any purpose but military.
* http://mindprod.com/contact/nonmil.html
*
* Requires: JDK 1.1+
*
* Created with: IntelliJ IDEA IDE.
*
* Version History:
* 1.8 2007-05-24
*/
package com.mindprod.ledatastream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Little-Endian version of DataInputStream.
* <p>
* Very similar to DataInputStream except it reads little-endian instead of
* big-endian binary data. We can't extend DataInputStream directly since it has
* only final methods, though DataInputStream itself is not final. This forces
* us implement LEDataInputStream with a DataInputStream object, and use wrapper
* methods.
*
* @author Roedy Green, Canadian Mind Products
* @version 1.8 2007-05-24
* @since 1998
*/
public final class LEDataInputStream implements DataInput {
// ------------------------------ CONSTANTS ------------------------------
/**
* undisplayed copyright notice.
*/
private static final String EMBEDDED_COPYRIGHT =
"copyright (c) 1999-2010 Roedy Green, Canadian Mind Products, http://mindprod.com";
// ------------------------------ FIELDS ------------------------------
/**
* to get at the big-Endian methods of a basic DataInputStream
*/
protected final DataInputStream dis;
/**
* to get at the a basic readBytes method.
*/
protected final InputStream is;
/**
* work array for buffering input.
*/
protected final byte[] work;
// -------------------------- PUBLIC STATIC METHODS
// --------------------------
/**
* constructor.
*
* @param in binary inputstream of little-endian data.
*/
public LEDataInputStream(InputStream in) {
this.is = in;
this.dis = new DataInputStream(in);
work = new byte[8];
}
// -------------------------- PUBLIC INSTANCE METHODS
// --------------------------
/**
* Note. This is a STATIC method!
*
* @param in stream to read UTF chars from (endian irrelevant)
* @return string from stream
* @throws IOException if read fails.
*/
public static String readUTF(DataInput in) throws IOException {
return DataInputStream.readUTF(in);
}
/**
* close.
*
* @throws IOException if close fails.
*/
public final void close() throws IOException {
dis.close();
}
/**
* Read bytes. Watch out, read may return fewer bytes than requested.
*
* @param ba where the bytes go.
* @param off offset in buffer, not offset in file.
* @param len count of bytes to read.
* @return how many bytes read.
* @throws IOException if read fails.
*/
public final int read(byte ba[], int off, int len) throws IOException {
// For efficiency, we avoid one layer of wrapper
return is.read(ba, off, len);
}
/**
* read only a one-byte boolean.
*
* @return true or false.
* @throws IOException if read fails.
* @see java.io.DataInput#readBoolean()
*/
@Override
public final boolean readBoolean() throws IOException {
return dis.readBoolean();
}
/**
* read byte.
*
* @return the byte read.
* @throws IOException if read fails.
* @see java.io.DataInput#readByte()
*/
@Override
public final byte readByte() throws IOException {
return dis.readByte();
}
/**
* Read on char. like DataInputStream.readChar except little endian.
*
* @return little endian 16-bit unicode char from the stream.
* @throws IOException if read fails.
*/
@Override
public final char readChar() throws IOException {
dis.readFully(work, 0, 2);
return (char) ((work[1] & 0xff) << 8 | (work[0] & 0xff));
}
/**
* Read a double. like DataInputStream.readDouble except little endian.
*
* @return little endian IEEE double from the datastream.
* @throws IOException ioexception
*/
@Override
public final double readDouble() throws IOException {
return Double.longBitsToDouble(readLong());
}
/**
* Read one float. Like DataInputStream.readFloat except little endian.
*
* @return little endian IEEE float from the datastream.
* @throws IOException if read fails.
*/
@Override
public final float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
/**
* Read bytes until the array is filled.
*
* @see java.io.DataInput#readFully(byte[])
*/
@Override
public final void readFully(byte ba[]) throws IOException {
dis.readFully(ba, 0, ba.length);
}
/**
* Read bytes until the count is satisfied.
*
* @throws IOException if read fails.
* @see java.io.DataInput#readFully(byte[], int, int)
*/
@Override
public final void readFully(byte ba[], int off, int len) throws IOException {
dis.readFully(ba, off, len);
}
/**
* Read an int, 32-bits. Like DataInputStream.readInt except little endian.
*
* @return little-endian binary int from the datastream
* @throws IOException if read fails.
*/
@Override
public final int readInt() throws IOException {
dis.readFully(work, 0, 4);
return (work[3]) << 24 | (work[2] & 0xff) << 16 | (work[1] & 0xff) << 8 | (work[0] & 0xff);
}
/**
* Read a line.
*
* @return a rough approximation of the 8-bit stream as a 16-bit unicode
* string
* @throws IOException ioexception
* @deprecated This method does not properly convert bytes to characters.
* Use a Reader instead with a little-endian encoding.
*/
@Deprecated
@Override
public final String readLine() throws IOException {
return dis.readLine();
}
/**
* read a long, 64-bits. Like DataInputStream.readLong except little endian.
*
* @return little-endian binary long from the datastream.
* @throws IOException ioexception
*/
@Override
public final long readLong() throws IOException {
dis.readFully(work, 0, 8);
return (long) (work[7]) << 56
|
/* long cast needed or shift done modulo 32 */
(long) (work[6] & 0xff) << 48
| (long) (work[5] & 0xff) << 40
| (long) (work[4] & 0xff) << 32
| (long) (work[3] & 0xff) << 24
| (long) (work[2] & 0xff) << 16
| (long) (work[1] & 0xff) << 8
| work[0] & 0xff;
}
/**
* Read short, 16-bits. Like DataInputStream.readShort except little endian.
*
* @return little endian binary short from stream.
* @throws IOException if read fails.
*/
@Override
public final short readShort() throws IOException {
dis.readFully(work, 0, 2);
return (short) ((work[1] & 0xff) << 8 | (work[0] & 0xff));
}
/**
* Read UTF counted string.
*
* @return String read.
*/
@Override
public final String readUTF() throws IOException {
return dis.readUTF();
}
/**
* Read an unsigned byte. Note: returns an int, even though says Byte
* (non-Javadoc)
*
* @throws IOException if read fails.
* @see java.io.DataInput#readUnsignedByte()
*/
@Override
public final int readUnsignedByte() throws IOException {
return dis.readUnsignedByte();
}
/**
* Read an unsigned short, 16 bits. Like DataInputStream.readUnsignedShort
* except little endian. Note, returns int even though it reads a short.
*
* @return little-endian int from the stream.
* @throws IOException if read fails.
*/
@Override
public final int readUnsignedShort() throws IOException {
dis.readFully(work, 0, 2);
return ((work[1] & 0xff) << 8 | (work[0] & 0xff));
}
/**
* Skip over bytes in the stream. See the general contract of the
* <code>skipBytes</code> method of <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @param n the number of bytes to be skipped.
* @return the actual number of bytes skipped.
* @throws IOException if an I/O error occurs.
*/
@Override
public final int skipBytes(int n) throws IOException {
return dis.skipBytes(n);
}
}
================================================
FILE: AndResGuard-core/src/main/java/com/mindprod/ledatastream/LEDataOutputStream.java
================================================
/**
* Description:
* LEDataOutputStream.java Create on 2014-5-14
*
* @author shaowenzhang <shaowenzhang@tencent.com>
* @version 1.0
* Copyright (c) 2014 Tecent WXG AndroidTeam. All Rights Reserved.
*/
package com.mindprod.ledatastream;
import java.io.OutputStream;
public class LEDataOutputStream extends LittleEndianDataOutputStream {
public LEDataOutputStream(OutputStream out) {
super(out);
// TODO Auto-generated constructor stub
}
}
================================================
FILE: AndResGuard-core/src/main/java/com/mindprod/ledatastream/LittleEndianDataOutputStream.java
================================================
/**
* Description:
* LittleEndianDataOutputStream.java Create on 2014-5-14
*
* @author shaowenzhang <shaowenzhang@tencent.com>
* @version 1.0
* Copyright (c) 2014 Tecent WXG AndroidTeam. All Rights Reserved.
*/
package com.mindprod.ledatastream;
/**
* Copyright (C) 2007 The Guava Authors
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/***
* An implementation of {@link DataOutput} that uses little-endian byte ordering
* for writing {@code char}, {@code short}, {@code int}, {@code float}, {@code
* double}, and {@code long} values.
* <p>
* <b>Note:</b> This class intentionally violates the specification of its
* supertype {@code DataOutput}, which explicitly requires big-endian byte
* order.
*
* @author Chris Nokleberg
* @author Keith Bottner
* @since 8.0
*/
public class LittleEndianDataOutputStream extends FilterOutputStream implements DataOutput {
/***
* Creates a {@code LittleEndianDataOutputStream} that wraps the given stream.
*
* @param out the stream to delegate to
*/
public LittleEndianDataOutputStream(OutputStream out) {
super(new DataOutputStream(out));
}
/***
* Returns a big-endian representation of {@code value} in an 8-element byte
* array; equivalent to {@code ByteBuffer.allocate(8).putLong(value).array()}.
* For example, the input value {@code 0x1213141516171819L} would yield the
* byte array {@code {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19}}.
* @param value long
* @return byte array
*/
public static byte[] toByteArray(long value) {
// Note that this code needs to stay compatible with GWT, which has known
// bugs when narrowing byte casts of long values occur.
byte[] result = new byte[8];
for (int i = 7; i >= 0; i--) {
result[i] = (byte) (value & 0xffL);
value >>= 8;
}
return result;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
// Override slow FilterOutputStream impl
out.write(b, off, len);
}
@Override
public void writeBoolean(boolean v) throws IOException {
((DataOutputStream) out).writeBoolean(v);
}
@Override
public void writeByte(int v) throws IOException {
((DataOutputStream) out).writeByte(v);
}
/***
* @deprecated The semantics of {@code writeBytes(String s)} are considered
* dangerous. Please use {@link #writeUTF(String s)},
* {@link #writeChars(String s)} or another write method instead.
*/
@Deprecated
@Override
public void writeBytes(String s) throws IOException {
((DataOutputStream) out).writeBytes(s);
}
/***
* Writes a char as specified by {@link DataOutputStream#writeChar(int)},
* except using little-endian byte order.
*
* @throws IOException if an I/O error occurs
*/
@Override
public void writeChar(int v) throws IOException {
writeShort(v);
}
/***
* Writes a {@code String} as specified by
* {@link DataOutputStream#writeChars(String)}, except each character is
* written using little-endian byte order.
*
* @throws IOException if an I/O error occurs
*/
@Override
public void writeChars(String s) throws IOException {
for (int i = 0; i < s.length(); i++) {
writeChar(s.charAt(i));
}
}
/***
* Writes a {@code double} as specified by
* {@link DataOutputStream#writeDouble(double)}, except using little-endian
* byte order.
*
* @throws IOException if an I/O error occurs
*/
@Override
public void writeDouble(double v) throws IOException {
writeLong(Double.doubleToLongBits(v));
}
/***
* Writes a {@code float} as specified by
* {@link DataOutputStream#writeFloat(float)}, except using little-endian byte
* order.
*
* @throws IOException if an I/O error occurs
*/
@Override
public void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v));
}
/***
* Writes an {@code int} as specified by
* {@link DataOutputStream#writeInt(int)}, except using little-endian byte
* order.
*
* @throws IOException if an I/O error occurs
*/
@Override
public void writeInt(int v) throws IOException {
out.write(0xFF & v);
out.write(0xFF & (v >> 8));
out.write(0xFF & (v >> 16));
out.write(0xFF & (v >> 24));
}
/***
* Writes a {@code long} as specified by
* {@link DataOutputStream#writeLong(long)}, except using little-endian byte
* order.
*
* @throws IOException if an I/O error occurs
*/
@Override
public void writeLong(long v) throws IOException {
byte[] bytes = toByteArray(Long.reverseBytes(v));
write(bytes, 0, bytes.length);
}
/***
* Writes a {@code short} as specified by
* {@link DataOutputStream#writeShort(int)}, except using little-endian byte
* order.
*
* @throws IOException if an I/O error occurs
*/
@Override
public void writeShort(int v) throws IOException {
out.write(0xFF & v);
out.write(0xFF & (v >> 8));
}
@Override
public void writeUTF(String str) throws IOException {
((DataOutputStream) out).writeUTF(str);
}
}
================================================
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/AndrolibException.java
================================================
package com.tencent.mm.androlib;
/**
* @author shwenzhang
*/
public class AndrolibException extends Exception {
public AndrolibException() {
}
public AndrolibException(String message) {
super(message);
}
public AndrolibException(String message, Throwable cause) {
super(message, cause);
}
public AndrolibException(Throwable cause) {
super(cause);
}
}
================================================
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/ApkDecoder.java
================================================
package com.tencent.mm.androlib;
import com.tencent.mm.androlib.res.data.ResPackage;
import com.tencent.mm.androlib.res.decoder.ARSCDecoder;
import com.tencent.mm.androlib.res.decoder.RawARSCDecoder;
import com.tencent.mm.androlib.res.util.ExtFile;
import com.tencent.mm.directory.DirectoryException;
import com.tencent.mm.resourceproguard.Configuration;
import com.tencent.mm.util.FileOperation;
import com.tencent.mm.util.TypedValue;
import com.tencent.mm.util.Utils;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.regex.Pattern;
/**
* @author shwenzhang
*/
public class ApkDecoder {
final HashSet<Path> mRawResourceFiles = new HashSet<>();
private final Configuration config;
private final ExtFile apkFile;
private File mOutDir;
private File mOutTempARSCFile;
private File mOutARSCFile;
private File mOutResFile;
private File mRawResFile;
private File mOutTempDir;
private File mResMappingFile;
private File mMergeDuplicatedResMappingFile;
private HashMap<String, Integer> mCompressData;
public ApkDecoder(Configuration config, File apkFile) {
this.config = config;
this.apkFile = new ExtFile(apkFile);
}
private void copyOtherResFiles() throws IOException {
if (mRawResourceFiles.isEmpty()) {
return;
}
Path resPath = mRawResFile.toPath();
Path destPath = mOutResFile.toPath();
for (Path path : mRawResourceFiles) {
Path relativePath = resPath.relativize(path);
Path dest = destPath.resolve(relativePath);
System.out.printf("copy res file not in resources.arsc file:%s\n", relativePath.toString());
FileOperation.copyFileUsingStream(path.toFile(), dest.toFile());
}
}
public void removeCopiedResFile(Path key) {
mRawResourceFiles.remove(key);
}
public Configuration getConfig() {
return config;
}
public boolean hasResources() throws AndrolibException {
try {
return apkFile.getDirectory().containsFile("resources.arsc");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
private void ensureFilePath() throws IOException {
Utils.cleanDir(mOutDir);
String unZipDest = new File(mOutDir, TypedValue.UNZIP_FILE_PATH).getAbsolutePath();
System.out.printf("unziping apk to %s\n", unZipDest);
mCompressData = FileOperation.unZipAPk(apkFile.getAbsoluteFile().getAbsolutePath(), unZipDest);
dealWithCompressConfig();
//将res混淆成r
if (!config.mKeepRoot) {
mOutResFile = new File(mOutDir.getAbsolutePath() + File.separator + TypedValue.RES_FILE_PATH);
} else {
mOutResFile = new File(mOutDir.getAbsolutePath() + File.separator + "res");
}
//这个需要混淆各个文件夹
mRawResFile = new File(mOutDir.getAbsoluteFile().getAbsolutePath()
+ File.separator
+ TypedValue.UNZIP_FILE_PATH
+ File.separator
+ "res");
mOutTempDir = new File(mOutDir.getAbsoluteFile().getAbsolutePath() + File.separator + TypedValue.UNZIP_FILE_PATH);
//这里纪录原始res目录的文件
Files.walkFileTree(mRawResFile.toPath(), new ResourceFilesVisitor());
if (!mRawResFile.exists() || !mRawResFile.isDirectory()) {
throw new IOException("can not found res dir in the apk or it is not a dir");
}
mOutTempARSCFile = new File(mOutDir.getAbsoluteFile().getAbsolutePath() + File.separator + "resources_temp.arsc");
mOutARSCFile = new File(mOutDir.getAbsoluteFile().getAbsolutePath() + File.separator + "resources.arsc");
String basename = apkFile.getName().substring(0, apkFile.getName().indexOf(".apk"));
mResMappingFile = new File(mOutDir.getAbsoluteFile().getAbsolutePath()
+ File.separator
+ TypedValue.RES_MAPPING_FILE
+ basename
+ TypedValue.TXT_FILE);
mMergeDuplicatedResMappingFile = new File(mOutDir.getAbsoluteFile().getAbsolutePath()
+ File.separator
+ TypedValue.MERGE_DUPLICATED_RES_MAPPING_FILE
+ basename
+ TypedValue.TXT_FILE);
}
/**
* 根据config来修改压缩的值
*/
private void dealWithCompressConfig() {
if (config.mUseCompress) {
HashSet<Pattern> patterns = config.mCompressPatterns;
if (!patterns.isEmpty()) {
for (Entry<String, Integer> entry : mCompressData.entrySet()) {
String name = entry.getKey();
for (Iterator<Pattern> it = patterns.iterator(); it.hasNext(); ) {
Pattern p = it.next();
if (p.matcher(name).matches()) {
mCompressData.put(name, TypedValue.ZIP_DEFLATED);
}
}
}
}
}
}
public HashMap<String, Integer> getCompressData() {
return mCompressData;
}
public File getOutDir() {
return mOutDir;
}
public void setOutDir(File outDir) throws AndrolibException {
mOutDir = outDir;
}
public File getOutResFile() {
return mOutResFile;
}
public File getRawResFile() {
return mRawResFile;
}
public File getOutTempARSCFile() {
return mOutTempARSCFile;
}
public File getOutARSCFile() {
return mOutARSCFile;
}
public File getOutTempDir() {
return mOutTempDir;
}
public File getResMappingFile() {
return mResMappingFile;
}
public File getMergeDuplicatedResMappingFile() {
return mMergeDuplicatedResMappingFile;
}
public void decode() throws AndrolibException, IOException, DirectoryException {
if (hasResources()) {
ensureFilePath();
// read the resources.arsc checking for STORED vs DEFLATE compression
// this will determine whether we compress on rebuild or not.
System.out.printf("decoding resources.arsc\n");
RawARSCDecoder.decode(apkFile.getDirectory().getFileInput("resources.arsc"));
ResPackage[] pkgs = ARSCDecoder.decode(apkFile.getDirectory().getFileInput("resources.arsc"), this);
//把没有纪录在resources.arsc的资源文件也拷进dest目录
copyOtherResFiles();
ARSCDecoder.write(apkFile.getDirectory().getFileInput("resources.arsc"), this, pkgs);
}
}
class ResourceFilesVisitor extends SimpleFileVisitor<Path> {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
mRawResourceFiles.add(file);
return FileVisitResult.CONTINUE;
}
}
}
================================================
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/ResourceApkBuilder.java
================================================
package com.tencent.mm.androlib;
import com.tencent.mm.androlib.res.decoder.ARSCDecoder;
import com.tencent.mm.resourceproguard.Configuration;
import com.tencent.mm.resourceproguard.InputParam;
import com.tencent.mm.util.FileOperation;
import com.tencent.mm.util.TypedValue;
import com.tencent.mm.util.Utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.Key;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import apksigner.ApkSignerTool;
import static com.tencent.mm.resourceproguard.InputParam.SignatureType.SchemaV3;
/**
* @author shwenzhang
* modified:
* @author jonychina162
* 为了使用v2签名,引入了google v2sign 模块
* 由于使用v2签名,会对整个包除了签名块验证完整性,即除了签名块的内容在签名之后包其他内容不允许再改动,因此修改了原有的签名逻辑,
* 现有逻辑:1 zipalign 2.sign 。具体请参考buildApkV2sign
*/
public class ResourceApkBuilder {
private final Configuration config;
private File mOutDir;
private File m7zipOutPutDir;
private File mUnSignedApk;
private File mSignedApk;
private File mSignedWith7ZipApk;
private File m7ZipApk;
private File mAlignedApk;
private File mAlignedWith7ZipApk;
private String mApkName;
private File finalApkFile;
public ResourceApkBuilder(Configuration config) {
this.config = config;
}
public void setOutDir(File outDir, String apkName, File finalApkFile) throws AndrolibException {
this.mOutDir = outDir;
this.mApkName = apkName;
this.finalApkFile = finalApkFile;
}
public void buildApkWithV1sign(HashMap<String, Integer> compressData) throws IOException, InterruptedException {
insureFileNameV1();
generalUnsignApk(compressData);
signApkV1(mUnSignedApk, mSignedApk);
use7zApk(compressData, mSignedApk, mSignedWith7ZipApk);
alignApks();
copyFinalApkV1();
}
private void copyFinalApkV1() throws IOException {
if (finalApkFile != null) {
System.out.println(String.format("Backup Final APk(V1) to %s", finalApkFile));
if (mSignedWith7ZipApk.exists()) {
FileOperation.copyFileUsingStream(mAlignedWith7ZipApk, finalApkFile);
} else if (mSignedApk.exists()) {
FileOperation.copyFileUsingStream(mAlignedApk, finalApkFile);
}
}
}
public void buildApkWithV2V3Sign(HashMap<String, Integer> compressData, int minSDKVersion, InputParam.SignatureType signatureType) throws Exception {
insureFileNameV2();
generalUnsignApk(compressData);
if (use7zApk(compressData, mUnSignedApk, m7ZipApk)) {
alignApk(m7ZipApk, mAlignedApk);
} else {
alignApk(mUnSignedApk, mAlignedApk);
}
/*
* Caution: If you sign your app using APK Signature Scheme v2 and make further changes to the app,
* the app's signature is invalidated.
* For this reason, use tools such as zipalign before signing your app using APK Signature Scheme v2, not after.
**/
signApkV2V3(mAlignedApk, mSignedApk, minSDKVersion, signatureType);
copyFinalApkV2();
}
private void copyFinalApkV2() throws IOException {
if (mSignedApk.exists() && finalApkFile != null) {
System.out.println(String.format("Backup Final APk(V2) to %s", finalApkFile));
FileOperation.copyFileUsingStream(mSignedApk, finalApkFile);
}
}
private void insureFileNameV1() {
mUnSignedApk = new File(mOutDir.getAbsolutePath(), mApkName + "_unsigned.apk");
mSignedWith7ZipApk = new File(mOutDir.getAbsolutePath(), mApkName + "_signed_7zip.apk");
mSignedApk = new File(mOutDir.getAbsolutePath(), mApkName + "_signed.apk");
mAlignedApk = new File(mOutDir.getAbsolutePath(), mApkName + "_signed_aligned.apk");
mAlignedWith7ZipApk = new File(mOutDir.getAbsolutePath(), mApkName + "_signed_7zip_aligned.apk");
m7zipOutPutDir = new File(mOutDir.getAbsolutePath(), TypedValue.OUT_7ZIP_FILE_PATH);
}
private void insureFileNameV2() {
mUnSignedApk = new File(mOutDir.getAbsolutePath(), mApkName + "_unsigned.apk");
m7ZipApk = new File(mOutDir.getAbsolutePath(), mApkName + "_7zip_unsigned.apk");
if (config.mUse7zip) {
mAlignedApk = new File(mOutDir.getAbsolutePath(), mApkName + "_7zip_aligned_unsigned.apk");
mSignedApk = new File(mOutDir.getAbsolutePath(), mApkName + "_7zip_aligned_signed.apk");
} else {
mAlignedApk = new File(mOutDir.getAbsolutePath(), mApkName + "_aligned_unsigned.apk");
mSignedApk = new File(mOutDir.getAbsolutePath(), mApkName + "_aligned_signed.apk");
}
m7zipOutPutDir = new File(mOutDir.getAbsolutePath(), TypedValue.OUT_7ZIP_FILE_PATH);
}
private boolean use7zApk(HashMap<String, Integer> compressData, File originalAPK, File outputAPK)
throws IOException, InterruptedException {
if (!config.mUse7zip) {
return false;
}
if (!config.mUseSignAPK) {
throw new IOException("if you want to use 7z, you must enable useSign in the config file first");
}
if (!originalAPK.exists()) {
throw new IOException(String.format("can not found the signed apk file to 7z, if you want to use 7z, "
+ "you must fill the sign data in the config file path=%s",
originalAPK.getAbsolutePath()
));
}
System.out.printf("use 7zip to repackage: %s, will cost much more time\n", outputAPK.getName());
FileOperation.unZipAPk(originalAPK.getAbsolutePath(), m7zipOutPutDir.getAbsolutePath());
//首先一次性生成一个全部都是压缩的安装包
generalRaw7zip(outputAPK);
ArrayList<String> storedFiles = new ArrayList<>();
//对于不压缩的要update回去
for (String name : compressData.keySet()) {
File file = new File(m7zipOutPutDir.getAbsolutePath(), name);
if (!file.exists()) {
continue;
}
int method = compressData.get(name);
if (method == TypedValue.ZIP_STORED) {
storedFiles.add(name);
}
}
addStoredFileIn7Zip(storedFiles, outputAPK);
if (!outputAPK.exists()) {
throw new IOException(String.format(
"[use7zApk]7z repackage signed apk fail,you must install 7z command line version first, linux: p7zip, window: 7za, path=%s",
mSignedWith7ZipApk.getAbsolutePath()
));
}
return true;
}
private String getSignatureAlgorithm(String hash) throws Exception {
String signatureAlgorithm;
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream fileIn = new FileInputStream(config.mSignatureFile);
keyStore.load(fileIn, config.mStorePass.toCharArray());
Key key = keyStore.getKey(config.mStoreAlias, config.mKeyPass.toCharArray());
if (key == null) {
throw new RuntimeException("Can't get private key, please check if storepass storealias and keypass are correct");
}
String keyAlgorithm = key.getAlgorithm();
hash = formatHashAlgorithName(hash);
if (keyAlgorithm.equalsIgnoreCase("DSA")) {
keyAlgorithm = "DSA";
} else if (keyAlgorithm.equalsIgnoreCase("RSA")) {
keyAlgorithm = "RSA";
} else if (keyAlgorithm.equalsIgnoreCase("EC")) {
keyAlgorithm = "ECDSA";
} else {
throw new RuntimeException("private key is not a DSA or RSA key");
}
signatureAlgorithm = String.format("%swith%s", hash, keyAlgorithm);
return signatureAlgorithm;
}
private String formatHashAlgorithName(String hash) {
return hash.replace("-", "");
}
private void signApkV1(File unSignedApk, File signedApk) throws IOException, InterruptedException {
if (config.mUseSignAPK) {
System.out.printf("signing apk: %s\n", signedApk.getName());
if (signedApk.exists()) {
signedApk.delete();
}
signWithV1sign(unSignedApk, signedApk);
if (!signedApk.exists()) {
throw new IOException("Can't Generate signed APK. Plz check your v1sign info is correct.");
}
}
}
private void signApkV2V3(File unSignedApk, File signedApk, int minSDKVersion, InputParam.SignatureType signatureType) throws Exception {
if (config.mUseSignAPK) {
System.out.printf("signing apk: %s\n", signedApk.getName());
signWithV2V3Sign(unSignedApk, signedApk, minSDKVersion, signatureType);
if (!signedApk.exists()) {
throw new IOException("Can't Generate signed APK v2. Plz check your v2sign info is correct.");
}
}
}
private void signWithV2V3Sign(File unSignedApk, File signedApk, int minSDKVersion, InputParam.SignatureType signatureType) throws Exception {
String[] params = new String[] {
"sign",
"--ks",
config.mSignatureFile.getAbsolutePath(),
"--ks-pass",
"pass:" + config.mStorePass,
"--min-sdk-version",
String.valueOf(minSDKVersion),
"--ks-key-alias",
config.mStoreAlias,
"--key-pass",
"pass:" + config.mKeyPass,
"--v3-signing-enabled",
String.valueOf(signatureType == SchemaV3),
"--out",
signedApk.getAbsolutePath(),
unSignedApk.getAbsolutePath()
};
ApkSignerTool.main(params);
}
private void signWithV1sign(File unSignedApk, File signedApk) throws IOException, InterruptedException {
String signatureAlgorithm = "MD5withRSA";
try {
signatureAlgorithm = getSignatureAlgorithm(config.digestAlg);
} catch (Exception e) {
e.printStackTrace();
}
String[] argv = {
"jarsigner",
"-sigalg",
signatureAlgorithm,
"-digestalg",
config.digestAlg,
"-keystore",
config.mSignatureFile.getAbsolutePath(),
"-storepass",
config.mStorePass,
"-keypass",
config.mKeyPass,
"-signedjar",
signedApk.getAbsolutePath(),
unSignedApk.getAbsolutePath(),
config.mStoreAlias
};
Utils.runExec(argv);
}
private void alignApks() throws IOException, InterruptedException {
//如果不签名就肯定不需要对齐了
if (!config.mUseSignAPK) {
return;
}
if (!mSignedApk.exists() && !mSignedWith7ZipApk.exists()) {
throw new IOException("Can not found any signed apk file");
}
if (mSignedApk.exists()) {
alignApk(mSignedApk, mAlignedApk);
}
if (mSignedWith7ZipApk.exists()) {
alignApk(mSignedWith7ZipApk, mAlignedWith7ZipApk);
}
}
private void alignApk(File before, File after) throws IOException, InterruptedException {
System.out.printf("zipaligning apk: %s, exists:%b\n", before.getAbsolutePath(), before.exists());
if (!before.exists()) {
throw new IOException(String.format("can not found the raw apk file to zipalign, path=%s",
before.getAbsolutePath()
));
}
String cmd = Utils.isPresent(config.mZipalignPath) ? config.mZipalignPath : TypedValue.COMMAND_ZIPALIGIN;
Utils.runCmd(cmd, "4", before.getAbsolutePath(), after.getAbsolutePath());
if (!after.exists()) {
throw new IOException(String.format("can not found the aligned apk file, the ZipAlign path is correct? path=%s",
mAlignedApk.getAbsolutePath()
));
}
}
private void generalUnsignApk(HashMap<String, Integer> compressData) throws IOException, InterruptedException {
System.out.printf("General unsigned apk: %s\n", mUnSignedApk.getName());
File tempOutDir = new File(mOutDir.getAbsolutePath(), TypedValue.UNZIP_FILE_PATH);
if (!tempOutDir.exists()) {
System.err.printf("Missing apk unzip files, path=%s\n", tempOutDir.getAbsolutePath());
System.exit(-1);
}
File[] unzipFiles = tempOutDir.listFiles();
assert unzipFiles != null;
List<File> collectFiles = new ArrayList<>();
for (File f : unzipFiles) {
String name = f.getName();
if (name.equals("res") || name.equals("resources.arsc")) {
continue;
} else if (name.equals(config.mMetaName)) {
addNonSignatureFiles(collectFiles, f);
continue;
}
collectFiles.add(f);
}
File destResDir = new File(mOutDir.getAbsolutePath(), "res");
//添加修改后的res文件
if (!config.mKeepRoot && FileOperation.getlist(destResDir) == 0) {
destResDir = new File(mOutDir.getAbsolutePath(), TypedValue.RES_FILE_PATH);
}
/*
* NOTE:文件数量应该是一样的,如果不一样肯定有问题
*/
File rawResDir = new File(tempOutDir.getAbsolutePath() + File.separator + "res");
System.out.printf("DestResDir %d rawResDir %d\n",
FileOperation.getlist(destResDir),
FileOperation.getlist(rawResDir)
);
if (FileOperation.getlist(destResDir) != (FileOperation.getlist(rawResDir) - ARSCDecoder.mMergeDuplicatedResCount)) {
throw new IOException(String.format(
"the file count of %s, and the file count of %s is not equal, there must be some problem\n",
rawResDir.getAbsolutePath(),
destResDir.getAbsolutePath()
));
}
if (!destResDir.exists()) {
System.err.printf("Missing res files, path=%s\n", destResDir.getAbsolutePath());
System.exit(-1);
}
//这个需要检查混淆前混淆后,两个res的文件数量是否相等
collectFiles.add(destResDir);
File rawARSCFile = new File(mOutDir.getAbsolutePath() + File.separator + "resources.arsc");
if (!rawARSCFile.exists()) {
System.err.printf("Missing resources.arsc files, path=%s\n", rawARSCFile.getAbsolutePath());
System.exit(-1);
}
collectFiles.add(rawARSCFile);
FileOperation.zipFiles(collectFiles, tempOutDir, mUnSignedApk, compressData);
if (!mUnSignedApk.exists()) {
throw new IOException(String.format("can not found the unsign apk file path=%s", mUnSignedApk.getAbsolutePath()));
}
}
private void addNonSignatureFiles(List<File> collectFiles, File metaFolder) {
File[] metaFiles = metaFolder.listFiles();
if (metaFiles != null) {
for (File metaFile : metaFiles) {
String metaFileName = metaFile.getName();
// Ignore signature files
if (!metaFileName.endsWith(".MF") && !metaFileName.endsWith(".RSA") && !metaFileName.endsWith(".SF")) {
System.out.println(String.format("add meta file %s", metaFile.getAbsolutePath()));
collectFiles.add(metaFile);
}
}
}
}
private void addStoredFileIn7Zip(ArrayList<String> storedFiles, File outSevenZipAPK)
throws IOException, InterruptedException {
System.out.printf("[addStoredFileIn7Zip]rewrite the stored file into the 7zip, file count: %d\n",
storedFiles.size()
);
if (storedFiles.size() == 0) return;
String storedParentName = mOutDir.getAbsolutePath() + File.separator + "storefiles" + File.separator;
String outputName = m7zipOutPutDir.getAbsolutePath() + File.separator;
for (String name : storedFiles) {
FileOperation.copyFileUsingStream(new File(outputName + name), new File(storedParentName + name));
}
storedParentName = storedParentName + File.separator + "*";
String cmd = Utils.isPresent(config.m7zipPath) ? config.m7zipPath : TypedValue.COMMAND_7ZIP;
Utils.runCmd(cmd, "a", "-tzip", outSevenZipAPK.getAbsolutePath(), storedParentName, "-mx0");
}
private void generalRaw7zip(File outSevenZipApk) throws IOException, InterruptedException {
String outPath = m7zipOutPutDir.getAbsoluteFile().getAbsolutePath();
String path = outPath + File.separator + "*";
String cmd = Utils.isPresent(config.m7zipPath) ? config.m7zipPath : TypedValue.COMMAND_7ZIP;
Utils.runCmd(cmd, "a", "-tzip", outSevenZipApk.getAbsolutePath(), path, "-mx9");
}
}
================================================
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/ResourceRepackage.java
================================================
package com.tencent.mm.androlib;
import com.tencent.mm.util.FileOperation;
import com.tencent.mm.util.TypedValue;
import com.tencent.mm.util.Utils;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.HashMap;
public class ResourceRepackage {
private final String zipalignPath;
private final String sevenZipPath;
private File mSignedApk;
private File mSignedWith7ZipApk;
private File mAlignedWith7ZipApk;
private File m7zipOutPutDir;
private File mStoredOutPutDir;
private String mApkName;
private File mOutDir;
public ResourceRepackage(String zipalignPath, String zipPath, File signedFile) {
this.zipalignPath = zipalignPath;
this.sevenZipPath = zipPath;
mSignedApk = signedFile;
}
public void setOutDir(File outDir) {
mOutDir = outDir;
}
public void repackageApk() throws IOException, InterruptedException {
insureFileName();
repackageWith7z();
alignApk();
deleteUnusedFiles();
}
private void deleteUnusedFiles() {
//删除目录
FileOperation.deleteDir(m7zipOutPutDir);
FileOperation.deleteDir(mStoredOutPutDir);
if (mSignedWith7ZipApk.exists()) {
mSignedWith7ZipApk.delete();
}
}
/**
* 这边有点不太一样,就是当输出目录存在的时候是不会强制删除目录的
*
* @throws IOException
*/
private void insureFileName() throws IOException {
if (!mSignedApk.exists()) {
throw new IOException(String.format("can not found the signed apk file to repackage" + ", path=%s",
mSignedApk.getAbsolutePath()
));
}
//需要自己安装7zip
String apkBasename = mSignedApk.getName();
mApkName = apkBasename.substring(0, apkBasename.indexOf(".apk"));
//如果外面设过,就不用设了
if (mOutDir == null) {
mOutDir = new File(mSignedApk.getAbsoluteFile().getParent(), mApkName);
}
mSignedWith7ZipApk = new File(mOutDir.getAbsolutePath(), mApkName + "_channel_7zip.apk");
mAlignedWith7ZipApk = new File(mOutDir.getAbsolutePath(), mApkName + "_channel_7zip_aligned.apk");
m7zipOutPutDir = new File(mOutDir.getAbsolutePath(), TypedValue.OUT_7ZIP_FILE_PATH);
mStoredOutPutDir = new File(mOutDir.getAbsolutePath(), "storefiles");
//删除目录,因为之前的方法是把整个输出目录都删除,所以不会有问题,现在不会,所以要单独删
FileOperation.deleteDir(m7zipOutPutDir);
FileOperation.deleteDir(mStoredOutPutDir);
FileOperation.deleteDir(mSignedWith7ZipApk);
FileOperation.deleteDir(mAlignedWith7ZipApk);
}
private void repackageWith7z() throws IOException, InterruptedException {
System.out.printf("use 7zip to repackage: %s, will cost much more time\n", mSignedWith7ZipApk.getName());
HashMap<String, Integer> compressData = FileOperation.unZipAPk(mSignedApk.getAbsolutePath(),
m7zipOutPutDir.getAbsolutePath()
);
//首先一次性生成一个全部都是压缩的安装包
generalRaw7zip();
ArrayList<String> storedFiles = new ArrayList<>();
//对于不压缩的要update回去
for (String name : compressData.keySet()) {
File file = new File(m7zipOutPutDir.getAbsolutePath(), name);
if (!file.exists()) {
continue;
}
int method = compressData.get(name);
if (method == TypedValue.ZIP_STORED) {
storedFiles.add(name);
}
}
addStoredFileIn7Zip(storedFiles);
if (!mSignedWith7ZipApk.exists()) {
throw new IOException(String.format(
"[repackageWith7z]7z repackage signed apk fail,you must install 7z command line version first, linux: p7zip, window: 7za, path=%s",
mSignedWith7ZipApk.getAbsolutePath()
));
}
}
private void generalRaw7zip() throws IOException, InterruptedException {
System.out.printf("general the raw 7zip file\n");
String outPath = m7zipOutPutDir.getAbsoluteFile().getAbsolutePath();
String path = outPath + File.separator + "*";
String cmd = Utils.isPresent(sevenZipPath) ? sevenZipPath : TypedValue.COMMAND_7ZIP;
ProcessBuilder pb = new ProcessBuilder(cmd, "a", "-tzip", mSignedWith7ZipApk.getAbsolutePath(), path, "-mx9");
Process pro = pb.start();
InputStreamReader ir = new InputStreamReader(pro.getInputStream());
LineNumberReader input = new LineNumberReader(ir);
//如果不读会有问题,被阻塞
while (input.readLine() != null) {
}
//destroy the stream
pro.waitFor();
pro.destroy();
}
private void addStoredFileIn7Zip(ArrayList<String> storedFiles) throws IOException, InterruptedException {
System.out.printf("[addStoredFileIn7Zip]rewrite the stored file into the 7zip, file count:%d\n",
storedFiles.size()
);
String storedParentName = mStoredOutPutDir.getAbsolutePath() + File.separator;
String outputName = m7zipOutPutDir.getAbsolutePath() + File.separator;
for (String name : storedFiles) {
FileOperation.copyFileUsingStream(new File(outputName + name), new File(storedParentName + name));
}
storedParentName = storedParentName + File.separator + "*";
//极限压缩
String cmd = Utils.isPresent(sevenZipPath) ? sevenZipPath : TypedValue.COMMAND_7ZIP;
ProcessBuilder pb = new ProcessBuilder(cmd,
"a",
"-tzip",
mSignedWith7ZipApk.getAbsolutePath(),
storedParentName,
"-mx0"
);
Process pro = pb.start();
InputStreamReader ir = new InputStreamReader(pro.getInputStream());
LineNumberReader input = new LineNumberReader(ir);
//如果不读会有问题,被阻塞
while (input.readLine() != null) {
}
//destroy the stream
pro.waitFor();
pro.destroy();
}
private void alignApk() throws IOException, InterruptedException {
if (mSignedWith7ZipApk.exists()) {
alignApk(mSignedWith7ZipApk, mAlignedWith7ZipApk);
}
}
private void alignApk(File before, File after) throws IOException, InterruptedException {
System.out.printf("zipaligning apk: %s\n", before.getName());
if (!before.exists()) {
throw new IOException(String.format("can not found the raw apk file to zipalign, path=%s",
before.getAbsolutePath()
));
}
String cmd = Utils.isPresent(zipalignPath) ? zipalignPath : TypedValue.COMMAND_ZIPALIGIN;
ProcessBuilder pb = new ProcessBuilder(cmd, "4", before.getAbsolutePath(), after.getAbsolutePath());
Process pro = pb.start();
//destroy the stream
pro.waitFor();
pro.destroy();
}
}
================================================
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/data/ResID.java
================================================
/**
* Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright 2016 sim sun <sunsj1231@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.mm.androlib.res.data;
public class ResID {
public final int package_;
public final int type;
public final int entry;
public final int id;
public ResID(int package_, int type, int entry) {
this(package_, type, entry, (package_ << 24) + (type << 16) + entry);
}
public ResID(int id) {
this((id >> 24) & 0xff, (id >> 16) & 0x000000ff, id & 0x0000ffff, id);
}
public ResID(int package_, int type, int entry, int id) {
this.package_ = package_;
this.type = type;
this.entry = entry;
this.id = id;
}
@Override
public String toString() {
return String.format("0x%08x", id);
}
@Override
public int hashCode() {
int hash = 17;
hash = 31 * hash + this.id;
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ResID other = (ResID) obj;
if (this.id != other.id) {
return false;
}
return true;
}
}
================================================
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/data/ResPackage.java
================================================
/**
* Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright 2016 sim sun <sunsj1231@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.mm.androlib.res.data;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class ResPackage {
private final String mName;
private final Map<Integer, String> mSpecNamesReplace;
private final Map<String, Set<String>> mSpecNamesBlock;
private boolean mCanProguard = false;
public ResPackage(int id, String name) {
this.mName = name;
mSpecNamesReplace = new LinkedHashMap<>();
mSpecNamesBlock = new LinkedHashMap<>();
}
public boolean isCanResguard() {
return mCanProguard;
}
public void setCanResguard(boolean set) {
mCanProguard = set;
}
public boolean hasSpecRepplace(String resID) {
return mSpecNamesReplace.containsKey(resID);
}
public String getSpecRepplace(int resID) {
return mSpecNamesReplace.get(resID);
}
public void putSpecNamesReplace(int resID, String value) {
mSpecNamesReplace.put(resID, value);
}
public void putSpecNamesblock(String specName, String value) {
Set<String> values = mSpecNamesBlock.get(specName);
if (values == null) {
values = new HashSet<>();
mSpecNamesBlock.put(specName, values);
}
values.add(value);
}
public Map<String, Set<String>> getSpecNamesBlock() {
return mSpecNamesBlock;
}
public String getName() {
return mName;
}
@Override
public String toString() {
return mName;
}
}
================================================
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/data/ResType.java
================================================
/**
* Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright 2016 sim sun <sunsj1231@gmail.com>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.mm.androlib.res.data;
import com.tencent.mm.androlib.AndrolibException;
import java.util.HashSet;
public final class ResType {
private final String mName;
private final ResPackage mPackage;
private final HashSet<String> specNames;
public ResType(String name, ResPackage package_) {
this.mName = name;
this.mPackage = package_;
specNames = new HashSet<>();
}
public String getName() {
return mName;
}
public void putSpecResguardName(String name) throws AndrolibException {
if (specNames.contains(name)) {
throw new AndrolibException(String.format(
"spec proguard name duplicate in a singal type %s, spec name: %s\n",
getName(),
name
));
}
specNames.add(name);
}
@Override
public String toString() {
return mName;
}
}
================================================
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/decoder/ARSCDecoder.java
================================================
/**
* Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright 2016 sim sun <sunsj1231@gmail.com>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.mm.androlib.res.decoder;
import com.mindprod.ledatastream.LEDataInputStream;
import com.mindprod.ledatastream.LEDataOutputStream;
import com.tencent.mm.androlib.AndrolibException;
import com.tencent.mm.androlib.ApkDecoder;
import com.tencent.mm.androlib.res.data.ResPackage;
import com.tencent.mm.androlib.res.data.ResType;
import com.tencent.mm.androlib.res.util.StringUtil;
import com.tencent.mm.resourceproguard.Configuration;
import com.tencent.mm.util.ExtDataInput;
import com.tencent.mm.util.ExtDataOutput;
import com.tencent.mm.util.FileOperation;
import com.tencent.mm.util.Md5Util;
import com.tencent.mm.util.TypedValue;
import com.tencent.mm.util.Utils;
import java.io.BufferedWriter;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Pattern;
public class ARSCDecoder {
private final static boolean DEBUG = false;
private final static short ENTRY_FLAG_COMPLEX = 0x0001;
private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName());
private static final int KNOWN_CONFIG_BYTES = 56;
public static Map<Integer, String> mTableStringsResguard = new LinkedHashMap<>();
public static int mMergeDuplicatedResCount = 0;
private final Map<String, String> mOldFileName;
private final Map<String, Integer> mCurSpecNameToPos;
private final HashSet<String> mShouldResguardTypeSet;
private final ApkDecoder mApkDecoder;
private ExtDataInput mIn;
private ExtDataOutput mOut;
private Header mHeader;
private StringBlock mTableStrings;
private StringBlock mTypeNames;
private StringBlock mSpecNames;
private ResPackage mPkg;
private ResType mType;
private ResPackage[] mPkgs;
private int[] mPkgsLenghtChange;
private int mTableLenghtChange = 0;
private int mResId;
private int mCurrTypeID = -1;
private int mCurEntryID = -1;
private int mCurPackageID = -1;
private long mMergeDuplicatedResTotalSize = 0L;
private ResguardStringBuilder mResguardBuilder;
private boolean mShouldResguardForType = false;
private Writer mMappingWriter;
private Writer mMergeDuplicatedResMappingWriter;
private Map<Long,List<MergeDuplicatedResInfo>> mMergeDuplicatedResInfoData = new HashMap<>();
private ARSCDecoder(InputStream arscStream, ApkDecoder decoder) throws AndrolibException, IOException {
mOldFileName = new LinkedHashMap<>();
mCurSpecNameToPos = new LinkedHashMap<>();
mShouldResguardTypeSet = new HashSet<>();
mIn = new ExtDataInput(new LEDataInputStream(arscStream));
mApkDecoder = decoder;
proguardFileName();
}
private ARSCDecoder(InputStream arscStream, ApkDecoder decoder, ResPackage[] pkgs) throws FileNotFoundException {
mOldFileName = new LinkedHashMap<>();
mCurSpecNameToPos = new LinkedHashMap<>();
mShouldResguardTypeSet = new HashSet<>();
mApkDecoder = decoder;
mIn = new ExtDataInput(new LEDataInputStream(arscStream));
mOut = new ExtDataOutput(new LEDataOutputStream(new FileOutputStream(mApkDecoder.getOutTempARSCFile(), false)));
mPkgs = pkgs;
mPkgsLenghtChange = new int[pkgs.length];
}
public static ResPackage[] decode(InputStream arscStream, ApkDecoder apkDecoder) throws AndrolibException {
try {
ARSCDecoder decoder = new ARSCDecoder(arscStream, apkDecoder);
ResPackage[] pkgs = decoder.readTable();
return pkgs;
} catch (IOException ex) {
throw new AndrolibException("Could not decode arsc file", ex);
}
}
public static void write(InputStream arscStream, ApkDecoder decoder, ResPackage[] pkgs) throws AndrolibException {
try {
ARSCDecoder writer = new ARSCDecoder(arscStream, decoder, pkgs);
writer.writeTable();
} catch (IOException ex) {
throw new AndrolibException("Could not decode arsc file", ex);
}
}
private void proguardFileName() throws IOException, AndrolibException {
mMappingWriter = new BufferedWriter(new FileWriter(mApkDecoder.getResMappingFile(), false));
mMergeDuplicatedResMappingWriter = new BufferedWriter(new FileWriter(mApkDecoder.getMergeDuplicatedResMappingFile(), false));
mMergeDuplicatedResMappingWriter.write("res filter path mapping:\n");
mMergeDuplicatedResMappingWriter.flush();
mResguardBuilder = new ResguardStringBuilder();
mResguardBuilder.reset(null);
final Configuration config = mApkDecoder.getConfig();
File rawResFile = mApkDecoder.getRawResFile();
File[] resFiles = rawResFile.listFiles();
// 需要看看哪些类型是要混淆文件路径的
for (File resFile : resFiles) {
String raw = resFile.getName();
if (raw.contains("-")) {
raw = raw.substring(0, raw.indexOf("-"));
}
mShouldResguardTypeSet.add(raw);
}
if (!config.mKeepRoot) {
// 需要保持之前的命名方式
if (config.mUseKeepMapping) {
HashMap<String, String> fileMapping = config.mOldFileMapping;
List<String> keepFileNames = new ArrayList<>();
// 这里面为了兼容以前,也需要用以前的文件名前缀,即res混淆成什么
String resRoot = TypedValue.RES_FILE_PATH;
for (String name : fileMapping.values()) {
int dot = name.indexOf("/");
if (dot == -1) {
throw new IOException(String.format("the old mapping res file path should be like r/a, yours %s\n", name));
}
resRoot = name.substring(0, dot);
keepFileNames.add(name.substring(dot + 1));
}
// 去掉所有之前保留的命名,为了简单操作,mapping里面有的都去掉
mResguardBuilder.removeStrings(keepFileNames);
for (File resFile : resFiles) {
String raw = "res" + "/" + resFile.getName();
if (fileMapping.containsKey(raw)) {
mOldFileName.put(raw, fileMapping.get(raw));
} else {
mOldFileName.put(raw, resRoot + "/" + mResguardBuilder.getReplaceString());
}
}
} else {
for (int i = 0; i < resFiles.length; i++) {
// 这里也要用linux的分隔符,如果普通的话,就是r
mOldFileName.put("res" + "/" + resFiles[i].getName(),
TypedValue.RES_FILE_PATH + "/" + mResguardBuilder.getReplaceString()
);
}
}
generalFileResMapping();
}
Utils.cleanDir(mApkDecoder.getOutResFile());
}
private ResPackage[] readTable() throws IOException, AndrolibException {
nextChunkCheckType(Header.TYPE_TABLE);
int packageCount = mIn.readInt();
mTableStrings = StringBlock.read(mIn);
ResPackage[] packages = new ResPackage[packageCount];
nextChunk();
for (int i = 0; i < packageCount; i++) {
packages[i] = readPackage();
}
mMappingWriter.close();
System.out.printf("resources mapping file %s done\n", mApkDecoder.getResMappingFile().getAbsolutePath());
generalFilterEnd(mMergeDuplicatedResCount, mMergeDuplicatedResTotalSize);
mMergeDuplicatedResMappingWriter.close();
System.out.printf("resources filter mapping file %s done\n", mApkDecoder.getMergeDuplicatedResMappingFile().getAbsolutePath());
return packages;
}
private void writeTable() throws IOException, AndrolibException {
System.out.printf("writing new resources.arsc \n");
mTableLenghtChange = 0;
writeNextChunkCheck(Header.TYPE_TABLE, 0);
int packageCount = mIn.readInt();
mOut.writeInt(packageCount);
mTableLenghtChange += StringBlock.writeTableNameStringBlock(mIn, mOut, mTableStringsResguard);
writeNextChunk(0);
if (packageCount != mPkgs.length) {
throw new AndrolibException(String.format("writeTable package count is different before %d, now %d",
mPkgs.length,
packageCount
));
}
for (int i = 0; i < packageCount; i++) {
mCurPackageID = i;
writePackage();
}
// 最后需要把整个的size重写回去
reWriteTable();
}
private void generalFileResMapping() throws IOException {
mMappingWriter.write("res path mapping:\n");
for (String raw : mOldFileName.keySet()) {
mMappingWriter.write(" " + raw + " -> " + mOldFileName.get(raw));
mMappingWriter.write("\n");
}
mMappingWriter.write("\n\n");
mMappingWriter.write("res id mapping:\n");
mMappingWriter.flush();
}
private void generalResIDMapping(
String packageName, String typename, String specName, String replace) throws IOException {
mMappingWriter.write(" "
+ packageName
+ ".R."
+ typename
+ "."
+ specName
+ " -> "
+ packageName
+ ".R."
+ typename
+ "."
+ replace);
mMappingWriter.write("\n");
mMappingWriter.flush();
}
private void generalFilterResIDMapping(
String originalFile, String original, String replaceFile, String replace, long fileLen) throws IOException {
mMergeDuplicatedResMappingWriter.write(" "
+ originalFile
+ " : "
+ original
+ " -> "
+ replaceFile
+ " : "
+ replace
+ " (size:"
+ getNetFileSizeDescription(fileLen)
+ ")");
mMergeDuplicatedResMappingWriter.write("\n");
mMergeDuplicatedResMappingWriter.flush();
}
private void generalFilterEnd(int count, long totalSize) throws IOException {
mMergeDuplicatedResMappingWriter.write(
"removed: count(" + count
+ "), totalSize(" + getNetFileSizeDescription(totalSize) + ")");
mMergeDuplicatedResMappingWriter.flush();
}
private static String getNetFileSizeDescription(long size) {
StringBuilder bytes = new StringBuilder();
DecimalFormat format = new DecimalFormat("###.0");
if (size >= 1024 * 1024 * 1024) {
double i = (size / (1024.0 * 1024.0 * 1024.0));
bytes.append(format.format(i)).append("GB");
} else if (size >= 1024 * 1024) {
double i = (size / (1024.0 * 1024.0));
bytes.append(format.format(i)).append("MB");
} else if (size >= 1024) {
double i = (size / (1024.0));
bytes.append(format.format(i)).append("KB");
} else {
if (size <= 0) {
bytes.append("0B");
} else {
bytes.append((int) size).append("B");
}
}
return bytes.toString();
}
private void reWriteTable() throws AndrolibException, IOException {
mIn = new ExtDataInput(new LEDataInputStream(new FileInputStream(mApkDecoder.getOutTempARSCFile())));
mOut = new ExtDataOutput(new LEDataOutputStream(new FileOutputStream(mApkDecoder.getOutARSCFile(), false)));
writeNextChunkCheck(Header.TYPE_TABLE, mTableLenghtChange);
int packageCount = mIn.readInt();
mOut.writeInt(packageCount);
StringBlock.writeAll(mIn, mOut);
for (int i = 0; i < packageCount; i++) {
mCurPackageID = i;
writeNextChunk(mPkgsLenghtChange[mCurPackageID]);
mOut.writeBytes(mIn, mHeader.chunkSize - 8);
}
mApkDecoder.getOutTempARSCFile().delete();
}
private ResPackage readPackage() throws IOException, AndrolibException {
checkChunkType(Header.TYPE_PACKAGE);
int id = (byte) mIn.readInt();
String name = mIn.readNullEndedString(128, true);
System.out.printf("reading packagename %s\n", name);
/* typeNameStrings */
mIn.skipInt();
/* typeNameCount */
mIn.skipInt();
/* specNameStrings */
mIn.skipInt();
/* specNameCount */
mIn.skipInt();
mCurrTypeID = -1;
mTypeNames = StringBlock.read(mIn);
mSpecNames = StringBlock.read(mIn);
mResId = id << 24;
mPkg = new ResPackage(id, name);
// 系统包名不混淆
if (mPkg.getName().equals("android")) {
mPkg.setCanResguard(false);
} else {
mPkg.setCanResguard(true);
}
nextChunk();
while (mHeader.type == Header.TYPE_LIBRARY) {
readLibraryType();
}
while (mHeader.type == Header.TYPE_SPEC_TYPE) {
readTableTypeSpec();
}
return mPkg;
}
private void writePackage() throws IOException, AndrolibException {
checkChunkType(Header.TYPE_PACKAGE);
int id = (byte) mIn.readInt();
mOut.writeInt(id);
mResId = id << 24;
//char_16的,一共256byte
mOut.writeBytes(mIn, 256);
/* typeNameStrings */
mOut.writeInt(mIn.readInt());
/* typeNameCount */
mOut.writeInt(mIn.readInt());
/* specNameStrings */
mOut.writeInt(mIn.readInt());
/* specNameCount */
mOut.writeInt(mIn.readInt());
StringBlock.writeAll(mIn, mOut);
if (mPkgs[mCurPackageID].isCanResguard()) {
int specSizeChange = StringBlock.writeSpecNameStringBlock(mIn,
mOut,
mPkgs[mCurPackageID].getSpecNamesBlock(),
mCurSpecNameToPos
);
mPkgsLenghtChange[mCurPackageID] += specSizeChange;
mTableLenghtChange += specSizeChange;
} else {
StringBlock.writeAll(mIn, mOut);
}
writeNextChunk(0);
while (mHeader.type == Header.TYPE_LIBRARY) {
writeLibraryType();
}
while (mHeader.type == Header.TYPE_SPEC_TYPE) {
writeTableTypeSpec();
}
}
/**
* 如果是保持mapping的话,需要去掉某部分已经用过的mapping
*/
private void reduceFromOldMappingFile() {
if (mPkg.isCanResguard()) {
if (mApkDecoder.getConfig().mUseKeepMapping) {
// 判断是否走keepmapping
HashMap<String, HashMap<String, HashMap<String, String>>> resMapping = mApkDecoder.getConfig().mOldResMapping;
String packName = mPkg.getName();
if (resMapping.containsKey(packName)) {
HashMap<String, HashMap<String, String>> typeMaps = resMapping.get(packName);
String typeName = mType.getName();
if (typeMaps.containsKey(typeName)) {
HashMap<String, String> proguard = typeMaps.get(typeName);
// 去掉所有之前保留的命名,为了简单操作,mapping里面有的都去掉
mResguardBuilder.removeStrings(proguard.values());
}
}
}
}
}
private HashSet<Pattern> getWhiteList(String resType) {
final String packName = mPkg.getName();
if (mApkDecoder.getConfig().mWhiteList.containsKey(packName)) {
if (mApkDecoder.getConfig().mUseWhiteList) {
HashMap<String, HashSet<Pattern>> typeMaps = mApkDecoder.getConfig().mWhiteList.get(packName);
return typeMaps.get(resType);
}
}
return null;
}
private void readLibraryType() throws AndrolibException, IOException {
checkChunkType(Header.TYPE_LIBRARY);
int libraryCount = mIn.readInt();
int packageId;
String packageName;
for (int i = 0; i < libraryCount; i++) {
packageId = mIn.readInt();
packageName = mIn.readNullEndedString(128, true);
System.out.printf("Decoding Shared Library (%s), pkgId: %d\n", packageName, packageId);
}
while (nextChunk().type == Header.TYPE_TYPE) {
readTableTypeSpec();
}
}
private void readTableTypeSpec() throws AndrolibException, IOException {
checkChunkType(Header.TYPE_SPEC_TYPE);
byte id = mIn.readByte();
mIn.skipBytes(3);
int entryCount = mIn.readInt();
mType = new ResType(mTypeNames.getString(id - 1), mPkg);
if (DEBUG) {
System.out.printf("[ReadTableType] type (%s) id: (%d) curr (%d)\n", mType, id, mCurrTypeID);
}
// first meet a type of resource
if (mCurrTypeID != id) {
mCurrTypeID = id;
initResGuardBuild(mCurrTypeID);
}
// 是否混淆文件路径
mShouldResguardForType = isToResguardFile(mTypeNames.getString(id - 1));
// 对,这里是用来描述差异性的!!!
mIn.skipBytes(entryCount * 4);
mResId = (0xff000000 & mResId) | id << 16;
while (nextChunk().type == Header.TYPE_TYPE) {
readConfig();
}
}
private void initResGuardBuild(int resTypeId) {
// we need remove string from resguard candidate list if it exists in white list
HashSet<Pattern> whiteListPatterns = getWhiteList(mType.getName());
// init resguard builder
mResguardBuilder.reset(whiteListPatterns);
mResguardBuilder.removeStrings(RawARSCDecoder.getExistTypeSpecNameStrings(resTypeId));
// 如果是保持mapping的话,需要去掉某部分已经用过的mapping
reduceFromOldMappingFile();
}
private void writeLibraryType() throws AndrolibException, IOException {
checkChunkType(Header.TYPE_LIBRARY);
int libraryCount = mIn.readInt();
mOut.writeInt(libraryCount);
for (int i = 0; i < libraryCount; i++) {
mOut.writeInt(mIn.readInt());/*packageId*/
mOut.writeBytes(mIn, 256); /*packageName*/
}
writeNextChunk(0);
while (mHeader.type == Header.TYPE_TYPE) {
writeTableTypeSpec();
}
}
private void writeTableTypeSpec() throws AndrolibException, IOException {
checkChunkType(Header.TYPE_SPEC_TYPE);
byte id = mIn.readByte();
mOut.writeByte(id);
mResId = (0xff000000 & mResId) | id << 16;
mOut.writeBytes(mIn, 3);
int entryCount = mIn.readInt();
mOut.writeInt(entryCount);
// 对,这里是用来描述差异性的!!!
///* flags */mIn.skipBytes(entryCount * 4);
int[] entryOffsets = mIn.readIntArray(entryCount);
mOut.writeIntArray(entryOffsets);
while (writeNextChunk(0).type == Header.TYPE_TYPE) {
writeConfig();
}
}
private void readConfig() throws IOException, AndrolibException {
checkChunkType(Header.TYPE_TYPE);
/* typeId */
mIn.skipInt();
int entryCount = mIn.readInt();
int entriesStart = mIn.readInt();
readConfigFlags();
int[] entryOffsets = mIn.readIntArray(entryCount);
for (int i = 0; i < entryOffsets.length; i++) {
mCurEntryID = i;
if (entryOffsets[i] != -1) {
mResId = (mResId & 0xffff0000) | i;
readEntry();
}
}
}
private void writeConfig() throws IOException, AndrolibException {
checkChunkType(Header.TYPE_TYPE);
/* typeId */
mOut.writeInt(mIn.readInt());
/* entryCount */
int entryCount = mIn.readInt();
mOut.writeInt(entryCount);
/* entriesStart */
mOut.writeInt(mIn.readInt());
writeConfigFlags();
int[] entryOffsets = mIn.readIntArray(entryCount);
mOut.writeIntArray(entryOffsets);
for (int i = 0; i < entryOffsets.length; i++) {
if (entryOffsets[i] != -1) {
mResId = (mResId & 0xffff0000) | i;
writeEntry();
}
}
}
private void readEntry() throws IOException, AndrolibException {
mIn.skipBytes(2);
short flags = mIn.readShort();
int specNamesId = mIn.readInt();
if (mPkg.isCanResguard()) {
// 混淆过或者已经添加到白名单的都不需要再处理了
if (!mResguardBuilder.isReplaced(mCurEntryID) && !mResguardBuilder.isInWhiteList(mCurEntryID)) {
Configuration config = mApkDecoder.getConfig();
boolean isWhiteList = false;
if (config.mUseWhiteList) {
isWhiteList = dealWithWhiteList(specNamesId, config);
}
if (!isWhiteList) {
dealWithNonWhiteList(specNamesId, config);
}
}
}
if ((flags & ENTRY_FLAG_COMPLEX) == 0) {
readValue(true, specNamesId);
} else {
readComplexEntry(false, specNamesId);
}
}
/**
* deal with whitelist
*
* @param specNamesId resource spec name id
* @param config {@Configuration} AndResGuard configuration
* @return isWhiteList whether this resource is processed by whitelist
*/
private boolean dealWithWhiteList(int specNamesId, Configuration config) throws AndrolibException {
String packName = mPkg.getName();
if (config.mWhiteList.containsKey(packName)) {
HashMap<String, HashSet<Pattern>> typeMaps = config.mWhiteList.get(packName);
String typeName = mType.getName();
if (typeMaps.containsKey(typeName)) {
String specName = mSpecNames.get(specNamesId).toString();
HashSet<Pattern> patterns = typeMaps.get(typeName);
for (Iterator<Pattern> it = patterns.iterator(); it.hasNext(); ) {
Pattern p = it.next();
if (p.matcher(specName).matches()) {
if (DEBUG) {
System.out.printf("[match] matcher %s ,typeName %s, specName :%s\n", p.pattern(), typeName, specName);
}
mPkg.putSpecNamesReplace(mResId, specName);
mPkg.putSpecNamesblock(specName, specName);
mResguardBuilder.setInWhiteList(mCurEntryID);
mType.putSpecResguardName(specName);
return true;
}
}
}
}
return false;
}
private void dealWithNonWhiteList(int specNamesId, Configuration config) throws AndrolibException, IOException {
String replaceString = null;
boolean keepMapping = false;
if (config.mUseKeepMapping) {
String packName = mPkg.getName();
if (config.mOldResMapping.containsKey(packName)) {
HashMap<String, HashMap<String, String>> typeMaps = config.mOldResMapping.get(packName);
String typeName = mType.getName();
if (typeMaps.containsKey(typeName)) {
HashMap<String, String> nameMap = typeMaps.get(typeName);
String specName = mSpecNames.get(specNamesId).toString();
if (nameMap.containsKey(specName)) {
keepMapping = true;
replaceString = nameMap.get(specName);
}
}
}
}
if (!keepMapping) {
replaceString = mResguardBuilder.getReplaceString();
}
mResguardBuilder.setInReplaceList(mCurEntryID);
if (replaceString == null) {
throw new AndrolibException("readEntry replaceString == null");
}
generalResIDMapping(mPkg.getName(), mType.getName(), mSpecNames.get(specNamesId).toString(), replaceString);
mPkg.putSpecNamesReplace(mResId, replaceString);
// arsc name列混淆成固定名字, 减少string pool大小
boolean useFixedName = config.mFixedResName != null && config.mFixedResName.length() > 0;
String fixedName = useFixedName ? config.mFixedResName : replaceString;
mPkg.putSpecNamesblock(fixedName, replaceString);
mType.putSpecResguardName(replaceString);
}
private void writeEntry() throws IOException, AndrolibException {
/* size */
mOut.writeBytes(mIn, 2);
short flags = mIn.readShort();
mOut.writeShort(flags);
int specNamesId = mIn.readInt();
ResPackage pkg = mPkgs[mCurPackageID];
if (pkg.isCanResguard()) {
specNamesId = mCurSpecNameToPos.get(pkg.getSpecRepplace(mResId));
if (specNamesId < 0) {
throw new AndrolibException(String.format("writeEntry new specNamesId < 0 %d", specNamesId));
}
}
mOut.writeInt(specNamesId);
if ((flags & ENTRY_FLAG_COMPLEX) == 0) {
writeValue();
} else {
writeComplexEntry();
}
}
/**
* @param flags whether read direct
*/
private void readComplexEntry(boolean flags, int specNamesId) throws IOException, AndrolibException {
int parent = mIn.readInt();
int count = mIn.readInt();
for (int i = 0; i < count; i++) {
mIn.readInt();
readValue(flags, specNamesId);
}
}
private void writeComplexEntry() throws IOException, AndrolibException {
mOut.writeInt(mIn.readInt());
int count = mIn.readInt();
mOut.writeInt(count);
for (int i = 0; i < count; i++) {
mOut.writeInt(mIn.readInt());
writeValue();
}
}
/**
* @param flags whether read direct
*/
private void readValue(boolean flags, int specNamesId) throws IOException, AndrolibException {
/* size */
mIn.skipCheckShort((short) 8);
/* zero */
mIn.skipCheckByte((byte) 0);
byte type = mIn.readByte();
int data = mIn.readInt();
//这里面有几个限制,一对于string ,id, array我们是知道肯定不用改的,第二看要那个type是否对应有文件路径
if (mPkg.isCanResguard()
&& flags
&& type == TypedValue.TYPE_STRING
&& mShouldResguardForType
&& mShouldResguardTypeSet.contains(mType.getName())) {
if (mTableStringsResguard.get(data) == null) {
String raw = mTableStrings.get(data).toString();
if (StringUtil.isBlank(raw) || raw.equalsIgnoreCase("null")) return;
String proguard = mPkg.getSpecRepplace(mResId);
//这个要写死这个,因为resources.arsc里面就是用这个
int secondSlash = raw.lastIndexOf("/");
if (secondSlash == -1) {
throw new AndrolibException(String.format("can not find \\ or raw string in res path = %s", raw));
}
String newFilePath = raw.substring(0, secondSlash);
if (!mApkDecoder.getConfig().mKeepRoot) {
newFilePath = mOldFileName.get(raw.substring(0, secondSlash));
}
if (newFilePath == null) {
System.err.printf("can not found new res path, raw=%s\n", raw);
return;
}
//同理这里不能用File.separator,因为resources.arsc里面就是用这个
String result = newFilePath + "/" + proguard;
int firstDot = raw.indexOf(".");
if (firstDot != -1) {
result += raw.substring(firstDot);
}
String compatibaleraw = new String(raw);
String compatibaleresult = new String(result);
//为了适配window要做一次转换
if (!File.separator.contains("/")) {
compatibaleresult = compatibaleresult.replace("/", File.separator);
compatibaleraw = compatibaleraw.replace("/", File.separator);
}
File resRawFile = new File(mApkDecoder.getOutTempDir().getAbsolutePath() + File.separator + compatibaleraw);
File resDestFile = new File(mApkDecoder.getOutDir().getAbsolutePath() + File.separator + compatibaleresult);
MergeDuplicatedResInfo filterInfo = null;
boolean mergeDuplicatedRes = mApkDecoder.getConfig().mMergeDuplicatedRes;
if (mergeDuplicatedRes) {
filterInfo = mergeDuplicated(resRawFile, resDestFile, compatibaleraw, result);
if (filterInfo != null) {
resDestFile = new File(filterInfo.filePath);
result = filterInfo.fileName;
}
}
//这里用的是linux的分隔符
HashMap<String, Integer> compressData = mApkDecoder.getCompressData();
if (compressData.containsKey(raw)) {
compressData.put(result, compressData.get(raw));
} else {
System.err.printf("can not find the compress dataresFile=%s\n", raw);
}
if (!resRawFile.exists()) {
System.err.printf("can not find res file, you delete it? path: resFile=%s\n", resRawFile.getAbsolutePath());
} else {
if (!mergeDuplicatedRes && resDestFile.exists()) {
throw new AndrolibException(String.format("res dest file is already found: destFile=%s",
resDestFile.getAbsolutePath()
));
}
if (filterInfo == null) {
FileOperation.copyFileUsingStream(resRawFile, resDestFile);
}
//already copied
mApkDecoder.removeCopiedResFile(resRawFile.toPath());
mTableStringsResguard.put(data, result);
}
}
}
}
/**
* resource filtering, filtering duplicate resources, reducing the volume of apk
*/
private MergeDuplicatedResInfo mergeDuplicated(File resRawFile, File resDestFile, String compatibaleraw, String result) throws IOException {
MergeDuplicatedResInfo filterInfo = null;
List<MergeDuplicatedResInfo> mergeDuplicatedResInfoList = mMergeDuplicatedResInfoData.get(resRawFile.length());
if (mergeDuplicatedResInfoList != null) {
for (MergeDuplicatedResInfo mergeDuplicatedResInfo : mergeDuplicatedResInfoList) {
if (mergeDuplicatedResInfo.md5 == null) {
mergeDuplicatedResInfo.md5 = Md5Util.getMD5Str(new File(mergeDuplicatedResInfo.filePath));
}
String resRawFileMd5 = Md5Util.getMD5Str(resRawFile);
if (!resRawFileMd5.isEmpty() && resRawFileMd5.equals(mergeDuplicatedResInfo.md5)) {
filterInfo = mergeDuplicatedResInfo;
filterInfo.md5 = resRawFileMd5;
break;
}
}
}
if (filterInfo != null) {
generalFilterResIDMapping(compatibaleraw, result, filterInfo.originalName, filterInfo.fileName, resRawFile.length());
mMergeDuplicatedResCount++;
mMergeDuplicatedResTotalSize += resRawFile.length();
} else {
MergeDuplicatedResInfo info = new MergeDuplicatedResInfo.Builder()
.setFileName(result)
.setFilePath(resDestFile.getAbsolutePath())
.setOriginalName(compatibaleraw)
.create();
info.fileName = result;
info.filePath = resDestFile.getAbsolutePath();
info.originalName = compatibaleraw;
if (mergeDuplicatedResInfoList == null) {
mergeDuplicatedResInfoList = new ArrayList<>();
mMergeDuplicatedResInfoData.put(resRawFile.length(), mergeDuplicatedResInfoList);
}
mergeDuplicatedResInfoList.add(info);
}
return filterInfo;
}
private void writeValue() throws IOException, AndrolibException {
/* size */
mOut.writeCheckShort(mIn.readShort(), (short) 8);
/* zero */
mOut.writeCheckByte(mIn.readByte(), (byte) 0);
byte type = mIn.readByte();
mOut.writeByte(type);
int data = mIn.readInt();
mOut.writeInt(data);
}
private void readConfigFlags() throws IOException, AndrolibException {
int size = mIn.readInt();
int read = 28;
if (size < 28) {
throw new AndrolibException("Config size < 28");
}
boolean isInvalid = false;
short mcc = mIn.readShort();
short mnc = mIn.readShort();
char[] language = new char[] { (char) mIn.readByte(), (char) mIn.readByte() };
char[] country = new char[] { (char) mIn.readByte(), (char) mIn.readByte() };
byte orientation = mIn.readByte();
byte touchscreen = mIn.readByte();
int density = mIn.readUnsignedShort();
byte keyboard = mIn.readByte();
byte navigation = mIn.readByte();
byte inputFlags = mIn.readByte();
/* inputPad0 */
mIn.skipBytes(1);
short screenWidth = mIn.readShort();
short screenHeight = mIn.readShort();
short sdkVersion = mIn.readShort();
/* minorVersion, now must always be 0 */
mIn.skipBytes(2);
byte screenLayout = 0;
byte uiMode = 0;
short smallestScreenWidthDp = 0;
if (size >= 32) {
screenLayout = mIn.readByte();
uiMode = mIn.readByte();
smallestScreenWidthDp = mIn.readShort();
read = 32;
}
short screenWidthDp = 0;
short screenHeightDp = 0;
if (size >= 36) {
screenWidthDp = mIn.readShort();
screenHeightDp = mIn.readShort();
read = 36;
}
char[] localeScript = null;
char[] localeVariant = null;
if (size >= 48) {
localeScript = readScriptOrVariantChar(4).toCharArray();
localeVariant = readScriptOrVariantChar(8).toCharArray();
read = 48;
}
byte screenLayout2 = 0;
if (size >= 52) {
screenLayout2 = mIn.readByte();
mIn.skipBytes(3); // reserved padding
read = 52;
}
if (size >= 56) {
mIn.skipBytes(4);
read = 56;
}
int exceedingSize = size - KNOWN_CONFIG_BYTES;
if (exceedingSize > 0) {
byte[] buf = new byte[exceedingSize];
read += exceedingSize;
mIn.readFully(buf);
BigInteger exceedingBI = new BigInteger(1, buf);
if (exceedingBI.equals(BigInteger.ZERO)) {
LOGGER.fine(String.format("Config flags size > %d, but exceeding bytes are all zero, so it should be ok.",
KNOWN_CONFIG_BYTES
));
} else {
LOGGER.warning(String.format("Config flags size > %d. Exceeding bytes: 0x%X.",
KNOWN_CONFIG_BYTES,
exceedingBI
));
isInvalid = true;
}
}
}
private String readScriptOrVariantChar(int length) throws AndrolibException, IOException {
StringBuilder string = new StringBuilder(16);
while (length-- != 0) {
short ch = mIn.readByte();
if (ch == 0) {
break;
}
string.append((char) ch);
}
mIn.skipBytes(length);
return string.toString();
}
private void writeConfigFlags() throws IOException, AndrolibException {
//总的有多大
int size = mIn.readInt();
if (size < 28) {
throw new AndrolibException("Config size < 28");
}
mOut.writeInt(size);
mOut.writeBytes(mIn, size - 4);
}
private Header nextChunk() throws IOException {
return mHeader = Header.read(mIn);
}
private void checkChunkType(int expectedType) throws AndrolibException {
if (mHeader.type != expectedType) {
throw new AndrolibException(String.format("Invalid chunk type: expected=0x%08x, got=0x%08x",
expectedType,
mHeader.type
));
}
}
private void nextChunkCheckType(int expectedType) throws IOException, AndrolibException {
nextChunk();
checkChunkType(expectedType);
}
private Header writeNextChunk(int diffSize) throws IOException, AndrolibException {
mHeader = Header.readAndWriteHeader(mIn, mOut, diffSize);
return mHeader;
}
private Header writeNextChunkCheck(int expectedType, int diffSize) throws IOException, AndrolibException {
mHeader = Header.readAndWriteHeader(mIn, mOut, diffSize);
if (mHeader.type != expectedType) {
throw new AndrolibException(String.format("Invalid chunk type: expected=%d, got=%d", expectedType, mHeader.type));
}
return mHeader;
}
/**
* 为了加速,不需要处理string,id,array,这几个是肯定不是的
*/
private boolean isToResguardFile(String name) {
return (!name.equals("string") && !name.equals("id") && !name.equals("array"));
}
public static class Header {
public final static short TYPE_NONE = -1, TYPE_TABLE = 0x0002, TYPE_PACKAGE = 0x0200, TYPE_TYPE = 0x0201,
TYPE_SPEC_TYPE = 0x0202, TYPE_LIBRARY = 0x0203;
public final short type;
public final int chunkSize;
public Header(short type, int size) {
this.type = type;
this.chunkSize = size;
}
public static Header read(ExtDataInput in) throws IOException {
short type;
try {
type = in.readShort();
short count = in.readShort();
int size = in.readInt();
return new Header(type, size);
} catch (EOFException ex) {
return new Header(TYPE_NONE, 0);
}
}
public static Header readAndWriteHeader(ExtDataInput in, ExtDataOutput out, int diffSize)
throws IOException, AndrolibException {
short type;
int size;
try {
type = in.readShort();
out.writeShort(type);
short count = in.readShort();
out.writeShort(count);
size = in.readInt();
size -= diffSize;
if (size <= 0) {
throw new AndrolibException(String.format("readAndWriteHeader size < 0: size=%d", size));
}
out.writeInt(size);
} catch (EOFException ex) {
return new Header(TYPE_NONE, 0);
}
return new Header(type, size);
}
}
public static class FlagsOffset {
public final int offset;
public final int count;
public FlagsOffset(int offset, int count) {
this.offset = offset;
this.count = count;
}
}
private static class MergeDuplicatedResInfo {
private String fileName;
private String filePath;
private String originalName;
private String md5;
private MergeDuplicatedResInfo(String fileName, String filePath, String originalName, String md5) {
this.fileName = fileName;
this.filePath = filePath;
this.originalName = originalName;
this.md5 = md5;
}
static class Builder {
private String fileName;
private String filePath;
private String originalName;
private String md5;
Builder setFileName(String fileName) {
this.fileName = fileName;
return this;
}
Builder setFilePath(String filePath) {
this.filePath = filePath;
return this;
}
public Builder setMd5(String md5) {
this.md5 = md5;
return this;
}
Builder setOriginalName(String originalName) {
this.originalName = originalName;
return this;
}
MergeDuplicatedResInfo create() {
return new MergeDuplicatedResInfo(fileName, filePath, originalName, md5);
}
}
}
private class ResguardStringBuilder {
private final List<String> mReplaceStringBuffer;
private final Set<Integer> mIsReplaced;
private final Set<Integer> mIsWhiteList;
private String[] mAToZ = {
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z"
};
private String[] mAToAll = {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "_", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
};
/**
* 在window上面有些关键字是不能作为文件名的
* CON, PRN, AUX, CLOCK$, NUL
* COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9
* LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9.
*/
private HashSet<String> mFileNameBlackList;
public ResguardStringBuilder() {
mFileNameBlackList = new HashSet<>();
mFileNameBlackList.add("con");
mFileNameBlackList.add("prn");
mFileNameBlackList.add("aux");
mFileNameBlackList.add("nul");
mReplaceStringBuffer = new ArrayList<>();
mIsReplaced = new HashSet<>();
mIsWhiteList = new HashSet<>();
}
public void reset(HashSet<Pattern> blacklistPatterns) {
mReplaceStringBuffer.clear();
mIsReplaced.clear();
mIsWhiteList.clear();
for (int i = 0; i < mAToZ.length; i++) {
String str = mAToZ[i];
if (!Utils.match(str, blacklistPatterns)) {
mReplaceStringBuffer.add(str);
}
}
for (int i = 0; i < mAToZ.length; i++) {
String first = mAToZ[i];
for (int j = 0; j < mAToAll.length; j++) {
String str = first + mAToAll[j];
if (!Utils.match(str, blacklistPatterns)) {
mReplaceStringBuffer.add(str);
}
}
}
for (int i = 0; i < mAToZ.length; i++) {
String first = mAToZ[i];
for (int j = 0; j < mAToAll.length; j++) {
String second = mAToAll[j];
for (int k = 0; k < mAToAll.length; k++) {
String third = mAToAll[k];
String str = first + second + third;
if (!mFileNameBlackList.contains(str) && !Utils.match(str, blacklistPatterns)) {
mReplaceStringBuffer.add(str);
}
}
}
}
}
// 对于某种类型用过的mapping,全部不能再用了
public void removeStrings(Collection<String> collection) {
if (collection == null) return;
mReplaceStringBuffer.removeAll(collection);
}
public boolean isReplaced(int id) {
return mIsReplaced.contains(id);
}
public boolean isInWhiteList(int id) {
return mIsWhiteList.contains(id);
}
public void setInWhiteList(int id) {
mIsWhiteList.add(id);
}
public void setInReplaceList(int id) {
mIsReplaced.add(id);
}
public String getReplaceString() throws AndrolibException {
if (mReplaceStringBuffer.isEmpty()) {
throw new AndrolibException(String.format("now can only proguard less than 35594 in a single type\n"));
}
return mReplaceStringBuffer.remove(0);
}
}
}
================================================
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/decoder/RawARSCDecoder.java
================================================
/**
* Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright 2016 sim sun <sunsj1231@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.mm.androlib.res.decoder;
import com.mindprod.ledatastream.LEDataInputStream;
import com.tencent.mm.androlib.AndrolibException;
import com.tencent.mm.androlib.res.data.ResPackage;
import com.tencent.mm.androlib.res.data.ResType;
import com.tencent.mm.util.ExtDataInput;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import org.apache.commons.io.input.CountingInputStream;
/**
* 其实应该是原来有,并且在白名单里面的才去掉!现在没有判断是否在白名单中
*
* @author shwenzhang
*/
public class RawARSCDecoder {
private final static short ENTRY_FLAG_COMPLEX = 0x0001;
private final static short ENTRY_FLAG_PUBLIC = 0x0002;
private final static short ENTRY_FLAG_WEAK = 0x0004;
private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName());
private static final int KNOWN_CONFIG_BYTES = 64;
private static HashMap<Integer, Set<String>> mExistTypeNames;
private final CountingInputStream mCountIn;
private ExtDataInput mIn;
private Header mHeader;
private StringBlock mTypeNames;
private StringBlock mSpecNames;
private ResPackage mPkg;
private ResType mType;
private int mTypeIdOffset = 0;
private int mCurTypeID = -1;
private ResPackage[] mPkgs;
private int mResId;
private RawARSCDecoder(InputStream arscStream) throws AndrolibException, IOException {
arscStream = mCountIn = new CountingInputStream(arscStream);
mIn = new ExtDataInput(new LEDataInputStream(arscStream));
mExistTypeNames = new HashMap<>();
}
public static ResPackage[] decode(InputStream arscStream) throws AndrolibException {
try {
RawARSCDecoder decoder = new RawARSCDecoder(arscStream);
System.out.printf("parse to get the exist names in the resouces.arsc first\n");
return decoder.readTable();
} catch (IOException ex) {
throw new AndrolibException("Could not decode arsc file", ex);
}
}
public static Set<String> getExistTypeSpecNameStrings(int type) {
return mExistTypeNames.get(type);
}
private ResPackage[] readTable() throws IOException, AndrolibException {
nextChunkCheckType(Header.TYPE_TABLE);
int packageCount = mIn.readInt();
StringBlock.read(mIn);
ResPackage[] packages = new ResPackage[packageCount];
nextChunk();
for (int i = 0; i < packageCount; i++) {
packages[i] = readTablePackage();
}
return packages;
}
private ResPackage readTablePackage() throws IOException, AndrolibException {
checkChunkType(Header.TYPE_PACKAGE);
int id = mIn.readInt();
String name = mIn.readNullEndedString(128, true);
/* typeNameStrings */
mIn.skipInt();
/* typeNameCount */
mIn.skipInt();
/* specNameStrings */
mIn.skipInt();
/* specNameCount */
mIn.skipInt();
// TypeIdOffset was added platform_frameworks_base/@f90f2f8dc36e7243b85e0b6a7fd5a590893c827e
// which is only in split/new applications.
int splitHeaderSize = (2 + 2 + 4 + 4 + (2 * 128) + (4 * 5)); // short, short, int, int, char[128], int * 4
if (mHeader.headerSize == splitHeaderSize) {
mTypeIdOffset = mIn.readInt();
}
mTypeNames = StringBlock.read(mIn);
mSpecNames = StringBlock.read(mIn);
mResId = id << 24;
mPkg = new ResPackage(id, name);
nextChunk();
while (mHeader.type == Header.TYPE_LIBRARY) {
readLibraryType();
}
while (mHeader.type == Header.TYPE_SPEC_TYPE) {
readTableTypeSpec();
}
return mPkg;
}
private void readLibraryType() throws AndrolibException, IOException {
checkChunkType(Header.TYPE_LIBRARY);
int libraryCount = mIn.readInt();
int packageId;
String packageName;
for (int i = 0; i < libraryCount; i++) {
packageId = mIn.readInt();
packageName = mIn.readNullEndedString(128, true);
System.out.printf("Decoding Shared Library (%s), pkgId: %d\n", packageName, packageId);
}
nextChunk();
while (mHeader.type == Header.TYPE_TYPE) {
readTableTypeSpec();
}
}
private void readTableTypeSpec() throws AndrolibException, IOException {
readSingleTableTypeSpec();
nextChunk();
while (mHeader.type == Header.TYPE_SPEC_TYPE) {
readSingleTableTypeSpec();
nextChunk();
}
while (mHeader.type == Header.TYPE_TYPE) {
readConfig();
nextChunk();
}
}
private void readSingleTableTypeSpec() throws AndrolibException, IOException {
checkChunkType(Header.TYPE_SPEC_TYPE);
int id = mIn.readUnsignedByte();
mIn.skipBytes(3);
int entryCount = mIn.readInt();
/* flags */
mIn.skipBytes(entryCount * 4);
mCurTypeID = id;
mResId = (0xff000000 & mResId) | id << 16;
mType = new ResType(mTypeNames.getString(id - 1), mPkg);
}
private void readConfig() throws IOException, AndrolibException {
checkChunkType(Header.TYPE_TYPE);
int typeId = mIn.readUnsignedByte() - mTypeIdOffset;
int typeFlags = mIn.readByte();
/* reserved */
mIn.skipBytes(2);
int entryCount = mIn.readInt();
int entriesStart = mIn.readInt();
readConfigFlags();
int[] entryOffsets = mIn.readIntArray(entryCount);
for (int i = 0; i < entryOffsets.length; i++) {
if (entryOffsets[i] != -1) {
mResId = (mResId & 0xffff0000) | i;
readEntry();
}
}
}
/**
* 需要防止由于某些非常恶心的白名单,导致出现重复id
*
* @throws IOException
* @throws AndrolibException
*/
private void readEntry() throws IOException, AndrolibException {
/* size */
mIn.skipBytes(2);
short flags = mIn.readShort();
int specNamesId = mIn.readInt();
putTypeSpecNameStrings(mCurTypeID, mSpecNames.getString(specNamesId));
boolean readDirect = false;
if ((flags & ENTRY_FLAG_COMPLEX) == 0) {
readDirect = true;
readValue(readDirect, specNamesId);
} else {
readDirect = false;
readComplexEntry(readDirect, specNamesId);
}
}
private void readComplexEntry(boolean flags, int specNamesId) throws IOException, AndrolibException {
int parent = mIn.readInt();
int count = mIn.readInt();
for (int i = 0; i < count; i++) {
mIn.readInt();
readValue(flags, specNamesId);
}
}
private void readValue(boolean flags, int specNamesId) throws IOException, AndrolibException {
/* size */
mIn.skipCheckShort((short) 8);
/* zero */
mIn.skipCheckByte((byte) 0);
byte type = mIn.readByte();
int data = mIn.readInt();
}
private void readConfigFlags() throws IOException, AndrolibException {
int read = 28;
int size = mIn.readInt();
if (size < 28) {
throw new AndrolibException("Config size < 28");
}
boolean isInvalid = false;
short mcc = mIn.readShort();
short mnc = mIn.readShort();
char[] language = new char[] { (char) mIn.readByte(), (char) mIn.readByte() };
char[] country = new char[] { (char) mIn.readByte(), (char) mIn.readByte() };
byte orientation = mIn.readByte();
byte touchscreen = mIn.readByte();
int density = mIn.readUnsignedShort();
byte keyboard = mIn.readByte();
byte navigation = mIn.readByte();
byte inputFlags = mIn.readByte();
/* inputPad0 */
mIn.skipBytes(1);
short screenWidth = mIn.readShort();
short screenHeight = mIn.readShort();
short sdkVersion = mIn.readShort();
/* minorVersion, now must always be 0 */
mIn.skipBytes(2);
byte screenLayout = 0;
byte uiMode = 0;
short smallestScreenWidthDp = 0;
if (size >= 32) {
screenLayout = mIn.readByte();
uiMode = mIn.readByte();
smallestScreenWidthDp = mIn.readShort();
read = 32;
}
short screenWidthDp = 0;
short screenHeightDp = 0;
if (size >= 36) {
screenWidthDp = mIn.readShort();
screenHeightDp = mIn.readShort();
read = 36;
}
char[] localeScript = null;
char[] localeVariant = null;
if (size >= 48) {
localeScript = readScriptOrVariantChar(4).toCharArray();
localeVariant = readScriptOrVariantChar(8).toCharArray();
read = 48;
}
byte screenLayout2 = 0;
if (size >= 52) {
screenLayout2 = mIn.readByte();
mIn.skipBytes(3); // reserved padding
read = 52;
}
if (size >= 56) {
mIn.skipBytes(4);
read = 56;
}
if (size >= 64) {
mIn.skipBytes(8);
read = 64;
}
int exceedingSize = size - KNOWN_CONFIG_BYTES;
if (exceedingSize > 0) {
byte[] buf = new byte[exceedingSize];
mIn.readFully(buf);
BigInteger exceedingBI = new BigInteger(1, buf);
if (exceedingBI.equals(BigInteger.ZERO)) {
LOGGER.fine(String.format("Config flags size > %d, but exceeding bytes are all zero, so it should be ok.",
KNOWN_CONFIG_BYTES
));
} else {
LOGGER.warning(String.format("Config flags size > %d. Exceeding bytes: 0x%X.",
KNOWN_CONFIG_BYTES,
exceedingBI
));
}
} else {
int remainingSize = size - read;
if (remainingSize > 0) {
mIn.skipBytes(remainingSize);
}
}
}
private String readScriptOrVariantChar(int length) throws AndrolibException, IOException {
StringBuilder string = new StringBuilder(16);
while (length-- != 0) {
short ch = mIn.readByte();
if (ch == 0) {
break;
}
string.append((char) ch);
}
mIn.skipBytes(length);
return string.toString();
}
private Header nextChunk() throws IOException {
return mHeader = Header.read(mIn, mCountIn);
}
private void checkChunkType(int expectedType) throws AndrolibException {
if (mHeader.type != expectedType) {
throw new AndrolibException(String.format("Invalid chunk type: expected=0x%08x, got=0x%08x",
expectedType,
mHeader.type
));
}
}
private void nextChunkCheckType(int expectedType) throws IOException, AndrolibException {
nextChunk();
checkChunkType(expectedType);
}
private void putTypeSpecNameStrings(int type, String name) {
Set<String> names = mExistTypeNames.get(type);
if (names == null) {
names = new HashSet<>();
}
names.add(name);
mExistTypeNames.put(type, names);
}
public static class Header {
public final static short TYPE_NONE = -1;
public final static short TYPE_TABLE = 0x0002;
public final static short TYPE_PACKAGE = 0x0200;
public final static short TYPE_TYPE = 0x0201;
public final static short TYPE_SPEC_TYPE = 0x0202;
public final static short TYPE_LIBRARY = 0x0203;
public final short type;
public final int headerSize;
public final int chunkSize;
public final int startPosition;
public final int endPosition;
public Header(short type, int headerSize, int chunkSize, int headerStart) {
this.type = type;
this.headerSize = headerSize;
this.chunkSize = chunkSize;
this.startPosition = headerStart;
this.endPosition = headerStart + chunkSize;
}
public static Header read(ExtDataInput in, CountingInputStream countIn) throws IOException {
short type;
int start = countIn.getCount();
try {
type = in.readShort();
} catch (EOFException ex) {
return new Header(TYPE_NONE, 0, 0, countIn.getCount());
}
return new Header(type, in.readShort(), in.readInt(), start);
}
}
public static class FlagsOffset {
public final int offset;
public final int count;
public FlagsOffset(int offset, int count) {
this.offset = offset;
this.count = count;
}
}
}
================================================
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/decoder/StringBlock.java
================================================
/**
* Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright 2016 sim sun <sunsj1231@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.mm.androlib.res.decoder;
import com.tencent.mm.androlib.AndrolibException;
import com.tencent.mm.util.ExtDataInput;
import com.tencent.mm.util.ExtDataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author shwenzhang
*/
public class StringBlock {
private static final CharsetDecoder UTF16LE_DECODER = Charset.forName("UTF-16LE").newDecoder();
private static final CharsetDecoder UTF8_DECODER = Charset.forName("UTF-8").newDecoder();
private static final Logger LOGGER = Logger.getLogger(StringBlock.class.getName());
// ResChunk_header = header.type (0x0001) + header.headerSize (0x001C)
private static final int CHUNK_STRINGPOOL_TYPE = 0x001C0001;
private static final int UTF8_FLAG = 0x00000100;
private static final int CHUNK_NULL_TYPE = 0x00000000;
private static final byte NULL = 0;
private int[] m_stringOffsets;
private byte[] m_strings;
private int[] m_styleOffsets;
private int[] m_styles;
private boolean m_isUTF8;
private int[] m_stringOwns;
private StringBlock() {
}
/**
* Reads whole (including chunk type) string block from stream. Stream must
* be at the chunk type.
*
* @param reader reader
* @return stringblock
* @throws IOException ioexcetpion
*/
public static StringBlock read(ExtDataInput reader) throws IOException {
reader.skipCheckChunkTypeInt(CHUNK_STRINGPOOL_TYPE, CHUNK_NULL_TYPE);
int chunkSize = reader.readInt();
int stringCount = reader.readInt();
int styleCount = reader.readInt();
int flags = reader.readInt();
int stringsOffset = reader.readInt();
int stylesOffset = reader.readInt();
StringBlock block = new StringBlock();
block.m_isUTF8 = (flags & UTF8_FLAG) != 0;
block.m_stringOffsets = reader.readIntArray(stringCount);
block.m_stringOwns = new int[stringCount];
Arrays.fill(block.m_stringOwns, -1);
if (styleCount != 0) {
block.m_styleOffsets = reader.readIntArray(styleCount);
}
{
int size = ((stylesOffset == 0) ? chunkSize : stylesOffset) - stringsOffset;
if ((size % 4) != 0) {
throw new IOException("String data size is not multiple of 4 (" + size + ").");
}
block.m_strings = new byte[size];
reader.readFully(block.m_strings);
}
if (stylesOffset != 0) {
int size = (chunkSize - stylesOffset);
if ((size % 4) != 0) {
throw new IOException("Style data size is not multiple of 4 (" + size + ").");
}
block.m_styles = reader.readIntArray(size / 4);
}
return block;
}
public static int writeSpecNameStringBlock(
ExtDataInput reader, ExtDataOutput out, Map<String, Set<String>> specNames, Map<String, Integer> curSpecNameToPos)
throws IOException, AndrolibException {
int type = reader.readInt();
int chunkSize = reader.readInt();
int stringCount = reader.readInt();
int styleOffsetCount = reader.readInt();
if (styleOffsetCount != 0) {
throw new AndrolibException(String.format("writeSpecNameStringBlock styleOffsetCount != 0 styleOffsetCount %d",
styleOffsetCount
));
}
int flags = reader.readInt();
boolean isUTF8 = (flags & UTF8_FLAG) != 0;
int stringsOffset = reader.readInt();
int stylesOffset = reader.readInt();
reader.readIntArray(stringCount);
int size = ((stylesOffset == 0) ? chunkSize : stylesOffset) - stringsOffset;
if ((size % 4) != 0) {
throw new IOException("String data size is not multiple of 4 (" + size + ").");
}
byte[] temp_strings = new byte[size];
reader.readFully(temp_strings);
int totalSize = 0;
out.writeCheckInt(type, CHUNK_STRINGPOOL_TYPE);
totalSize += 4;
stringCount = specNames.keySet().size();
System.out.println("String pool size: " + stringCount);
totalSize += 6 * 4 + 4 * stringCount;
stringsOffset = totalSize;
int[] stringOffsets = new int[stringCount];
// make twice size buffer for avoiding out of bounds error
byte[] stringBytes = new byte[size * 2];
int offset = 0;
int i = 0;
curSpecNameToPos.clear();
for (Iterator<String> it = specNames.keySet().iterator(); it.hasNext(); ) {
stringOffsets[i] = offset;
String name = it.next();
for (String specName : specNames.get(name)) {
// N res entry item point to one string constant
curSpecNameToPos.put(specName, i);
}
if (isUTF8) {
stringBytes[offset++] = (byte) name.length();
stringBytes[offset++] = (byte) name.length();
totalSize += 2;
byte[] tempByte = name.getBytes(Charset.forName("UTF-8"));
if (name.length() != tempByte.length) {
throw new AndrolibException(String.format(
"writeSpecNameStringBlock %s UTF-8 length is different name %d, tempByte %d\n",
name,
name.length(),
tempByte.length
));
}
System.arraycopy(tempByte, 0, stringBytes, offset, tempByte.length);
offset += name.length();
stringBytes[offset++] = NULL;
totalSize += name.length() + 1;
} else {
writeShort(stringBytes, offset, (short) name.length());
offset += 2;
totalSize += 2;
byte[] tempByte = name.getBytes(Charset.forName("UTF-16LE"));
if ((name.length() * 2) != tempByte.length) {
throw new AndrolibException(String.format(
"writeSpecNameStringBlock %s UTF-16LE length is different name %d, tempByte %d\n",
name,
name.length(),
tempByte.length
));
}
System.arraycopy(tempByte, 0, stringBytes, offset, tempByte.length);
offset += tempByte.length;
stringBytes[offset++] = NULL;
stringBytes[offset++] = NULL;
totalSize += tempByte.length + 2;
}
i++;
}
//要保证string size 是4的倍数,要补零
size = totalSize - stringsOffset;
if ((size % 4) != 0) {
int add = 4 - (size % 4);
for (i = 0; i < add; i++) {
stringBytes[offset++] = NULL;
totalSize++;
}
}
out.writeInt(totalSize);
out.writeInt(stringCount);
out.writeInt(styleOffsetCount);
out.writeInt(flags);
out.writeInt(stringsOffset);
out.writeInt(stylesOffset);
out.writeIntArray(stringOffsets);
out.write(stringBytes, 0, offset);
return (chunkSize - totalSize);
}
public static int writeTableNameStringBlock(
ExtDataInput reader, ExtDataOutput out, Map<Integer, String> tableProguardMap)
throws IOException, AndrolibException {
int type = reader.readInt();
int chunkSize = reader.readInt();
int stringCount = reader.readInt();
int styleOffsetCount = reader.readInt();
int flags = reader.readInt();
int stringsOf
gitextract_al65wnr7/
├── .gitignore
├── .travis.yml
├── AndResGuard-cli/
│ ├── build.gradle
│ └── src/
│ └── main/
│ └── java/
│ └── com/
│ └── tencent/
│ └── mm/
│ └── resourceproguard/
│ └── cli/
│ └── CliMain.java
├── AndResGuard-core/
│ ├── build.gradle
│ ├── gradle.properties
│ └── src/
│ └── main/
│ └── java/
│ ├── apksigner/
│ │ ├── ApkSignerTool.java
│ │ ├── HexEncoding.java
│ │ ├── OptionsParser.java
│ │ ├── PasswordRetriever.java
│ │ ├── help.txt
│ │ ├── help_sign.txt
│ │ └── help_verify.txt
│ └── com/
│ ├── mindprod/
│ │ └── ledatastream/
│ │ ├── LEDataInputStream.java
│ │ ├── LEDataOutputStream.java
│ │ └── LittleEndianDataOutputStream.java
│ └── tencent/
│ └── mm/
│ ├── androlib/
│ │ ├── AndrolibException.java
│ │ ├── ApkDecoder.java
│ │ ├── ResourceApkBuilder.java
│ │ ├── ResourceRepackage.java
│ │ └── res/
│ │ ├── data/
│ │ │ ├── ResID.java
│ │ │ ├── ResPackage.java
│ │ │ └── ResType.java
│ │ ├── decoder/
│ │ │ ├── ARSCDecoder.java
│ │ │ ├── RawARSCDecoder.java
│ │ │ └── StringBlock.java
│ │ └── util/
│ │ ├── ExtFile.java
│ │ └── StringUtil.java
│ ├── directory/
│ │ ├── AbstractDirectory.java
│ │ ├── Directory.java
│ │ ├── DirectoryException.java
│ │ ├── FileDirectory.java
│ │ ├── PathAlreadyExists.java
│ │ ├── PathNotExist.java
│ │ └── ZipRODirectory.java
│ ├── resourceproguard/
│ │ ├── Configuration.java
│ │ ├── InputParam.java
│ │ └── Main.java
│ └── util/
│ ├── DataInputDelegate.java
│ ├── DataOutputDelegate.java
│ ├── ExtDataInput.java
│ ├── ExtDataOutput.java
│ ├── FileOperation.java
│ ├── Md5Util.java
│ ├── TypedValue.java
│ └── Utils.java
├── AndResGuard-example/
│ ├── .gitignore
│ ├── app/
│ │ ├── .gitignore
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ ├── resource_mapping.txt
│ │ └── src/
│ │ ├── androidTest/
│ │ │ └── java/
│ │ │ └── andresguard/
│ │ │ └── tencent/
│ │ │ └── com/
│ │ │ └── andresguard_example/
│ │ │ └── ApplicationTest.java
│ │ ├── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java/
│ │ │ │ └── andresguard/
│ │ │ │ └── tencent/
│ │ │ │ └── com/
│ │ │ │ └── andresguard_example/
│ │ │ │ └── MainActivity.java
│ │ │ └── res/
│ │ │ └── .keep
│ │ └── test/
│ │ └── java/
│ │ └── andresguard/
│ │ └── tencent/
│ │ └── com/
│ │ └── andresguard_example/
│ │ └── ExampleUnitTest.java
│ ├── app1/
│ │ ├── .gitignore
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ ├── resource_mapping.txt
│ │ └── src/
│ │ ├── androidTest/
│ │ │ └── java/
│ │ │ └── andresguard/
│ │ │ └── tencent/
│ │ │ └── com/
│ │ │ └── andresguard_example/
│ │ │ └── ApplicationTest.java
│ │ ├── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java/
│ │ │ │ └── andresguard/
│ │ │ │ └── tencent/
│ │ │ │ └── com/
│ │ │ │ └── andresguard_example/
│ │ │ │ └── MainActivity.java
│ │ │ └── res/
│ │ │ └── .keep
│ │ └── test/
│ │ └── java/
│ │ └── andresguard/
│ │ └── tencent/
│ │ └── com/
│ │ └── andresguard_example/
│ │ └── ExampleUnitTest.java
│ ├── app2/
│ │ ├── .gitignore
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ ├── resource_mapping.txt
│ │ └── src/
│ │ ├── androidTest/
│ │ │ └── java/
│ │ │ └── andresguard/
│ │ │ └── tencent/
│ │ │ └── com/
│ │ │ └── andresguard_example/
│ │ │ └── ApplicationTest.java
│ │ ├── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java/
│ │ │ │ └── andresguard/
│ │ │ │ └── tencent/
│ │ │ │ └── com/
│ │ │ │ └── andresguard_example/
│ │ │ │ └── MainActivity.java
│ │ │ └── res/
│ │ │ └── .keep
│ │ └── test/
│ │ └── java/
│ │ └── andresguard/
│ │ └── tencent/
│ │ └── com/
│ │ └── andresguard_example/
│ │ └── ExampleUnitTest.java
│ ├── build.gradle
│ ├── gradle/
│ │ └── wrapper/
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ ├── gradlew
│ ├── gradlew.bat
│ ├── keystore test/
│ │ ├── debug.keystore
│ │ ├── release.keystore
│ │ └── testKey.jks
│ ├── libres/
│ │ ├── .gitignore
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ └── src/
│ │ ├── androidTest/
│ │ │ └── java/
│ │ │ └── com/
│ │ │ └── tinkerpatch/
│ │ │ └── libres/
│ │ │ └── ExampleInstrumentedTest.java
│ │ ├── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ └── res/
│ │ │ ├── drawable/
│ │ │ │ └── side_nav_bar.xml
│ │ │ ├── drawable-v21/
│ │ │ │ ├── ic_menu_camera.xml
│ │ │ │ ├── ic_menu_gallery.xml
│ │ │ │ ├── ic_menu_manage.xml
│ │ │ │ ├── ic_menu_send.xml
│ │ │ │ ├── ic_menu_share.xml
│ │ │ │ └── ic_menu_slideshow.xml
│ │ │ ├── layout/
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── app_bar_main.xml
│ │ │ │ ├── content_main.xml
│ │ │ │ └── nav_header_main.xml
│ │ │ ├── menu/
│ │ │ │ ├── activity_main_drawer.xml
│ │ │ │ └── main.xml
│ │ │ ├── values/
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── drawables.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-v21/
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-w820dp/
│ │ │ │ ├── dimens.xml
│ │ │ │ └── strings.xml
│ │ │ └── values-xxhdpi/
│ │ │ └── strings.xml
│ │ └── test/
│ │ └── java/
│ │ └── com/
│ │ └── tinkerpatch/
│ │ └── libres/
│ │ └── ExampleUnitTest.java
│ └── settings.gradle
├── AndResGuard-gradle-plugin/
│ ├── build.gradle
│ ├── gradle.properties
│ └── src/
│ └── main/
│ ├── groovy/
│ │ └── com/
│ │ └── tencent/
│ │ └── gradle/
│ │ ├── AndResGuardExtension.groovy
│ │ ├── AndResGuardPlugin.groovy
│ │ ├── AndResGuardTask.groovy
│ │ ├── BuildInfo.groovy
│ │ └── ExecutorExtension.groovy
│ └── resources/
│ └── META-INF/
│ └── gradle-plugins/
│ └── AndResGuard.properties
├── LICENSE
├── README.md
├── README.zh-cn.md
├── SECURITY.md
├── SevenZip/
│ ├── build.gradle
│ └── gradle.properties
├── appveyol.yml
├── build.gradle
├── doc/
│ ├── how_to_work.md
│ ├── how_to_work.zh-cn.md
│ └── white_list.md
├── gradle/
│ ├── gradle-mvn-push.gradle
│ ├── java-artifacts.gradle
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── package.json
├── settings.gradle
└── tool_output/
├── AndResGuard-cli-1.2.15.jar
├── build_apk.bat
├── build_apk.sh
├── config.xml
└── release.keystore
SYMBOL INDEX (560 symbols across 49 files)
FILE: AndResGuard-cli/src/main/java/com/tencent/mm/resourceproguard/cli/CliMain.java
class CliMain (line 19) | public class CliMain extends Main {
method main (line 34) | public static void main(String[] args) {
method setRunningLocation (line 41) | private static void setRunningLocation(CliMain m) {
method printUsage (line 55) | private static void printUsage(PrintStream out) {
method printUsage (line 140) | private static void printUsage(PrintStream out, String[] args) {
method wrap (line 165) | private static String wrap(String explanation, int lineWidth, String h...
method run (line 207) | private void run(String[] args) {
method loadConfigFromXml (line 250) | private void loadConfigFromXml(
method diffTimeFromBegin (line 283) | public double diffTimeFromBegin() {
method goToError (line 288) | protected void goToError() {
class ReadArgs (line 293) | private class ReadArgs {
method ReadArgs (line 307) | public ReadArgs(String[] args) {
method getConfigFile (line 311) | public File getConfigFile() {
method getOutputFile (line 315) | public File getOutputFile() {
method getFinalApkFile (line 319) | public File getFinalApkFile() {
method getApkFileName (line 323) | public String getApkFileName() {
method getSignatureFile (line 327) | public File getSignatureFile() {
method getMappingFile (line 331) | public File getMappingFile() {
method getKeypass (line 335) | public String getKeypass() {
method getStorealias (line 339) | public String getStorealias() {
method getStorepass (line 343) | public String getStorepass() {
method getSignatureType (line 347) | public InputParam.SignatureType getSignatureType() {
method getSignedFile (line 351) | public String getSignedFile() {
method invoke (line 355) | public ReadArgs invoke() {
FILE: AndResGuard-core/src/main/java/apksigner/ApkSignerTool.java
class ApkSignerTool (line 68) | public class ApkSignerTool {
method main (line 75) | public static void main(String[] params) throws Exception {
method sign (line 108) | private static void sign(String[] params) throws Exception {
method verify (line 295) | private static void verify(String[] params) throws Exception {
method printUsage (line 485) | private static void printUsage(String page) {
method readFully (line 499) | private static byte[] readFully(File file) throws IOException {
method drain (line 507) | private static void drain(InputStream in, OutputStream out) throws IOE...
class SignerParams (line 515) | private static class SignerParams {
method loadKeyStoreFromFile (line 535) | private static void loadKeyStoreFromFile(KeyStore ks, String file, L...
method getKeyStoreKey (line 554) | private static Key getKeyStoreKey(KeyStore ks, String keyAlias, List...
method decryptPkcs8EncodedKey (line 571) | private static PKCS8EncodedKeySpec decryptPkcs8EncodedKey(
method loadPkcs8EncodedPrivateKey (line 597) | private static PrivateKey loadPkcs8EncodedPrivateKey(PKCS8EncodedKey...
method isEmpty (line 614) | private boolean isEmpty() {
method loadPrivateKeyAndCerts (line 631) | private void loadPrivateKeyAndCerts(PasswordRetriever passwordRetrie...
method loadPrivateKeyAndCertsFromKeyStore (line 646) | private void loadPrivateKeyAndCertsFromKeyStore(PasswordRetriever pa...
method loadPrivateKeyAndCertsFromFiles (line 777) | private void loadPrivateKeyAndCertsFromFiles(PasswordRetriever passw...
class ParameterException (line 829) | private static class ParameterException extends Exception {
method ParameterException (line 832) | ParameterException(String message) {
FILE: AndResGuard-core/src/main/java/apksigner/HexEncoding.java
class HexEncoding (line 24) | class HexEncoding {
method HexEncoding (line 31) | private HexEncoding() {
method encode (line 37) | public static String encode(byte[] data, int offset, int length) {
method encode (line 50) | public static String encode(byte[] data) {
method encodeRemaining (line 57) | public static String encodeRemaining(ByteBuffer data) {
FILE: AndResGuard-core/src/main/java/apksigner/OptionsParser.java
class OptionsParser (line 35) | class OptionsParser {
method OptionsParser (line 44) | public OptionsParser(String[] params) {
method nextOption (line 55) | public String nextOption() {
method getOptionOriginalForm (line 93) | public String getOptionOriginalForm() {
method getRequiredValue (line 100) | public String getRequiredValue(String valueDescription) throws Options...
method getRequiredIntValue (line 123) | public int getRequiredIntValue(String valueDescription) throws Options...
method getOptionalBooleanValue (line 140) | public boolean getOptionalBooleanValue(boolean defaultValue) throws Op...
method getRemainingParams (line 178) | public String[] getRemainingParams() {
class OptionsException (line 194) | public static class OptionsException extends Exception {
method OptionsException (line 197) | public OptionsException(String message) {
FILE: AndResGuard-core/src/main/java/apksigner/PasswordRetriever.java
class PasswordRetriever (line 49) | class PasswordRetriever implements AutoCloseable {
method getPasswords (line 62) | private static List<char[]> getPasswords(char[] pwd) {
method getPasswords (line 75) | private static List<char[]> getPasswords(byte[] encodedPwd, Charset......
method addPasswords (line 97) | private static void addPasswords(List<char[]> passwords, char[] pwd) {
method addPassword (line 122) | private static void addPassword(List<char[]> passwords, char[] passwor...
method encodePassword (line 131) | private static byte[] encodePassword(char[] pwd, Charset cs) throws IO...
method decodePassword (line 139) | private static char[] decodePassword(byte[] pwdBytes, Charset encoding...
method castBytesToChars (line 151) | private static char[] castBytesToChars(byte[] bytes) {
method getConsoleEncoding (line 166) | private static Charset getConsoleEncoding() {
method readEncodedPassword (line 201) | private static byte[] readEncodedPassword(InputStream in) throws IOExc...
method getPasswords (line 241) | public List<char[]> getPasswords(String spec, String description) thro...
method assertNotClosed (line 330) | private void assertNotClosed() {
method close (line 336) | @Override
FILE: AndResGuard-core/src/main/java/com/mindprod/ledatastream/LEDataInputStream.java
class LEDataInputStream (line 38) | public final class LEDataInputStream implements DataInput {
method LEDataInputStream (line 72) | public LEDataInputStream(InputStream in) {
method readUTF (line 88) | public static String readUTF(DataInput in) throws IOException {
method close (line 97) | public final void close() throws IOException {
method read (line 110) | public final int read(byte ba[], int off, int len) throws IOException {
method readBoolean (line 122) | @Override
method readByte (line 134) | @Override
method readChar (line 145) | @Override
method readDouble (line 157) | @Override
method readFloat (line 168) | @Override
method readFully (line 178) | @Override
method readFully (line 189) | @Override
method readInt (line 200) | @Override
method readLine (line 215) | @Deprecated
method readLong (line 227) | @Override
method readShort (line 248) | @Override
method readUTF (line 259) | @Override
method readUnsignedByte (line 271) | @Override
method readUnsignedShort (line 283) | @Override
method skipBytes (line 299) | @Override
FILE: AndResGuard-core/src/main/java/com/mindprod/ledatastream/LEDataOutputStream.java
class LEDataOutputStream (line 13) | public class LEDataOutputStream extends LittleEndianDataOutputStream {
method LEDataOutputStream (line 15) | public LEDataOutputStream(OutputStream out) {
FILE: AndResGuard-core/src/main/java/com/mindprod/ledatastream/LittleEndianDataOutputStream.java
class LittleEndianDataOutputStream (line 47) | public class LittleEndianDataOutputStream extends FilterOutputStream imp...
method LittleEndianDataOutputStream (line 54) | public LittleEndianDataOutputStream(OutputStream out) {
method toByteArray (line 66) | public static byte[] toByteArray(long value) {
method write (line 77) | @Override
method writeBoolean (line 83) | @Override
method writeByte (line 88) | @Override
method writeBytes (line 98) | @Deprecated
method writeChar (line 110) | @Override
method writeChars (line 122) | @Override
method writeDouble (line 136) | @Override
method writeFloat (line 148) | @Override
method writeInt (line 160) | @Override
method writeLong (line 175) | @Override
method writeShort (line 188) | @Override
method writeUTF (line 194) | @Override
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/AndrolibException.java
class AndrolibException (line 6) | public class AndrolibException extends Exception {
method AndrolibException (line 7) | public AndrolibException() {
method AndrolibException (line 10) | public AndrolibException(String message) {
method AndrolibException (line 14) | public AndrolibException(String message, Throwable cause) {
method AndrolibException (line 18) | public AndrolibException(Throwable cause) {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/ApkDecoder.java
class ApkDecoder (line 28) | public class ApkDecoder {
method ApkDecoder (line 43) | public ApkDecoder(Configuration config, File apkFile) {
method copyOtherResFiles (line 48) | private void copyOtherResFiles() throws IOException {
method removeCopiedResFile (line 64) | public void removeCopiedResFile(Path key) {
method getConfig (line 68) | public Configuration getConfig() {
method hasResources (line 72) | public boolean hasResources() throws AndrolibException {
method ensureFilePath (line 80) | private void ensureFilePath() throws IOException {
method dealWithCompressConfig (line 128) | private void dealWithCompressConfig() {
method getCompressData (line 145) | public HashMap<String, Integer> getCompressData() {
method getOutDir (line 149) | public File getOutDir() {
method setOutDir (line 153) | public void setOutDir(File outDir) throws AndrolibException {
method getOutResFile (line 157) | public File getOutResFile() {
method getRawResFile (line 161) | public File getRawResFile() {
method getOutTempARSCFile (line 165) | public File getOutTempARSCFile() {
method getOutARSCFile (line 169) | public File getOutARSCFile() {
method getOutTempDir (line 173) | public File getOutTempDir() {
method getResMappingFile (line 177) | public File getResMappingFile() {
method getMergeDuplicatedResMappingFile (line 181) | public File getMergeDuplicatedResMappingFile() {
method decode (line 185) | public void decode() throws AndrolibException, IOException, DirectoryE...
class ResourceFilesVisitor (line 201) | class ResourceFilesVisitor extends SimpleFileVisitor<Path> {
method visitFile (line 202) | @Override
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/ResourceApkBuilder.java
class ResourceApkBuilder (line 31) | public class ResourceApkBuilder {
method ResourceApkBuilder (line 49) | public ResourceApkBuilder(Configuration config) {
method setOutDir (line 53) | public void setOutDir(File outDir, String apkName, File finalApkFile) ...
method buildApkWithV1sign (line 59) | public void buildApkWithV1sign(HashMap<String, Integer> compressData) ...
method copyFinalApkV1 (line 68) | private void copyFinalApkV1() throws IOException {
method buildApkWithV2V3Sign (line 79) | public void buildApkWithV2V3Sign(HashMap<String, Integer> compressData...
method copyFinalApkV2 (line 97) | private void copyFinalApkV2() throws IOException {
method insureFileNameV1 (line 104) | private void insureFileNameV1() {
method insureFileNameV2 (line 113) | private void insureFileNameV2() {
method use7zApk (line 126) | private boolean use7zApk(HashMap<String, Integer> compressData, File o...
method getSignatureAlgorithm (line 168) | private String getSignatureAlgorithm(String hash) throws Exception {
method formatHashAlgorithName (line 192) | private String formatHashAlgorithName(String hash) {
method signApkV1 (line 196) | private void signApkV1(File unSignedApk, File signedApk) throws IOExce...
method signApkV2V3 (line 209) | private void signApkV2V3(File unSignedApk, File signedApk, int minSDKV...
method signWithV2V3Sign (line 219) | private void signWithV2V3Sign(File unSignedApk, File signedApk, int mi...
method signWithV1sign (line 241) | private void signWithV1sign(File unSignedApk, File signedApk) throws I...
method alignApks (line 268) | private void alignApks() throws IOException, InterruptedException {
method alignApk (line 284) | private void alignApk(File before, File after) throws IOException, Int...
method generalUnsignApk (line 300) | private void generalUnsignApk(HashMap<String, Integer> compressData) t...
method addNonSignatureFiles (line 362) | private void addNonSignatureFiles(List<File> collectFiles, File metaFo...
method addStoredFileIn7Zip (line 376) | private void addStoredFileIn7Zip(ArrayList<String> storedFiles, File o...
method generalRaw7zip (line 392) | private void generalRaw7zip(File outSevenZipApk) throws IOException, I...
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/ResourceRepackage.java
class ResourceRepackage (line 13) | public class ResourceRepackage {
method ResourceRepackage (line 25) | public ResourceRepackage(String zipalignPath, String zipPath, File sig...
method setOutDir (line 31) | public void setOutDir(File outDir) {
method repackageApk (line 35) | public void repackageApk() throws IOException, InterruptedException {
method deleteUnusedFiles (line 43) | private void deleteUnusedFiles() {
method insureFileName (line 57) | private void insureFileName() throws IOException {
method repackageWith7z (line 83) | private void repackageWith7z() throws IOException, InterruptedException {
method generalRaw7zip (line 112) | private void generalRaw7zip() throws IOException, InterruptedException {
method addStoredFileIn7Zip (line 131) | private void addStoredFileIn7Zip(ArrayList<String> storedFiles) throws...
method alignApk (line 162) | private void alignApk() throws IOException, InterruptedException {
method alignApk (line 168) | private void alignApk(File before, File after) throws IOException, Int...
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/data/ResID.java
class ResID (line 19) | public class ResID {
method ResID (line 26) | public ResID(int package_, int type, int entry) {
method ResID (line 30) | public ResID(int id) {
method ResID (line 34) | public ResID(int package_, int type, int entry, int id) {
method toString (line 41) | @Override
method hashCode (line 46) | @Override
method equals (line 53) | @Override
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/data/ResPackage.java
class ResPackage (line 25) | public class ResPackage {
method ResPackage (line 32) | public ResPackage(int id, String name) {
method isCanResguard (line 38) | public boolean isCanResguard() {
method setCanResguard (line 42) | public void setCanResguard(boolean set) {
method hasSpecRepplace (line 46) | public boolean hasSpecRepplace(String resID) {
method getSpecRepplace (line 50) | public String getSpecRepplace(int resID) {
method putSpecNamesReplace (line 54) | public void putSpecNamesReplace(int resID, String value) {
method putSpecNamesblock (line 58) | public void putSpecNamesblock(String specName, String value) {
method getSpecNamesBlock (line 67) | public Map<String, Set<String>> getSpecNamesBlock() {
method getName (line 71) | public String getName() {
method toString (line 75) | @Override
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/data/ResType.java
class ResType (line 22) | public final class ResType {
method ResType (line 28) | public ResType(String name, ResPackage package_) {
method getName (line 34) | public String getName() {
method putSpecResguardName (line 38) | public void putSpecResguardName(String name) throws AndrolibException {
method toString (line 49) | @Override
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/decoder/ARSCDecoder.java
class ARSCDecoder (line 59) | public class ARSCDecoder {
method ARSCDecoder (line 95) | private ARSCDecoder(InputStream arscStream, ApkDecoder decoder) throws...
method ARSCDecoder (line 104) | private ARSCDecoder(InputStream arscStream, ApkDecoder decoder, ResPac...
method decode (line 115) | public static ResPackage[] decode(InputStream arscStream, ApkDecoder a...
method write (line 125) | public static void write(InputStream arscStream, ApkDecoder decoder, R...
method proguardFileName (line 134) | private void proguardFileName() throws IOException, AndrolibException {
method readTable (line 198) | private ResPackage[] readTable() throws IOException, AndrolibException {
method writeTable (line 215) | private void writeTable() throws IOException, AndrolibException {
method generalFileResMapping (line 238) | private void generalFileResMapping() throws IOException {
method generalResIDMapping (line 249) | private void generalResIDMapping(
method generalFilterResIDMapping (line 267) | private void generalFilterResIDMapping(
method generalFilterEnd (line 284) | private void generalFilterEnd(int count, long totalSize) throws IOExce...
method getNetFileSizeDescription (line 291) | private static String getNetFileSizeDescription(long size) {
method reWriteTable (line 313) | private void reWriteTable() throws AndrolibException, IOException {
method readPackage (line 330) | private ResPackage readPackage() throws IOException, AndrolibException {
method writePackage (line 366) | private void writePackage() throws IOException, AndrolibException {
method reduceFromOldMappingFile (line 406) | private void reduceFromOldMappingFile() {
method getWhiteList (line 426) | private HashSet<Pattern> getWhiteList(String resType) {
method readLibraryType (line 437) | private void readLibraryType() throws AndrolibException, IOException {
method readTableTypeSpec (line 455) | private void readTableTypeSpec() throws AndrolibException, IOException {
method initResGuardBuild (line 481) | private void initResGuardBuild(int resTypeId) {
method writeLibraryType (line 491) | private void writeLibraryType() throws AndrolibException, IOException {
method writeTableTypeSpec (line 505) | private void writeTableTypeSpec() throws AndrolibException, IOException {
method readConfig (line 523) | private void readConfig() throws IOException, AndrolibException {
method writeConfig (line 540) | private void writeConfig() throws IOException, AndrolibException {
method readEntry (line 562) | private void readEntry() throws IOException, AndrolibException {
method dealWithWhiteList (line 596) | private boolean dealWithWhiteList(int specNamesId, Configuration confi...
method dealWithNonWhiteList (line 623) | private void dealWithNonWhiteList(int specNamesId, Configuration confi...
method writeEntry (line 659) | private void writeEntry() throws IOException, AndrolibException {
method readComplexEntry (line 684) | private void readComplexEntry(boolean flags, int specNamesId) throws I...
method writeComplexEntry (line 693) | private void writeComplexEntry() throws IOException, AndrolibException {
method readValue (line 706) | private void readValue(boolean flags, int specNamesId) throws IOExcept...
method mergeDuplicated (line 798) | private MergeDuplicatedResInfo mergeDuplicated(File resRawFile, File r...
method writeValue (line 837) | private void writeValue() throws IOException, AndrolibException {
method readConfigFlags (line 848) | private void readConfigFlags() throws IOException, AndrolibException {
method readScriptOrVariantChar (line 941) | private String readScriptOrVariantChar(int length) throws AndrolibExce...
method writeConfigFlags (line 956) | private void writeConfigFlags() throws IOException, AndrolibException {
method nextChunk (line 967) | private Header nextChunk() throws IOException {
method checkChunkType (line 971) | private void checkChunkType(int expectedType) throws AndrolibException {
method nextChunkCheckType (line 980) | private void nextChunkCheckType(int expectedType) throws IOException, ...
method writeNextChunk (line 985) | private Header writeNextChunk(int diffSize) throws IOException, Androl...
method writeNextChunkCheck (line 990) | private Header writeNextChunkCheck(int expectedType, int diffSize) thr...
method isToResguardFile (line 1001) | private boolean isToResguardFile(String name) {
class Header (line 1005) | public static class Header {
method Header (line 1012) | public Header(short type, int size) {
method read (line 1017) | public static Header read(ExtDataInput in) throws IOException {
method readAndWriteHeader (line 1029) | public static Header readAndWriteHeader(ExtDataInput in, ExtDataOutp...
class FlagsOffset (line 1051) | public static class FlagsOffset {
method FlagsOffset (line 1055) | public FlagsOffset(int offset, int count) {
class MergeDuplicatedResInfo (line 1061) | private static class MergeDuplicatedResInfo {
method MergeDuplicatedResInfo (line 1067) | private MergeDuplicatedResInfo(String fileName, String filePath, Str...
class Builder (line 1074) | static class Builder {
method setFileName (line 1080) | Builder setFileName(String fileName) {
method setFilePath (line 1085) | Builder setFilePath(String filePath) {
method setMd5 (line 1090) | public Builder setMd5(String md5) {
method setOriginalName (line 1095) | Builder setOriginalName(String originalName) {
method create (line 1100) | MergeDuplicatedResInfo create() {
class ResguardStringBuilder (line 1106) | private class ResguardStringBuilder {
method ResguardStringBuilder (line 1126) | public ResguardStringBuilder() {
method reset (line 1137) | public void reset(HashSet<Pattern> blacklistPatterns) {
method removeStrings (line 1175) | public void removeStrings(Collection<String> collection) {
method isReplaced (line 1180) | public boolean isReplaced(int id) {
method isInWhiteList (line 1184) | public boolean isInWhiteList(int id) {
method setInWhiteList (line 1188) | public void setInWhiteList(int id) {
method setInReplaceList (line 1192) | public void setInReplaceList(int id) {
method getReplaceString (line 1196) | public String getReplaceString() throws AndrolibException {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/decoder/RawARSCDecoder.java
class RawARSCDecoder (line 40) | public class RawARSCDecoder {
method RawARSCDecoder (line 63) | private RawARSCDecoder(InputStream arscStream) throws AndrolibExceptio...
method decode (line 69) | public static ResPackage[] decode(InputStream arscStream) throws Andro...
method getExistTypeSpecNameStrings (line 79) | public static Set<String> getExistTypeSpecNameStrings(int type) {
method readTable (line 83) | private ResPackage[] readTable() throws IOException, AndrolibException {
method readTablePackage (line 95) | private ResPackage readTablePackage() throws IOException, AndrolibExce...
method readLibraryType (line 130) | private void readLibraryType() throws AndrolibException, IOException {
method readTableTypeSpec (line 149) | private void readTableTypeSpec() throws AndrolibException, IOException {
method readSingleTableTypeSpec (line 163) | private void readSingleTableTypeSpec() throws AndrolibException, IOExc...
method readConfig (line 177) | private void readConfig() throws IOException, AndrolibException {
method readEntry (line 203) | private void readEntry() throws IOException, AndrolibException {
method readComplexEntry (line 219) | private void readComplexEntry(boolean flags, int specNamesId) throws I...
method readValue (line 228) | private void readValue(boolean flags, int specNamesId) throws IOExcept...
method readConfigFlags (line 237) | private void readConfigFlags() throws IOException, AndrolibException {
method readScriptOrVariantChar (line 333) | private String readScriptOrVariantChar(int length) throws AndrolibExce...
method nextChunk (line 348) | private Header nextChunk() throws IOException {
method checkChunkType (line 352) | private void checkChunkType(int expectedType) throws AndrolibException {
method nextChunkCheckType (line 361) | private void nextChunkCheckType(int expectedType) throws IOException, ...
method putTypeSpecNameStrings (line 366) | private void putTypeSpecNameStrings(int type, String name) {
class Header (line 375) | public static class Header {
method Header (line 388) | public Header(short type, int headerSize, int chunkSize, int headerS...
method read (line 396) | public static Header read(ExtDataInput in, CountingInputStream count...
class FlagsOffset (line 408) | public static class FlagsOffset {
method FlagsOffset (line 412) | public FlagsOffset(int offset, int count) {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/decoder/StringBlock.java
class StringBlock (line 38) | public class StringBlock {
method StringBlock (line 57) | private StringBlock() {
method read (line 68) | public static StringBlock read(ExtDataInput reader) throws IOException {
method writeSpecNameStringBlock (line 106) | public static int writeSpecNameStringBlock(
method writeTableNameStringBlock (line 214) | public static int writeTableNameStringBlock(
method writeAll (line 358) | public static void writeAll(ExtDataInput reader, ExtDataOutput out) th...
method getUtf8 (line 365) | private static final int[] getUtf8(byte[] array, int offset) {
method getUtf16 (line 387) | private static final int[] getUtf16(byte[] array, int offset) {
method getShort (line 399) | private static final int getShort(byte[] array, int offset) {
method writeShort (line 403) | private static final void writeShort(byte[] array, int offset, short v...
method getShort (line 408) | private static final int getShort(int[] array, int offset) {
method getCount (line 422) | public int getCount() {
method getString (line 432) | public String getString(int index) {
method get (line 459) | public CharSequence get(int index) {
method find (line 469) | public int find(String string) {
method decodeString (line 493) | private String decodeString(int offset, int length) {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/util/ExtFile.java
class ExtFile (line 24) | public class ExtFile extends File {
method ExtFile (line 27) | public ExtFile(File file) {
method ExtFile (line 31) | public ExtFile(URI uri) {
method ExtFile (line 35) | public ExtFile(File parent, String child) {
method ExtFile (line 39) | public ExtFile(String parent, String child) {
method ExtFile (line 43) | public ExtFile(String pathname) {
method getDirectory (line 47) | public Directory getDirectory() throws DirectoryException {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/util/StringUtil.java
class StringUtil (line 7) | public class StringUtil {
method isPresent (line 8) | public static boolean isPresent(final String string) {
method isBlank (line 12) | public static boolean isBlank(final String string) {
method readInputStream (line 16) | public static String readInputStream(InputStream inputStream) throws I...
FILE: AndResGuard-core/src/main/java/com/tencent/mm/directory/AbstractDirectory.java
class AbstractDirectory (line 24) | public abstract class AbstractDirectory implements Directory {
method getFiles (line 28) | @Override
method getFiles (line 33) | @Override
method containsFile (line 51) | @Override
method containsDir (line 66) | @Override
method getDirs (line 81) | @Override
method getDirs (line 86) | @Override
method getFileInput (line 91) | @Override
method getFileOutput (line 103) | @Override
method getDir (line 121) | @Override
method createDir (line 133) | @Override
method removeFile (line 155) | @Override
method getAbstractDirs (line 175) | protected Map<String, AbstractDirectory> getAbstractDirs() {
method getAbstractDirs (line 179) | protected Map<String, AbstractDirectory> getAbstractDirs(boolean recur...
method getSubPath (line 196) | private SubPath getSubPath(String path) throws PathNotExist {
method parsePath (line 207) | private ParsedPath parsePath(String path) {
method loadFiles (line 215) | abstract protected void loadFiles();
method loadDirs (line 217) | abstract protected void loadDirs();
method getFileInputLocal (line 219) | abstract protected InputStream getFileInputLocal(String name) throws D...
method getFileOutputLocal (line 221) | abstract protected OutputStream getFileOutputLocal(String name) throws...
method createDirLocal (line 223) | abstract protected AbstractDirectory createDirLocal(String name) throw...
method removeFileLocal (line 225) | abstract protected void removeFileLocal(String name);
class ParsedPath (line 227) | private class ParsedPath {
method ParsedPath (line 231) | public ParsedPath(String dir, String subpath) {
class SubPath (line 237) | private class SubPath {
method SubPath (line 241) | public SubPath(AbstractDirectory dir, String path) {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/directory/Directory.java
type Directory (line 22) | public interface Directory {
method getFiles (line 25) | public Set<String> getFiles();
method getFiles (line 27) | public Set<String> getFiles(boolean recursive);
method getDirs (line 29) | public Map<String, Directory> getDirs();
method getDirs (line 31) | public Map<String, Directory> getDirs(boolean recursive);
method containsFile (line 33) | public boolean containsFile(String path);
method containsDir (line 35) | public boolean containsDir(String path);
method getFileInput (line 37) | public InputStream getFileInput(String path) throws DirectoryException;
method getFileOutput (line 39) | public OutputStream getFileOutput(String path) throws DirectoryException;
method getDir (line 41) | public Directory getDir(String path) throws PathNotExist;
method createDir (line 43) | public Directory createDir(String path) throws DirectoryException;
method removeFile (line 45) | public boolean removeFile(String path);
FILE: AndResGuard-core/src/main/java/com/tencent/mm/directory/DirectoryException.java
class DirectoryException (line 17) | public class DirectoryException extends Exception {
method DirectoryException (line 20) | public DirectoryException(String detailMessage, Throwable throwable) {
method DirectoryException (line 24) | public DirectoryException(String detailMessage) {
method DirectoryException (line 28) | public DirectoryException(Throwable throwable) {
method DirectoryException (line 32) | public DirectoryException() {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/directory/FileDirectory.java
class FileDirectory (line 26) | public class FileDirectory extends AbstractDirectory {
method FileDirectory (line 29) | public FileDirectory(String dir) throws DirectoryException {
method FileDirectory (line 33) | public FileDirectory(File dir) throws DirectoryException {
method createDirLocal (line 41) | @Override
method getFileInputLocal (line 48) | @Override
method getFileOutputLocal (line 57) | @Override
method loadDirs (line 66) | @Override
method loadFiles (line 71) | @Override
method removeFileLocal (line 76) | @Override
method generatePath (line 81) | private String generatePath(String name) {
method loadAll (line 85) | private void loadAll() {
method getDir (line 104) | private File getDir() {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/directory/PathAlreadyExists.java
class PathAlreadyExists (line 17) | public class PathAlreadyExists extends DirectoryException {
method PathAlreadyExists (line 20) | public PathAlreadyExists() {
method PathAlreadyExists (line 23) | public PathAlreadyExists(Throwable throwable) {
method PathAlreadyExists (line 27) | public PathAlreadyExists(String detailMessage) {
method PathAlreadyExists (line 31) | public PathAlreadyExists(String detailMessage, Throwable throwable) {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/directory/PathNotExist.java
class PathNotExist (line 17) | public class PathNotExist extends DirectoryException {
method PathNotExist (line 20) | public PathNotExist() {
method PathNotExist (line 24) | public PathNotExist(String detailMessage, Throwable throwable) {
method PathNotExist (line 28) | public PathNotExist(String detailMessage) {
method PathNotExist (line 32) | public PathNotExist(Throwable throwable) {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/directory/ZipRODirectory.java
class ZipRODirectory (line 27) | public class ZipRODirectory extends AbstractDirectory {
method ZipRODirectory (line 31) | public ZipRODirectory(String zipFileName) throws DirectoryException {
method ZipRODirectory (line 35) | public ZipRODirectory(File zipFile) throws DirectoryException {
method ZipRODirectory (line 39) | public ZipRODirectory(ZipFile zipFile) {
method ZipRODirectory (line 43) | public ZipRODirectory(String zipFileName, String path) throws Director...
method ZipRODirectory (line 47) | public ZipRODirectory(File zipFile, String path) throws DirectoryExcep...
method ZipRODirectory (line 57) | public ZipRODirectory(ZipFile zipFile, String path) {
method createDirLocal (line 63) | @Override
method getFileInputLocal (line 68) | @Override
method getFileOutputLocal (line 77) | @Override
method loadDirs (line 82) | @Override
method loadFiles (line 87) | @Override
method removeFileLocal (line 92) | @Override
method loadAll (line 97) | private void loadAll() {
method getPath (line 129) | private String getPath() {
method getZipFile (line 133) | private ZipFile getZipFile() {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/resourceproguard/Configuration.java
class Configuration (line 28) | public class Configuration {
method Configuration (line 87) | public Configuration(
method Configuration (line 119) | public Configuration(InputParam param) throws IOException {
method setSignData (line 149) | private void setSignData(
method setKeepMappingData (line 163) | private void setKeepMappingData(File mappingFile) throws IOException {
method readXmlConfig (line 176) | private void readXmlConfig(File xmlConfigFile) throws IOException, Par...
method readWhiteListFromXml (line 248) | private void readWhiteListFromXml(Node node) throws IOException {
method addWhiteList (line 262) | private void addWhiteList(String item) throws IOException {
method readSignFromXml (line 304) | private void readSignFromXml(Node node, File xmlConfigFileParentFile) ...
method readCompressFromXml (line 366) | private void readCompressFromXml(Node node) throws IOException {
method addToCompressPatterns (line 380) | private void addToCompressPatterns(String value) throws IOException {
method loadMappingFilesFromXml (line 389) | private void loadMappingFilesFromXml(Node node) throws IOException {
method readPropertyFromXml (line 410) | private void readPropertyFromXml(Node node) throws IOException {
method readOldMapping (line 447) | private void readOldMapping(String filePath) throws IOException {
method processOldMappingFile (line 460) | private void processOldMappingFile() throws IOException {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/resourceproguard/InputParam.java
class InputParam (line 7) | public class InputParam {
method InputParam (line 32) | private InputParam(
type SignatureType (line 80) | public enum SignatureType {
class Builder (line 84) | public static class Builder {
method Builder (line 109) | public Builder() {
method setMappingFile (line 115) | public Builder setMappingFile(File mappingFile) {
method setUse7zip (line 120) | public Builder setUse7zip(boolean use7zip) {
method setUseSign (line 125) | public Builder setUseSign(boolean useSign) {
method setKeepRoot (line 130) | public Builder setKeepRoot(boolean keepRoot) {
method setMergeDuplicatedRes (line 135) | public Builder setMergeDuplicatedRes(boolean mergeDuplicatedRes) {
method setWhiteList (line 140) | public Builder setWhiteList(ArrayList<String> whiteList) {
method setCompressFilePattern (line 145) | public Builder setCompressFilePattern(ArrayList<String> compressFile...
method setApkPath (line 154) | public Builder setApkPath(String apkPath) {
method setOutBuilder (line 159) | public Builder setOutBuilder(String outFolder) {
method setSignFile (line 164) | public Builder setSignFile(File signFile) {
method setKeypass (line 169) | public Builder setKeypass(String keypass) {
method setStorealias (line 174) | public Builder setStorealias(String storealias) {
method setStorepass (line 179) | public Builder setStorepass(String storepass) {
method setMetaName (line 184) | public Builder setMetaName(String metaName) {
method setFixedResName (line 189) | public Builder setFixedResName(String fixedResName) {
method setZipAlign (line 194) | public Builder setZipAlign(String zipAlignPath) {
method setSevenZipPath (line 199) | public Builder setSevenZipPath(String sevenZipPath) {
method setSignatureType (line 204) | public Builder setSignatureType(SignatureType signatureType) {
method setFinalApkBackupPath (line 209) | public Builder setFinalApkBackupPath(String finalApkBackupPath) {
method setDigestAlg (line 214) | public Builder setDigestAlg(String digestAlg) {
method setMinSDKVersion (line 224) | public Builder setMinSDKVersion(int minSDKVersion) {
method setTargetSDKVersion (line 229) | public Builder setTargetSDKVersion(int targetSDKVersion) {
method create (line 234) | public InputParam create() {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/resourceproguard/Main.java
class Main (line 17) | public class Main {
method gradleRun (line 37) | public static void gradleRun(InputParam inputParam) {
method run (line 42) | private void run(InputParam inputParam) {
method clean (line 68) | protected void clean() {
method loadConfigFromGradle (line 74) | private void loadConfigFromGradle(InputParam inputParam) {
method resourceProguard (line 82) | protected void resourceProguard(
method resourceProguard (line 87) | protected void resourceProguard(
method decodeResource (line 106) | private void decodeResource(File outputFile, ApkDecoder decoder, File ...
method buildApk (line 117) | private void buildApk(
method goToError (line 136) | protected void goToError() {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/util/DataInputDelegate.java
class DataInputDelegate (line 23) | abstract public class DataInputDelegate implements DataInput {
method DataInputDelegate (line 26) | public DataInputDelegate(DataInput delegate) {
method skipBytes (line 30) | public int skipBytes(int n) throws IOException {
method readUnsignedShort (line 34) | public int readUnsignedShort() throws IOException {
method readUnsignedByte (line 38) | public int readUnsignedByte() throws IOException {
method readUTF (line 42) | public String readUTF() throws IOException {
method readShort (line 46) | public short readShort() throws IOException {
method readLong (line 50) | public long readLong() throws IOException {
method readLine (line 54) | public String readLine() throws IOException {
method readInt (line 58) | public int readInt() throws IOException {
method readFully (line 62) | public void readFully(byte[] b, int off, int len) throws IOException {
method readFully (line 66) | public void readFully(byte[] b) throws IOException {
method readFloat (line 70) | public float readFloat() throws IOException {
method readDouble (line 74) | public double readDouble() throws IOException {
method readChar (line 78) | public char readChar() throws IOException {
method readByte (line 82) | public byte readByte() throws IOException {
method readBoolean (line 86) | public boolean readBoolean() throws IOException {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/util/DataOutputDelegate.java
class DataOutputDelegate (line 6) | public class DataOutputDelegate implements DataOutput {
method DataOutputDelegate (line 9) | public DataOutputDelegate(DataOutput delegate) {
method write (line 13) | @Override
method write (line 19) | @Override
method write (line 25) | @Override
method writeBoolean (line 31) | @Override
method writeByte (line 37) | @Override
method writeShort (line 43) | @Override
method writeChar (line 49) | @Override
method writeInt (line 55) | @Override
method writeLong (line 61) | @Override
method writeFloat (line 67) | @Override
method writeDouble (line 73) | @Override
method writeBytes (line 79) | @Override
method writeChars (line 85) | @Override
method writeUTF (line 91) | @Override
FILE: AndResGuard-core/src/main/java/com/tencent/mm/util/ExtDataInput.java
class ExtDataInput (line 25) | public class ExtDataInput extends DataInputDelegate {
method ExtDataInput (line 26) | public ExtDataInput(InputStream in) {
method ExtDataInput (line 30) | public ExtDataInput(DataInput delegate) {
method readIntArray (line 34) | public int[] readIntArray(int length) throws IOException {
method skipInt (line 42) | public void skipInt() throws IOException {
method skipCheckInt (line 46) | public void skipCheckInt(int expected) throws IOException {
method skipCheckChunkTypeInt (line 53) | public void skipCheckChunkTypeInt(int expected, int possible) throws I...
method skipCheckShort (line 63) | public void skipCheckShort(short expected) throws IOException {
method skipCheckByte (line 70) | public void skipCheckByte(byte expected) throws IOException {
method readNullEndedString (line 77) | public String readNullEndedString(int length, boolean fixed) throws IO...
FILE: AndResGuard-core/src/main/java/com/tencent/mm/util/ExtDataOutput.java
class ExtDataOutput (line 6) | public class ExtDataOutput extends DataOutputDelegate {
method ExtDataOutput (line 8) | public ExtDataOutput(DataOutput delegate) {
method writeIntArray (line 13) | public void writeIntArray(int[] array) throws IOException {
method writeBytes (line 20) | public void writeBytes(ExtDataInput in, int length) throws IOException {
method writeCheckInt (line 26) | public void writeCheckInt(int value, int expected) throws IOException {
method writeCheckChunkTypeInt (line 33) | public void writeCheckChunkTypeInt(ExtDataInput reader, int expected, ...
method writeCheckShort (line 43) | public void writeCheckShort(short value, short expected) throws IOExce...
method writeCheckByte (line 50) | public void writeCheckByte(byte value, byte expected) throws IOExcepti...
FILE: AndResGuard-core/src/main/java/com/tencent/mm/util/FileOperation.java
class FileOperation (line 18) | public class FileOperation {
method fileExists (line 21) | public static boolean fileExists(String filePath) {
method deleteFile (line 31) | public static boolean deleteFile(String filePath) {
method getlist (line 43) | public static long getlist(File f) {
method getFileSizes (line 62) | public static long getFileSizes(File f) {
method deleteDir (line 84) | public static boolean deleteDir(File file) {
method copyFileUsingStream (line 100) | public static void copyFileUsingStream(File source, File dest) throws ...
method checkDirectory (line 126) | public static boolean checkDirectory(String dir) {
method checkFile (line 136) | public static File checkFile(String dir) {
method unZipAPk (line 148) | @SuppressWarnings("rawtypes")
method zipFiles (line 202) | public static void zipFiles(
method zipFile (line 225) | private static void zipFile(
method readContents (line 263) | private static byte[] readContents(final File file) throws IOException {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/util/Md5Util.java
class Md5Util (line 14) | public class Md5Util {
method getMD5Str (line 16) | public static String getMD5Str(String str) {
method getMD5Str (line 27) | public static String getMD5Str(File file) {
method bytesToHexString (line 41) | public static String bytesToHexString(byte[] src) {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/util/TypedValue.java
class TypedValue (line 24) | public class TypedValue {
FILE: AndResGuard-core/src/main/java/com/tencent/mm/util/Utils.java
class Utils (line 16) | public class Utils {
method isPresent (line 17) | public static boolean isPresent(String str) {
method isBlank (line 21) | public static boolean isBlank(String str) {
method isPresent (line 25) | public static boolean isPresent(Iterator iterator) {
method isBlank (line 29) | public static boolean isBlank(Iterator iterator) {
method convertToPatternString (line 33) | public static String convertToPatternString(String input) {
method match (line 42) | public static boolean match(String str, HashSet<Pattern> patterns) {
method cleanDir (line 53) | public static void cleanDir(File dir) {
method runCmd (line 60) | public static String runCmd(String... cmd) throws IOException, Interru...
method runExec (line 79) | public static String runExec(String[] argv) throws IOException, Interr...
method processOutputStreamInThread (line 98) | private static void processOutputStreamInThread(Process process) throw...
method replaceEach (line 106) | private static String replaceEach(String text, String[] searchList, St...
class SearchTracker (line 133) | static class SearchTracker {
method SearchTracker (line 142) | SearchTracker(String text, String[] searchList, String[] replacement...
method hasNextMatch (line 151) | boolean hasNextMatch(int start) {
class MatchInfo (line 174) | private static class MatchInfo {
method MatchInfo (line 179) | MatchInfo(String pattern, String replacement, int textIndex) {
FILE: AndResGuard-example/app/src/androidTest/java/andresguard/tencent/com/andresguard_example/ApplicationTest.java
class ApplicationTest (line 9) | public class ApplicationTest extends ApplicationTestCase<Application> {
method ApplicationTest (line 10) | public ApplicationTest() {
FILE: AndResGuard-example/app/src/main/java/andresguard/tencent/com/andresguard_example/MainActivity.java
class MainActivity (line 19) | public class MainActivity extends AppCompatActivity implements Navigatio...
method onCreate (line 21) | @Override
method onBackPressed (line 52) | @Override
method onCreateOptionsMenu (line 62) | @Override
method onOptionsItemSelected (line 69) | @Override
method onNavigationItemSelected (line 83) | @SuppressWarnings("StatementWithEmptyBody")
FILE: AndResGuard-example/app/src/test/java/andresguard/tencent/com/andresguard_example/ExampleUnitTest.java
class ExampleUnitTest (line 10) | public class ExampleUnitTest {
method addition_isCorrect (line 11) | @Test
FILE: AndResGuard-example/app1/src/androidTest/java/andresguard/tencent/com/andresguard_example/ApplicationTest.java
class ApplicationTest (line 9) | public class ApplicationTest extends ApplicationTestCase<Application> {
method ApplicationTest (line 10) | public ApplicationTest() {
FILE: AndResGuard-example/app1/src/main/java/andresguard/tencent/com/andresguard_example/MainActivity.java
class MainActivity (line 19) | public class MainActivity extends AppCompatActivity implements Navigatio...
method onCreate (line 21) | @Override
method onBackPressed (line 52) | @Override
method onCreateOptionsMenu (line 62) | @Override
method onOptionsItemSelected (line 69) | @Override
method onNavigationItemSelected (line 83) | @SuppressWarnings("StatementWithEmptyBody")
FILE: AndResGuard-example/app1/src/test/java/andresguard/tencent/com/andresguard_example/ExampleUnitTest.java
class ExampleUnitTest (line 10) | public class ExampleUnitTest {
method addition_isCorrect (line 11) | @Test
FILE: AndResGuard-example/app2/src/androidTest/java/andresguard/tencent/com/andresguard_example/ApplicationTest.java
class ApplicationTest (line 9) | public class ApplicationTest extends ApplicationTestCase<Application> {
method ApplicationTest (line 10) | public ApplicationTest() {
FILE: AndResGuard-example/app2/src/main/java/andresguard/tencent/com/andresguard_example/MainActivity.java
class MainActivity (line 19) | public class MainActivity extends AppCompatActivity implements Navigatio...
method onCreate (line 21) | @Override
method onBackPressed (line 52) | @Override
method onCreateOptionsMenu (line 62) | @Override
method onOptionsItemSelected (line 69) | @Override
method onNavigationItemSelected (line 83) | @SuppressWarnings("StatementWithEmptyBody")
FILE: AndResGuard-example/app2/src/test/java/andresguard/tencent/com/andresguard_example/ExampleUnitTest.java
class ExampleUnitTest (line 10) | public class ExampleUnitTest {
method addition_isCorrect (line 11) | @Test
FILE: AndResGuard-example/libres/src/androidTest/java/com/tinkerpatch/libres/ExampleInstrumentedTest.java
class ExampleInstrumentedTest (line 16) | @RunWith(AndroidJUnit4.class)
method useAppContext (line 18) | @Test
FILE: AndResGuard-example/libres/src/test/java/com/tinkerpatch/libres/ExampleUnitTest.java
class ExampleUnitTest (line 12) | public class ExampleUnitTest {
method addition_isCorrect (line 13) | @Test
Condensed preview — 146 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (437K chars).
[
{
"path": ".gitignore",
"chars": 445,
"preview": "# Built application files\n*.apk\n*.ap_\n\n# Files for the Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbi"
},
{
"path": ".travis.yml",
"chars": 387,
"preview": "language: java\ndist: trusty\nscript: \"./gradlew check\"\njdk:\n - oraclejdk8\nnotifications:\n webhooks:\n urls:\n - h"
},
{
"path": "AndResGuard-cli/build.gradle",
"chars": 1041,
"preview": "apply plugin: 'java'\n\n[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8'\n\nversion rootProject.ext.VERS"
},
{
"path": "AndResGuard-cli/src/main/java/com/tencent/mm/resourceproguard/cli/CliMain.java",
"chars": 17307,
"preview": "package com.tencent.mm.resourceproguard.cli;\n\nimport com.tencent.mm.androlib.ResourceRepackage;\nimport com.tencent.mm.re"
},
{
"path": "AndResGuard-core/build.gradle",
"chars": 564,
"preview": "apply plugin: 'java'\n\n\n\nversion rootProject.ext.VERSION_NAME\ngroup rootProject.ext.GROUP\n\n[compileJava, compileTestJava,"
},
{
"path": "AndResGuard-core/gradle.properties",
"chars": 76,
"preview": "POM_ARTIFACT_ID=AndResGuard-core\nPOM_NAME=AndResGuard Core\nPOM_PACKAGING=jar"
},
{
"path": "AndResGuard-core/src/main/java/apksigner/ApkSignerTool.java",
"chars": 35013,
"preview": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "AndResGuard-core/src/main/java/apksigner/HexEncoding.java",
"chars": 1771,
"preview": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "AndResGuard-core/src/main/java/apksigner/OptionsParser.java",
"chars": 6380,
"preview": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "AndResGuard-core/src/main/java/apksigner/PasswordRetriever.java",
"chars": 13784,
"preview": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "AndResGuard-core/src/main/java/apksigner/help.txt",
"chars": 610,
"preview": "USAGE: apksigner <command> [options]\n apksigner --version\n apksigner --help\n\nEXAMPLE:\n apksigner sign "
},
{
"path": "AndResGuard-core/src/main/java/apksigner/help_sign.txt",
"chars": 8013,
"preview": "USAGE: apksigner sign [options] apk\n\nThis signs the provided APK, stripping out any pre-existing signatures. Signing\nis "
},
{
"path": "AndResGuard-core/src/main/java/apksigner/help_verify.txt",
"chars": 1455,
"preview": "USAGE: apksigner verify [options] apk\n\nThis checks whether the provided APK will verify on Android. By default, this\nche"
},
{
"path": "AndResGuard-core/src/main/java/com/mindprod/ledatastream/LEDataInputStream.java",
"chars": 8240,
"preview": "/*\n * @(#)LEDataInputStream.java\n *\n * Summary: Little-Endian version of DataInputStream.\n *\n * Copyright: (c) 1998-2010"
},
{
"path": "AndResGuard-core/src/main/java/com/mindprod/ledatastream/LEDataOutputStream.java",
"chars": 464,
"preview": "/**\n * Description:\n * LEDataOutputStream.java Create on 2014-5-14\n *\n * @author shaowenzhang <shaowenzhang@tencent.com>"
},
{
"path": "AndResGuard-core/src/main/java/com/mindprod/ledatastream/LittleEndianDataOutputStream.java",
"chars": 5773,
"preview": "/**\n * Description:\n * LittleEndianDataOutputStream.java Create on 2014-5-14\n *\n * @author shaowenzhang <shaowenzhang@te"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/AndrolibException.java",
"chars": 385,
"preview": "package com.tencent.mm.androlib;\n\n/**\n * @author shwenzhang\n */\npublic class AndrolibException extends Exception {\n pub"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/ApkDecoder.java",
"chars": 6772,
"preview": "package com.tencent.mm.androlib;\n\nimport com.tencent.mm.androlib.res.data.ResPackage;\nimport com.tencent.mm.androlib.res"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/ResourceApkBuilder.java",
"chars": 15374,
"preview": "package com.tencent.mm.androlib;\n\nimport com.tencent.mm.androlib.res.decoder.ARSCDecoder;\nimport com.tencent.mm.resource"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/ResourceRepackage.java",
"chars": 6305,
"preview": "package com.tencent.mm.androlib;\n\nimport com.tencent.mm.util.FileOperation;\nimport com.tencent.mm.util.TypedValue;\nimpor"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/data/ResID.java",
"chars": 1712,
"preview": "/**\n * Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>\n * Copyright 2016 sim sun <sunsj1231@gmail.com>\n *\n * Lic"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/data/ResPackage.java",
"chars": 2093,
"preview": "/**\n * Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>\n * Copyright 2016 sim sun <sunsj1231@gmail.com>\n *\n * Lic"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/data/ResType.java",
"chars": 1508,
"preview": "/**\n * Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>\n * Copyright 2016 sim sun <sunsj1231@gmail.com>\n * Licens"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/decoder/ARSCDecoder.java",
"chars": 40006,
"preview": "/**\n * Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>\n * Copyright 2016 sim sun <sunsj1231@gmail.com>\n * <p>\n *"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/decoder/RawARSCDecoder.java",
"chars": 12354,
"preview": "/**\n * Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>\n * Copyright 2016 sim sun <sunsj1231@gmail.com>\n *\n * Lic"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/decoder/StringBlock.java",
"chars": 16335,
"preview": "/**\n * Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>\n * Copyright 2016 sim sun <sunsj1231@gmail.com>\n *\n * Lic"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/util/ExtFile.java",
"chars": 1512,
"preview": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/util/StringUtil.java",
"chars": 712,
"preview": "package com.tencent.mm.androlib.res.util;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/directory/AbstractDirectory.java",
"chars": 6794,
"preview": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/directory/Directory.java",
"chars": 1378,
"preview": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/directory/DirectoryException.java",
"chars": 1053,
"preview": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/directory/FileDirectory.java",
"chars": 2805,
"preview": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/directory/PathAlreadyExists.java",
"chars": 1043,
"preview": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/directory/PathNotExist.java",
"chars": 1032,
"preview": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/directory/ZipRODirectory.java",
"chars": 3536,
"preview": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/resourceproguard/Configuration.java",
"chars": 18979,
"preview": "package com.tencent.mm.resourceproguard;\n\nimport com.tencent.mm.util.Utils;\nimport java.io.BufferedInputStream;\nimport j"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/resourceproguard/InputParam.java",
"chars": 7309,
"preview": "package com.tencent.mm.resourceproguard;\n\nimport com.tencent.mm.androlib.res.util.StringUtil;\nimport java.io.File;\nimpor"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/resourceproguard/Main.java",
"chars": 4494,
"preview": "package com.tencent.mm.resourceproguard;\n\nimport com.tencent.mm.androlib.AndrolibException;\nimport com.tencent.mm.androl"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/util/DataInputDelegate.java",
"chars": 2227,
"preview": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/util/DataOutputDelegate.java",
"chars": 2288,
"preview": "package com.tencent.mm.util;\n\nimport java.io.DataOutput;\nimport java.io.IOException;\n\npublic class DataOutputDelegate im"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/util/ExtDataInput.java",
"chars": 2563,
"preview": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/util/ExtDataOutput.java",
"chars": 1669,
"preview": "package com.tencent.mm.util;\n\nimport java.io.DataOutput;\nimport java.io.IOException;\n\npublic class ExtDataOutput extends"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/util/FileOperation.java",
"chars": 8286,
"preview": "package com.tencent.mm.util;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.By"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/util/Md5Util.java",
"chars": 1437,
"preview": "package com.tencent.mm.util;\n\n\nimport org.apache.commons.io.FileUtils;\n\nimport java.io.File;\nimport java.nio.charset.Sta"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/util/TypedValue.java",
"chars": 1793,
"preview": "/*\n * Copyright (C) 2007 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "AndResGuard-core/src/main/java/com/tencent/mm/util/Utils.java",
"chars": 5529,
"preview": "package com.tencent.mm.util;\n\nimport com.tencent.mm.androlib.res.util.StringUtil;\nimport java.io.File;\nimport java.io.IO"
},
{
"path": "AndResGuard-example/.gitignore",
"chars": 139,
"preview": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n**/fabric.properties\n.g"
},
{
"path": "AndResGuard-example/app/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "AndResGuard-example/app/build.gradle",
"chars": 5069,
"preview": "apply plugin: 'AndResGuard'\napply plugin: 'com.android.application'\n\nbuildscript {\n repositories {\n mavenLocal()\n "
},
{
"path": "AndResGuard-example/app/proguard-rules.pro",
"chars": 661,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
},
{
"path": "AndResGuard-example/app/resource_mapping.txt",
"chars": 255,
"preview": "res path mapping:\n res/mipmap-hdpi-v4 -> res/mipmap-hdpi-v4\n res/mipmap-mdpi-v4 -> res/mipmap-mdpi-v4\n res/mipm"
},
{
"path": "AndResGuard-example/app/src/androidTest/java/andresguard/tencent/com/andresguard_example/ApplicationTest.java",
"chars": 366,
"preview": "package andresguard.tencent.com.andresguard_example;\n\nimport android.app.Application;\nimport android.test.ApplicationTes"
},
{
"path": "AndResGuard-example/app/src/main/AndroidManifest.xml",
"chars": 1044,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"andresguard.tencent.com.andresguard_example\"\n xmlns:a"
},
{
"path": "AndResGuard-example/app/src/main/java/andresguard/tencent/com/andresguard_example/MainActivity.java",
"chars": 3422,
"preview": "package andresguard.tencent.com.andresguard_example;\n\nimport android.os.Bundle;\nimport android.support.design.widget.Flo"
},
{
"path": "AndResGuard-example/app/src/main/res/.keep",
"chars": 0,
"preview": ""
},
{
"path": "AndResGuard-example/app/src/test/java/andresguard/tencent/com/andresguard_example/ExampleUnitTest.java",
"chars": 337,
"preview": "package andresguard.tencent.com.andresguard_example;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEqual"
},
{
"path": "AndResGuard-example/app1/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "AndResGuard-example/app1/build.gradle",
"chars": 3538,
"preview": "apply plugin: 'AndResGuard'\napply plugin: 'com.android.application'\n\nbuildscript {\n repositories {\n mavenLocal()\n "
},
{
"path": "AndResGuard-example/app1/proguard-rules.pro",
"chars": 661,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
},
{
"path": "AndResGuard-example/app1/resource_mapping.txt",
"chars": 255,
"preview": "res path mapping:\n res/mipmap-hdpi-v4 -> res/mipmap-hdpi-v4\n res/mipmap-mdpi-v4 -> res/mipmap-mdpi-v4\n res/mipm"
},
{
"path": "AndResGuard-example/app1/src/androidTest/java/andresguard/tencent/com/andresguard_example/ApplicationTest.java",
"chars": 366,
"preview": "package andresguard.tencent.com.andresguard_example;\n\nimport android.app.Application;\nimport android.test.ApplicationTes"
},
{
"path": "AndResGuard-example/app1/src/main/AndroidManifest.xml",
"chars": 1003,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"andresguard.tencent.com.andresguard_example\"\n xmlns:a"
},
{
"path": "AndResGuard-example/app1/src/main/java/andresguard/tencent/com/andresguard_example/MainActivity.java",
"chars": 3422,
"preview": "package andresguard.tencent.com.andresguard_example;\n\nimport android.os.Bundle;\nimport android.support.design.widget.Flo"
},
{
"path": "AndResGuard-example/app1/src/main/res/.keep",
"chars": 0,
"preview": ""
},
{
"path": "AndResGuard-example/app1/src/test/java/andresguard/tencent/com/andresguard_example/ExampleUnitTest.java",
"chars": 337,
"preview": "package andresguard.tencent.com.andresguard_example;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEqual"
},
{
"path": "AndResGuard-example/app2/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "AndResGuard-example/app2/build.gradle",
"chars": 3478,
"preview": "apply plugin: 'AndResGuard'\napply plugin: 'com.android.application'\n\nbuildscript {\n repositories {\n mavenLocal()\n "
},
{
"path": "AndResGuard-example/app2/proguard-rules.pro",
"chars": 661,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
},
{
"path": "AndResGuard-example/app2/resource_mapping.txt",
"chars": 255,
"preview": "res path mapping:\n res/mipmap-hdpi-v4 -> res/mipmap-hdpi-v4\n res/mipmap-mdpi-v4 -> res/mipmap-mdpi-v4\n res/mipm"
},
{
"path": "AndResGuard-example/app2/src/androidTest/java/andresguard/tencent/com/andresguard_example/ApplicationTest.java",
"chars": 366,
"preview": "package andresguard.tencent.com.andresguard_example;\n\nimport android.app.Application;\nimport android.test.ApplicationTes"
},
{
"path": "AndResGuard-example/app2/src/main/AndroidManifest.xml",
"chars": 1003,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"andresguard.tencent.com.andresguard_example\"\n xmlns:a"
},
{
"path": "AndResGuard-example/app2/src/main/java/andresguard/tencent/com/andresguard_example/MainActivity.java",
"chars": 3422,
"preview": "package andresguard.tencent.com.andresguard_example;\n\nimport android.os.Bundle;\nimport android.support.design.widget.Flo"
},
{
"path": "AndResGuard-example/app2/src/main/res/.keep",
"chars": 0,
"preview": ""
},
{
"path": "AndResGuard-example/app2/src/test/java/andresguard/tencent/com/andresguard_example/ExampleUnitTest.java",
"chars": 337,
"preview": "package andresguard.tencent.com.andresguard_example;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEqual"
},
{
"path": "AndResGuard-example/build.gradle",
"chars": 429,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n rep"
},
{
"path": "AndResGuard-example/gradle/wrapper/gradle-wrapper.properties",
"chars": 232,
"preview": "#Fri May 29 18:04:51 PDT 2020\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
},
{
"path": "AndResGuard-example/gradle.properties",
"chars": 893,
"preview": "## Project-wide Gradle settings.\n#\n# For more details on how to configure your build environment visit\n# http://www.grad"
},
{
"path": "AndResGuard-example/gradlew",
"chars": 4971,
"preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n## Gradle start "
},
{
"path": "AndResGuard-example/gradlew.bat",
"chars": 2404,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
},
{
"path": "AndResGuard-example/libres/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "AndResGuard-example/libres/build.gradle",
"chars": 802,
"preview": "apply plugin: 'com.android.library'\n\nandroid {\n compileSdkVersion 26\n buildToolsVersion '29.0.2'\n defaultConfig {\n "
},
{
"path": "AndResGuard-example/libres/proguard-rules.pro",
"chars": 932,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
},
{
"path": "AndResGuard-example/libres/src/androidTest/java/com/tinkerpatch/libres/ExampleInstrumentedTest.java",
"chars": 734,
"preview": "package com.tinkerpatch.libres;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationRegistry;\nim"
},
{
"path": "AndResGuard-example/libres/src/main/AndroidManifest.xml",
"chars": 59,
"preview": "<manifest package=\"com.tinkerpatch.libres\"\n>\n\n\n</manifest>\n"
},
{
"path": "AndResGuard-example/libres/src/main/res/drawable/side_nav_bar.xml",
"chars": 292,
"preview": "<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:shape=\"rectangle\">\n <gradient\n "
},
{
"path": "AndResGuard-example/libres/src/main/res/drawable-v21/ic_menu_camera.xml",
"chars": 597,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "AndResGuard-example/libres/src/main/res/drawable-v21/ic_menu_gallery.xml",
"chars": 452,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "AndResGuard-example/libres/src/main/res/drawable-v21/ic_menu_manage.xml",
"chars": 488,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "AndResGuard-example/libres/src/main/res/drawable-v21/ic_menu_send.xml",
"chars": 328,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "AndResGuard-example/libres/src/main/res/drawable-v21/ic_menu_share.xml",
"chars": 721,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "AndResGuard-example/libres/src/main/res/drawable-v21/ic_menu_slideshow.xml",
"chars": 442,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "AndResGuard-example/libres/src/main/res/layout/activity_main.xml",
"chars": 1221,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.v4.widget.DrawerLayout android:id=\"@+id/drawer_layout\"\n "
},
{
"path": "AndResGuard-example/libres/src/main/res/layout/app_bar_main.xml",
"chars": 1688,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.design.widget.CoordinatorLayout xmlns:android=\"http://schemas.an"
},
{
"path": "AndResGuard-example/libres/src/main/res/layout/content_main.xml",
"chars": 1015,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "AndResGuard-example/libres/src/main/res/layout/nav_header_main.xml",
"chars": 1462,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "AndResGuard-example/libres/src/main/res/menu/activity_main_drawer.xml",
"chars": 1156,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <group and"
},
{
"path": "AndResGuard-example/libres/src/main/res/menu/main.xml",
"chars": 344,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "AndResGuard-example/libres/src/main/res/values/colors.xml",
"chars": 208,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"colorPrimary\">#3F51B5</color>\n <color name=\"color"
},
{
"path": "AndResGuard-example/libres/src/main/res/values/dimens.xml",
"chars": 434,
"preview": "<resources>\n <!-- Default screen margins, per the Android Design guidelines. -->\n <dimen name=\"nav_header_vertical"
},
{
"path": "AndResGuard-example/libres/src/main/res/values/drawables.xml",
"chars": 555,
"preview": "<resources>\n <item name=\"ic_menu_camera\" type=\"drawable\">@android:drawable/ic_menu_camera</item>\n <item name=\"ic_m"
},
{
"path": "AndResGuard-example/libres/src/main/res/values/strings.xml",
"chars": 287,
"preview": "<resources>\n <string name=\"app_name\">AndResGuard-example</string>\n\n <string name=\"navigation_drawer_open\">Open nav"
},
{
"path": "AndResGuard-example/libres/src/main/res/values/styles.xml",
"chars": 706,
"preview": "<resources>\n\n <!-- Base application theme. -->\n <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar"
},
{
"path": "AndResGuard-example/libres/src/main/res/values-v21/strings.xml",
"chars": 303,
"preview": "<resources>\n <string name=\"app_name\">AndResGuard-example-v21</string>\n\n <string name=\"navigation_drawer_open\">Open"
},
{
"path": "AndResGuard-example/libres/src/main/res/values-v21/styles.xml",
"chars": 327,
"preview": "<resources>\n\n <style name=\"AppTheme.NoActionBar\">\n <item name=\"windowActionBar\">false</item>\n <item nam"
},
{
"path": "AndResGuard-example/libres/src/main/res/values-w820dp/dimens.xml",
"chars": 358,
"preview": "<resources>\n <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n (such as s"
},
{
"path": "AndResGuard-example/libres/src/main/res/values-w820dp/strings.xml",
"chars": 303,
"preview": "<resources>\n <string name=\"app_name\">AndResGuard-example 820</string>\n\n <string name=\"navigation_drawer_open\">Open"
},
{
"path": "AndResGuard-example/libres/src/main/res/values-xxhdpi/strings.xml",
"chars": 303,
"preview": "<resources>\n <string name=\"app_name\">AndResGuard-example xxh</string>\n\n <string name=\"navigation_drawer_open\">Open"
},
{
"path": "AndResGuard-example/libres/src/test/java/com/tinkerpatch/libres/ExampleUnitTest.java",
"chars": 390,
"preview": "package com.tinkerpatch.libres;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit te"
},
{
"path": "AndResGuard-example/settings.gradle",
"chars": 44,
"preview": "include ':app', ':libres', ':app1', ':app2'\n"
},
{
"path": "AndResGuard-gradle-plugin/build.gradle",
"chars": 491,
"preview": "apply plugin: 'groovy'\n\nversion rootProject.ext.VERSION_NAME\ngroup rootProject.ext.GROUP\n\ndependencies {\n compile gradl"
},
{
"path": "AndResGuard-gradle-plugin/gradle.properties",
"chars": 94,
"preview": "POM_ARTIFACT_ID=AndResGuard-gradle-plugin\nPOM_NAME=AndResGuard Gradle Plugin\nPOM_PACKAGING=jar"
},
{
"path": "AndResGuard-gradle-plugin/src/main/groovy/com/tencent/gradle/AndResGuardExtension.groovy",
"chars": 2281,
"preview": "package com.tencent.gradle\n\n/**\n * The configuration properties.\n *\n * @author sim sun (sunsj1231@gmail.com)\n */\n\nclass "
},
{
"path": "AndResGuard-gradle-plugin/src/main/groovy/com/tencent/gradle/AndResGuardPlugin.groovy",
"chars": 1513,
"preview": "package com.tencent.gradle\n\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\n\n/**\n * Registers the plugin's ta"
},
{
"path": "AndResGuard-gradle-plugin/src/main/groovy/com/tencent/gradle/AndResGuardTask.groovy",
"chars": 7339,
"preview": "package com.tencent.gradle\n\nimport com.tencent.mm.androlib.res.util.StringUtil\nimport com.tencent.mm.directory.PathNotEx"
},
{
"path": "AndResGuard-gradle-plugin/src/main/groovy/com/tencent/gradle/BuildInfo.groovy",
"chars": 893,
"preview": "package com.tencent.gradle\n\n/**\n * Created by simsun on 5/13/16.*/\n\nclass BuildInfo {\n def file\n def signConfig\n def "
},
{
"path": "AndResGuard-gradle-plugin/src/main/groovy/com/tencent/gradle/ExecutorExtension.groovy",
"chars": 1892,
"preview": "package com.tencent.gradle\n\nimport org.gradle.api.GradleException\nimport org.gradle.api.Named\nimport org.gradle.api.Proj"
},
{
"path": "AndResGuard-gradle-plugin/src/main/resources/META-INF/gradle-plugins/AndResGuard.properties",
"chars": 57,
"preview": "implementation-class=com.tencent.gradle.AndResGuardPlugin"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 5953,
"preview": "# AndResGuard\n\n[](https://travis-ci.org/s"
},
{
"path": "README.zh-cn.md",
"chars": 4102,
"preview": "# Android资源混淆工具使用说明 #\n\n[](https://travis"
},
{
"path": "SECURITY.md",
"chars": 619,
"preview": "# Security Policy\n\n## Supported Versions\n\nUse this section to tell people about which versions of your project are\ncurre"
},
{
"path": "SevenZip/build.gradle",
"chars": 1252,
"preview": "//apply plugin: 'com.google.osdetector'\napply plugin: 'maven'\napply plugin: 'maven-publish'\napply plugin: 'java'\n\nversio"
},
{
"path": "SevenZip/gradle.properties",
"chars": 44,
"preview": "POM_ARTIFACT_ID=SevenZip\nPOM_NAME=Seven Zip\n"
},
{
"path": "appveyol.yml",
"chars": 587,
"preview": "version: '{build}'\nskip_tags: true\nskip_commits:\n message: /\\[ci skip\\]/\nclone_depth: 10\nenvironment:\n TERM: dumb\n ma"
},
{
"path": "build.gradle",
"chars": 1384,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n repo"
},
{
"path": "doc/how_to_work.md",
"chars": 7004,
"preview": "### With Command Line\n```\n java -jar andresguard-x.x.x.jar -h\n```\n\n**You can find a simple example in `tools_output` "
},
{
"path": "doc/how_to_work.zh-cn.md",
"chars": 6237,
"preview": "\n## 如何使用资源混淆工具 ##\n\n### 使用命令行###\n\n**`tools_output`文件夹有使用命令行工具的简单例子,可以参考**\n\n我们先看看它的help描述,最简单的使用方式是:java -jar andresguard."
},
{
"path": "doc/white_list.md",
"chars": 2645,
"preview": "### Umeng sdk\n```\n\"R.anim.umeng*\",\n\"R.string.umeng*\",\n\"R.string.UM*\",\n\"R.string.tb_*\",\n\"R.layout.umeng*\",\n\"R.layout.soci"
},
{
"path": "gradle/gradle-mvn-push.gradle",
"chars": 1215,
"preview": "apply plugin: 'maven'\napply plugin: 'com.jfrog.bintray'\n\ndef getBintrayUser() {\n return hasProperty('BINTRAY_USER') ? B"
},
{
"path": "gradle/java-artifacts.gradle",
"chars": 677,
"preview": "apply plugin: 'maven-publish'\n\nsourceCompatibility = rootProject.ext.javaVersion\ntargetCompatibility = rootProject.ext.j"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 233,
"preview": "#Fri Jan 01 00:19:05 CST 2016\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
},
{
"path": "gradle.properties",
"chars": 879,
"preview": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will ov"
},
{
"path": "gradlew",
"chars": 5080,
"preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n## Gradle start "
},
{
"path": "gradlew.bat",
"chars": 2404,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
},
{
"path": "package.json",
"chars": 1332,
"preview": "{\n \"name\": \"AndResGuard\",\n \"version\": \"1.0.0\",\n \"description\": \"[![Join the chat at https://gitter.im/shwenzhang/AndR"
},
{
"path": "settings.gradle",
"chars": 97,
"preview": "include ':AndResGuard-core', ':AndResGuard-gradle-plugin', ':AndResGuard-cli'\ninclude 'SevenZip'\n"
},
{
"path": "tool_output/build_apk.bat",
"chars": 394,
"preview": "set jdkpath=D:\\Program Files\\Java\\jdk1.7.0_79\\bin\\java.exe\nset storepath=release.keystore\nset storepass=testres\nset keyp"
},
{
"path": "tool_output/build_apk.sh",
"chars": 169,
"preview": "#!/usr/bin/env bash\n\njava -jar AndResGuard-cli-1.2.15.jar input.apk -config config.xml -out outapk -signatureType v2 -si"
},
{
"path": "tool_output/config.xml",
"chars": 4041,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resproguard>\n <!--defaut property to set -->\n <issue id=\"property\">\n <!--w"
}
]
// ... and 7 more files (download for full content)
About this extraction
This page contains the full source code of the shwenzhang/AndResGuard GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 146 files (399.6 KB), approximately 107.9k tokens, and a symbol index with 560 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.