[
  {
    "path": ".gitignore",
    "content": "# 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\nbin/\ngen/\n\n# Gradle files\n.gradle/\nbuild/\n/*/build/\n*.iml\n/*/*.iml\n.idea\n/*/.idea/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Proguard folder generated by Eclipse\nproguard/\n\n# Log Files\n*.log\n\n.DS_Store\nwork/outapk/*\nlocal_repo\n\ntool_output/outapk\n\nnode_modules/\nclasses\nAndResGuard-example/true\nfinal.apk\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: java\ndist: trusty\nscript: \"./gradlew check\"\njdk:\n  - oraclejdk8\nnotifications:\n  webhooks:\n    urls:\n      - https://webhooks.gitter.im/e/cbb5207fafff92a021b7\n    on_success: change  # options: [always|never|change] default: always\n    on_failure: always  # options: [always|never|change] default: always\n    on_start: never     # options: [always|never|change] default: always"
  },
  {
    "path": "AndResGuard-cli/build.gradle",
    "content": "apply plugin: 'java'\n\n[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8'\n\nversion rootProject.ext.VERSION_NAME\ngroup rootProject.ext.GROUP\n\ndependencies {\n  compile fileTree(dir: 'libs', include: ['*.jar'])\n  //compile group: 'com.tencent.mm', name: 'AndResGuard-core', version: version\n  compile project(':AndResGuard-core')\n}\n\nsourceSets {\n  main {\n    java {\n      srcDir 'src'\n    }\n  }\n}\n\njar {\n  manifest {\n    attributes 'Main-Class': 'com.tencent.mm.resourceproguard.cli.CliMain'\n    attributes 'Manifest-Version': version\n    attributes \"Jar-Version\": \"${ANDRESGUARD_VESSION}\"\n    attributes \"Build-Time\": releaseTime()\n  }\n  from {\n    configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }\n  }\n}\n\n// copy the jar to work directory\ntask buildJar(type: Copy, dependsOn: [build, jar]) {\n  from('build/libs') {\n    include '*' + version + '*.jar'\n  }\n  into('../tool_output')\n}\n\ndef releaseTime() {\n  return new Date().format(\"yyyy-MM-dd HH:mm ZZZ\", TimeZone.getDefault())\n}\n\ndefaultTasks 'buildJar'\n"
  },
  {
    "path": "AndResGuard-cli/src/main/java/com/tencent/mm/resourceproguard/cli/CliMain.java",
    "content": "package com.tencent.mm.resourceproguard.cli;\n\nimport com.tencent.mm.androlib.ResourceRepackage;\nimport com.tencent.mm.resourceproguard.Configuration;\nimport com.tencent.mm.resourceproguard.InputParam;\nimport com.tencent.mm.resourceproguard.Main;\nimport com.tencent.mm.util.TypedValue;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLDecoder;\nimport javax.xml.parsers.ParserConfigurationException;\nimport org.xml.sax.SAXException;\n\n/**\n * Created by simsun on 1/9/16.\n */\npublic class CliMain extends Main {\n\n  private static final String ARG_HELP = \"--help\";\n  private static final String ARG_OUT = \"-out\";\n  private static final String ARG_FINAL_APK_PATH = \"-finalApkPath\";\n  private static final String ARG_CONFIG = \"-config\";\n  private static final String ARG_7ZIP = \"-7zip\";\n  private static final String ARG_ZIPALIGN = \"-zipalign\";\n  private static final String ARG_SIGNATURE = \"-signature\";\n  private static final String ARG_KEEPMAPPING = \"-mapping\";\n  private static final String ARG_REPACKAGE = \"-repackage\";\n  private static final String ARG_SIGNATURE_TYPE = \"-signatureType\";\n  private static final String VALUE_SIGNATURE_TYPE_V1 = \"v1\";\n  private static final String VALUE_SIGNATURE_TYPE_V2 = \"v2\";\n\n  public static void main(String[] args) {\n    mBeginTime = System.currentTimeMillis();\n    CliMain m = new CliMain();\n    setRunningLocation(m);\n    m.run(args);\n  }\n\n  private static void setRunningLocation(CliMain m) {\n    mRunningLocation = m.getClass().getProtectionDomain().getCodeSource().getLocation().getPath();\n    try {\n      mRunningLocation = URLDecoder.decode(mRunningLocation, \"utf-8\");\n    } catch (UnsupportedEncodingException e) {\n      e.printStackTrace();\n    }\n    if (mRunningLocation.endsWith(\".jar\")) {\n      mRunningLocation = mRunningLocation.substring(0, mRunningLocation.lastIndexOf(File.separator) + 1);\n    }\n    File f = new File(mRunningLocation);\n    mRunningLocation = f.getAbsolutePath();\n  }\n\n  private static void printUsage(PrintStream out) {\n    // TODO: Look up launcher script name!\n    String command = \"resousceproguard.jar\"; //$NON-NLS-1$\n    out.println();\n    out.println();\n    out.println(\"Usage: java -jar \" + command + \" input.apk\");\n    out.println(\"if you want to special the output path or config file path, you can input:\");\n    out.println(\"Such as: java -jar \"\n                + command\n                + \" \"\n                + \"input.apk \"\n                + ARG_CONFIG\n                + \" yourconfig.xml \"\n                + ARG_OUT\n                + \" output_directory\");\n    out.println(\"if you want to special the sign or mapping data, you can input:\");\n    out.println(\"Such as: java -jar \"\n                + command\n                + \" \"\n                + \"input.apk \"\n                + ARG_CONFIG\n                + \" yourconfig.xml \"\n                + ARG_OUT\n                + \" output_directory \"\n                + ARG_SIGNATURE\n                + \" signature_file_path storepass keypass storealias \"\n                + ARG_KEEPMAPPING\n                + \" mapping_file_path\");\n    out.println(\"if you want to special the signature type, you can input:\");\n    out.printf(\"Such as: java -jar %s input.apk %s %s/%s\\n\",\n        command,\n        ARG_SIGNATURE_TYPE,\n        VALUE_SIGNATURE_TYPE_V1,\n        VALUE_SIGNATURE_TYPE_V2\n    );\n\n    out.println(\"if you want to special 7za or zipalign path, you can input:\");\n    out.println(\"Such as: java -jar \"\n                + command\n                + \" \"\n                + \"input.apk \"\n                + ARG_7ZIP\n                + \" /home/shwenzhang/tools/7za \"\n                + ARG_ZIPALIGN\n                + \" /home/shwenzhang/sdk/tools/zipalign\");\n\n    out.println(\"if you just want to repackage an apk compress with 7z:\");\n    out.println(\"Such as: java -jar \" + command + \" \" + ARG_REPACKAGE + \" input.apk\");\n    out.println(\"if you want to special the output path, 7za or zipalign path, you can input:\");\n    out.println(\"Such as: java -jar \"\n                + command\n                + \" \"\n                + ARG_REPACKAGE\n                + \" input.apk\"\n                + ARG_OUT\n                + \" output_directory \"\n                + ARG_7ZIP\n                + \" /home/shwenzhang/tools/7za \"\n                + ARG_ZIPALIGN\n                + \"/home/shwenzhang/sdk/tools/zipalign\");\n    out.println(\"if you want to special the final apk path, you can input:\");\n    out.printf(\"Such as: java -jar %s input.apk %s final_apk_path\\n\", command, ARG_FINAL_APK_PATH);\n    out.println();\n    out.println(\"Flags:\\n\");\n\n    printUsage(out, new String[] {\n        ARG_HELP, \"This message.\", \"-h\", \"short for -help\", ARG_OUT,\n        \"set the output directory yourself, if not, the default directory is the running location with name of the input file\",\n        ARG_CONFIG,\n        \"set the config file yourself, if not, the default path is the running location with name config.xml\",\n        ARG_SIGNATURE, \"set sign property, following by parameters: signature_file_path storepass keypass storealias\",\n        \"  \", \"if you set these, the sign data in the config file will be overlayed\", ARG_KEEPMAPPING,\n        \"set keep mapping property, following by parameters: mapping_file_path\", \"  \",\n        \"if you set these, the mapping data in the config file will be overlayed\", ARG_7ZIP,\n        \"set the 7zip path, such as /home/shwenzhang/tools/7za, window will be end of 7za.exe\", ARG_ZIPALIGN,\n        \"set the zipalign, such as /home/shwenzhang/sdk/tools/zipalign, window will be end of zipalign.exe\",\n        ARG_REPACKAGE, \"usually, when we build the channeles apk, it may destroy the 7zip.\", \"  \",\n        \"so you may need to use 7zip to repackage the apk\",\n    });\n    out.println();\n    out.println(\"if you donot know how to write the config file, look at the comment in the default config.xml\");\n    out.println(\"if you want to use 7z, you must install the 7z command line version in window;\");\n    out.println(\"sudo apt-get install p7zip-full in linux\");\n  }\n\n  private static void printUsage(PrintStream out, String[] args) {\n    int argWidth = 0;\n    for (int i = 0; i < args.length; i += 2) {\n      String arg = args[i];\n      argWidth = Math.max(argWidth, arg.length());\n    }\n    argWidth += 2;\n    StringBuilder sb = new StringBuilder();\n    for (int i = 0; i < argWidth; i++) {\n      sb.append(' ');\n    }\n    String indent = sb.toString();\n    String formatString = \"%1$-\" + argWidth + \"s%2$s\"; //$NON-NLS-1$\n\n    for (int i = 0; i < args.length; i += 2) {\n      String arg = args[i];\n      String description = args[i + 1];\n      if (arg.length() == 0) {\n        out.println(description);\n      } else {\n        out.print(wrap(String.format(formatString, arg, description), 300, indent));\n      }\n    }\n  }\n\n  private static String wrap(String explanation, int lineWidth, String hangingIndent) {\n    int explanationLength = explanation.length();\n    StringBuilder sb = new StringBuilder(explanationLength * 2);\n    int index = 0;\n\n    while (index < explanationLength) {\n      int lineEnd = explanation.indexOf('\\n', index);\n      int next;\n\n      if (lineEnd != -1 && (lineEnd - index) < lineWidth) {\n        next = lineEnd + 1;\n      } else {\n        // Line is longer than available width; grab as much as we can\n        lineEnd = Math.min(index + lineWidth, explanationLength);\n        if (lineEnd - index < lineWidth) {\n          next = explanationLength;\n        } else {\n          // then back up to the last space\n          int lastSpace = explanation.lastIndexOf(' ', lineEnd);\n          if (lastSpace > index) {\n            lineEnd = lastSpace;\n            next = lastSpace + 1;\n          } else {\n            // No space anywhere on the line: it contains something wider than\n            // can fit (like a long URL) so just hard break it\n            next = lineEnd + 1;\n          }\n        }\n      }\n      if (sb.length() > 0) {\n        sb.append(hangingIndent);\n      } else {\n        lineWidth -= hangingIndent.length();\n      }\n      sb.append(explanation.substring(index, lineEnd));\n      sb.append('\\n');\n      index = next;\n    }\n\n    return sb.toString();\n  }\n\n  private void run(String[] args) {\n    synchronized (CliMain.class) {\n      if (args.length < 1) {\n        goToError();\n      }\n      final ReadArgs readArgs = new ReadArgs(args).invoke();\n      final File configFile = readArgs.getConfigFile();\n      final File signatureFile = readArgs.getSignatureFile();\n      final File mappingFile = readArgs.getMappingFile();\n      final String keypass = readArgs.getKeypass();\n      final String storealias = readArgs.getStorealias();\n      final String storepass = readArgs.getStorepass();\n      final String signedFile = readArgs.getSignedFile();\n      final File outputFile = readArgs.getOutputFile();\n      final File finalApkFile = readArgs.getFinalApkFile();\n      final String apkFileName = readArgs.getApkFileName();\n      final InputParam.SignatureType signatureType = readArgs.getSignatureType();\n      loadConfigFromXml(configFile, signatureFile, mappingFile, keypass, storealias, storepass);\n\n      //对于repackage模式，不管之前的东东，直接return\n      if (signedFile != null) {\n        ResourceRepackage repackage = new ResourceRepackage(config.mZipalignPath,\n            config.m7zipPath,\n            new File(signedFile)\n        );\n        try {\n          if (outputFile != null) {\n            repackage.setOutDir(outputFile);\n          }\n          repackage.repackageApk();\n        } catch (IOException | InterruptedException e) {\n          e.printStackTrace();\n        }\n        return;\n      }\n      System.out.printf(\"[AndResGuard] begin: %s, %s, %s\\n\", outputFile, finalApkFile, apkFileName);\n      resourceProguard(outputFile, finalApkFile, apkFileName, signatureType);\n      System.out.printf(\"[AndResGuard] done, total time cost: %fs\\n\", diffTimeFromBegin());\n      System.out.printf(\"[AndResGuard] done, you can go to file to find the output %s\\n\", mOutDir.getAbsolutePath());\n      clean();\n    }\n  }\n\n  private void loadConfigFromXml(\n      File configFile, File signatureFile, File mappingFile, String keypass, String storealias, String storepass) {\n    if (configFile == null) {\n      configFile = new File(mRunningLocation + File.separator + TypedValue.CONFIG_FILE);\n      if (!configFile.exists()) {\n        System.err.printf(\"the config file %s does not exit\", configFile.getAbsolutePath());\n        printUsage(System.err);\n        System.exit(ERRNO_USAGE);\n      }\n    }\n    try {\n      //不需要检查命令行的设置\n      if (!mSetSignThroughCmd) {\n        signatureFile = null;\n      }\n      if (!mSetMappingThroughCmd) {\n        mappingFile = null;\n      }\n      config = new Configuration(configFile,\n          m7zipPath,\n          mZipalignPath,\n          mappingFile,\n          signatureFile,\n          keypass,\n          storealias,\n          storepass\n      );\n    } catch (IOException | ParserConfigurationException | SAXException e) {\n      e.printStackTrace();\n      goToError();\n    }\n  }\n\n  public double diffTimeFromBegin() {\n    long end = System.currentTimeMillis();\n    return (end - mBeginTime) / 1000.0;\n  }\n\n  protected void goToError() {\n    printUsage(System.err);\n    System.exit(ERRNO_USAGE);\n  }\n\n  private class ReadArgs {\n    private String[] args;\n    private File configFile;\n    private File outputFile;\n    private File finalApkFile;\n    private String apkFileName;\n    private File signatureFile;\n    private File mappingFile;\n    private String keypass;\n    private String storealias;\n    private String storepass;\n    private InputParam.SignatureType signatureType = InputParam.SignatureType.SchemaV1;\n    private String signedFile;\n\n    public ReadArgs(String[] args) {\n      this.args = args;\n    }\n\n    public File getConfigFile() {\n      return configFile;\n    }\n\n    public File getOutputFile() {\n      return outputFile;\n    }\n\n    public File getFinalApkFile() {\n      return finalApkFile;\n    }\n\n    public String getApkFileName() {\n      return apkFileName;\n    }\n\n    public File getSignatureFile() {\n      return signatureFile;\n    }\n\n    public File getMappingFile() {\n      return mappingFile;\n    }\n\n    public String getKeypass() {\n      return keypass;\n    }\n\n    public String getStorealias() {\n      return storealias;\n    }\n\n    public String getStorepass() {\n      return storepass;\n    }\n\n    public InputParam.SignatureType getSignatureType() {\n      return signatureType;\n    }\n\n    public String getSignedFile() {\n      return signedFile;\n    }\n\n    public ReadArgs invoke() {\n      for (int index = 0; index < args.length; index++) {\n        String arg = args[index];\n        if (arg.equals(ARG_HELP) || arg.equals(\"-h\")) {\n          goToError();\n        } else if (arg.equals(ARG_CONFIG)) {\n          if (index == args.length - 1 || !args[index + 1].endsWith(TypedValue.XML_FILE)) {\n            System.err.println(\"Missing XML configuration file argument\");\n            goToError();\n          }\n          configFile = new File(args[++index]);\n          if (!configFile.exists()) {\n            System.err.println(configFile.getAbsolutePath() + \" does not exist\");\n            goToError();\n          }\n          System.out.printf(\"special configFile file path: %s\\n\", configFile.getAbsolutePath());\n        } else if (arg.equals(ARG_OUT)) {\n          if (index == args.length - 1) {\n            System.err.println(\"Missing output file argument\");\n            goToError();\n          }\n          outputFile = new File(args[++index]);\n          File parent = outputFile.getParentFile();\n          if (parent != null && (!parent.exists())) {\n            parent.mkdirs();\n          }\n          System.out.printf(\"special output directory path: %s\\n\", outputFile.getAbsolutePath());\n        } else if (arg.equals(ARG_FINAL_APK_PATH)) {\n          if (index == args.length - 1) {\n            System.err.println(\"Missing output file argument\");\n            goToError();\n          }\n          finalApkFile = new File(args[++index]);\n          File parent = finalApkFile.getParentFile();\n          if (parent != null && (!parent.exists())) {\n            parent.mkdirs();\n          }\n          System.out.printf(\"special final apk file path: %s\\n\", finalApkFile.getAbsolutePath());\n        } else if (arg.equals(ARG_SIGNATURE)) {\n          //需要检查是否有四个参数\n          if (index == args.length - 1) {\n            System.err.println(\"Missing signature data argument, should be \"\n                               + ARG_SIGNATURE\n                               + \" signature_file_path storepass keypass storealias\");\n            goToError();\n          }\n\n          //在后面设置的时候会检查文件是否存在\n          signatureFile = new File(args[++index]);\n\n          if (index == args.length - 1) {\n            System.err.println(\"Missing signature data argument, should be \"\n                               + ARG_SIGNATURE\n                               + \" signature_file_path storepass keypass storealias\");\n            goToError();\n          }\n\n          storepass = args[++index];\n\n          if (index == args.length - 1) {\n            System.err.println(\"Missing signature data argument, should be \"\n                               + ARG_SIGNATURE\n                               + \" signature_file_path storepass keypass storealias\");\n            goToError();\n          }\n\n          keypass = args[++index];\n\n          if (index == args.length - 1) {\n            System.err.println(\"Missing signature data argument, should be \"\n                               + ARG_SIGNATURE\n                               + \" signature_file_path storepass keypass storealias\");\n            goToError();\n          }\n          storealias = args[++index];\n          mSetSignThroughCmd = true;\n        } else if (arg.equals(ARG_SIGNATURE_TYPE)) {\n          if (index == args.length - 1) {\n            System.err.println(\"Missing signature type argument\");\n            goToError();\n          }\n\n          if (VALUE_SIGNATURE_TYPE_V2.equalsIgnoreCase(args[++index])) {\n            signatureType = InputParam.SignatureType.SchemaV2;\n          } else {\n            signatureType = InputParam.SignatureType.SchemaV1;\n          }\n        } else if (arg.equals(ARG_KEEPMAPPING)) {\n          if (index == args.length - 1) {\n            System.err.println(\"Missing mapping file argument\");\n            goToError();\n          }\n          //在后面设置的时候会检查文件是否存在\n          mappingFile = new File(args[++index]);\n          mSetMappingThroughCmd = true;\n        } else if (arg.equals(ARG_7ZIP)) {\n          if (index == args.length - 1) {\n            System.err.println(\"Missing 7zip path argument\");\n            goToError();\n          }\n          m7zipPath = args[++index];\n        } else if (arg.equals(ARG_ZIPALIGN)) {\n          if (index == args.length - 1) {\n            System.err.println(\"Missing zipalign path argument\");\n            goToError();\n          }\n\n          mZipalignPath = args[++index];\n        } else if (arg.equals(ARG_REPACKAGE)) {\n          //这个模式的话就直接干活了，不会再理其他命令！\n          if (index == args.length - 1) {\n            System.err.println(\"Missing the signed apk file argument\");\n            goToError();\n          }\n          signedFile = args[++index];\n        } else {\n          apkFileName = arg;\n        }\n      }\n      return this;\n    }\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/build.gradle",
    "content": "apply plugin: 'java'\n\n\n\nversion rootProject.ext.VERSION_NAME\ngroup rootProject.ext.GROUP\n\n[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8'\ndependencies {\n  compile fileTree(dir: 'libs', include: ['*.jar'])\n  compile 'com.android.tools.build:gradle:4.1.2'\n  compile 'commons-io:commons-io:2.6'\n}\n\nsourceSets {\n  main {\n    java {\n      srcDir 'src'\n    }\n  }\n}\n[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'\n\napply from: rootProject.file('gradle/java-artifacts.gradle')\napply from: rootProject.file('gradle/gradle-mvn-push.gradle')\n"
  },
  {
    "path": "AndResGuard-core/gradle.properties",
    "content": "POM_ARTIFACT_ID=AndResGuard-core\nPOM_NAME=AndResGuard Core\nPOM_PACKAGING=jar"
  },
  {
    "path": "AndResGuard-core/src/main/java/apksigner/ApkSignerTool.java",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage apksigner;\n\nimport com.android.apksig.ApkSigner;\nimport com.android.apksig.ApkVerifier;\nimport com.android.apksig.apk.MinSdkVersionException;\nimport java.io.BufferedReader;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.io.PrintStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.StandardCopyOption;\nimport java.security.InvalidKeyException;\nimport java.security.Key;\nimport java.security.KeyFactory;\nimport java.security.KeyStore;\nimport java.security.KeyStoreException;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.PrivateKey;\nimport java.security.Provider;\nimport java.security.PublicKey;\nimport java.security.UnrecoverableKeyException;\nimport java.security.cert.Certificate;\nimport java.security.cert.CertificateFactory;\nimport java.security.cert.X509Certificate;\nimport java.security.interfaces.DSAKey;\nimport java.security.interfaces.DSAParams;\nimport java.security.interfaces.ECKey;\nimport java.security.interfaces.RSAKey;\nimport java.security.spec.InvalidKeySpecException;\nimport java.security.spec.PKCS8EncodedKeySpec;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Enumeration;\nimport java.util.List;\nimport javax.crypto.EncryptedPrivateKeyInfo;\nimport javax.crypto.SecretKey;\nimport javax.crypto.SecretKeyFactory;\nimport javax.crypto.spec.PBEKeySpec;\n\n/**\n * Command-line tool for signing APKs and for checking whether an APK's signature are expected to\n * verify on Android devices.\n */\npublic class ApkSignerTool {\n\n  private static final String VERSION = \"0.5\";\n  private static final String HELP_PAGE_GENERAL = \"help.txt\";\n  private static final String HELP_PAGE_SIGN = \"help_sign.txt\";\n  private static final String HELP_PAGE_VERIFY = \"help_verify.txt\";\n\n  public static void main(String[] params) throws Exception {\n    if ((params.length == 0) || (\"--help\".equals(params[0])) || (\"-h\".equals(params[0]))) {\n      printUsage(HELP_PAGE_GENERAL);\n      return;\n    } else if (\"--version\".equals(params[0])) {\n      System.out.println(VERSION);\n      return;\n    }\n\n    String cmd = params[0];\n    try {\n      if (\"sign\".equals(cmd)) {\n        sign(Arrays.copyOfRange(params, 1, params.length));\n        return;\n      } else if (\"verify\".equals(cmd)) {\n        verify(Arrays.copyOfRange(params, 1, params.length));\n        return;\n      } else if (\"help\".equals(cmd)) {\n        printUsage(HELP_PAGE_GENERAL);\n        return;\n      } else if (\"version\".equals(cmd)) {\n        System.out.println(VERSION);\n        return;\n      } else {\n        throw new ParameterException(\"Unsupported command: \" + cmd + \". See --help for supported commands\");\n      }\n    } catch (ParameterException | OptionsParser.OptionsException e) {\n      System.err.println(e.getMessage());\n      System.exit(1);\n      return;\n    }\n  }\n\n  private static void sign(String[] params) throws Exception {\n    if (params.length == 0) {\n      printUsage(HELP_PAGE_SIGN);\n      return;\n    }\n\n    File outputApk = null;\n    File inputApk = null;\n    boolean verbose = false;\n    boolean v1SigningEnabled = true;\n    boolean v2SigningEnabled = true;\n    boolean v3SigningEnabled = false;\n    int minSdkVersion = 1;\n    boolean minSdkVersionSpecified = false;\n    int maxSdkVersion = Integer.MAX_VALUE;\n    List<SignerParams> signers = new ArrayList<>(1);\n    SignerParams signerParams = new SignerParams();\n    OptionsParser optionsParser = new OptionsParser(params);\n    String optionName;\n    String optionOriginalForm = null;\n    while ((optionName = optionsParser.nextOption()) != null) {\n      optionOriginalForm = optionsParser.getOptionOriginalForm();\n      if ((\"help\".equals(optionName)) || (\"h\".equals(optionName))) {\n        printUsage(HELP_PAGE_SIGN);\n        return;\n      } else if (\"out\".equals(optionName)) {\n        outputApk = new File(optionsParser.getRequiredValue(\"Output file name\"));\n      } else if (\"in\".equals(optionName)) {\n        inputApk = new File(optionsParser.getRequiredValue(\"Input file name\"));\n      } else if (\"min-sdk-version\".equals(optionName)) {\n        minSdkVersion = optionsParser.getRequiredIntValue(\"Mininimum API Level\");\n        minSdkVersionSpecified = true;\n      } else if (\"max-sdk-version\".equals(optionName)) {\n        maxSdkVersion = optionsParser.getRequiredIntValue(\"Maximum API Level\");\n      } else if (\"v1-signing-enabled\".equals(optionName)) {\n        v1SigningEnabled = optionsParser.getOptionalBooleanValue(true);\n      } else if (\"v2-signing-enabled\".equals(optionName)) {\n        v2SigningEnabled = optionsParser.getOptionalBooleanValue(true);\n      }  else if (\"v3-signing-enabled\".equals(optionName)) {\n        v3SigningEnabled = optionsParser.getOptionalBooleanValue(false);\n      } else if (\"next-signer\".equals(optionName)) {\n        if (!signerParams.isEmpty()) {\n          signers.add(signerParams);\n          signerParams = new SignerParams();\n        }\n      } else if (\"ks\".equals(optionName)) {\n        signerParams.keystoreFile = optionsParser.getRequiredValue(\"KeyStore file\");\n      } else if (\"ks-key-alias\".equals(optionName)) {\n        signerParams.keystoreKeyAlias = optionsParser.getRequiredValue(\"KeyStore key alias\");\n      } else if (\"ks-pass\".equals(optionName)) {\n        signerParams.keystorePasswordSpec = optionsParser.getRequiredValue(\"KeyStore password\");\n      } else if (\"key-pass\".equals(optionName)) {\n        signerParams.keyPasswordSpec = optionsParser.getRequiredValue(\"Key password\");\n      } else if (\"v1-signer-name\".equals(optionName)) {\n        signerParams.v1SigFileBasename = optionsParser.getRequiredValue(\"JAR signature file basename\");\n      } else if (\"ks-type\".equals(optionName)) {\n        signerParams.keystoreType = optionsParser.getRequiredValue(\"KeyStore type\");\n      } else if (\"ks-provider-name\".equals(optionName)) {\n        signerParams.keystoreProviderName = optionsParser.getRequiredValue(\"JCA KeyStore Provider name\");\n      } else if (\"ks-provider-class\".equals(optionName)) {\n        signerParams.keystoreProviderClass = optionsParser.getRequiredValue(\"JCA KeyStore Provider class name\");\n      } else if (\"ks-provider-arg\".equals(optionName)) {\n        signerParams.keystoreProviderArg = optionsParser.getRequiredValue(\"JCA KeyStore Provider constructor argument\");\n      } else if (\"key\".equals(optionName)) {\n        signerParams.keyFile = optionsParser.getRequiredValue(\"Private key file\");\n      } else if (\"cert\".equals(optionName)) {\n        signerParams.certFile = optionsParser.getRequiredValue(\"Certificate file\");\n      } else if ((\"v\".equals(optionName)) || (\"verbose\".equals(optionName))) {\n        verbose = optionsParser.getOptionalBooleanValue(true);\n      } else {\n        throw new ParameterException(\"Unsupported option: \"\n                                     + optionOriginalForm\n                                     + \". See --help for supported\"\n                                     + \" options.\");\n      }\n    }\n    if (!signerParams.isEmpty()) {\n      signers.add(signerParams);\n    }\n    signerParams = null;\n\n    if (signers.isEmpty()) {\n      throw new ParameterException(\"At least one signer must be specified\");\n    }\n\n    params = optionsParser.getRemainingParams();\n    if (inputApk != null) {\n      // Input APK has been specified via preceding parameters. We don't expect any more\n      // parameters.\n      if (params.length > 0) {\n        throw new ParameterException(\"Unexpected parameter(s) after \" + optionOriginalForm + \": \" + params[0]);\n      }\n    } else {\n      // Input APK has not been specified via preceding parameters. The next parameter is\n      // supposed to be the path to input APK.\n      if (params.length < 1) {\n        throw new ParameterException(\"Missing input APK\");\n      } else if (params.length > 1) {\n        throw new ParameterException(\"Unexpected parameter(s) after input APK (\" + params[1] + \")\");\n      }\n      inputApk = new File(params[0]);\n    }\n    if ((minSdkVersionSpecified) && (minSdkVersion > maxSdkVersion)) {\n      throw new ParameterException(\"Min API Level (\" + minSdkVersion + \") > max API Level (\" + maxSdkVersion + \")\");\n    }\n\n    List<ApkSigner.SignerConfig> signerConfigs = new ArrayList<>(signers.size());\n    int signerNumber = 0;\n    try (PasswordRetriever passwordRetriever = new PasswordRetriever()) {\n      for (SignerParams signer : signers) {\n        signerNumber++;\n        signer.name = \"signer #\" + signerNumber;\n        try {\n          signer.loadPrivateKeyAndCerts(passwordRetriever);\n        } catch (ParameterException e) {\n          System.err.println(\"Failed to load signer \\\"\" + signer.name + \"\\\": \" + e.getMessage());\n          System.exit(2);\n          return;\n        } catch (Exception e) {\n          System.err.println(\"Failed to load signer \\\"\" + signer.name + \"\\\"\");\n          e.printStackTrace();\n          System.exit(2);\n          return;\n        }\n        String v1SigBasename;\n        if (signer.v1SigFileBasename != null) {\n          v1SigBasename = signer.v1SigFileBasename;\n        } else if (signer.keystoreKeyAlias != null) {\n          v1SigBasename = signer.keystoreKeyAlias;\n        } else if (signer.keyFile != null) {\n          String keyFileName = new File(signer.keyFile).getName();\n          int delimiterIndex = keyFileName.indexOf('.');\n          if (delimiterIndex == -1) {\n            v1SigBasename = keyFileName;\n          } else {\n            v1SigBasename = keyFileName.substring(0, delimiterIndex);\n          }\n        } else {\n          throw new RuntimeException(\"Neither KeyStore key alias nor private key file available\");\n        }\n        ApkSigner.SignerConfig signerConfig = new ApkSigner.SignerConfig.Builder(v1SigBasename,\n            signer.privateKey,\n            signer.certs\n        ).build();\n        signerConfigs.add(signerConfig);\n      }\n    }\n\n    if (outputApk == null) {\n      outputApk = inputApk;\n    }\n    File tmpOutputApk;\n    if (inputApk.getCanonicalPath().equals(outputApk.getCanonicalPath())) {\n      tmpOutputApk = File.createTempFile(\"apksigner\", \".apk\");\n      tmpOutputApk.deleteOnExit();\n    } else {\n      tmpOutputApk = outputApk;\n    }\n    ApkSigner.Builder apkSignerBuilder = new ApkSigner.Builder(signerConfigs).setInputApk(inputApk)\n        .setOutputApk(tmpOutputApk)\n        .setOtherSignersSignaturesPreserved(false)\n        .setV1SigningEnabled(v1SigningEnabled)\n        .setV2SigningEnabled(v2SigningEnabled)\n        .setV3SigningEnabled(v3SigningEnabled);\n    if (minSdkVersionSpecified) {\n      apkSignerBuilder.setMinSdkVersion(minSdkVersion);\n    }\n    ApkSigner apkSigner = apkSignerBuilder.build();\n    try {\n      apkSigner.sign();\n    } catch (MinSdkVersionException e) {\n      String msg = e.getMessage();\n      if (!msg.endsWith(\".\")) {\n        msg += '.';\n      }\n      throw new MinSdkVersionException(\"Failed to determine APK's minimum supported platform version\"\n                                       + \". Use --min-sdk-version to override\", e);\n    }\n    if (!tmpOutputApk.getCanonicalPath().equals(outputApk.getCanonicalPath())) {\n      Files.move(tmpOutputApk.toPath(), outputApk.toPath(), StandardCopyOption.REPLACE_EXISTING);\n    }\n\n    if (verbose) {\n      System.out.println(\"Signed\");\n    }\n  }\n\n  private static void verify(String[] params) throws Exception {\n    if (params.length == 0) {\n      printUsage(HELP_PAGE_VERIFY);\n      return;\n    }\n\n    File inputApk = null;\n    int minSdkVersion = 1;\n    boolean minSdkVersionSpecified = false;\n    int maxSdkVersion = Integer.MAX_VALUE;\n    boolean maxSdkVersionSpecified = false;\n    boolean printCerts = false;\n    boolean verbose = false;\n    boolean warningsTreatedAsErrors = false;\n    OptionsParser optionsParser = new OptionsParser(params);\n    String optionName;\n    String optionOriginalForm = null;\n    while ((optionName = optionsParser.nextOption()) != null) {\n      optionOriginalForm = optionsParser.getOptionOriginalForm();\n      if (\"min-sdk-version\".equals(optionName)) {\n        minSdkVersion = optionsParser.getRequiredIntValue(\"Mininimum API Level\");\n        minSdkVersionSpecified = true;\n      } else if (\"max-sdk-version\".equals(optionName)) {\n        maxSdkVersion = optionsParser.getRequiredIntValue(\"Maximum API Level\");\n        maxSdkVersionSpecified = true;\n      } else if (\"print-certs\".equals(optionName)) {\n        printCerts = optionsParser.getOptionalBooleanValue(true);\n      } else if ((\"v\".equals(optionName)) || (\"verbose\".equals(optionName))) {\n        verbose = optionsParser.getOptionalBooleanValue(true);\n      } else if (\"Werr\".equals(optionName)) {\n        warningsTreatedAsErrors = optionsParser.getOptionalBooleanValue(true);\n      } else if ((\"help\".equals(optionName)) || (\"h\".equals(optionName))) {\n        printUsage(HELP_PAGE_VERIFY);\n        return;\n      } else if (\"in\".equals(optionName)) {\n        inputApk = new File(optionsParser.getRequiredValue(\"Input APK file\"));\n      } else {\n        throw new ParameterException(\"Unsupported option: \"\n                                     + optionOriginalForm\n                                     + \". See --help for supported\"\n                                     + \" options.\");\n      }\n    }\n    params = optionsParser.getRemainingParams();\n\n    if (inputApk != null) {\n      // Input APK has been specified in preceding parameters. We don't expect any more\n      // parameters.\n      if (params.length > 0) {\n        throw new ParameterException(\"Unexpected parameter(s) after \" + optionOriginalForm + \": \" + params[0]);\n      }\n    } else {\n      // Input APK has not been specified in preceding parameters. The next parameter is\n      // supposed to be the input APK.\n      if (params.length < 1) {\n        throw new ParameterException(\"Missing APK\");\n      } else if (params.length > 1) {\n        throw new ParameterException(\"Unexpected parameter(s) after APK (\" + params[1] + \")\");\n      }\n      inputApk = new File(params[0]);\n    }\n\n    if ((minSdkVersionSpecified) && (maxSdkVersionSpecified) && (minSdkVersion > maxSdkVersion)) {\n      throw new ParameterException(\"Min API Level (\" + minSdkVersion + \") > max API Level (\" + maxSdkVersion + \")\");\n    }\n\n    ApkVerifier.Builder apkVerifierBuilder = new ApkVerifier.Builder(inputApk);\n    if (minSdkVersionSpecified) {\n      apkVerifierBuilder.setMinCheckedPlatformVersion(minSdkVersion);\n    }\n    if (maxSdkVersionSpecified) {\n      apkVerifierBuilder.setMaxCheckedPlatformVersion(maxSdkVersion);\n    }\n    ApkVerifier apkVerifier = apkVerifierBuilder.build();\n    ApkVerifier.Result result;\n    try {\n      result = apkVerifier.verify();\n    } catch (MinSdkVersionException e) {\n      String msg = e.getMessage();\n      if (!msg.endsWith(\".\")) {\n        msg += '.';\n      }\n      throw new MinSdkVersionException(\"Failed to determine APK's minimum supported platform version\"\n                                       + \". Use --min-sdk-version to override\", e);\n    }\n    boolean verified = result.isVerified();\n\n    boolean warningsEncountered = false;\n    if (verified) {\n      List<X509Certificate> signerCerts = result.getSignerCertificates();\n      if (verbose) {\n        System.out.println(\"Verifies\");\n        System.out.println(\"Verified using v1 scheme (JAR signing): \" + result.isVerifiedUsingV1Scheme());\n        System.out.println(\"Verified using v2 scheme (APK Signature Scheme v2): \" + result.isVerifiedUsingV2Scheme());\n        System.out.println(\"Number of signers: \" + signerCerts.size());\n      }\n      if (printCerts) {\n        int signerNumber = 0;\n        MessageDigest sha256 = MessageDigest.getInstance(\"SHA-256\");\n        MessageDigest sha1 = MessageDigest.getInstance(\"SHA-1\");\n        MessageDigest md5 = MessageDigest.getInstance(\"MD5\");\n        for (X509Certificate signerCert : signerCerts) {\n          signerNumber++;\n          System.out.println(\"Signer #\" + signerNumber + \" certificate DN\" + \": \" + signerCert.getSubjectDN());\n          byte[] encodedCert = signerCert.getEncoded();\n          System.out.println(\"Signer #\"\n                             + signerNumber\n                             + \" certificate SHA-256 digest: \"\n                             + HexEncoding.encode(sha256.digest(encodedCert)));\n          System.out.println(\"Signer #\" + signerNumber + \" certificate SHA-1 digest: \" + HexEncoding.encode(sha1.digest(\n              encodedCert)));\n          System.out.println(\"Signer #\" + signerNumber + \" certificate MD5 digest: \" + HexEncoding.encode(md5.digest(\n              encodedCert)));\n          if (verbose) {\n            PublicKey publicKey = signerCert.getPublicKey();\n            System.out.println(\"Signer #\" + signerNumber + \" key algorithm: \" + publicKey.getAlgorithm());\n            int keySize = -1;\n            if (publicKey instanceof RSAKey) {\n              keySize = ((RSAKey) publicKey).getModulus().bitLength();\n            } else if (publicKey instanceof ECKey) {\n              keySize = ((ECKey) publicKey).getParams().getOrder().bitLength();\n            } else if (publicKey instanceof DSAKey) {\n              // DSA parameters may be inherited from the certificate. We\n              // don't handle this case at the moment.\n              DSAParams dsaParams = ((DSAKey) publicKey).getParams();\n              if (dsaParams != null) {\n                keySize = dsaParams.getP().bitLength();\n              }\n            }\n            System.out.println(\"Signer #\" + signerNumber + \" key size (bits): \" + ((keySize != -1) ? String.valueOf(\n                keySize) : \"n/a\"));\n            byte[] encodedKey = publicKey.getEncoded();\n            System.out.println(\"Signer #\"\n                               + signerNumber\n                               + \" public key SHA-256 digest: \"\n                               + HexEncoding.encode(sha256.digest(encodedKey)));\n            System.out.println(\"Signer #\"\n                               + signerNumber\n                               + \" public key SHA-1 digest: \"\n                               + HexEncoding.encode(sha1.digest(encodedKey)));\n            System.out.println(\"Signer #\" + signerNumber + \" public key MD5 digest: \" + HexEncoding.encode(md5.digest(\n                encodedKey)));\n          }\n        }\n      }\n    } else {\n      System.err.println(\"DOES NOT VERIFY\");\n    }\n\n    for (ApkVerifier.IssueWithParams error : result.getErrors()) {\n      System.err.println(\"ERROR: \" + error);\n    }\n\n    @SuppressWarnings(\"resource\") // false positive -- this resource is not opened here\n        PrintStream warningsOut = (warningsTreatedAsErrors) ? System.err : System.out;\n    for (ApkVerifier.IssueWithParams warning : result.getWarnings()) {\n      warningsEncountered = true;\n      warningsOut.println(\"WARNING: \" + warning);\n    }\n    for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) {\n      String signerName = signer.getName();\n      for (ApkVerifier.IssueWithParams error : signer.getErrors()) {\n        System.err.println(\"ERROR: JAR signer \" + signerName + \": \" + error);\n      }\n      for (ApkVerifier.IssueWithParams warning : signer.getWarnings()) {\n        warningsEncountered = true;\n        warningsOut.println(\"WARNING: JAR signer \" + signerName + \": \" + warning);\n      }\n    }\n    for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) {\n      String signerName = \"signer #\" + (signer.getIndex() + 1);\n      for (ApkVerifier.IssueWithParams error : signer.getErrors()) {\n        System.err.println(\"ERROR: APK Signature Scheme v2 \" + signerName + \": \" + error);\n      }\n      for (ApkVerifier.IssueWithParams warning : signer.getWarnings()) {\n        warningsEncountered = true;\n        warningsOut.println(\"WARNING: APK Signature Scheme v2 \" + signerName + \": \" + warning);\n      }\n    }\n\n    if (!verified) {\n      System.exit(1);\n      return;\n    }\n    if ((warningsTreatedAsErrors) && (warningsEncountered)) {\n      System.exit(1);\n      return;\n    }\n  }\n\n  private static void printUsage(String page) {\n    try (BufferedReader in = new BufferedReader(new InputStreamReader(\n        ApkSignerTool.class.getResourceAsStream(page),\n        StandardCharsets.UTF_8\n    ))) {\n      String line;\n      while ((line = in.readLine()) != null) {\n        System.out.println(line);\n      }\n    } catch (IOException e) {\n      throw new RuntimeException(\"Failed to read \" + page + \" resource\");\n    }\n  }\n\n  private static byte[] readFully(File file) throws IOException {\n    ByteArrayOutputStream result = new ByteArrayOutputStream();\n    try (FileInputStream in = new FileInputStream(file)) {\n      drain(in, result);\n    }\n    return result.toByteArray();\n  }\n\n  private static void drain(InputStream in, OutputStream out) throws IOException {\n    byte[] buf = new byte[65536];\n    int chunkSize;\n    while ((chunkSize = in.read(buf)) != -1) {\n      out.write(buf, 0, chunkSize);\n    }\n  }\n\n  private static class SignerParams {\n    String name;\n\n    String keystoreFile;\n    String keystoreKeyAlias;\n    String keystorePasswordSpec;\n    String keyPasswordSpec;\n    String keystoreType;\n    String keystoreProviderName;\n    String keystoreProviderClass;\n    String keystoreProviderArg;\n\n    String keyFile;\n    String certFile;\n\n    String v1SigFileBasename;\n\n    PrivateKey privateKey;\n    List<X509Certificate> certs;\n\n    private static void loadKeyStoreFromFile(KeyStore ks, String file, List<char[]> passwords) throws Exception {\n      Exception lastFailure = null;\n      for (char[] password : passwords) {\n        try {\n          try (FileInputStream in = new FileInputStream(file)) {\n            ks.load(in, password);\n          }\n          return;\n        } catch (Exception e) {\n          lastFailure = e;\n        }\n      }\n      if (lastFailure == null) {\n        throw new RuntimeException(\"No keystore passwords\");\n      } else {\n        throw lastFailure;\n      }\n    }\n\n    private static Key getKeyStoreKey(KeyStore ks, String keyAlias, List<char[]> passwords)\n        throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException {\n      UnrecoverableKeyException lastFailure = null;\n      for (char[] password : passwords) {\n        try {\n          return ks.getKey(keyAlias, password);\n        } catch (UnrecoverableKeyException e) {\n          lastFailure = e;\n        }\n      }\n      if (lastFailure == null) {\n        throw new RuntimeException(\"No key passwords\");\n      } else {\n        throw lastFailure;\n      }\n    }\n\n    private static PKCS8EncodedKeySpec decryptPkcs8EncodedKey(\n        EncryptedPrivateKeyInfo encryptedPrivateKeyInfo, List<char[]> passwords)\n        throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {\n      SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());\n      InvalidKeySpecException lastKeySpecException = null;\n      InvalidKeyException lastKeyException = null;\n      for (char[] password : passwords) {\n        PBEKeySpec decryptionKeySpec = new PBEKeySpec(password);\n        try {\n          SecretKey decryptionKey = keyFactory.generateSecret(decryptionKeySpec);\n          return encryptedPrivateKeyInfo.getKeySpec(decryptionKey);\n        } catch (InvalidKeySpecException e) {\n          lastKeySpecException = e;\n        } catch (InvalidKeyException e) {\n          lastKeyException = e;\n        }\n      }\n      if ((lastKeyException == null) && (lastKeySpecException == null)) {\n        throw new RuntimeException(\"No passwords\");\n      } else if (lastKeyException != null) {\n        throw lastKeyException;\n      } else {\n        throw lastKeySpecException;\n      }\n    }\n\n    private static PrivateKey loadPkcs8EncodedPrivateKey(PKCS8EncodedKeySpec spec)\n        throws InvalidKeySpecException, NoSuchAlgorithmException {\n      try {\n        return KeyFactory.getInstance(\"RSA\").generatePrivate(spec);\n      } catch (InvalidKeySpecException expected) {\n      }\n      try {\n        return KeyFactory.getInstance(\"EC\").generatePrivate(spec);\n      } catch (InvalidKeySpecException expected) {\n      }\n      try {\n        return KeyFactory.getInstance(\"DSA\").generatePrivate(spec);\n      } catch (InvalidKeySpecException expected) {\n      }\n      throw new InvalidKeySpecException(\"Not an RSA, EC, or DSA private key\");\n    }\n\n    private boolean isEmpty() {\n      return (name == null)\n             && (keystoreFile == null)\n             && (keystoreKeyAlias == null)\n             && (keystorePasswordSpec == null)\n             && (keyPasswordSpec == null)\n             && (keystoreType == null)\n             && (keystoreProviderName == null)\n             && (keystoreProviderClass == null)\n             && (keystoreProviderArg == null)\n             && (keyFile == null)\n             && (certFile == null)\n             && (v1SigFileBasename == null)\n             && (privateKey == null)\n             && (certs == null);\n    }\n\n    private void loadPrivateKeyAndCerts(PasswordRetriever passwordRetriever) throws Exception {\n      if (keystoreFile != null) {\n        if (keyFile != null) {\n          throw new ParameterException(\"--ks and --key may not be specified at the same time\");\n        } else if (certFile != null) {\n          throw new ParameterException(\"--ks and --cert may not be specified at the same time\");\n        }\n        loadPrivateKeyAndCertsFromKeyStore(passwordRetriever);\n      } else if (keyFile != null) {\n        loadPrivateKeyAndCertsFromFiles(passwordRetriever);\n      } else {\n        throw new ParameterException(\"KeyStore (--ks) or private key file (--key) must be specified\");\n      }\n    }\n\n    private void loadPrivateKeyAndCertsFromKeyStore(PasswordRetriever passwordRetriever) throws Exception {\n      if (keystoreFile == null) {\n        throw new ParameterException(\"KeyStore (--ks) must be specified\");\n      }\n\n      // 1. Obtain a KeyStore implementation\n      String ksType = (keystoreType != null) ? keystoreType : KeyStore.getDefaultType();\n      KeyStore ks;\n      if (keystoreProviderName != null) {\n        // Use a named Provider (assumes the provider is already installed)\n        ks = KeyStore.getInstance(ksType, keystoreProviderName);\n      } else if (keystoreProviderClass != null) {\n        // Use a new Provider instance (does not require the provider to be installed)\n        Class<?> ksProviderClass = Class.forName(keystoreProviderClass);\n        if (!Provider.class.isAssignableFrom(ksProviderClass)) {\n          throw new ParameterException(\"Keystore Provider class \"\n                                       + keystoreProviderClass\n                                       + \" not subclass of \"\n                                       + Provider.class.getName());\n        }\n        Provider ksProvider;\n        if (keystoreProviderArg != null) {\n          // Single-arg Provider constructor\n          ksProvider = (Provider) ksProviderClass.getConstructor(String.class).newInstance(keystoreProviderArg);\n        } else {\n          // No-arg Provider constructor\n          ksProvider = (Provider) ksProviderClass.getConstructor().newInstance();\n        }\n        ks = KeyStore.getInstance(ksType, ksProvider);\n      } else {\n        // Use the highest-priority Provider which offers the requested KeyStore type\n        ks = KeyStore.getInstance(ksType);\n      }\n\n      // 2. Load the KeyStore\n      List<char[]> keystorePasswords = null;\n      if (\"NONE\".equals(keystoreFile)) {\n        ks.load(null);\n      } else {\n        String keystorePasswordSpec =\n            (this.keystorePasswordSpec != null) ? this.keystorePasswordSpec : PasswordRetriever.SPEC_STDIN;\n        keystorePasswords = passwordRetriever.getPasswords(keystorePasswordSpec, \"Keystore password for \" + name);\n        loadKeyStoreFromFile(ks, keystoreFile, keystorePasswords);\n      }\n\n      // 3. Load the PrivateKey and cert chain from KeyStore\n      String keyAlias = null;\n      PrivateKey key = null;\n      try {\n        if (keystoreKeyAlias == null) {\n          // Private key entry alias not specified. Find the key entry contained in this\n          // KeyStore. If the KeyStore contains multiple key entries, return an error.\n          Enumeration<String> aliases = ks.aliases();\n          if (aliases != null) {\n            while (aliases.hasMoreElements()) {\n              String entryAlias = aliases.nextElement();\n              if (ks.isKeyEntry(entryAlias)) {\n                keyAlias = entryAlias;\n                if (keystoreKeyAlias != null) {\n                  throw new ParameterException(keystoreFile\n                                               + \" contains multiple key entries\"\n                                               + \". --ks-key-alias option must be used to specify\"\n                                               + \" which entry to use.\");\n                }\n                keystoreKeyAlias = keyAlias;\n              }\n            }\n          }\n          if (keystoreKeyAlias == null) {\n            throw new ParameterException(keystoreFile + \" does not contain key entries\");\n          }\n        }\n\n        // Private key entry alias known. Load that entry's private key.\n        keyAlias = keystoreKeyAlias;\n        if (!ks.isKeyEntry(keyAlias)) {\n          throw new ParameterException(keystoreFile + \" entry \\\"\" + keyAlias + \"\\\" does not contain a key\");\n        }\n\n        Key entryKey;\n        if (keyPasswordSpec != null) {\n          // Key password spec is explicitly specified. Use this spec to obtain the\n          // password and then load the key using that password.\n          List<char[]> keyPasswords = passwordRetriever.getPasswords(keyPasswordSpec,\n              \"Key \\\"\" + keyAlias + \"\\\" password for \" + name\n          );\n          entryKey = getKeyStoreKey(ks, keyAlias, keyPasswords);\n        } else {\n          // Key password spec is not specified. This means we should assume that key\n          // password is the same as the keystore password and that, if this assumption is\n          // wrong, we should prompt for key password and retry loading the key using that\n          // password.\n          try {\n            entryKey = getKeyStoreKey(ks, keyAlias, keystorePasswords);\n          } catch (UnrecoverableKeyException expected) {\n            List<char[]> keyPasswords = passwordRetriever.getPasswords(PasswordRetriever.SPEC_STDIN,\n                \"Key \\\"\" + keyAlias + \"\\\" password for \" + name\n            );\n            entryKey = getKeyStoreKey(ks, keyAlias, keyPasswords);\n          }\n        }\n\n        if (entryKey == null) {\n          throw new ParameterException(keystoreFile + \" entry \\\"\" + keyAlias + \"\\\" does not contain a key\");\n        } else if (!(entryKey instanceof PrivateKey)) {\n          throw new ParameterException(keystoreFile\n                                       + \" entry \\\"\"\n                                       + keyAlias\n                                       + \"\\\" does not contain a private\"\n                                       + \" key. It contains a key of algorithm: \"\n                                       + entryKey.getAlgorithm());\n        }\n        key = (PrivateKey) entryKey;\n      } catch (UnrecoverableKeyException e) {\n        throw new IOException(\"Failed to obtain key with alias \\\"\"\n                              + keyAlias\n                              + \"\\\" from \"\n                              + keystoreFile\n                              + \". Wrong password?\", e);\n      }\n      this.privateKey = key;\n      Certificate[] certChain = ks.getCertificateChain(keyAlias);\n      if ((certChain == null) || (certChain.length == 0)) {\n        throw new ParameterException(keystoreFile + \" entry \\\"\" + keyAlias + \"\\\" does not contain certificates\");\n      }\n      this.certs = new ArrayList<>(certChain.length);\n      for (Certificate cert : certChain) {\n        this.certs.add((X509Certificate) cert);\n      }\n    }\n\n    private void loadPrivateKeyAndCertsFromFiles(PasswordRetriever passwordRetriver) throws Exception {\n      if (keyFile == null) {\n        throw new ParameterException(\"Private key file (--key) must be specified\");\n      }\n      if (certFile == null) {\n        throw new ParameterException(\"Certificate file (--cert) must be specified\");\n      }\n      byte[] privateKeyBlob = readFully(new File(keyFile));\n\n      PKCS8EncodedKeySpec keySpec;\n      // Potentially encrypted key blob\n      try {\n        EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(privateKeyBlob);\n\n        // The blob is indeed an encrypted private key blob\n        String passwordSpec = (keyPasswordSpec != null) ? keyPasswordSpec : PasswordRetriever.SPEC_STDIN;\n        List<char[]> keyPasswords = passwordRetriver.getPasswords(passwordSpec, \"Private key password for \" + name);\n        keySpec = decryptPkcs8EncodedKey(encryptedPrivateKeyInfo, keyPasswords);\n      } catch (IOException e) {\n        // The blob is not an encrypted private key blob\n        if (keyPasswordSpec == null) {\n          // Given that no password was specified, assume the blob is an unencrypted\n          // private key blob\n          keySpec = new PKCS8EncodedKeySpec(privateKeyBlob);\n        } else {\n          throw new InvalidKeySpecException(\"Failed to parse encrypted private key blob \" + keyFile, e);\n        }\n      }\n\n      // Load the private key from its PKCS #8 encoded form.\n      try {\n        privateKey = loadPkcs8EncodedPrivateKey(keySpec);\n      } catch (InvalidKeySpecException e) {\n        throw new InvalidKeySpecException(\"Failed to load PKCS #8 encoded private key from \" + keyFile, e);\n      }\n\n      // Load certificates\n      Collection<? extends Certificate> certs;\n      try (FileInputStream in = new FileInputStream(certFile)) {\n        certs = CertificateFactory.getInstance(\"X.509\").generateCertificates(in);\n      }\n      List<X509Certificate> certList = new ArrayList<>(certs.size());\n      for (Certificate cert : certs) {\n        certList.add((X509Certificate) cert);\n      }\n      this.certs = certList;\n    }\n  }\n\n  /**\n   * Indicates that there is an issue with command-line parameters provided to this tool.\n   */\n  private static class ParameterException extends Exception {\n    private static final long serialVersionUID = 1L;\n\n    ParameterException(String message) {\n      super(message);\n    }\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/apksigner/HexEncoding.java",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage apksigner;\n\nimport java.nio.ByteBuffer;\n\n/**\n * Hexadecimal encoding where each byte is represented by two hexadecimal digits.\n */\nclass HexEncoding {\n\n  private static final char[] HEX_DIGITS = \"0123456789abcdef\".toCharArray();\n\n  /**\n   * Hidden constructor to prevent instantiation.\n   */\n  private HexEncoding() {\n  }\n\n  /**\n   * Encodes the provided data as a hexadecimal string.\n   */\n  public static String encode(byte[] data, int offset, int length) {\n    StringBuilder result = new StringBuilder(length * 2);\n    for (int i = 0; i < length; i++) {\n      byte b = data[offset + i];\n      result.append(HEX_DIGITS[(b >>> 4) & 0x0f]);\n      result.append(HEX_DIGITS[b & 0x0f]);\n    }\n    return result.toString();\n  }\n\n  /**\n   * Encodes the provided data as a hexadecimal string.\n   */\n  public static String encode(byte[] data) {\n    return encode(data, 0, data.length);\n  }\n\n  /**\n   * Encodes the remaining bytes of the provided {@link ByteBuffer} as a hexadecimal string.\n   */\n  public static String encodeRemaining(ByteBuffer data) {\n    return encode(data.array(), data.arrayOffset() + data.position(), data.remaining());\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/apksigner/OptionsParser.java",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage apksigner;\n\nimport java.util.Arrays;\n\n/**\n * Parser of command-line options/switches/flags.\n *\n * <p>Supported option formats:\n * <ul>\n * <li>{@code --name value}</li>\n * <li>{@code --name=value}</li>\n * <li>{@code -name value}</li>\n * <li>{@code --name} (boolean options only)</li>\n * </ul>\n *\n * <p>To use the parser, create an instance, providing it with the command-line parameters, then\n * iterate over options by invoking {@link #nextOption()} until it returns {@code null}.\n */\nclass OptionsParser {\n  private final String[] mParams;\n  private int mIndex;\n  private String mLastOptionValue;\n  private String mLastOptionOriginalForm;\n\n  /**\n   * Constructs a new {@code OptionsParser} initialized with the provided command-line.\n   */\n  public OptionsParser(String[] params) {\n    mParams = params.clone();\n  }\n\n  /**\n   * Returns the name (without leading dashes) of the next option (starting with the very first\n   * option) or {@code null} if there are no options left.\n   *\n   * <p>The value of this option can be obtained via {@link #getRequiredValue(String)},\n   * {@link #getRequiredIntValue(String)}, and {@link #getOptionalBooleanValue(boolean)}.\n   */\n  public String nextOption() {\n    if (mIndex >= mParams.length) {\n      // No more parameters left\n      return null;\n    }\n    String param = mParams[mIndex];\n    if (!param.startsWith(\"-\")) {\n      // Not an option\n      return null;\n    }\n\n    mIndex++;\n    mLastOptionOriginalForm = param;\n    mLastOptionValue = null;\n    if (param.startsWith(\"--\")) {\n      // FORMAT: --name value OR --name=value\n      if (\"--\".equals(param)) {\n        // End of options marker\n        return null;\n      }\n      int valueDelimiterIndex = param.indexOf('=');\n      if (valueDelimiterIndex != -1) {\n        mLastOptionValue = param.substring(valueDelimiterIndex + 1);\n        mLastOptionOriginalForm = param.substring(0, valueDelimiterIndex);\n        return param.substring(\"--\".length(), valueDelimiterIndex);\n      } else {\n        return param.substring(\"--\".length());\n      }\n    } else {\n      // FORMAT: -name value\n      return param.substring(\"-\".length());\n    }\n  }\n\n  /**\n   * Returns the original form of the current option. The original form includes the leading dash\n   * or dashes. This is intended to be used for referencing the option in error messages.\n   */\n  public String getOptionOriginalForm() {\n    return mLastOptionOriginalForm;\n  }\n\n  /**\n   * Returns the value of the current option, throwing an exception if the value is missing.\n   */\n  public String getRequiredValue(String valueDescription) throws OptionsException {\n    if (mLastOptionValue != null) {\n      String result = mLastOptionValue;\n      mLastOptionValue = null;\n      return result;\n    }\n    if (mIndex >= mParams.length) {\n      // No more parameters left\n      throw new OptionsException(valueDescription + \" missing after \" + mLastOptionOriginalForm);\n    }\n    String param = mParams[mIndex];\n    if (\"--\".equals(param)) {\n      // End of options marker\n      throw new OptionsException(valueDescription + \" missing after \" + mLastOptionOriginalForm);\n    }\n    mIndex++;\n    return param;\n  }\n\n  /**\n   * Returns the value of the current numeric option, throwing an exception if the value is\n   * missing or is not numeric.\n   */\n  public int getRequiredIntValue(String valueDescription) throws OptionsException {\n    String value = getRequiredValue(valueDescription);\n    try {\n      return Integer.parseInt(value);\n    } catch (NumberFormatException e) {\n      throw new OptionsException(valueDescription\n                                 + \" (\"\n                                 + mLastOptionOriginalForm\n                                 + \") must be a decimal number: \"\n                                 + value);\n    }\n  }\n\n  /**\n   * Gets the value of the current boolean option. Boolean options are not required to have\n   * explicitly specified values.\n   */\n  public boolean getOptionalBooleanValue(boolean defaultValue) throws OptionsException {\n    if (mLastOptionValue != null) {\n      // --option=value form\n      String stringValue = mLastOptionValue;\n      mLastOptionValue = null;\n      if (\"true\".equals(stringValue)) {\n        return true;\n      } else if (\"false\".equals(stringValue)) {\n        return false;\n      }\n      throw new OptionsException(\"Unsupported value for \"\n                                 + mLastOptionOriginalForm\n                                 + \": \"\n                                 + stringValue\n                                 + \". Only true or false supported.\");\n    }\n\n    // --option (true|false) form OR just --option\n    if (mIndex >= mParams.length) {\n      return defaultValue;\n    }\n\n    String stringValue = mParams[mIndex];\n    if (\"true\".equals(stringValue)) {\n      mIndex++;\n      return true;\n    } else if (\"false\".equals(stringValue)) {\n      mIndex++;\n      return false;\n    } else {\n      return defaultValue;\n    }\n  }\n\n  /**\n   * Returns the remaining command-line parameters. This is intended to be invoked once\n   * {@link #nextOption()} returns {@code null}.\n   */\n  public String[] getRemainingParams() {\n    if (mIndex >= mParams.length) {\n      return new String[0];\n    }\n    String param = mParams[mIndex];\n    if (\"--\".equals(param)) {\n      // Skip end of options marker\n      return Arrays.copyOfRange(mParams, mIndex + 1, mParams.length);\n    } else {\n      return Arrays.copyOfRange(mParams, mIndex, mParams.length);\n    }\n  }\n\n  /**\n   * Indicates that an error was encountered while parsing command-line options.\n   */\n  public static class OptionsException extends Exception {\n    private static final long serialVersionUID = 1L;\n\n    public OptionsException(String message) {\n      super(message);\n    }\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/apksigner/PasswordRetriever.java",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage apksigner;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.Console;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PushbackInputStream;\nimport java.lang.reflect.Method;\nimport java.nio.ByteBuffer;\nimport java.nio.CharBuffer;\nimport java.nio.charset.Charset;\nimport java.nio.charset.CodingErrorAction;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Retriever of passwords based on password specs supported by {@code apksigner} tool.\n *\n * <p>apksigner supports retrieving multiple passwords from the same source (e.g., file, standard\n * input) which adds the need to keep some sources open across password retrievals. This class\n * addresses the need.\n *\n * <p>To use this retriever, construct a new instance, use {@link #getPasswords(String, String)} to\n * retrieve passwords, and then invoke {@link #close()} on the instance when done, enabling the\n * instance to release any held resources.\n */\nclass PasswordRetriever implements AutoCloseable {\n  public static final String SPEC_STDIN = \"stdin\";\n\n  private static final Charset CONSOLE_CHARSET = getConsoleEncoding();\n\n  private final Map<File, InputStream> mFileInputStreams = new HashMap<>();\n\n  private boolean mClosed;\n\n  /**\n   * Returns the provided password and all password variants derived from the password. The\n   * resulting list is guaranteed to contain at least one element.\n   */\n  private static List<char[]> getPasswords(char[] pwd) {\n    List<char[]> passwords = new ArrayList<>(3);\n    addPasswords(passwords, pwd);\n    return passwords;\n  }\n\n  /**\n   * Returns the provided password and all password variants derived from the password. The\n   * resulting list is guaranteed to contain at least one element.\n   *\n   * @param encodedPwd password encoded using the provided character encoding.\n   * @param encodings character encodings in which the password is encoded in {@code encodedPwd}.\n   */\n  private static List<char[]> getPasswords(byte[] encodedPwd, Charset... encodings) {\n    List<char[]> passwords = new ArrayList<>(4);\n\n    for (Charset encoding : encodings) {\n      // Decode password and add it and its variants to the list\n      try {\n        char[] pwd = decodePassword(encodedPwd, encoding);\n        addPasswords(passwords, pwd);\n      } catch (IOException ignored) {\n      }\n    }\n\n    // Add the original encoded form\n    addPassword(passwords, castBytesToChars(encodedPwd));\n    return passwords;\n  }\n\n  /**\n   * Adds the provided password and its variants to the provided list of passwords.\n   *\n   * <p>NOTE: This method adds only the passwords/variants which are not yet in the list.\n   */\n  private static void addPasswords(List<char[]> passwords, char[] pwd) {\n    // Verbatim password\n    addPassword(passwords, pwd);\n\n    // Password encoded using the JVM default character encoding and upcast into char[]\n    try {\n      char[] encodedPwd = castBytesToChars(encodePassword(pwd, Charset.defaultCharset()));\n      addPassword(passwords, encodedPwd);\n    } catch (IOException ignored) {\n    }\n\n    // Password encoded using console character encoding and upcast into char[]\n    if (!CONSOLE_CHARSET.equals(Charset.defaultCharset())) {\n      try {\n        char[] encodedPwd = castBytesToChars(encodePassword(pwd, CONSOLE_CHARSET));\n        addPassword(passwords, encodedPwd);\n      } catch (IOException ignored) {\n      }\n    }\n  }\n\n  /**\n   * Adds the provided password to the provided list. Does nothing if the password is already in\n   * the list.\n   */\n  private static void addPassword(List<char[]> passwords, char[] password) {\n    for (char[] existingPassword : passwords) {\n      if (Arrays.equals(password, existingPassword)) {\n        return;\n      }\n    }\n    passwords.add(password);\n  }\n\n  private static byte[] encodePassword(char[] pwd, Charset cs) throws IOException {\n    ByteBuffer pwdBytes = cs.newEncoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(\n        CodingErrorAction.REPLACE).encode(CharBuffer.wrap(pwd));\n    byte[] encoded = new byte[pwdBytes.remaining()];\n    pwdBytes.get(encoded);\n    return encoded;\n  }\n\n  private static char[] decodePassword(byte[] pwdBytes, Charset encoding) throws IOException {\n    CharBuffer pwdChars = encoding.newDecoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(\n        CodingErrorAction.REPLACE).decode(ByteBuffer.wrap(pwdBytes));\n    char[] result = new char[pwdChars.remaining()];\n    pwdChars.get(result);\n    return result;\n  }\n\n  /**\n   * Upcasts each {@code byte} in the provided array of bytes to a {@code char} and returns the\n   * resulting array of characters.\n   */\n  private static char[] castBytesToChars(byte[] bytes) {\n    if (bytes == null) {\n      return null;\n    }\n\n    char[] chars = new char[bytes.length];\n    for (int i = 0; i < bytes.length; i++) {\n      chars[i] = (char) (bytes[i] & 0xff);\n    }\n    return chars;\n  }\n\n  /**\n   * Returns the character encoding used by the console.\n   */\n  private static Charset getConsoleEncoding() {\n    // IMPLEMENTATION NOTE: There is no public API for obtaining the console's character\n    // encoding. We thus cheat by using implementation details of the most popular JVMs.\n    String consoleCharsetName;\n    try {\n      Method encodingMethod = Console.class.getDeclaredMethod(\"encoding\");\n      encodingMethod.setAccessible(true);\n      consoleCharsetName = (String) encodingMethod.invoke(null);\n      if (consoleCharsetName == null) {\n        return Charset.defaultCharset();\n      }\n    } catch (ReflectiveOperationException e) {\n      Charset defaultCharset = Charset.defaultCharset();\n      System.err.println(\"warning: Failed to obtain console character encoding name. Assuming \" + defaultCharset);\n      return defaultCharset;\n    }\n\n    try {\n      return Charset.forName(consoleCharsetName);\n    } catch (IllegalArgumentException e) {\n      // On Windows 10, cp65001 is the UTF-8 code page. For some reason, popular JVMs don't\n      // have a mapping for cp65001...\n      if (\"cp65001\".equals(consoleCharsetName)) {\n        return StandardCharsets.UTF_8;\n      }\n      Charset defaultCharset = Charset.defaultCharset();\n      System.err.println(\"warning: Console uses unknown character encoding: \"\n                         + consoleCharsetName\n                         + \". Using \"\n                         + defaultCharset\n                         + \" instead\");\n      return defaultCharset;\n    }\n  }\n\n  private static byte[] readEncodedPassword(InputStream in) throws IOException {\n    ByteArrayOutputStream result = new ByteArrayOutputStream();\n    int b;\n    while ((b = in.read()) != -1) {\n      if (b == '\\n') {\n        break;\n      } else if (b == '\\r') {\n        int next = in.read();\n        if ((next == -1) || (next == '\\n')) {\n          break;\n        }\n\n        if (!(in instanceof PushbackInputStream)) {\n          in = new PushbackInputStream(in);\n        }\n        ((PushbackInputStream) in).unread(next);\n      }\n      result.write(b);\n    }\n    return result.toByteArray();\n  }\n\n  /**\n   * Returns the passwords described by the provided spec. The reason there may be more than one\n   * password is compatibility with {@code keytool} and {@code jarsigner} which in certain cases\n   * use the form of passwords encoded using the console's character encoding.\n   *\n   * <p>Supported specs:\n   * <ul>\n   * <li><em>stdin</em> -- read password as a line from console, if available, or standard\n   * input if console is not available</li>\n   * <li><em>pass:password</em> -- password specified inside the spec, starting after\n   * {@code pass:}</li>\n   * <li><em>file:path</em> -- read password as a line from the specified file</li>\n   * <li><em>env:name</em> -- password is in the specified environment variable</li>\n   * </ul>\n   *\n   * <p>When the same file (including standard input) is used for providing multiple passwords,\n   * the passwords are read from the file one line at a time.\n   */\n  public List<char[]> getPasswords(String spec, String description) throws IOException {\n    // IMPLEMENTATION NOTE: Java KeyStore and PBEKeySpec APIs take passwords as arrays of\n    // Unicode characters (char[]). Unfortunately, it appears that Sun/Oracle keytool and\n    // jarsigner in some cases use passwords which are the encoded form obtained using the\n    // console's character encoding. For example, if the encoding is UTF-8, keytool and\n    // jarsigner will use the password which is obtained by upcasting each byte of the UTF-8\n    // encoded form to char. This occurs only when the password is read from stdin/console, and\n    // does not occur when the password is read from a command-line parameter.\n    // There are other tools which use the Java KeyStore API correctly.\n    // Thus, for each password spec, there may be up to three passwords:\n    // * Unicode characters,\n    // * characters (upcast bytes) obtained from encoding the password using the console's\n    //   character encoding,\n    // * characters (upcast bytes) obtained from encoding the password using the JVM's default\n    //   character encoding.\n    //\n    // For a sample password \"\\u0061\\u0062\\u00a1\\u00e4\\u044e\\u0031\":\n    // On Windows 10 with English US as the UI language, IBM437 is used as console encoding and\n    // windows-1252 is used as the JVM default encoding:\n    // * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000\n    //     -alias test\n    //   generates a keystore and key which decrypt only with\n    //   \"\\u0061\\u0062\\u00ad\\u0084\\u003f\\u0031\"\n    // * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000\n    //     -alias test -storepass <pass here>\n    //   generates a keystore and key which decrypt only with\n    //   \"\\u0061\\u0062\\u00a1\\u00e4\\u003f\\u0031\"\n    // On modern OSX/Linux UTF-8 is used as the console and JVM default encoding:\n    // * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000\n    //     -alias test\n    //   generates a keystore and key which decrypt only with\n    //   \"\\u0061\\u0062\\u00c2\\u00a1\\u00c3\\u00a4\\u00d1\\u008e\\u0031\"\n    // * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000\n    //     -alias test\n    //   generates a keystore and key which decrypt only with\n    //   \"\\u0061\\u0062\\u00a1\\u00e4\\u044e\\u0031\"\n\n    assertNotClosed();\n    if (spec.startsWith(\"pass:\")) {\n      char[] pwd = spec.substring(\"pass:\".length()).toCharArray();\n      return getPasswords(pwd);\n    } else if (SPEC_STDIN.equals(spec)) {\n      Console console = System.console();\n      if (console != null) {\n        // Reading from console\n        char[] pwd = console.readPassword(description + \": \");\n        if (pwd == null) {\n          throw new IOException(\"Failed to read \" + description + \": console closed\");\n        }\n        return getPasswords(pwd);\n      } else {\n        // Console not available -- reading from redirected input\n        System.out.println(description + \": \");\n        byte[] encodedPwd = readEncodedPassword(System.in);\n        if (encodedPwd.length == 0) {\n          throw new IOException(\"Failed to read \" + description + \": standard input closed\");\n        }\n        // By default, textual input obtained via standard input is supposed to be decoded\n        // using the in JVM default character encoding but we also try the console's\n        // encoding just in case.\n        return getPasswords(encodedPwd, Charset.defaultCharset(), CONSOLE_CHARSET);\n      }\n    } else if (spec.startsWith(\"file:\")) {\n      String name = spec.substring(\"file:\".length());\n      File file = new File(name).getCanonicalFile();\n      InputStream in = mFileInputStreams.get(file);\n      if (in == null) {\n        in = new FileInputStream(file);\n        mFileInputStreams.put(file, in);\n      }\n      byte[] encodedPwd = readEncodedPassword(in);\n      if (encodedPwd.length == 0) {\n        throw new IOException(\"Failed to read \" + description + \" : end of file reached in \" + file);\n      }\n      // By default, textual input from files is supposed to be treated as encoded using JVM's\n      // default character encoding.\n      return getPasswords(encodedPwd, Charset.defaultCharset());\n    } else if (spec.startsWith(\"env:\")) {\n      String name = spec.substring(\"env:\".length());\n      String value = System.getenv(name);\n      if (value == null) {\n        throw new IOException(\"Failed to read \" + description + \": environment variable \" + value + \" not specified\");\n      }\n      return getPasswords(value.toCharArray());\n    } else {\n      throw new IOException(\"Unsupported password spec for \" + description + \": \" + spec);\n    }\n  }\n\n  private void assertNotClosed() {\n    if (mClosed) {\n      throw new IllegalStateException(\"Closed\");\n    }\n  }\n\n  @Override\n  public void close() {\n    for (InputStream in : mFileInputStreams.values()) {\n      try {\n        in.close();\n      } catch (IOException ignored) {\n      }\n    }\n    mFileInputStreams.clear();\n    mClosed = true;\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/apksigner/help.txt",
    "content": "USAGE: apksigner <command> [options]\n       apksigner --version\n       apksigner --help\n\nEXAMPLE:\n       apksigner sign --ks release.jks app.apk\n       apksigner verify --verbose app.apk\n\napksigner is a tool for signing Android APK files and for checking whether\nsignatures of APK files will verify on Android devices.\n\n\n        COMMANDS\n\nsign                  Sign the provided APK\n\nverify                Check whether the provided APK is expected to verify on\n                      Android\n\nversion               Show this tool's version number and exit\n\nhelp                  Show this usage page and exit\n\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/apksigner/help_sign.txt",
    "content": "USAGE: apksigner sign [options] apk\n\nThis signs the provided APK, stripping out any pre-existing signatures. Signing\nis performed using one or more signers, each represented by an asymmetric key\npair and a corresponding certificate. Typically, an APK is signed by just one\nsigner. For each signer, you need to provide the signer's private key and\ncertificate.\n\n\n        GENERAL OPTIONS\n\n--in                  Input APK file to sign. This is an alternative to\n                      specifying the APK as the very last parameter, after all\n                      options. Unless --out is specified, this file will be\n                      overwritten with the resulting signed APK.\n\n--out                 File into which to output the signed APK. By default, the\n                      APK is signed in-place, overwriting the input file.\n\n-v, --verbose         Verbose output mode\n\n--v1-signing-enabled  Whether to enable signing using JAR signing scheme (aka v1\n                      signing scheme) used in Android since day one. By default,\n                      signing using this scheme is enabled based on min and max\n                      SDK version (see --min-sdk-version and --max-sdk-version).\n\n--v2-signing-enabled  Whether to enable signing using APK Signature Scheme v2\n                      (aka v2 signing scheme) introduced in Android Nougat,\n                      API Level 24. By default, signing using this scheme is\n                      enabled based on min and max SDK version (see\n                      --min-sdk-version and --max-sdk-version).\n\n--min-sdk-version     Lowest API Level on which this APK's signatures will be\n                      verified. By default, the value from AndroidManifest.xml\n                      is used. The higher the value, the stronger security\n                      parameters are used when signing.\n\n--max-sdk-version     Highest API Level on which this APK's signatures will be\n                      verified. By default, the highest possible value is used.\n\n-h, --help            Show help about this command and exit\n\n\n        PER-SIGNER OPTIONS\nThese options specify the configuration of a particular signer. To delimit\noptions of different signers, use --next-signer.\n\n--next-signer         Delimits options of two different signers. There is no\n                      need to use this option when only one signer is used.\n\n--v1-signer-name      Basename for files comprising the JAR signature scheme\n                      (aka v1 scheme) signature of this signer. By default,\n                      KeyStore key alias or basename of key file is used.\n\n        PER-SIGNER SIGNING KEY & CERTIFICATE OPTIONS\nThere are two ways to provide the signer's private key and certificate: (1) Java\nKeyStore (see --ks), or (2) private key file in PKCS #8 format and certificate\nfile in X.509 format (see --key and --cert).\n\n--ks                  Load private key and certificate chain from the Java\n                      KeyStore initialized from the specified file. NONE means\n                      no file is needed by KeyStore, which is the case for some\n                      PKCS #11 KeyStores.\n\n--ks-key-alias        Alias under which the private key and certificate are\n                      stored in the KeyStore. This must be specified if the\n                      KeyStore contains multiple keys.\n\n--ks-pass             KeyStore password (see --ks). The following formats are\n                      supported:\n                          pass:<password> password provided inline\n                          env:<name>      password provided in the named\n                                          environment variable\n                          file:<file>     password provided in the named\n                                          file, as a single line\n                          stdin           password provided on standard input,\n                                          as a single line\n                      A password is required to open a KeyStore.\n                      By default, the tool will prompt for password via console\n                      or standard input.\n                      When the same file (including standard input) is used for\n                      providing multiple passwords, the passwords are read from\n                      the file one line at a time. Passwords are read in the\n                      order in which signers are specified and, within each\n                      signer, KeyStore password is read before the key password\n                      is read.\n\n--key-pass            Password with which the private key is protected.\n                      The following formats are supported:\n                          pass:<password> password provided inline\n                          env:<name>      password provided in the named\n                                          environment variable\n                          file:<file>     password provided in the named\n                                          file, as a single line\n                          stdin           password provided on standard input,\n                                          as a single line\n                      If --key-pass is not specified for a KeyStore key, this\n                      tool will attempt to load the key using the KeyStore\n                      password and, if that fails, will prompt for key password\n                      and attempt to load the key using that password.\n                      If --key-pass is not specified for a private key file key,\n                      this tool will prompt for key password only if a password\n                      is required.\n                      When the same file (including standard input) is used for\n                      providing multiple passwords, the passwords are read from\n                      the file one line at a time. Passwords are read in the\n                      order in which signers are specified and, within each\n                      signer, KeyStore password is read before the key password\n                      is read.\n\n--ks-type             Type/algorithm of KeyStore to use. By default, the default\n                      type is used.\n\n--ks-provider-name    Name of the JCA Provider from which to request the\n                      KeyStore implementation. By default, the highest priority\n                      provider is used. See --ks-provider-class for the\n                      alternative way to specify a provider.\n\n--ks-provider-class   Fully-qualified class name of the JCA Provider from which\n                      to request the KeyStore implementation. By default, the\n                      provider is chosen based on --ks-provider-name.\n\n--ks-provider-arg     Value to pass into the constructor of the JCA Provider\n                      class specified by --ks-provider-class. The value is\n                      passed into the constructor as java.lang.String. By\n                      default, the no-arg provider's constructor is used.\n\n--key                 Load private key from the specified file. If the key is\n                      password-protected, the password will be prompted via\n                      standard input unless specified otherwise using\n                      --key-pass. The file must be in PKCS #8 DER format.\n\n--cert                Load certificate chain from the specified file. The file\n                      must be in X.509 PEM or DER format.\n\n\n        EXAMPLES\n\n1. Sign an APK, in-place, using the one and only key in keystore release.jks:\n$ apksigner sign --ks release.jks app.apk\n\n1. Sign an APK, without overwriting, using the one and only key in keystore\n   release.jks:\n$ apksigner sign --ks release.jks --in app.apk --out app-signed.apk\n\n3. Sign an APK using a private key and certificate stored as individual files:\n$ apksigner sign --key release.pk8 --cert release.x509.pem app.apk\n\n4. Sign an APK using two keys:\n$ apksigner sign --ks release.jks --next-signer --ks magic.jks app.apk\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/apksigner/help_verify.txt",
    "content": "USAGE: apksigner verify [options] apk\n\nThis checks whether the provided APK will verify on Android. By default, this\nchecks whether the APK will verify on all Android platform versions supported\nby the APK (as declared using minSdkVersion in AndroidManifest.xml). Use\n--min-sdk-version and/or --max-sdk-version to verify the APK against a custom\nrange of API Levels.\n\n\n        OPTIONS\n\n--print-certs         Show information about the APK's signing certificates\n\n-v, --verbose         Verbose output mode\n\n--min-sdk-version     Lowest API Level on which this APK's signatures will be\n                      verified. By default, the value from AndroidManifest.xml\n                      is used.\n\n--max-sdk-version     Highest API Level on which this APK's signatures will be\n                      verified. By default, the highest possible value is used.\n\n-Werr                 Treat warnings as errors\n\n--in                  APK file to verify. This is an alternative to specifying\n                      the APK as the very last parameter, after all options.\n\n-h, --help            Show help about this command and exit\n\n\n        EXAMPLES\n\n1. Check whether the APK's signatures are expected to verify on all Android\n   platforms declared as supported by this APK:\n$ apksigner verify app.apk\n\n2. Check whether the APK's signatures are expected to verify on Android\n   platforms with API Level 15 and higher:\n$ apksigner verify --min-sdk-version 15 app.apk\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/mindprod/ledatastream/LEDataInputStream.java",
    "content": "/*\n * @(#)LEDataInputStream.java\n *\n * Summary: Little-Endian version of DataInputStream.\n *\n * Copyright: (c) 1998-2010 Roedy Green, Canadian Mind Products, http://mindprod.com\n *\n * Licence: This software may be copied and used freely for any purpose but military.\n *          http://mindprod.com/contact/nonmil.html\n *\n * Requires: JDK 1.1+\n *\n * Created with: IntelliJ IDEA IDE.\n *\n * Version History:\n *  1.8 2007-05-24\n */\npackage com.mindprod.ledatastream;\n\nimport java.io.DataInput;\nimport java.io.DataInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Little-Endian version of DataInputStream.\n * <p>\n * Very similar to DataInputStream except it reads little-endian instead of\n * big-endian binary data. We can't extend DataInputStream directly since it has\n * only final methods, though DataInputStream itself is not final. This forces\n * us implement LEDataInputStream with a DataInputStream object, and use wrapper\n * methods.\n *\n * @author Roedy Green, Canadian Mind Products\n * @version 1.8 2007-05-24\n * @since 1998\n */\npublic final class LEDataInputStream implements DataInput {\n  // ------------------------------ CONSTANTS ------------------------------\n\n  /**\n   * undisplayed copyright notice.\n   */\n  private static final String EMBEDDED_COPYRIGHT =\n      \"copyright (c) 1999-2010 Roedy Green, Canadian Mind Products, http://mindprod.com\";\n\n  // ------------------------------ FIELDS ------------------------------\n\n  /**\n   * to get at the big-Endian methods of a basic DataInputStream\n   */\n  protected final DataInputStream dis;\n\n  /**\n   * to get at the a basic readBytes method.\n   */\n  protected final InputStream is;\n\n  /**\n   * work array for buffering input.\n   */\n  protected final byte[] work;\n\n  // -------------------------- PUBLIC STATIC METHODS\n  // --------------------------\n\n  /**\n   * constructor.\n   *\n   * @param in binary inputstream of little-endian data.\n   */\n  public LEDataInputStream(InputStream in) {\n    this.is = in;\n    this.dis = new DataInputStream(in);\n    work = new byte[8];\n  }\n\n  // -------------------------- PUBLIC INSTANCE METHODS\n  // --------------------------\n\n  /**\n   * Note. This is a STATIC method!\n   *\n   * @param in stream to read UTF chars from (endian irrelevant)\n   * @return string from stream\n   * @throws IOException if read fails.\n   */\n  public static String readUTF(DataInput in) throws IOException {\n    return DataInputStream.readUTF(in);\n  }\n\n  /**\n   * close.\n   *\n   * @throws IOException if close fails.\n   */\n  public final void close() throws IOException {\n    dis.close();\n  }\n\n  /**\n   * Read bytes. Watch out, read may return fewer bytes than requested.\n   *\n   * @param ba where the bytes go.\n   * @param off offset in buffer, not offset in file.\n   * @param len count of bytes to read.\n   * @return how many bytes read.\n   * @throws IOException if read fails.\n   */\n  public final int read(byte ba[], int off, int len) throws IOException {\n    // For efficiency, we avoid one layer of wrapper\n    return is.read(ba, off, len);\n  }\n\n  /**\n   * read only a one-byte boolean.\n   *\n   * @return true or false.\n   * @throws IOException if read fails.\n   * @see java.io.DataInput#readBoolean()\n   */\n  @Override\n  public final boolean readBoolean() throws IOException {\n    return dis.readBoolean();\n  }\n\n  /**\n   * read byte.\n   *\n   * @return the byte read.\n   * @throws IOException if read fails.\n   * @see java.io.DataInput#readByte()\n   */\n  @Override\n  public final byte readByte() throws IOException {\n    return dis.readByte();\n  }\n\n  /**\n   * Read on char. like DataInputStream.readChar except little endian.\n   *\n   * @return little endian 16-bit unicode char from the stream.\n   * @throws IOException if read fails.\n   */\n  @Override\n  public final char readChar() throws IOException {\n    dis.readFully(work, 0, 2);\n    return (char) ((work[1] & 0xff) << 8 | (work[0] & 0xff));\n  }\n\n  /**\n   * Read a double. like DataInputStream.readDouble except little endian.\n   *\n   * @return little endian IEEE double from the datastream.\n   * @throws IOException ioexception\n   */\n  @Override\n  public final double readDouble() throws IOException {\n    return Double.longBitsToDouble(readLong());\n  }\n\n  /**\n   * Read one float. Like DataInputStream.readFloat except little endian.\n   *\n   * @return little endian IEEE float from the datastream.\n   * @throws IOException if read fails.\n   */\n  @Override\n  public final float readFloat() throws IOException {\n    return Float.intBitsToFloat(readInt());\n  }\n\n  /**\n   * Read bytes until the array is filled.\n   *\n   * @see java.io.DataInput#readFully(byte[])\n   */\n  @Override\n  public final void readFully(byte ba[]) throws IOException {\n    dis.readFully(ba, 0, ba.length);\n  }\n\n  /**\n   * Read bytes until the count is satisfied.\n   *\n   * @throws IOException if read fails.\n   * @see java.io.DataInput#readFully(byte[], int, int)\n   */\n  @Override\n  public final void readFully(byte ba[], int off, int len) throws IOException {\n    dis.readFully(ba, off, len);\n  }\n\n  /**\n   * Read an int, 32-bits. Like DataInputStream.readInt except little endian.\n   *\n   * @return little-endian binary int from the datastream\n   * @throws IOException if read fails.\n   */\n  @Override\n  public final int readInt() throws IOException {\n    dis.readFully(work, 0, 4);\n    return (work[3]) << 24 | (work[2] & 0xff) << 16 | (work[1] & 0xff) << 8 | (work[0] & 0xff);\n  }\n\n  /**\n   * Read a line.\n   *\n   * @return a rough approximation of the 8-bit stream as a 16-bit unicode\n   *     string\n   * @throws IOException ioexception\n   * @deprecated This method does not properly convert bytes to characters.\n   *     Use a Reader instead with a little-endian encoding.\n   */\n  @Deprecated\n  @Override\n  public final String readLine() throws IOException {\n    return dis.readLine();\n  }\n\n  /**\n   * read a long, 64-bits. Like DataInputStream.readLong except little endian.\n   *\n   * @return little-endian binary long from the datastream.\n   * @throws IOException ioexception\n   */\n  @Override\n  public final long readLong() throws IOException {\n    dis.readFully(work, 0, 8);\n    return (long) (work[7]) << 56\n           |\n        /* long cast needed or shift done modulo 32 */\n           (long) (work[6] & 0xff) << 48\n           | (long) (work[5] & 0xff) << 40\n           | (long) (work[4] & 0xff) << 32\n           | (long) (work[3] & 0xff) << 24\n           | (long) (work[2] & 0xff) << 16\n           | (long) (work[1] & 0xff) << 8\n           | work[0] & 0xff;\n  }\n\n  /**\n   * Read short, 16-bits. Like DataInputStream.readShort except little endian.\n   *\n   * @return little endian binary short from stream.\n   * @throws IOException if read fails.\n   */\n  @Override\n  public final short readShort() throws IOException {\n    dis.readFully(work, 0, 2);\n    return (short) ((work[1] & 0xff) << 8 | (work[0] & 0xff));\n  }\n\n  /**\n   * Read UTF counted string.\n   *\n   * @return String read.\n   */\n  @Override\n  public final String readUTF() throws IOException {\n    return dis.readUTF();\n  }\n\n  /**\n   * Read an unsigned byte. Note: returns an int, even though says Byte\n   * (non-Javadoc)\n   *\n   * @throws IOException if read fails.\n   * @see java.io.DataInput#readUnsignedByte()\n   */\n  @Override\n  public final int readUnsignedByte() throws IOException {\n    return dis.readUnsignedByte();\n  }\n\n  /**\n   * Read an unsigned short, 16 bits. Like DataInputStream.readUnsignedShort\n   * except little endian. Note, returns int even though it reads a short.\n   *\n   * @return little-endian int from the stream.\n   * @throws IOException if read fails.\n   */\n  @Override\n  public final int readUnsignedShort() throws IOException {\n    dis.readFully(work, 0, 2);\n    return ((work[1] & 0xff) << 8 | (work[0] & 0xff));\n  }\n\n  /**\n   * Skip over bytes in the stream. See the general contract of the\n   * <code>skipBytes</code> method of <code>DataInput</code>.\n   * <p>\n   * Bytes for this operation are read from the contained input stream.\n   *\n   * @param n the number of bytes to be skipped.\n   * @return the actual number of bytes skipped.\n   * @throws IOException if an I/O error occurs.\n   */\n  @Override\n  public final int skipBytes(int n) throws IOException {\n    return dis.skipBytes(n);\n  }\n}"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/mindprod/ledatastream/LEDataOutputStream.java",
    "content": "/**\n * Description:\n * LEDataOutputStream.java Create on 2014-5-14\n *\n * @author shaowenzhang <shaowenzhang@tencent.com>\n * @version 1.0\n *     Copyright (c) 2014 Tecent WXG AndroidTeam. All Rights Reserved.\n */\npackage com.mindprod.ledatastream;\n\nimport java.io.OutputStream;\n\npublic class LEDataOutputStream extends LittleEndianDataOutputStream {\n\n  public LEDataOutputStream(OutputStream out) {\n    super(out);\n    // TODO Auto-generated constructor stub\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/mindprod/ledatastream/LittleEndianDataOutputStream.java",
    "content": "/**\n * Description:\n * LittleEndianDataOutputStream.java Create on 2014-5-14\n *\n * @author shaowenzhang <shaowenzhang@tencent.com>\n * @version 1.0\n *     Copyright (c) 2014 Tecent WXG AndroidTeam. All Rights Reserved.\n */\npackage com.mindprod.ledatastream;\n\n/**\n * Copyright (C) 2007 The Guava Authors\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport java.io.DataOutput;\nimport java.io.DataOutputStream;\nimport java.io.FilterOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/***\n * An implementation of {@link DataOutput} that uses little-endian byte ordering\n * for writing {@code char}, {@code short}, {@code int}, {@code float}, {@code\n * double}, and {@code long} values.\n * <p>\n * <b>Note:</b> This class intentionally violates the specification of its\n * supertype {@code DataOutput}, which explicitly requires big-endian byte\n * order.\n *\n * @author Chris Nokleberg\n * @author Keith Bottner\n * @since 8.0\n */\n\npublic class LittleEndianDataOutputStream extends FilterOutputStream implements DataOutput {\n\n  /***\n   * Creates a {@code LittleEndianDataOutputStream} that wraps the given stream.\n   *\n   * @param out the stream to delegate to\n   */\n  public LittleEndianDataOutputStream(OutputStream out) {\n    super(new DataOutputStream(out));\n  }\n\n  /***\n   * Returns a big-endian representation of {@code value} in an 8-element byte\n   * array; equivalent to {@code ByteBuffer.allocate(8).putLong(value).array()}.\n   * For example, the input value {@code 0x1213141516171819L} would yield the\n   * byte array {@code {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19}}.\n   * @param value long\n   * @return byte array\n   */\n  public static byte[] toByteArray(long value) {\n    // Note that this code needs to stay compatible with GWT, which has known\n    // bugs when narrowing byte casts of long values occur.\n    byte[] result = new byte[8];\n    for (int i = 7; i >= 0; i--) {\n      result[i] = (byte) (value & 0xffL);\n      value >>= 8;\n    }\n    return result;\n  }\n\n  @Override\n  public void write(byte[] b, int off, int len) throws IOException {\n    // Override slow FilterOutputStream impl\n    out.write(b, off, len);\n  }\n\n  @Override\n  public void writeBoolean(boolean v) throws IOException {\n    ((DataOutputStream) out).writeBoolean(v);\n  }\n\n  @Override\n  public void writeByte(int v) throws IOException {\n    ((DataOutputStream) out).writeByte(v);\n  }\n\n  /***\n   * @deprecated The semantics of {@code writeBytes(String s)} are considered\n   *             dangerous. Please use {@link #writeUTF(String s)},\n   *             {@link #writeChars(String s)} or another write method instead.\n   */\n  @Deprecated\n  @Override\n  public void writeBytes(String s) throws IOException {\n    ((DataOutputStream) out).writeBytes(s);\n  }\n\n  /***\n   * Writes a char as specified by {@link DataOutputStream#writeChar(int)},\n   * except using little-endian byte order.\n   *\n   * @throws IOException if an I/O error occurs\n   */\n  @Override\n  public void writeChar(int v) throws IOException {\n    writeShort(v);\n  }\n\n  /***\n   * Writes a {@code String} as specified by\n   * {@link DataOutputStream#writeChars(String)}, except each character is\n   * written using little-endian byte order.\n   *\n   * @throws IOException if an I/O error occurs\n   */\n  @Override\n  public void writeChars(String s) throws IOException {\n    for (int i = 0; i < s.length(); i++) {\n      writeChar(s.charAt(i));\n    }\n  }\n\n  /***\n   * Writes a {@code double} as specified by\n   * {@link DataOutputStream#writeDouble(double)}, except using little-endian\n   * byte order.\n   *\n   * @throws IOException if an I/O error occurs\n   */\n  @Override\n  public void writeDouble(double v) throws IOException {\n    writeLong(Double.doubleToLongBits(v));\n  }\n\n  /***\n   * Writes a {@code float} as specified by\n   * {@link DataOutputStream#writeFloat(float)}, except using little-endian byte\n   * order.\n   *\n   * @throws IOException if an I/O error occurs\n   */\n  @Override\n  public void writeFloat(float v) throws IOException {\n    writeInt(Float.floatToIntBits(v));\n  }\n\n  /***\n   * Writes an {@code int} as specified by\n   * {@link DataOutputStream#writeInt(int)}, except using little-endian byte\n   * order.\n   *\n   * @throws IOException if an I/O error occurs\n   */\n  @Override\n  public void writeInt(int v) throws IOException {\n    out.write(0xFF & v);\n    out.write(0xFF & (v >> 8));\n    out.write(0xFF & (v >> 16));\n    out.write(0xFF & (v >> 24));\n  }\n\n  /***\n   * Writes a {@code long} as specified by\n   * {@link DataOutputStream#writeLong(long)}, except using little-endian byte\n   * order.\n   *\n   * @throws IOException if an I/O error occurs\n   */\n  @Override\n  public void writeLong(long v) throws IOException {\n    byte[] bytes = toByteArray(Long.reverseBytes(v));\n    write(bytes, 0, bytes.length);\n  }\n\n  /***\n   * Writes a {@code short} as specified by\n   * {@link DataOutputStream#writeShort(int)}, except using little-endian byte\n   * order.\n   *\n   * @throws IOException if an I/O error occurs\n   */\n  @Override\n  public void writeShort(int v) throws IOException {\n    out.write(0xFF & v);\n    out.write(0xFF & (v >> 8));\n  }\n\n  @Override\n  public void writeUTF(String str) throws IOException {\n    ((DataOutputStream) out).writeUTF(str);\n  }\n}"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/AndrolibException.java",
    "content": "package com.tencent.mm.androlib;\n\n/**\n * @author shwenzhang\n */\npublic class AndrolibException extends Exception {\n  public AndrolibException() {\n  }\n\n  public AndrolibException(String message) {\n    super(message);\n  }\n\n  public AndrolibException(String message, Throwable cause) {\n    super(message, cause);\n  }\n\n  public AndrolibException(Throwable cause) {\n    super(cause);\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/ApkDecoder.java",
    "content": "package com.tencent.mm.androlib;\n\nimport com.tencent.mm.androlib.res.data.ResPackage;\nimport com.tencent.mm.androlib.res.decoder.ARSCDecoder;\nimport com.tencent.mm.androlib.res.decoder.RawARSCDecoder;\nimport com.tencent.mm.androlib.res.util.ExtFile;\nimport com.tencent.mm.directory.DirectoryException;\nimport com.tencent.mm.resourceproguard.Configuration;\nimport com.tencent.mm.util.FileOperation;\nimport com.tencent.mm.util.TypedValue;\nimport com.tencent.mm.util.Utils;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.FileVisitResult;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.SimpleFileVisitor;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.Map.Entry;\nimport java.util.regex.Pattern;\n\n/**\n * @author shwenzhang\n */\npublic class ApkDecoder {\n\n  final HashSet<Path> mRawResourceFiles = new HashSet<>();\n  private final Configuration config;\n  private final ExtFile apkFile;\n  private File mOutDir;\n  private File mOutTempARSCFile;\n  private File mOutARSCFile;\n  private File mOutResFile;\n  private File mRawResFile;\n  private File mOutTempDir;\n  private File mResMappingFile;\n  private File mMergeDuplicatedResMappingFile;\n  private HashMap<String, Integer> mCompressData;\n\n  public ApkDecoder(Configuration config, File apkFile) {\n    this.config = config;\n    this.apkFile = new ExtFile(apkFile);\n  }\n\n  private void copyOtherResFiles() throws IOException {\n    if (mRawResourceFiles.isEmpty()) {\n      return;\n    }\n    Path resPath = mRawResFile.toPath();\n    Path destPath = mOutResFile.toPath();\n\n    for (Path path : mRawResourceFiles) {\n      Path relativePath = resPath.relativize(path);\n      Path dest = destPath.resolve(relativePath);\n\n      System.out.printf(\"copy res file not in resources.arsc file:%s\\n\", relativePath.toString());\n      FileOperation.copyFileUsingStream(path.toFile(), dest.toFile());\n    }\n  }\n\n  public void removeCopiedResFile(Path key) {\n    mRawResourceFiles.remove(key);\n  }\n\n  public Configuration getConfig() {\n    return config;\n  }\n\n  public boolean hasResources() throws AndrolibException {\n    try {\n      return apkFile.getDirectory().containsFile(\"resources.arsc\");\n    } catch (DirectoryException ex) {\n      throw new AndrolibException(ex);\n    }\n  }\n\n  private void ensureFilePath() throws IOException {\n    Utils.cleanDir(mOutDir);\n\n    String unZipDest = new File(mOutDir, TypedValue.UNZIP_FILE_PATH).getAbsolutePath();\n    System.out.printf(\"unziping apk to %s\\n\", unZipDest);\n    mCompressData = FileOperation.unZipAPk(apkFile.getAbsoluteFile().getAbsolutePath(), unZipDest);\n    dealWithCompressConfig();\n    //将res混淆成r\n    if (!config.mKeepRoot) {\n      mOutResFile = new File(mOutDir.getAbsolutePath() + File.separator + TypedValue.RES_FILE_PATH);\n    } else {\n      mOutResFile = new File(mOutDir.getAbsolutePath() + File.separator + \"res\");\n    }\n\n    //这个需要混淆各个文件夹\n    mRawResFile = new File(mOutDir.getAbsoluteFile().getAbsolutePath()\n                           + File.separator\n                           + TypedValue.UNZIP_FILE_PATH\n                           + File.separator\n                           + \"res\");\n    mOutTempDir = new File(mOutDir.getAbsoluteFile().getAbsolutePath() + File.separator + TypedValue.UNZIP_FILE_PATH);\n\n    //这里纪录原始res目录的文件\n    Files.walkFileTree(mRawResFile.toPath(), new ResourceFilesVisitor());\n\n    if (!mRawResFile.exists() || !mRawResFile.isDirectory()) {\n      throw new IOException(\"can not found res dir in the apk or it is not a dir\");\n    }\n\n    mOutTempARSCFile = new File(mOutDir.getAbsoluteFile().getAbsolutePath() + File.separator + \"resources_temp.arsc\");\n    mOutARSCFile = new File(mOutDir.getAbsoluteFile().getAbsolutePath() + File.separator + \"resources.arsc\");\n\n    String basename = apkFile.getName().substring(0, apkFile.getName().indexOf(\".apk\"));\n    mResMappingFile = new File(mOutDir.getAbsoluteFile().getAbsolutePath()\n                               + File.separator\n                               + TypedValue.RES_MAPPING_FILE\n                               + basename\n                               + TypedValue.TXT_FILE);\n    mMergeDuplicatedResMappingFile = new File(mOutDir.getAbsoluteFile().getAbsolutePath()\n                             + File.separator\n                             + TypedValue.MERGE_DUPLICATED_RES_MAPPING_FILE\n                             + basename\n                             + TypedValue.TXT_FILE);\n  }\n\n  /**\n   * 根据config来修改压缩的值\n   */\n  private void dealWithCompressConfig() {\n    if (config.mUseCompress) {\n      HashSet<Pattern> patterns = config.mCompressPatterns;\n      if (!patterns.isEmpty()) {\n        for (Entry<String, Integer> entry : mCompressData.entrySet()) {\n          String name = entry.getKey();\n          for (Iterator<Pattern> it = patterns.iterator(); it.hasNext(); ) {\n            Pattern p = it.next();\n            if (p.matcher(name).matches()) {\n              mCompressData.put(name, TypedValue.ZIP_DEFLATED);\n            }\n          }\n        }\n      }\n    }\n  }\n\n  public HashMap<String, Integer> getCompressData() {\n    return mCompressData;\n  }\n\n  public File getOutDir() {\n    return mOutDir;\n  }\n\n  public void setOutDir(File outDir) throws AndrolibException {\n    mOutDir = outDir;\n  }\n\n  public File getOutResFile() {\n    return mOutResFile;\n  }\n\n  public File getRawResFile() {\n    return mRawResFile;\n  }\n\n  public File getOutTempARSCFile() {\n    return mOutTempARSCFile;\n  }\n\n  public File getOutARSCFile() {\n    return mOutARSCFile;\n  }\n\n  public File getOutTempDir() {\n    return mOutTempDir;\n  }\n\n  public File getResMappingFile() {\n    return mResMappingFile;\n  }\n\n  public File getMergeDuplicatedResMappingFile() {\n    return mMergeDuplicatedResMappingFile;\n  }\n\n  public void decode() throws AndrolibException, IOException, DirectoryException {\n    if (hasResources()) {\n      ensureFilePath();\n      // read the resources.arsc checking for STORED vs DEFLATE compression\n      // this will determine whether we compress on rebuild or not.\n      System.out.printf(\"decoding resources.arsc\\n\");\n      RawARSCDecoder.decode(apkFile.getDirectory().getFileInput(\"resources.arsc\"));\n      ResPackage[] pkgs = ARSCDecoder.decode(apkFile.getDirectory().getFileInput(\"resources.arsc\"), this);\n\n      //把没有纪录在resources.arsc的资源文件也拷进dest目录\n      copyOtherResFiles();\n\n      ARSCDecoder.write(apkFile.getDirectory().getFileInput(\"resources.arsc\"), this, pkgs);\n    }\n  }\n\n  class ResourceFilesVisitor extends SimpleFileVisitor<Path> {\n    @Override\n    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\n      mRawResourceFiles.add(file);\n      return FileVisitResult.CONTINUE;\n    }\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/ResourceApkBuilder.java",
    "content": "package com.tencent.mm.androlib;\n\nimport com.tencent.mm.androlib.res.decoder.ARSCDecoder;\nimport com.tencent.mm.resourceproguard.Configuration;\nimport com.tencent.mm.resourceproguard.InputParam;\nimport com.tencent.mm.util.FileOperation;\nimport com.tencent.mm.util.TypedValue;\nimport com.tencent.mm.util.Utils;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.security.Key;\nimport java.security.KeyStore;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\n\nimport apksigner.ApkSignerTool;\n\nimport static com.tencent.mm.resourceproguard.InputParam.SignatureType.SchemaV3;\n\n/**\n * @author shwenzhang\n *     modified:\n * @author jonychina162\n *     为了使用v2签名，引入了google v2sign 模块\n *     由于使用v2签名，会对整个包除了签名块验证完整性，即除了签名块的内容在签名之后包其他内容不允许再改动，因此修改了原有的签名逻辑，\n *     现有逻辑：1 zipalign 2.sign 。具体请参考buildApkV2sign\n */\npublic class ResourceApkBuilder {\n\n  private final Configuration config;\n  private File mOutDir;\n  private File m7zipOutPutDir;\n\n  private File mUnSignedApk;\n  private File mSignedApk;\n  private File mSignedWith7ZipApk;\n\n  private File m7ZipApk;\n\n  private File mAlignedApk;\n  private File mAlignedWith7ZipApk;\n\n  private String mApkName;\n  private File finalApkFile;\n\n  public ResourceApkBuilder(Configuration config) {\n    this.config = config;\n  }\n\n  public void setOutDir(File outDir, String apkName, File finalApkFile) throws AndrolibException {\n    this.mOutDir = outDir;\n    this.mApkName = apkName;\n    this.finalApkFile = finalApkFile;\n  }\n\n  public void buildApkWithV1sign(HashMap<String, Integer> compressData) throws IOException, InterruptedException {\n    insureFileNameV1();\n    generalUnsignApk(compressData);\n    signApkV1(mUnSignedApk, mSignedApk);\n    use7zApk(compressData, mSignedApk, mSignedWith7ZipApk);\n    alignApks();\n    copyFinalApkV1();\n  }\n\n  private void copyFinalApkV1() throws IOException {\n    if (finalApkFile != null) {\n      System.out.println(String.format(\"Backup Final APk(V1) to %s\", finalApkFile));\n      if (mSignedWith7ZipApk.exists()) {\n        FileOperation.copyFileUsingStream(mAlignedWith7ZipApk, finalApkFile);\n      } else if (mSignedApk.exists()) {\n        FileOperation.copyFileUsingStream(mAlignedApk, finalApkFile);\n      }\n    }\n  }\n\n  public void buildApkWithV2V3Sign(HashMap<String, Integer> compressData, int minSDKVersion, InputParam.SignatureType signatureType) throws Exception {\n    insureFileNameV2();\n    generalUnsignApk(compressData);\n    if (use7zApk(compressData, mUnSignedApk, m7ZipApk)) {\n      alignApk(m7ZipApk, mAlignedApk);\n    } else {\n      alignApk(mUnSignedApk, mAlignedApk);\n    }\n\n    /*\n     * Caution: If you sign your app using APK Signature Scheme v2 and make further changes to the app,\n     * the app's signature is invalidated.\n     * For this reason, use tools such as zipalign before signing your app using APK Signature Scheme v2, not after.\n     **/\n    signApkV2V3(mAlignedApk, mSignedApk, minSDKVersion, signatureType);\n    copyFinalApkV2();\n  }\n\n  private void copyFinalApkV2() throws IOException {\n    if (mSignedApk.exists() && finalApkFile != null) {\n      System.out.println(String.format(\"Backup Final APk(V2) to %s\", finalApkFile));\n      FileOperation.copyFileUsingStream(mSignedApk, finalApkFile);\n    }\n  }\n\n  private void insureFileNameV1() {\n    mUnSignedApk = new File(mOutDir.getAbsolutePath(), mApkName + \"_unsigned.apk\");\n    mSignedWith7ZipApk = new File(mOutDir.getAbsolutePath(), mApkName + \"_signed_7zip.apk\");\n    mSignedApk = new File(mOutDir.getAbsolutePath(), mApkName + \"_signed.apk\");\n    mAlignedApk = new File(mOutDir.getAbsolutePath(), mApkName + \"_signed_aligned.apk\");\n    mAlignedWith7ZipApk = new File(mOutDir.getAbsolutePath(), mApkName + \"_signed_7zip_aligned.apk\");\n    m7zipOutPutDir = new File(mOutDir.getAbsolutePath(), TypedValue.OUT_7ZIP_FILE_PATH);\n  }\n\n  private void insureFileNameV2() {\n    mUnSignedApk = new File(mOutDir.getAbsolutePath(), mApkName + \"_unsigned.apk\");\n    m7ZipApk = new File(mOutDir.getAbsolutePath(), mApkName + \"_7zip_unsigned.apk\");\n    if (config.mUse7zip) {\n      mAlignedApk = new File(mOutDir.getAbsolutePath(), mApkName + \"_7zip_aligned_unsigned.apk\");\n      mSignedApk = new File(mOutDir.getAbsolutePath(), mApkName + \"_7zip_aligned_signed.apk\");\n    } else {\n      mAlignedApk = new File(mOutDir.getAbsolutePath(), mApkName + \"_aligned_unsigned.apk\");\n      mSignedApk = new File(mOutDir.getAbsolutePath(), mApkName + \"_aligned_signed.apk\");\n    }\n    m7zipOutPutDir = new File(mOutDir.getAbsolutePath(), TypedValue.OUT_7ZIP_FILE_PATH);\n  }\n\n  private boolean use7zApk(HashMap<String, Integer> compressData, File originalAPK, File outputAPK)\n      throws IOException, InterruptedException {\n    if (!config.mUse7zip) {\n      return false;\n    }\n    if (!config.mUseSignAPK) {\n      throw new IOException(\"if you want to use 7z, you must enable useSign in the config file first\");\n    }\n    if (!originalAPK.exists()) {\n      throw new IOException(String.format(\"can not found the signed apk file to 7z, if you want to use 7z, \"\n                                          + \"you must fill the sign data in the config file path=%s\",\n          originalAPK.getAbsolutePath()\n      ));\n    }\n    System.out.printf(\"use 7zip to repackage: %s, will cost much more time\\n\", outputAPK.getName());\n    FileOperation.unZipAPk(originalAPK.getAbsolutePath(), m7zipOutPutDir.getAbsolutePath());\n    //首先一次性生成一个全部都是压缩的安装包\n    generalRaw7zip(outputAPK);\n\n    ArrayList<String> storedFiles = new ArrayList<>();\n    //对于不压缩的要update回去\n    for (String name : compressData.keySet()) {\n      File file = new File(m7zipOutPutDir.getAbsolutePath(), name);\n      if (!file.exists()) {\n        continue;\n      }\n      int method = compressData.get(name);\n      if (method == TypedValue.ZIP_STORED) {\n        storedFiles.add(name);\n      }\n    }\n\n    addStoredFileIn7Zip(storedFiles, outputAPK);\n    if (!outputAPK.exists()) {\n      throw new IOException(String.format(\n          \"[use7zApk]7z repackage signed apk fail,you must install 7z command line version first, linux: p7zip, window: 7za, path=%s\",\n          mSignedWith7ZipApk.getAbsolutePath()\n      ));\n    }\n    return true;\n  }\n\n  private String getSignatureAlgorithm(String hash) throws Exception {\n    String signatureAlgorithm;\n    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());\n    FileInputStream fileIn = new FileInputStream(config.mSignatureFile);\n    keyStore.load(fileIn, config.mStorePass.toCharArray());\n    Key key = keyStore.getKey(config.mStoreAlias, config.mKeyPass.toCharArray());\n    if (key == null) {\n      throw new RuntimeException(\"Can't get private key, please check if storepass storealias and keypass are correct\");\n    }\n    String keyAlgorithm = key.getAlgorithm();\n    hash = formatHashAlgorithName(hash);\n    if (keyAlgorithm.equalsIgnoreCase(\"DSA\")) {\n      keyAlgorithm = \"DSA\";\n    } else if (keyAlgorithm.equalsIgnoreCase(\"RSA\")) {\n      keyAlgorithm = \"RSA\";\n    } else if (keyAlgorithm.equalsIgnoreCase(\"EC\")) {\n      keyAlgorithm = \"ECDSA\";\n    } else {\n      throw new RuntimeException(\"private key is not a DSA or RSA key\");\n    }\n    signatureAlgorithm = String.format(\"%swith%s\", hash, keyAlgorithm);\n    return signatureAlgorithm;\n  }\n\n  private String formatHashAlgorithName(String hash) {\n    return hash.replace(\"-\", \"\");\n  }\n\n  private void signApkV1(File unSignedApk, File signedApk) throws IOException, InterruptedException {\n    if (config.mUseSignAPK) {\n      System.out.printf(\"signing apk: %s\\n\", signedApk.getName());\n      if (signedApk.exists()) {\n        signedApk.delete();\n      }\n      signWithV1sign(unSignedApk, signedApk);\n      if (!signedApk.exists()) {\n        throw new IOException(\"Can't Generate signed APK. Plz check your v1sign info is correct.\");\n      }\n    }\n  }\n\n  private void signApkV2V3(File unSignedApk, File signedApk, int minSDKVersion, InputParam.SignatureType signatureType) throws Exception {\n    if (config.mUseSignAPK) {\n      System.out.printf(\"signing apk: %s\\n\", signedApk.getName());\n      signWithV2V3Sign(unSignedApk, signedApk, minSDKVersion, signatureType);\n      if (!signedApk.exists()) {\n        throw new IOException(\"Can't Generate signed APK v2. Plz check your v2sign info is correct.\");\n      }\n    }\n  }\n\n  private void signWithV2V3Sign(File unSignedApk, File signedApk, int minSDKVersion, InputParam.SignatureType signatureType) throws Exception {\n    String[] params = new String[] {\n        \"sign\",\n        \"--ks\",\n        config.mSignatureFile.getAbsolutePath(),\n        \"--ks-pass\",\n        \"pass:\" + config.mStorePass,\n        \"--min-sdk-version\",\n        String.valueOf(minSDKVersion),\n        \"--ks-key-alias\",\n        config.mStoreAlias,\n        \"--key-pass\",\n        \"pass:\" + config.mKeyPass,\n        \"--v3-signing-enabled\",\n        String.valueOf(signatureType == SchemaV3),\n        \"--out\",\n        signedApk.getAbsolutePath(),\n        unSignedApk.getAbsolutePath()\n    };\n    ApkSignerTool.main(params);\n  }\n\n  private void signWithV1sign(File unSignedApk, File signedApk) throws IOException, InterruptedException {\n    String signatureAlgorithm = \"MD5withRSA\";\n    try {\n      signatureAlgorithm = getSignatureAlgorithm(config.digestAlg);\n    } catch (Exception e) {\n      e.printStackTrace();\n    }\n    String[] argv = {\n        \"jarsigner\",\n        \"-sigalg\",\n        signatureAlgorithm,\n        \"-digestalg\",\n        config.digestAlg,\n        \"-keystore\",\n        config.mSignatureFile.getAbsolutePath(),\n        \"-storepass\",\n        config.mStorePass,\n        \"-keypass\",\n        config.mKeyPass,\n        \"-signedjar\",\n        signedApk.getAbsolutePath(),\n        unSignedApk.getAbsolutePath(),\n        config.mStoreAlias\n    };\n    Utils.runExec(argv);\n  }\n\n  private void alignApks() throws IOException, InterruptedException {\n    //如果不签名就肯定不需要对齐了\n    if (!config.mUseSignAPK) {\n      return;\n    }\n    if (!mSignedApk.exists() && !mSignedWith7ZipApk.exists()) {\n      throw new IOException(\"Can not found any signed apk file\");\n    }\n    if (mSignedApk.exists()) {\n      alignApk(mSignedApk, mAlignedApk);\n    }\n    if (mSignedWith7ZipApk.exists()) {\n      alignApk(mSignedWith7ZipApk, mAlignedWith7ZipApk);\n    }\n  }\n\n  private void alignApk(File before, File after) throws IOException, InterruptedException {\n    System.out.printf(\"zipaligning apk: %s, exists:%b\\n\", before.getAbsolutePath(), before.exists());\n    if (!before.exists()) {\n      throw new IOException(String.format(\"can not found the raw apk file to zipalign, path=%s\",\n          before.getAbsolutePath()\n      ));\n    }\n    String cmd = Utils.isPresent(config.mZipalignPath) ? config.mZipalignPath : TypedValue.COMMAND_ZIPALIGIN;\n    Utils.runCmd(cmd, \"4\", before.getAbsolutePath(), after.getAbsolutePath());\n    if (!after.exists()) {\n      throw new IOException(String.format(\"can not found the aligned apk file, the ZipAlign path is correct? path=%s\",\n          mAlignedApk.getAbsolutePath()\n      ));\n    }\n  }\n\n  private void generalUnsignApk(HashMap<String, Integer> compressData) throws IOException, InterruptedException {\n    System.out.printf(\"General unsigned apk: %s\\n\", mUnSignedApk.getName());\n    File tempOutDir = new File(mOutDir.getAbsolutePath(), TypedValue.UNZIP_FILE_PATH);\n    if (!tempOutDir.exists()) {\n      System.err.printf(\"Missing apk unzip files, path=%s\\n\", tempOutDir.getAbsolutePath());\n      System.exit(-1);\n    }\n\n    File[] unzipFiles = tempOutDir.listFiles();\n    assert unzipFiles != null;\n    List<File> collectFiles = new ArrayList<>();\n    for (File f : unzipFiles) {\n      String name = f.getName();\n      if (name.equals(\"res\") || name.equals(\"resources.arsc\")) {\n        continue;\n      } else if (name.equals(config.mMetaName)) {\n        addNonSignatureFiles(collectFiles, f);\n        continue;\n      }\n      collectFiles.add(f);\n    }\n\n    File destResDir = new File(mOutDir.getAbsolutePath(), \"res\");\n    //添加修改后的res文件\n    if (!config.mKeepRoot && FileOperation.getlist(destResDir) == 0) {\n      destResDir = new File(mOutDir.getAbsolutePath(), TypedValue.RES_FILE_PATH);\n    }\n\n    /*\n     * NOTE:文件数量应该是一样的，如果不一样肯定有问题\n     */\n    File rawResDir = new File(tempOutDir.getAbsolutePath() + File.separator + \"res\");\n    System.out.printf(\"DestResDir %d rawResDir %d\\n\",\n        FileOperation.getlist(destResDir),\n        FileOperation.getlist(rawResDir)\n    );\n    if (FileOperation.getlist(destResDir) != (FileOperation.getlist(rawResDir) - ARSCDecoder.mMergeDuplicatedResCount)) {\n      throw new IOException(String.format(\n          \"the file count of %s, and the file count of %s is not equal, there must be some problem\\n\",\n          rawResDir.getAbsolutePath(),\n          destResDir.getAbsolutePath()\n      ));\n    }\n    if (!destResDir.exists()) {\n      System.err.printf(\"Missing res files, path=%s\\n\", destResDir.getAbsolutePath());\n      System.exit(-1);\n    }\n    //这个需要检查混淆前混淆后，两个res的文件数量是否相等\n    collectFiles.add(destResDir);\n    File rawARSCFile = new File(mOutDir.getAbsolutePath() + File.separator + \"resources.arsc\");\n    if (!rawARSCFile.exists()) {\n      System.err.printf(\"Missing resources.arsc files, path=%s\\n\", rawARSCFile.getAbsolutePath());\n      System.exit(-1);\n    }\n    collectFiles.add(rawARSCFile);\n    FileOperation.zipFiles(collectFiles, tempOutDir, mUnSignedApk, compressData);\n\n    if (!mUnSignedApk.exists()) {\n      throw new IOException(String.format(\"can not found the unsign apk file path=%s\", mUnSignedApk.getAbsolutePath()));\n    }\n  }\n\n  private void addNonSignatureFiles(List<File> collectFiles, File metaFolder) {\n    File[] metaFiles = metaFolder.listFiles();\n    if (metaFiles != null) {\n      for (File metaFile : metaFiles) {\n        String metaFileName = metaFile.getName();\n        // Ignore signature files\n        if (!metaFileName.endsWith(\".MF\") && !metaFileName.endsWith(\".RSA\") && !metaFileName.endsWith(\".SF\")) {\n          System.out.println(String.format(\"add meta file %s\", metaFile.getAbsolutePath()));\n          collectFiles.add(metaFile);\n        }\n      }\n    }\n  }\n\n  private void addStoredFileIn7Zip(ArrayList<String> storedFiles, File outSevenZipAPK)\n      throws IOException, InterruptedException {\n    System.out.printf(\"[addStoredFileIn7Zip]rewrite the stored file into the 7zip, file count: %d\\n\",\n        storedFiles.size()\n    );\n    if (storedFiles.size() == 0) return;\n    String storedParentName = mOutDir.getAbsolutePath() + File.separator + \"storefiles\" + File.separator;\n    String outputName = m7zipOutPutDir.getAbsolutePath() + File.separator;\n    for (String name : storedFiles) {\n      FileOperation.copyFileUsingStream(new File(outputName + name), new File(storedParentName + name));\n    }\n    storedParentName = storedParentName + File.separator + \"*\";\n    String cmd = Utils.isPresent(config.m7zipPath) ? config.m7zipPath : TypedValue.COMMAND_7ZIP;\n    Utils.runCmd(cmd, \"a\", \"-tzip\", outSevenZipAPK.getAbsolutePath(), storedParentName, \"-mx0\");\n  }\n\n  private void generalRaw7zip(File outSevenZipApk) throws IOException, InterruptedException {\n    String outPath = m7zipOutPutDir.getAbsoluteFile().getAbsolutePath();\n    String path = outPath + File.separator + \"*\";\n    String cmd = Utils.isPresent(config.m7zipPath) ? config.m7zipPath : TypedValue.COMMAND_7ZIP;\n    Utils.runCmd(cmd, \"a\", \"-tzip\", outSevenZipApk.getAbsolutePath(), path, \"-mx9\");\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/ResourceRepackage.java",
    "content": "package com.tencent.mm.androlib;\n\nimport com.tencent.mm.util.FileOperation;\nimport com.tencent.mm.util.TypedValue;\nimport com.tencent.mm.util.Utils;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.LineNumberReader;\nimport java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class ResourceRepackage {\n\n  private final String zipalignPath;\n  private final String sevenZipPath;\n  private File mSignedApk;\n  private File mSignedWith7ZipApk;\n  private File mAlignedWith7ZipApk;\n  private File m7zipOutPutDir;\n  private File mStoredOutPutDir;\n  private String mApkName;\n  private File mOutDir;\n\n  public ResourceRepackage(String zipalignPath, String zipPath, File signedFile) {\n    this.zipalignPath = zipalignPath;\n    this.sevenZipPath = zipPath;\n    mSignedApk = signedFile;\n  }\n\n  public void setOutDir(File outDir) {\n    mOutDir = outDir;\n  }\n\n  public void repackageApk() throws IOException, InterruptedException {\n    insureFileName();\n\n    repackageWith7z();\n    alignApk();\n    deleteUnusedFiles();\n  }\n\n  private void deleteUnusedFiles() {\n    //删除目录\n    FileOperation.deleteDir(m7zipOutPutDir);\n    FileOperation.deleteDir(mStoredOutPutDir);\n    if (mSignedWith7ZipApk.exists()) {\n      mSignedWith7ZipApk.delete();\n    }\n  }\n\n  /**\n   * 这边有点不太一样，就是当输出目录存在的时候是不会强制删除目录的\n   *\n   * @throws IOException\n   */\n  private void insureFileName() throws IOException {\n    if (!mSignedApk.exists()) {\n      throw new IOException(String.format(\"can not found the signed apk file to repackage\" + \", path=%s\",\n          mSignedApk.getAbsolutePath()\n      ));\n    }\n    //需要自己安装7zip\n    String apkBasename = mSignedApk.getName();\n    mApkName = apkBasename.substring(0, apkBasename.indexOf(\".apk\"));\n    //如果外面设过，就不用设了\n    if (mOutDir == null) {\n      mOutDir = new File(mSignedApk.getAbsoluteFile().getParent(), mApkName);\n    }\n\n    mSignedWith7ZipApk = new File(mOutDir.getAbsolutePath(), mApkName + \"_channel_7zip.apk\");\n    mAlignedWith7ZipApk = new File(mOutDir.getAbsolutePath(), mApkName + \"_channel_7zip_aligned.apk\");\n\n    m7zipOutPutDir = new File(mOutDir.getAbsolutePath(), TypedValue.OUT_7ZIP_FILE_PATH);\n    mStoredOutPutDir = new File(mOutDir.getAbsolutePath(), \"storefiles\");\n    //删除目录,因为之前的方法是把整个输出目录都删除，所以不会有问题，现在不会，所以要单独删\n    FileOperation.deleteDir(m7zipOutPutDir);\n    FileOperation.deleteDir(mStoredOutPutDir);\n    FileOperation.deleteDir(mSignedWith7ZipApk);\n    FileOperation.deleteDir(mAlignedWith7ZipApk);\n  }\n\n  private void repackageWith7z() throws IOException, InterruptedException {\n    System.out.printf(\"use 7zip to repackage: %s, will cost much more time\\n\", mSignedWith7ZipApk.getName());\n    HashMap<String, Integer> compressData = FileOperation.unZipAPk(mSignedApk.getAbsolutePath(),\n        m7zipOutPutDir.getAbsolutePath()\n    );\n    //首先一次性生成一个全部都是压缩的安装包\n    generalRaw7zip();\n    ArrayList<String> storedFiles = new ArrayList<>();\n    //对于不压缩的要update回去\n    for (String name : compressData.keySet()) {\n      File file = new File(m7zipOutPutDir.getAbsolutePath(), name);\n      if (!file.exists()) {\n        continue;\n      }\n      int method = compressData.get(name);\n      if (method == TypedValue.ZIP_STORED) {\n        storedFiles.add(name);\n      }\n    }\n\n    addStoredFileIn7Zip(storedFiles);\n    if (!mSignedWith7ZipApk.exists()) {\n      throw new IOException(String.format(\n          \"[repackageWith7z]7z repackage signed apk fail,you must install 7z command line version first, linux: p7zip, window: 7za, path=%s\",\n          mSignedWith7ZipApk.getAbsolutePath()\n      ));\n    }\n  }\n\n  private void generalRaw7zip() throws IOException, InterruptedException {\n    System.out.printf(\"general the raw 7zip file\\n\");\n    String outPath = m7zipOutPutDir.getAbsoluteFile().getAbsolutePath();\n    String path = outPath + File.separator + \"*\";\n\n    String cmd = Utils.isPresent(sevenZipPath) ? sevenZipPath : TypedValue.COMMAND_7ZIP;\n    ProcessBuilder pb = new ProcessBuilder(cmd, \"a\", \"-tzip\", mSignedWith7ZipApk.getAbsolutePath(), path, \"-mx9\");\n    Process pro = pb.start();\n\n    InputStreamReader ir = new InputStreamReader(pro.getInputStream());\n    LineNumberReader input = new LineNumberReader(ir);\n    //如果不读会有问题，被阻塞\n    while (input.readLine() != null) {\n    }\n    //destroy the stream\n    pro.waitFor();\n    pro.destroy();\n  }\n\n  private void addStoredFileIn7Zip(ArrayList<String> storedFiles) throws IOException, InterruptedException {\n    System.out.printf(\"[addStoredFileIn7Zip]rewrite the stored file into the 7zip, file count:%d\\n\",\n        storedFiles.size()\n    );\n    String storedParentName = mStoredOutPutDir.getAbsolutePath() + File.separator;\n    String outputName = m7zipOutPutDir.getAbsolutePath() + File.separator;\n    for (String name : storedFiles) {\n      FileOperation.copyFileUsingStream(new File(outputName + name), new File(storedParentName + name));\n    }\n    storedParentName = storedParentName + File.separator + \"*\";\n    //极限压缩\n    String cmd = Utils.isPresent(sevenZipPath) ? sevenZipPath : TypedValue.COMMAND_7ZIP;\n    ProcessBuilder pb = new ProcessBuilder(cmd,\n        \"a\",\n        \"-tzip\",\n        mSignedWith7ZipApk.getAbsolutePath(),\n        storedParentName,\n        \"-mx0\"\n    );\n    Process pro = pb.start();\n\n    InputStreamReader ir = new InputStreamReader(pro.getInputStream());\n    LineNumberReader input = new LineNumberReader(ir);\n    //如果不读会有问题，被阻塞\n    while (input.readLine() != null) {\n    }\n    //destroy the stream\n    pro.waitFor();\n    pro.destroy();\n  }\n\n  private void alignApk() throws IOException, InterruptedException {\n    if (mSignedWith7ZipApk.exists()) {\n      alignApk(mSignedWith7ZipApk, mAlignedWith7ZipApk);\n    }\n  }\n\n  private void alignApk(File before, File after) throws IOException, InterruptedException {\n    System.out.printf(\"zipaligning apk: %s\\n\", before.getName());\n    if (!before.exists()) {\n      throw new IOException(String.format(\"can not found the raw apk file to zipalign, path=%s\",\n          before.getAbsolutePath()\n      ));\n    }\n    String cmd = Utils.isPresent(zipalignPath) ? zipalignPath : TypedValue.COMMAND_ZIPALIGIN;\n    ProcessBuilder pb = new ProcessBuilder(cmd, \"4\", before.getAbsolutePath(), after.getAbsolutePath());\n    Process pro = pb.start();\n    //destroy the stream\n    pro.waitFor();\n    pro.destroy();\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/data/ResID.java",
    "content": "/**\n * Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>\n * Copyright 2016 sim sun <sunsj1231@gmail.com>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.tencent.mm.androlib.res.data;\n\npublic class ResID {\n  public final int package_;\n  public final int type;\n  public final int entry;\n\n  public final int id;\n\n  public ResID(int package_, int type, int entry) {\n    this(package_, type, entry, (package_ << 24) + (type << 16) + entry);\n  }\n\n  public ResID(int id) {\n    this((id >> 24) & 0xff, (id >> 16) & 0x000000ff, id & 0x0000ffff, id);\n  }\n\n  public ResID(int package_, int type, int entry, int id) {\n    this.package_ = package_;\n    this.type = type;\n    this.entry = entry;\n    this.id = id;\n  }\n\n  @Override\n  public String toString() {\n    return String.format(\"0x%08x\", id);\n  }\n\n  @Override\n  public int hashCode() {\n    int hash = 17;\n    hash = 31 * hash + this.id;\n    return hash;\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (obj == null) {\n      return false;\n    }\n    if (getClass() != obj.getClass()) {\n      return false;\n    }\n    final ResID other = (ResID) obj;\n    if (this.id != other.id) {\n      return false;\n    }\n    return true;\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/data/ResPackage.java",
    "content": "/**\n * Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>\n * Copyright 2016 sim sun <sunsj1231@gmail.com>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.androlib.res.data;\n\nimport java.util.HashSet;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class ResPackage {\n  private final String mName;\n\n  private final Map<Integer, String> mSpecNamesReplace;\n  private final Map<String, Set<String>> mSpecNamesBlock;\n  private boolean mCanProguard = false;\n\n  public ResPackage(int id, String name) {\n    this.mName = name;\n    mSpecNamesReplace = new LinkedHashMap<>();\n    mSpecNamesBlock = new LinkedHashMap<>();\n  }\n\n  public boolean isCanResguard() {\n    return mCanProguard;\n  }\n\n  public void setCanResguard(boolean set) {\n    mCanProguard = set;\n  }\n\n  public boolean hasSpecRepplace(String resID) {\n    return mSpecNamesReplace.containsKey(resID);\n  }\n\n  public String getSpecRepplace(int resID) {\n    return mSpecNamesReplace.get(resID);\n  }\n\n  public void putSpecNamesReplace(int resID, String value) {\n    mSpecNamesReplace.put(resID, value);\n  }\n\n  public void putSpecNamesblock(String specName, String value) {\n    Set<String> values = mSpecNamesBlock.get(specName);\n    if (values == null) {\n      values = new HashSet<>();\n      mSpecNamesBlock.put(specName, values);\n    }\n    values.add(value);\n  }\n\n  public Map<String, Set<String>> getSpecNamesBlock() {\n    return mSpecNamesBlock;\n  }\n\n  public String getName() {\n    return mName;\n  }\n\n  @Override\n  public String toString() {\n    return mName;\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/data/ResType.java",
    "content": "/**\n * Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>\n * Copyright 2016 sim sun <sunsj1231@gmail.com>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.androlib.res.data;\n\nimport com.tencent.mm.androlib.AndrolibException;\nimport java.util.HashSet;\n\npublic final class ResType {\n  private final String mName;\n\n  private final ResPackage mPackage;\n  private final HashSet<String> specNames;\n\n  public ResType(String name, ResPackage package_) {\n    this.mName = name;\n    this.mPackage = package_;\n    specNames = new HashSet<>();\n  }\n\n  public String getName() {\n    return mName;\n  }\n\n  public void putSpecResguardName(String name) throws AndrolibException {\n    if (specNames.contains(name)) {\n      throw new AndrolibException(String.format(\n          \"spec proguard name duplicate in a singal type %s, spec name: %s\\n\",\n          getName(),\n          name\n      ));\n    }\n    specNames.add(name);\n  }\n\n  @Override\n  public String toString() {\n    return mName;\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/decoder/ARSCDecoder.java",
    "content": "/**\n * Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>\n * Copyright 2016 sim sun <sunsj1231@gmail.com>\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.androlib.res.decoder;\n\nimport com.mindprod.ledatastream.LEDataInputStream;\nimport com.mindprod.ledatastream.LEDataOutputStream;\nimport com.tencent.mm.androlib.AndrolibException;\nimport com.tencent.mm.androlib.ApkDecoder;\nimport com.tencent.mm.androlib.res.data.ResPackage;\nimport com.tencent.mm.androlib.res.data.ResType;\nimport com.tencent.mm.androlib.res.util.StringUtil;\nimport com.tencent.mm.resourceproguard.Configuration;\nimport com.tencent.mm.util.ExtDataInput;\nimport com.tencent.mm.util.ExtDataOutput;\nimport com.tencent.mm.util.FileOperation;\nimport com.tencent.mm.util.Md5Util;\nimport com.tencent.mm.util.TypedValue;\nimport com.tencent.mm.util.Utils;\n\nimport java.io.BufferedWriter;\nimport java.io.EOFException;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.Writer;\nimport java.math.BigInteger;\nimport java.text.DecimalFormat;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.logging.Logger;\nimport java.util.regex.Pattern;\n\npublic class ARSCDecoder {\n\n  private final static boolean DEBUG = false;\n\n  private final static short ENTRY_FLAG_COMPLEX = 0x0001;\n  private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName());\n  private static final int KNOWN_CONFIG_BYTES = 56;\n\n  public static Map<Integer, String> mTableStringsResguard = new LinkedHashMap<>();\n  public static int mMergeDuplicatedResCount = 0;\n  private final Map<String, String> mOldFileName;\n  private final Map<String, Integer> mCurSpecNameToPos;\n  private final HashSet<String> mShouldResguardTypeSet;\n  private final ApkDecoder mApkDecoder;\n  private ExtDataInput mIn;\n  private ExtDataOutput mOut;\n  private Header mHeader;\n  private StringBlock mTableStrings;\n  private StringBlock mTypeNames;\n  private StringBlock mSpecNames;\n  private ResPackage mPkg;\n  private ResType mType;\n  private ResPackage[] mPkgs;\n  private int[] mPkgsLenghtChange;\n  private int mTableLenghtChange = 0;\n  private int mResId;\n  private int mCurrTypeID = -1;\n  private int mCurEntryID = -1;\n  private int mCurPackageID = -1;\n  private long mMergeDuplicatedResTotalSize = 0L;\n  private ResguardStringBuilder mResguardBuilder;\n  private boolean mShouldResguardForType = false;\n  private Writer mMappingWriter;\n  private Writer mMergeDuplicatedResMappingWriter;\n  private Map<Long,List<MergeDuplicatedResInfo>> mMergeDuplicatedResInfoData = new HashMap<>();\n\n  private ARSCDecoder(InputStream arscStream, ApkDecoder decoder) throws AndrolibException, IOException {\n    mOldFileName = new LinkedHashMap<>();\n    mCurSpecNameToPos = new LinkedHashMap<>();\n    mShouldResguardTypeSet = new HashSet<>();\n    mIn = new ExtDataInput(new LEDataInputStream(arscStream));\n    mApkDecoder = decoder;\n    proguardFileName();\n  }\n\n  private ARSCDecoder(InputStream arscStream, ApkDecoder decoder, ResPackage[] pkgs) throws FileNotFoundException {\n    mOldFileName = new LinkedHashMap<>();\n    mCurSpecNameToPos = new LinkedHashMap<>();\n    mShouldResguardTypeSet = new HashSet<>();\n    mApkDecoder = decoder;\n    mIn = new ExtDataInput(new LEDataInputStream(arscStream));\n    mOut = new ExtDataOutput(new LEDataOutputStream(new FileOutputStream(mApkDecoder.getOutTempARSCFile(), false)));\n    mPkgs = pkgs;\n    mPkgsLenghtChange = new int[pkgs.length];\n  }\n\n  public static ResPackage[] decode(InputStream arscStream, ApkDecoder apkDecoder) throws AndrolibException {\n    try {\n      ARSCDecoder decoder = new ARSCDecoder(arscStream, apkDecoder);\n      ResPackage[] pkgs = decoder.readTable();\n      return pkgs;\n    } catch (IOException ex) {\n      throw new AndrolibException(\"Could not decode arsc file\", ex);\n    }\n  }\n\n  public static void write(InputStream arscStream, ApkDecoder decoder, ResPackage[] pkgs) throws AndrolibException {\n    try {\n      ARSCDecoder writer = new ARSCDecoder(arscStream, decoder, pkgs);\n      writer.writeTable();\n    } catch (IOException ex) {\n      throw new AndrolibException(\"Could not decode arsc file\", ex);\n    }\n  }\n\n  private void proguardFileName() throws IOException, AndrolibException {\n    mMappingWriter = new BufferedWriter(new FileWriter(mApkDecoder.getResMappingFile(), false));\n    mMergeDuplicatedResMappingWriter = new BufferedWriter(new FileWriter(mApkDecoder.getMergeDuplicatedResMappingFile(), false));\n    mMergeDuplicatedResMappingWriter.write(\"res filter path mapping:\\n\");\n    mMergeDuplicatedResMappingWriter.flush();\n\n    mResguardBuilder = new ResguardStringBuilder();\n    mResguardBuilder.reset(null);\n\n    final Configuration config = mApkDecoder.getConfig();\n\n    File rawResFile = mApkDecoder.getRawResFile();\n\n    File[] resFiles = rawResFile.listFiles();\n\n    // 需要看看哪些类型是要混淆文件路径的\n    for (File resFile : resFiles) {\n      String raw = resFile.getName();\n      if (raw.contains(\"-\")) {\n        raw = raw.substring(0, raw.indexOf(\"-\"));\n      }\n      mShouldResguardTypeSet.add(raw);\n    }\n\n    if (!config.mKeepRoot) {\n      // 需要保持之前的命名方式\n      if (config.mUseKeepMapping) {\n        HashMap<String, String> fileMapping = config.mOldFileMapping;\n        List<String> keepFileNames = new ArrayList<>();\n        // 这里面为了兼容以前，也需要用以前的文件名前缀，即res混淆成什么\n        String resRoot = TypedValue.RES_FILE_PATH;\n        for (String name : fileMapping.values()) {\n          int dot = name.indexOf(\"/\");\n          if (dot == -1) {\n            throw new IOException(String.format(\"the old mapping res file path should be like r/a, yours %s\\n\", name));\n          }\n          resRoot = name.substring(0, dot);\n          keepFileNames.add(name.substring(dot + 1));\n        }\n        // 去掉所有之前保留的命名，为了简单操作，mapping里面有的都去掉\n        mResguardBuilder.removeStrings(keepFileNames);\n\n        for (File resFile : resFiles) {\n          String raw = \"res\" + \"/\" + resFile.getName();\n          if (fileMapping.containsKey(raw)) {\n            mOldFileName.put(raw, fileMapping.get(raw));\n          } else {\n            mOldFileName.put(raw, resRoot + \"/\" + mResguardBuilder.getReplaceString());\n          }\n        }\n      } else {\n        for (int i = 0; i < resFiles.length; i++) {\n          // 这里也要用linux的分隔符,如果普通的话，就是r\n          mOldFileName.put(\"res\" + \"/\" + resFiles[i].getName(),\n             TypedValue.RES_FILE_PATH + \"/\" + mResguardBuilder.getReplaceString()\n          );\n        }\n      }\n      generalFileResMapping();\n    }\n\n    Utils.cleanDir(mApkDecoder.getOutResFile());\n  }\n\n  private ResPackage[] readTable() throws IOException, AndrolibException {\n    nextChunkCheckType(Header.TYPE_TABLE);\n    int packageCount = mIn.readInt();\n    mTableStrings = StringBlock.read(mIn);\n    ResPackage[] packages = new ResPackage[packageCount];\n    nextChunk();\n    for (int i = 0; i < packageCount; i++) {\n      packages[i] = readPackage();\n    }\n    mMappingWriter.close();\n    System.out.printf(\"resources mapping file %s done\\n\", mApkDecoder.getResMappingFile().getAbsolutePath());\n    generalFilterEnd(mMergeDuplicatedResCount, mMergeDuplicatedResTotalSize);\n    mMergeDuplicatedResMappingWriter.close();\n    System.out.printf(\"resources filter mapping file %s done\\n\", mApkDecoder.getMergeDuplicatedResMappingFile().getAbsolutePath());\n    return packages;\n  }\n\n  private void writeTable() throws IOException, AndrolibException {\n    System.out.printf(\"writing new resources.arsc \\n\");\n    mTableLenghtChange = 0;\n    writeNextChunkCheck(Header.TYPE_TABLE, 0);\n    int packageCount = mIn.readInt();\n    mOut.writeInt(packageCount);\n\n    mTableLenghtChange += StringBlock.writeTableNameStringBlock(mIn, mOut, mTableStringsResguard);\n    writeNextChunk(0);\n    if (packageCount != mPkgs.length) {\n      throw new AndrolibException(String.format(\"writeTable package count is different before %d, now %d\",\n         mPkgs.length,\n         packageCount\n      ));\n    }\n    for (int i = 0; i < packageCount; i++) {\n      mCurPackageID = i;\n      writePackage();\n    }\n    // 最后需要把整个的size重写回去\n    reWriteTable();\n  }\n\n  private void generalFileResMapping() throws IOException {\n    mMappingWriter.write(\"res path mapping:\\n\");\n    for (String raw : mOldFileName.keySet()) {\n      mMappingWriter.write(\"    \" + raw + \" -> \" + mOldFileName.get(raw));\n      mMappingWriter.write(\"\\n\");\n    }\n    mMappingWriter.write(\"\\n\\n\");\n    mMappingWriter.write(\"res id mapping:\\n\");\n    mMappingWriter.flush();\n  }\n\n  private void generalResIDMapping(\n     String packageName, String typename, String specName, String replace) throws IOException {\n    mMappingWriter.write(\"    \"\n       + packageName\n       + \".R.\"\n       + typename\n       + \".\"\n       + specName\n       + \" -> \"\n       + packageName\n       + \".R.\"\n       + typename\n       + \".\"\n       + replace);\n    mMappingWriter.write(\"\\n\");\n    mMappingWriter.flush();\n  }\n\n  private void generalFilterResIDMapping(\n     String originalFile, String original, String replaceFile, String replace, long fileLen) throws IOException {\n    mMergeDuplicatedResMappingWriter.write(\"    \"\n       + originalFile\n       + \" : \"\n       + original\n       + \" -> \"\n       + replaceFile\n       + \" : \"\n       + replace\n       + \" (size:\"\n       + getNetFileSizeDescription(fileLen)\n       + \")\");\n    mMergeDuplicatedResMappingWriter.write(\"\\n\");\n    mMergeDuplicatedResMappingWriter.flush();\n  }\n\n  private void generalFilterEnd(int count, long totalSize) throws IOException {\n    mMergeDuplicatedResMappingWriter.write(\n       \"removed: count(\" + count\n          + \"), totalSize(\" + getNetFileSizeDescription(totalSize) + \")\");\n    mMergeDuplicatedResMappingWriter.flush();\n  }\n\n  private static String getNetFileSizeDescription(long size) {\n    StringBuilder bytes = new StringBuilder();\n    DecimalFormat format = new DecimalFormat(\"###.0\");\n    if (size >= 1024 * 1024 * 1024) {\n      double i = (size / (1024.0 * 1024.0 * 1024.0));\n      bytes.append(format.format(i)).append(\"GB\");\n    } else if (size >= 1024 * 1024) {\n      double i = (size / (1024.0 * 1024.0));\n      bytes.append(format.format(i)).append(\"MB\");\n    } else if (size >= 1024) {\n      double i = (size / (1024.0));\n      bytes.append(format.format(i)).append(\"KB\");\n    } else {\n      if (size <= 0) {\n        bytes.append(\"0B\");\n      } else {\n        bytes.append((int) size).append(\"B\");\n      }\n    }\n    return bytes.toString();\n  }\n\n  private void reWriteTable() throws AndrolibException, IOException {\n\n    mIn = new ExtDataInput(new LEDataInputStream(new FileInputStream(mApkDecoder.getOutTempARSCFile())));\n    mOut = new ExtDataOutput(new LEDataOutputStream(new FileOutputStream(mApkDecoder.getOutARSCFile(), false)));\n    writeNextChunkCheck(Header.TYPE_TABLE, mTableLenghtChange);\n    int packageCount = mIn.readInt();\n    mOut.writeInt(packageCount);\n    StringBlock.writeAll(mIn, mOut);\n\n    for (int i = 0; i < packageCount; i++) {\n      mCurPackageID = i;\n      writeNextChunk(mPkgsLenghtChange[mCurPackageID]);\n      mOut.writeBytes(mIn, mHeader.chunkSize - 8);\n    }\n    mApkDecoder.getOutTempARSCFile().delete();\n  }\n\n  private ResPackage readPackage() throws IOException, AndrolibException {\n    checkChunkType(Header.TYPE_PACKAGE);\n    int id = (byte) mIn.readInt();\n    String name = mIn.readNullEndedString(128, true);\n    System.out.printf(\"reading packagename %s\\n\", name);\n\n    /* typeNameStrings */\n    mIn.skipInt();\n    /* typeNameCount */\n    mIn.skipInt();\n    /* specNameStrings */\n    mIn.skipInt();\n    /* specNameCount */\n    mIn.skipInt();\n    mCurrTypeID = -1;\n    mTypeNames = StringBlock.read(mIn);\n    mSpecNames = StringBlock.read(mIn);\n    mResId = id << 24;\n\n    mPkg = new ResPackage(id, name);\n    // 系统包名不混淆\n    if (mPkg.getName().equals(\"android\")) {\n      mPkg.setCanResguard(false);\n    } else {\n      mPkg.setCanResguard(true);\n    }\n    nextChunk();\n    while (mHeader.type == Header.TYPE_LIBRARY) {\n      readLibraryType();\n    }\n    while (mHeader.type == Header.TYPE_SPEC_TYPE) {\n      readTableTypeSpec();\n    }\n    return mPkg;\n  }\n\n  private void writePackage() throws IOException, AndrolibException {\n    checkChunkType(Header.TYPE_PACKAGE);\n    int id = (byte) mIn.readInt();\n    mOut.writeInt(id);\n    mResId = id << 24;\n    //char_16的，一共256byte\n    mOut.writeBytes(mIn, 256);\n    /* typeNameStrings */\n    mOut.writeInt(mIn.readInt());\n    /* typeNameCount */\n    mOut.writeInt(mIn.readInt());\n    /* specNameStrings */\n    mOut.writeInt(mIn.readInt());\n    /* specNameCount */\n    mOut.writeInt(mIn.readInt());\n    StringBlock.writeAll(mIn, mOut);\n\n    if (mPkgs[mCurPackageID].isCanResguard()) {\n      int specSizeChange = StringBlock.writeSpecNameStringBlock(mIn,\n         mOut,\n         mPkgs[mCurPackageID].getSpecNamesBlock(),\n         mCurSpecNameToPos\n      );\n      mPkgsLenghtChange[mCurPackageID] += specSizeChange;\n      mTableLenghtChange += specSizeChange;\n    } else {\n      StringBlock.writeAll(mIn, mOut);\n    }\n    writeNextChunk(0);\n    while (mHeader.type == Header.TYPE_LIBRARY) {\n      writeLibraryType();\n    }\n    while (mHeader.type == Header.TYPE_SPEC_TYPE) {\n      writeTableTypeSpec();\n    }\n  }\n\n  /**\n   * 如果是保持mapping的话，需要去掉某部分已经用过的mapping\n   */\n  private void reduceFromOldMappingFile() {\n    if (mPkg.isCanResguard()) {\n      if (mApkDecoder.getConfig().mUseKeepMapping) {\n        // 判断是否走keepmapping\n        HashMap<String, HashMap<String, HashMap<String, String>>> resMapping = mApkDecoder.getConfig().mOldResMapping;\n        String packName = mPkg.getName();\n        if (resMapping.containsKey(packName)) {\n          HashMap<String, HashMap<String, String>> typeMaps = resMapping.get(packName);\n          String typeName = mType.getName();\n\n          if (typeMaps.containsKey(typeName)) {\n            HashMap<String, String> proguard = typeMaps.get(typeName);\n            // 去掉所有之前保留的命名，为了简单操作，mapping里面有的都去掉\n            mResguardBuilder.removeStrings(proguard.values());\n          }\n        }\n      }\n    }\n  }\n\n  private HashSet<Pattern> getWhiteList(String resType) {\n    final String packName = mPkg.getName();\n    if (mApkDecoder.getConfig().mWhiteList.containsKey(packName)) {\n      if (mApkDecoder.getConfig().mUseWhiteList) {\n        HashMap<String, HashSet<Pattern>> typeMaps = mApkDecoder.getConfig().mWhiteList.get(packName);\n        return typeMaps.get(resType);\n      }\n    }\n    return null;\n  }\n\n  private void readLibraryType() throws AndrolibException, IOException {\n    checkChunkType(Header.TYPE_LIBRARY);\n    int libraryCount = mIn.readInt();\n\n    int packageId;\n    String packageName;\n\n    for (int i = 0; i < libraryCount; i++) {\n      packageId = mIn.readInt();\n      packageName = mIn.readNullEndedString(128, true);\n      System.out.printf(\"Decoding Shared Library (%s), pkgId: %d\\n\", packageName, packageId);\n    }\n\n    while (nextChunk().type == Header.TYPE_TYPE) {\n      readTableTypeSpec();\n    }\n  }\n\n  private void readTableTypeSpec() throws AndrolibException, IOException {\n    checkChunkType(Header.TYPE_SPEC_TYPE);\n    byte id = mIn.readByte();\n    mIn.skipBytes(3);\n    int entryCount = mIn.readInt();\n    mType = new ResType(mTypeNames.getString(id - 1), mPkg);\n    if (DEBUG) {\n      System.out.printf(\"[ReadTableType] type (%s) id: (%d) curr (%d)\\n\", mType, id, mCurrTypeID);\n    }\n    // first meet a type of resource\n    if (mCurrTypeID != id) {\n      mCurrTypeID = id;\n      initResGuardBuild(mCurrTypeID);\n    }\n    // 是否混淆文件路径\n    mShouldResguardForType = isToResguardFile(mTypeNames.getString(id - 1));\n\n    // 对，这里是用来描述差异性的！！！\n    mIn.skipBytes(entryCount * 4);\n    mResId = (0xff000000 & mResId) | id << 16;\n\n    while (nextChunk().type == Header.TYPE_TYPE) {\n      readConfig();\n    }\n  }\n\n  private void initResGuardBuild(int resTypeId) {\n    // we need remove string from resguard candidate list if it exists in white list\n    HashSet<Pattern> whiteListPatterns = getWhiteList(mType.getName());\n    // init resguard builder\n    mResguardBuilder.reset(whiteListPatterns);\n    mResguardBuilder.removeStrings(RawARSCDecoder.getExistTypeSpecNameStrings(resTypeId));\n    // 如果是保持mapping的话，需要去掉某部分已经用过的mapping\n    reduceFromOldMappingFile();\n  }\n\n  private void writeLibraryType() throws AndrolibException, IOException {\n    checkChunkType(Header.TYPE_LIBRARY);\n    int libraryCount = mIn.readInt();\n    mOut.writeInt(libraryCount);\n    for (int i = 0; i < libraryCount; i++) {\n      mOut.writeInt(mIn.readInt());/*packageId*/\n      mOut.writeBytes(mIn, 256); /*packageName*/\n    }\n    writeNextChunk(0);\n    while (mHeader.type == Header.TYPE_TYPE) {\n      writeTableTypeSpec();\n    }\n  }\n\n  private void writeTableTypeSpec() throws AndrolibException, IOException {\n    checkChunkType(Header.TYPE_SPEC_TYPE);\n    byte id = mIn.readByte();\n    mOut.writeByte(id);\n    mResId = (0xff000000 & mResId) | id << 16;\n    mOut.writeBytes(mIn, 3);\n    int entryCount = mIn.readInt();\n    mOut.writeInt(entryCount);\n    // 对，这里是用来描述差异性的！！！\n    ///* flags */mIn.skipBytes(entryCount * 4);\n    int[] entryOffsets = mIn.readIntArray(entryCount);\n    mOut.writeIntArray(entryOffsets);\n\n    while (writeNextChunk(0).type == Header.TYPE_TYPE) {\n      writeConfig();\n    }\n  }\n\n  private void readConfig() throws IOException, AndrolibException {\n    checkChunkType(Header.TYPE_TYPE);\n    /* typeId */\n    mIn.skipInt();\n    int entryCount = mIn.readInt();\n    int entriesStart = mIn.readInt();\n    readConfigFlags();\n    int[] entryOffsets = mIn.readIntArray(entryCount);\n    for (int i = 0; i < entryOffsets.length; i++) {\n      mCurEntryID = i;\n      if (entryOffsets[i] != -1) {\n        mResId = (mResId & 0xffff0000) | i;\n        readEntry();\n      }\n    }\n  }\n\n  private void writeConfig() throws IOException, AndrolibException {\n    checkChunkType(Header.TYPE_TYPE);\n    /* typeId */\n    mOut.writeInt(mIn.readInt());\n    /* entryCount */\n    int entryCount = mIn.readInt();\n    mOut.writeInt(entryCount);\n    /* entriesStart */\n    mOut.writeInt(mIn.readInt());\n\n    writeConfigFlags();\n    int[] entryOffsets = mIn.readIntArray(entryCount);\n    mOut.writeIntArray(entryOffsets);\n\n    for (int i = 0; i < entryOffsets.length; i++) {\n      if (entryOffsets[i] != -1) {\n        mResId = (mResId & 0xffff0000) | i;\n        writeEntry();\n      }\n    }\n  }\n\n  private void readEntry() throws IOException, AndrolibException {\n    mIn.skipBytes(2);\n    short flags = mIn.readShort();\n    int specNamesId = mIn.readInt();\n\n    if (mPkg.isCanResguard()) {\n      // 混淆过或者已经添加到白名单的都不需要再处理了\n      if (!mResguardBuilder.isReplaced(mCurEntryID) && !mResguardBuilder.isInWhiteList(mCurEntryID)) {\n        Configuration config = mApkDecoder.getConfig();\n        boolean isWhiteList = false;\n        if (config.mUseWhiteList) {\n          isWhiteList = dealWithWhiteList(specNamesId, config);\n        }\n\n        if (!isWhiteList) {\n          dealWithNonWhiteList(specNamesId, config);\n        }\n      }\n    }\n\n    if ((flags & ENTRY_FLAG_COMPLEX) == 0) {\n      readValue(true, specNamesId);\n    } else {\n      readComplexEntry(false, specNamesId);\n    }\n  }\n\n  /**\n   * deal with whitelist\n   *\n   * @param specNamesId resource spec name id\n   * @param config      {@Configuration} AndResGuard configuration\n   * @return isWhiteList whether this resource is processed by whitelist\n   */\n  private boolean dealWithWhiteList(int specNamesId, Configuration config) throws AndrolibException {\n    String packName = mPkg.getName();\n    if (config.mWhiteList.containsKey(packName)) {\n      HashMap<String, HashSet<Pattern>> typeMaps = config.mWhiteList.get(packName);\n      String typeName = mType.getName();\n      if (typeMaps.containsKey(typeName)) {\n        String specName = mSpecNames.get(specNamesId).toString();\n        HashSet<Pattern> patterns = typeMaps.get(typeName);\n        for (Iterator<Pattern> it = patterns.iterator(); it.hasNext(); ) {\n          Pattern p = it.next();\n          if (p.matcher(specName).matches()) {\n            if (DEBUG) {\n              System.out.printf(\"[match] matcher %s ,typeName %s, specName :%s\\n\", p.pattern(), typeName, specName);\n            }\n            mPkg.putSpecNamesReplace(mResId, specName);\n            mPkg.putSpecNamesblock(specName, specName);\n            mResguardBuilder.setInWhiteList(mCurEntryID);\n\n            mType.putSpecResguardName(specName);\n            return true;\n          }\n        }\n      }\n    }\n    return false;\n  }\n\n  private void dealWithNonWhiteList(int specNamesId, Configuration config) throws AndrolibException, IOException {\n    String replaceString = null;\n    boolean keepMapping = false;\n    if (config.mUseKeepMapping) {\n      String packName = mPkg.getName();\n      if (config.mOldResMapping.containsKey(packName)) {\n        HashMap<String, HashMap<String, String>> typeMaps = config.mOldResMapping.get(packName);\n        String typeName = mType.getName();\n        if (typeMaps.containsKey(typeName)) {\n          HashMap<String, String> nameMap = typeMaps.get(typeName);\n          String specName = mSpecNames.get(specNamesId).toString();\n          if (nameMap.containsKey(specName)) {\n            keepMapping = true;\n            replaceString = nameMap.get(specName);\n          }\n        }\n      }\n    }\n\n    if (!keepMapping) {\n      replaceString = mResguardBuilder.getReplaceString();\n    }\n\n    mResguardBuilder.setInReplaceList(mCurEntryID);\n    if (replaceString == null) {\n      throw new AndrolibException(\"readEntry replaceString == null\");\n    }\n    generalResIDMapping(mPkg.getName(), mType.getName(), mSpecNames.get(specNamesId).toString(), replaceString);\n    mPkg.putSpecNamesReplace(mResId, replaceString);\n    // arsc name列混淆成固定名字, 减少string pool大小\n    boolean useFixedName = config.mFixedResName != null && config.mFixedResName.length() > 0;\n    String fixedName = useFixedName ? config.mFixedResName : replaceString;\n    mPkg.putSpecNamesblock(fixedName, replaceString);\n    mType.putSpecResguardName(replaceString);\n  }\n\n  private void writeEntry() throws IOException, AndrolibException {\n    /* size */\n    mOut.writeBytes(mIn, 2);\n    short flags = mIn.readShort();\n    mOut.writeShort(flags);\n    int specNamesId = mIn.readInt();\n    ResPackage pkg = mPkgs[mCurPackageID];\n    if (pkg.isCanResguard()) {\n      specNamesId = mCurSpecNameToPos.get(pkg.getSpecRepplace(mResId));\n      if (specNamesId < 0) {\n        throw new AndrolibException(String.format(\"writeEntry new specNamesId < 0 %d\", specNamesId));\n      }\n    }\n    mOut.writeInt(specNamesId);\n\n    if ((flags & ENTRY_FLAG_COMPLEX) == 0) {\n      writeValue();\n    } else {\n      writeComplexEntry();\n    }\n  }\n\n  /**\n   * @param flags whether read direct\n   */\n  private void readComplexEntry(boolean flags, int specNamesId) throws IOException, AndrolibException {\n    int parent = mIn.readInt();\n    int count = mIn.readInt();\n    for (int i = 0; i < count; i++) {\n      mIn.readInt();\n      readValue(flags, specNamesId);\n    }\n  }\n\n  private void writeComplexEntry() throws IOException, AndrolibException {\n    mOut.writeInt(mIn.readInt());\n    int count = mIn.readInt();\n    mOut.writeInt(count);\n    for (int i = 0; i < count; i++) {\n      mOut.writeInt(mIn.readInt());\n      writeValue();\n    }\n  }\n\n  /**\n   * @param flags whether read direct\n   */\n  private void readValue(boolean flags, int specNamesId) throws IOException, AndrolibException {\n    /* size */\n    mIn.skipCheckShort((short) 8);\n    /* zero */\n    mIn.skipCheckByte((byte) 0);\n    byte type = mIn.readByte();\n    int data = mIn.readInt();\n\n    //这里面有几个限制，一对于string ,id, array我们是知道肯定不用改的，第二看要那个type是否对应有文件路径\n    if (mPkg.isCanResguard()\n       && flags\n       && type == TypedValue.TYPE_STRING\n       && mShouldResguardForType\n       && mShouldResguardTypeSet.contains(mType.getName())) {\n      if (mTableStringsResguard.get(data) == null) {\n        String raw = mTableStrings.get(data).toString();\n        if (StringUtil.isBlank(raw) || raw.equalsIgnoreCase(\"null\")) return;\n\n        String proguard = mPkg.getSpecRepplace(mResId);\n        //这个要写死这个，因为resources.arsc里面就是用这个\n        int secondSlash = raw.lastIndexOf(\"/\");\n        if (secondSlash == -1) {\n          throw new AndrolibException(String.format(\"can not find \\\\ or raw string in res path = %s\", raw));\n        }\n\n        String newFilePath = raw.substring(0, secondSlash);\n\n        if (!mApkDecoder.getConfig().mKeepRoot) {\n          newFilePath = mOldFileName.get(raw.substring(0, secondSlash));\n        }\n        if (newFilePath == null) {\n          System.err.printf(\"can not found new res path, raw=%s\\n\", raw);\n          return;\n        }\n        //同理这里不能用File.separator，因为resources.arsc里面就是用这个\n        String result = newFilePath + \"/\" + proguard;\n        int firstDot = raw.indexOf(\".\");\n        if (firstDot != -1) {\n          result += raw.substring(firstDot);\n        }\n        String compatibaleraw = new String(raw);\n        String compatibaleresult = new String(result);\n\n        //为了适配window要做一次转换\n        if (!File.separator.contains(\"/\")) {\n          compatibaleresult = compatibaleresult.replace(\"/\", File.separator);\n          compatibaleraw = compatibaleraw.replace(\"/\", File.separator);\n        }\n\n        File resRawFile = new File(mApkDecoder.getOutTempDir().getAbsolutePath() + File.separator + compatibaleraw);\n        File resDestFile = new File(mApkDecoder.getOutDir().getAbsolutePath() + File.separator + compatibaleresult);\n\n        MergeDuplicatedResInfo filterInfo = null;\n        boolean mergeDuplicatedRes = mApkDecoder.getConfig().mMergeDuplicatedRes;\n        if (mergeDuplicatedRes) {\n          filterInfo = mergeDuplicated(resRawFile, resDestFile, compatibaleraw, result);\n          if (filterInfo != null) {\n            resDestFile = new File(filterInfo.filePath);\n            result = filterInfo.fileName;\n          }\n        }\n\n        //这里用的是linux的分隔符\n        HashMap<String, Integer> compressData = mApkDecoder.getCompressData();\n        if (compressData.containsKey(raw)) {\n          compressData.put(result, compressData.get(raw));\n        } else {\n          System.err.printf(\"can not find the compress dataresFile=%s\\n\", raw);\n        }\n\n        if (!resRawFile.exists()) {\n          System.err.printf(\"can not find res file, you delete it? path: resFile=%s\\n\", resRawFile.getAbsolutePath());\n        } else {\n          if (!mergeDuplicatedRes && resDestFile.exists()) {\n            throw new AndrolibException(String.format(\"res dest file is already  found: destFile=%s\",\n               resDestFile.getAbsolutePath()\n            ));\n          }\n          if (filterInfo == null) {\n            FileOperation.copyFileUsingStream(resRawFile, resDestFile);\n          }\n          //already copied\n          mApkDecoder.removeCopiedResFile(resRawFile.toPath());\n          mTableStringsResguard.put(data, result);\n        }\n      }\n    }\n  }\n\n  /**\n   * resource filtering, filtering duplicate resources, reducing the volume of apk\n   */\n  private MergeDuplicatedResInfo mergeDuplicated(File resRawFile, File resDestFile, String compatibaleraw, String result) throws IOException {\n    MergeDuplicatedResInfo filterInfo = null;\n    List<MergeDuplicatedResInfo> mergeDuplicatedResInfoList = mMergeDuplicatedResInfoData.get(resRawFile.length());\n    if (mergeDuplicatedResInfoList != null) {\n      for (MergeDuplicatedResInfo mergeDuplicatedResInfo : mergeDuplicatedResInfoList) {\n        if (mergeDuplicatedResInfo.md5 == null) {\n          mergeDuplicatedResInfo.md5 = Md5Util.getMD5Str(new File(mergeDuplicatedResInfo.filePath));\n        }\n        String resRawFileMd5 = Md5Util.getMD5Str(resRawFile);\n        if (!resRawFileMd5.isEmpty() && resRawFileMd5.equals(mergeDuplicatedResInfo.md5)) {\n          filterInfo = mergeDuplicatedResInfo;\n          filterInfo.md5 = resRawFileMd5;\n          break;\n        }\n      }\n    }\n    if (filterInfo != null) {\n      generalFilterResIDMapping(compatibaleraw, result, filterInfo.originalName, filterInfo.fileName, resRawFile.length());\n      mMergeDuplicatedResCount++;\n      mMergeDuplicatedResTotalSize += resRawFile.length();\n    } else {\n      MergeDuplicatedResInfo info = new MergeDuplicatedResInfo.Builder()\n              .setFileName(result)\n              .setFilePath(resDestFile.getAbsolutePath())\n              .setOriginalName(compatibaleraw)\n              .create();\n      info.fileName = result;\n      info.filePath = resDestFile.getAbsolutePath();\n      info.originalName = compatibaleraw;\n\n      if (mergeDuplicatedResInfoList == null) {\n        mergeDuplicatedResInfoList = new ArrayList<>();\n        mMergeDuplicatedResInfoData.put(resRawFile.length(), mergeDuplicatedResInfoList);\n      }\n      mergeDuplicatedResInfoList.add(info);\n    }\n    return filterInfo;\n  }\n\n  private void writeValue() throws IOException, AndrolibException {\n    /* size */\n    mOut.writeCheckShort(mIn.readShort(), (short) 8);\n    /* zero */\n    mOut.writeCheckByte(mIn.readByte(), (byte) 0);\n    byte type = mIn.readByte();\n    mOut.writeByte(type);\n    int data = mIn.readInt();\n    mOut.writeInt(data);\n  }\n\n  private void readConfigFlags() throws IOException, AndrolibException {\n    int size = mIn.readInt();\n    int read = 28;\n    if (size < 28) {\n      throw new AndrolibException(\"Config size < 28\");\n    }\n\n    boolean isInvalid = false;\n\n    short mcc = mIn.readShort();\n    short mnc = mIn.readShort();\n\n    char[] language = new char[] { (char) mIn.readByte(), (char) mIn.readByte() };\n    char[] country = new char[] { (char) mIn.readByte(), (char) mIn.readByte() };\n\n    byte orientation = mIn.readByte();\n    byte touchscreen = mIn.readByte();\n\n    int density = mIn.readUnsignedShort();\n\n    byte keyboard = mIn.readByte();\n    byte navigation = mIn.readByte();\n    byte inputFlags = mIn.readByte();\n    /* inputPad0 */\n    mIn.skipBytes(1);\n\n    short screenWidth = mIn.readShort();\n    short screenHeight = mIn.readShort();\n\n    short sdkVersion = mIn.readShort();\n    /* minorVersion, now must always be 0 */\n    mIn.skipBytes(2);\n\n    byte screenLayout = 0;\n    byte uiMode = 0;\n    short smallestScreenWidthDp = 0;\n\n    if (size >= 32) {\n      screenLayout = mIn.readByte();\n      uiMode = mIn.readByte();\n      smallestScreenWidthDp = mIn.readShort();\n      read = 32;\n    }\n\n    short screenWidthDp = 0;\n    short screenHeightDp = 0;\n    if (size >= 36) {\n      screenWidthDp = mIn.readShort();\n      screenHeightDp = mIn.readShort();\n      read = 36;\n    }\n\n    char[] localeScript = null;\n    char[] localeVariant = null;\n    if (size >= 48) {\n      localeScript = readScriptOrVariantChar(4).toCharArray();\n      localeVariant = readScriptOrVariantChar(8).toCharArray();\n      read = 48;\n    }\n\n    byte screenLayout2 = 0;\n    if (size >= 52) {\n      screenLayout2 = mIn.readByte();\n      mIn.skipBytes(3); // reserved padding\n      read = 52;\n    }\n\n    if (size >= 56) {\n      mIn.skipBytes(4);\n      read = 56;\n    }\n\n    int exceedingSize = size - KNOWN_CONFIG_BYTES;\n    if (exceedingSize > 0) {\n      byte[] buf = new byte[exceedingSize];\n      read += exceedingSize;\n      mIn.readFully(buf);\n      BigInteger exceedingBI = new BigInteger(1, buf);\n\n      if (exceedingBI.equals(BigInteger.ZERO)) {\n        LOGGER.fine(String.format(\"Config flags size > %d, but exceeding bytes are all zero, so it should be ok.\",\n           KNOWN_CONFIG_BYTES\n        ));\n      } else {\n        LOGGER.warning(String.format(\"Config flags size > %d. Exceeding bytes: 0x%X.\",\n           KNOWN_CONFIG_BYTES,\n           exceedingBI\n        ));\n        isInvalid = true;\n      }\n    }\n  }\n\n  private String readScriptOrVariantChar(int length) throws AndrolibException, IOException {\n    StringBuilder string = new StringBuilder(16);\n\n    while (length-- != 0) {\n      short ch = mIn.readByte();\n      if (ch == 0) {\n        break;\n      }\n      string.append((char) ch);\n    }\n    mIn.skipBytes(length);\n\n    return string.toString();\n  }\n\n  private void writeConfigFlags() throws IOException, AndrolibException {\n    //总的有多大\n    int size = mIn.readInt();\n    if (size < 28) {\n      throw new AndrolibException(\"Config size < 28\");\n    }\n    mOut.writeInt(size);\n\n    mOut.writeBytes(mIn, size - 4);\n  }\n\n  private Header nextChunk() throws IOException {\n    return mHeader = Header.read(mIn);\n  }\n\n  private void checkChunkType(int expectedType) throws AndrolibException {\n    if (mHeader.type != expectedType) {\n      throw new AndrolibException(String.format(\"Invalid chunk type: expected=0x%08x, got=0x%08x\",\n         expectedType,\n         mHeader.type\n      ));\n    }\n  }\n\n  private void nextChunkCheckType(int expectedType) throws IOException, AndrolibException {\n    nextChunk();\n    checkChunkType(expectedType);\n  }\n\n  private Header writeNextChunk(int diffSize) throws IOException, AndrolibException {\n    mHeader = Header.readAndWriteHeader(mIn, mOut, diffSize);\n    return mHeader;\n  }\n\n  private Header writeNextChunkCheck(int expectedType, int diffSize) throws IOException, AndrolibException {\n    mHeader = Header.readAndWriteHeader(mIn, mOut, diffSize);\n    if (mHeader.type != expectedType) {\n      throw new AndrolibException(String.format(\"Invalid chunk type: expected=%d, got=%d\", expectedType, mHeader.type));\n    }\n    return mHeader;\n  }\n\n  /**\n   * 为了加速，不需要处理string,id,array，这几个是肯定不是的\n   */\n  private boolean isToResguardFile(String name) {\n    return (!name.equals(\"string\") && !name.equals(\"id\") && !name.equals(\"array\"));\n  }\n\n  public static class Header {\n    public final static short TYPE_NONE = -1, TYPE_TABLE = 0x0002, TYPE_PACKAGE = 0x0200, TYPE_TYPE = 0x0201,\n       TYPE_SPEC_TYPE = 0x0202, TYPE_LIBRARY = 0x0203;\n\n    public final short type;\n    public final int chunkSize;\n\n    public Header(short type, int size) {\n      this.type = type;\n      this.chunkSize = size;\n    }\n\n    public static Header read(ExtDataInput in) throws IOException {\n      short type;\n      try {\n        type = in.readShort();\n        short count = in.readShort();\n        int size = in.readInt();\n        return new Header(type, size);\n      } catch (EOFException ex) {\n        return new Header(TYPE_NONE, 0);\n      }\n    }\n\n    public static Header readAndWriteHeader(ExtDataInput in, ExtDataOutput out, int diffSize)\n       throws IOException, AndrolibException {\n      short type;\n      int size;\n      try {\n        type = in.readShort();\n        out.writeShort(type);\n        short count = in.readShort();\n        out.writeShort(count);\n        size = in.readInt();\n        size -= diffSize;\n        if (size <= 0) {\n          throw new AndrolibException(String.format(\"readAndWriteHeader size < 0: size=%d\", size));\n        }\n        out.writeInt(size);\n      } catch (EOFException ex) {\n        return new Header(TYPE_NONE, 0);\n      }\n      return new Header(type, size);\n    }\n  }\n\n  public static class FlagsOffset {\n    public final int offset;\n    public final int count;\n\n    public FlagsOffset(int offset, int count) {\n      this.offset = offset;\n      this.count = count;\n    }\n  }\n\n  private static class MergeDuplicatedResInfo {\n    private String fileName;\n    private String filePath;\n    private String originalName;\n    private String md5;\n\n    private MergeDuplicatedResInfo(String fileName, String filePath, String originalName, String md5) {\n      this.fileName = fileName;\n      this.filePath = filePath;\n      this.originalName = originalName;\n      this.md5 = md5;\n    }\n\n    static class Builder {\n      private String fileName;\n      private String filePath;\n      private String originalName;\n      private String md5;\n\n      Builder setFileName(String fileName) {\n        this.fileName = fileName;\n        return this;\n      }\n\n      Builder setFilePath(String filePath) {\n        this.filePath = filePath;\n        return this;\n      }\n\n      public Builder setMd5(String md5) {\n        this.md5 = md5;\n        return this;\n      }\n\n      Builder setOriginalName(String originalName) {\n        this.originalName = originalName;\n        return this;\n      }\n\n      MergeDuplicatedResInfo create() {\n        return new MergeDuplicatedResInfo(fileName, filePath, originalName, md5);\n      }\n    }\n  }\n\n  private class ResguardStringBuilder {\n    private final List<String> mReplaceStringBuffer;\n    private final Set<Integer> mIsReplaced;\n    private final Set<Integer> mIsWhiteList;\n    private String[] mAToZ = {\n       \"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\", \"l\", \"m\", \"n\", \"o\", \"p\", \"q\", \"r\", \"s\", \"t\", \"u\", \"v\",\n       \"w\", \"x\", \"y\", \"z\"\n    };\n    private String[] mAToAll = {\n       \"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"_\", \"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\",\n       \"l\", \"m\", \"n\", \"o\", \"p\", \"q\", \"r\", \"s\", \"t\", \"u\", \"v\", \"w\", \"x\", \"y\", \"z\"\n    };\n    /**\n     * 在window上面有些关键字是不能作为文件名的\n     * CON, PRN, AUX, CLOCK$, NUL\n     * COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9\n     * LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9.\n     */\n    private HashSet<String> mFileNameBlackList;\n\n    public ResguardStringBuilder() {\n      mFileNameBlackList = new HashSet<>();\n      mFileNameBlackList.add(\"con\");\n      mFileNameBlackList.add(\"prn\");\n      mFileNameBlackList.add(\"aux\");\n      mFileNameBlackList.add(\"nul\");\n      mReplaceStringBuffer = new ArrayList<>();\n      mIsReplaced = new HashSet<>();\n      mIsWhiteList = new HashSet<>();\n    }\n\n    public void reset(HashSet<Pattern> blacklistPatterns) {\n      mReplaceStringBuffer.clear();\n      mIsReplaced.clear();\n      mIsWhiteList.clear();\n\n      for (int i = 0; i < mAToZ.length; i++) {\n        String str = mAToZ[i];\n        if (!Utils.match(str, blacklistPatterns)) {\n          mReplaceStringBuffer.add(str);\n        }\n      }\n\n      for (int i = 0; i < mAToZ.length; i++) {\n        String first = mAToZ[i];\n        for (int j = 0; j < mAToAll.length; j++) {\n          String str = first + mAToAll[j];\n          if (!Utils.match(str, blacklistPatterns)) {\n            mReplaceStringBuffer.add(str);\n          }\n        }\n      }\n\n      for (int i = 0; i < mAToZ.length; i++) {\n        String first = mAToZ[i];\n        for (int j = 0; j < mAToAll.length; j++) {\n          String second = mAToAll[j];\n          for (int k = 0; k < mAToAll.length; k++) {\n            String third = mAToAll[k];\n            String str = first + second + third;\n            if (!mFileNameBlackList.contains(str) && !Utils.match(str, blacklistPatterns)) {\n              mReplaceStringBuffer.add(str);\n            }\n          }\n        }\n      }\n    }\n\n    // 对于某种类型用过的mapping，全部不能再用了\n    public void removeStrings(Collection<String> collection) {\n      if (collection == null) return;\n      mReplaceStringBuffer.removeAll(collection);\n    }\n\n    public boolean isReplaced(int id) {\n      return mIsReplaced.contains(id);\n    }\n\n    public boolean isInWhiteList(int id) {\n      return mIsWhiteList.contains(id);\n    }\n\n    public void setInWhiteList(int id) {\n      mIsWhiteList.add(id);\n    }\n\n    public void setInReplaceList(int id) {\n      mIsReplaced.add(id);\n    }\n\n    public String getReplaceString() throws AndrolibException {\n      if (mReplaceStringBuffer.isEmpty()) {\n        throw new AndrolibException(String.format(\"now can only proguard less than 35594 in a single type\\n\"));\n      }\n      return mReplaceStringBuffer.remove(0);\n    }\n  }\n}"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/decoder/RawARSCDecoder.java",
    "content": "/**\n * Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>\n * Copyright 2016 sim sun <sunsj1231@gmail.com>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.androlib.res.decoder;\n\nimport com.mindprod.ledatastream.LEDataInputStream;\nimport com.tencent.mm.androlib.AndrolibException;\nimport com.tencent.mm.androlib.res.data.ResPackage;\nimport com.tencent.mm.androlib.res.data.ResType;\nimport com.tencent.mm.util.ExtDataInput;\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.math.BigInteger;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.logging.Logger;\nimport org.apache.commons.io.input.CountingInputStream;\n\n/**\n * 其实应该是原来有，并且在白名单里面的才去掉！现在没有判断是否在白名单中\n *\n * @author shwenzhang\n */\npublic class RawARSCDecoder {\n  private final static short ENTRY_FLAG_COMPLEX = 0x0001;\n  private final static short ENTRY_FLAG_PUBLIC = 0x0002;\n  private final static short ENTRY_FLAG_WEAK = 0x0004;\n\n  private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName());\n  private static final int KNOWN_CONFIG_BYTES = 64;\n\n  private static HashMap<Integer, Set<String>> mExistTypeNames;\n\n  private final CountingInputStream mCountIn;\n\n  private ExtDataInput mIn;\n  private Header mHeader;\n  private StringBlock mTypeNames;\n  private StringBlock mSpecNames;\n  private ResPackage mPkg;\n  private ResType mType;\n  private int mTypeIdOffset = 0;\n  private int mCurTypeID = -1;\n  private ResPackage[] mPkgs;\n  private int mResId;\n\n  private RawARSCDecoder(InputStream arscStream) throws AndrolibException, IOException {\n    arscStream = mCountIn = new CountingInputStream(arscStream);\n    mIn = new ExtDataInput(new LEDataInputStream(arscStream));\n    mExistTypeNames = new HashMap<>();\n  }\n\n  public static ResPackage[] decode(InputStream arscStream) throws AndrolibException {\n    try {\n      RawARSCDecoder decoder = new RawARSCDecoder(arscStream);\n      System.out.printf(\"parse to get the exist names in the resouces.arsc first\\n\");\n      return decoder.readTable();\n    } catch (IOException ex) {\n      throw new AndrolibException(\"Could not decode arsc file\", ex);\n    }\n  }\n\n  public static Set<String> getExistTypeSpecNameStrings(int type) {\n    return mExistTypeNames.get(type);\n  }\n\n  private ResPackage[] readTable() throws IOException, AndrolibException {\n    nextChunkCheckType(Header.TYPE_TABLE);\n    int packageCount = mIn.readInt();\n    StringBlock.read(mIn);\n    ResPackage[] packages = new ResPackage[packageCount];\n    nextChunk();\n    for (int i = 0; i < packageCount; i++) {\n      packages[i] = readTablePackage();\n    }\n    return packages;\n  }\n\n  private ResPackage readTablePackage() throws IOException, AndrolibException {\n    checkChunkType(Header.TYPE_PACKAGE);\n    int id = mIn.readInt();\n    String name = mIn.readNullEndedString(128, true);\n    /* typeNameStrings */\n    mIn.skipInt();\n    /* typeNameCount */\n    mIn.skipInt();\n    /* specNameStrings */\n    mIn.skipInt();\n    /* specNameCount */\n    mIn.skipInt();\n\n    // TypeIdOffset was added platform_frameworks_base/@f90f2f8dc36e7243b85e0b6a7fd5a590893c827e\n    // which is only in split/new applications.\n    int splitHeaderSize = (2 + 2 + 4 + 4 + (2 * 128) + (4 * 5)); // short, short, int, int, char[128], int * 4\n    if (mHeader.headerSize == splitHeaderSize) {\n      mTypeIdOffset = mIn.readInt();\n    }\n\n    mTypeNames = StringBlock.read(mIn);\n    mSpecNames = StringBlock.read(mIn);\n    mResId = id << 24;\n    mPkg = new ResPackage(id, name);\n    nextChunk();\n    while (mHeader.type == Header.TYPE_LIBRARY) {\n      readLibraryType();\n    }\n    while (mHeader.type == Header.TYPE_SPEC_TYPE) {\n      readTableTypeSpec();\n    }\n\n    return mPkg;\n  }\n\n  private void readLibraryType() throws AndrolibException, IOException {\n    checkChunkType(Header.TYPE_LIBRARY);\n    int libraryCount = mIn.readInt();\n\n    int packageId;\n    String packageName;\n\n    for (int i = 0; i < libraryCount; i++) {\n      packageId = mIn.readInt();\n      packageName = mIn.readNullEndedString(128, true);\n      System.out.printf(\"Decoding Shared Library (%s), pkgId: %d\\n\", packageName, packageId);\n    }\n\n    nextChunk();\n    while (mHeader.type == Header.TYPE_TYPE) {\n      readTableTypeSpec();\n    }\n  }\n\n  private void readTableTypeSpec() throws AndrolibException, IOException {\n    readSingleTableTypeSpec();\n\n    nextChunk();\n    while (mHeader.type == Header.TYPE_SPEC_TYPE) {\n      readSingleTableTypeSpec();\n      nextChunk();\n    }\n    while (mHeader.type == Header.TYPE_TYPE) {\n      readConfig();\n      nextChunk();\n    }\n  }\n\n  private void readSingleTableTypeSpec() throws AndrolibException, IOException {\n    checkChunkType(Header.TYPE_SPEC_TYPE);\n    int id = mIn.readUnsignedByte();\n    mIn.skipBytes(3);\n    int entryCount = mIn.readInt();\n\n    /* flags */\n    mIn.skipBytes(entryCount * 4);\n\n    mCurTypeID = id;\n    mResId = (0xff000000 & mResId) | id << 16;\n    mType = new ResType(mTypeNames.getString(id - 1), mPkg);\n  }\n\n  private void readConfig() throws IOException, AndrolibException {\n    checkChunkType(Header.TYPE_TYPE);\n    int typeId = mIn.readUnsignedByte() - mTypeIdOffset;\n\n    int typeFlags = mIn.readByte();\n    /* reserved */\n    mIn.skipBytes(2);\n\n    int entryCount = mIn.readInt();\n    int entriesStart = mIn.readInt();\n    readConfigFlags();\n    int[] entryOffsets = mIn.readIntArray(entryCount);\n    for (int i = 0; i < entryOffsets.length; i++) {\n      if (entryOffsets[i] != -1) {\n        mResId = (mResId & 0xffff0000) | i;\n        readEntry();\n      }\n    }\n  }\n\n  /**\n   * 需要防止由于某些非常恶心的白名单，导致出现重复id\n   *\n   * @throws IOException\n   * @throws AndrolibException\n   */\n  private void readEntry() throws IOException, AndrolibException {\n    /* size */\n    mIn.skipBytes(2);\n    short flags = mIn.readShort();\n    int specNamesId = mIn.readInt();\n    putTypeSpecNameStrings(mCurTypeID, mSpecNames.getString(specNamesId));\n    boolean readDirect = false;\n    if ((flags & ENTRY_FLAG_COMPLEX) == 0) {\n      readDirect = true;\n      readValue(readDirect, specNamesId);\n    } else {\n      readDirect = false;\n      readComplexEntry(readDirect, specNamesId);\n    }\n  }\n\n  private void readComplexEntry(boolean flags, int specNamesId) throws IOException, AndrolibException {\n    int parent = mIn.readInt();\n    int count = mIn.readInt();\n    for (int i = 0; i < count; i++) {\n      mIn.readInt();\n      readValue(flags, specNamesId);\n    }\n  }\n\n  private void readValue(boolean flags, int specNamesId) throws IOException, AndrolibException {\n    /* size */\n    mIn.skipCheckShort((short) 8);\n    /* zero */\n    mIn.skipCheckByte((byte) 0);\n    byte type = mIn.readByte();\n    int data = mIn.readInt();\n  }\n\n  private void readConfigFlags() throws IOException, AndrolibException {\n    int read = 28;\n    int size = mIn.readInt();\n    if (size < 28) {\n      throw new AndrolibException(\"Config size < 28\");\n    }\n\n    boolean isInvalid = false;\n    short mcc = mIn.readShort();\n    short mnc = mIn.readShort();\n    char[] language = new char[] { (char) mIn.readByte(), (char) mIn.readByte() };\n    char[] country = new char[] { (char) mIn.readByte(), (char) mIn.readByte() };\n    byte orientation = mIn.readByte();\n    byte touchscreen = mIn.readByte();\n    int density = mIn.readUnsignedShort();\n    byte keyboard = mIn.readByte();\n    byte navigation = mIn.readByte();\n    byte inputFlags = mIn.readByte();\n    /* inputPad0 */\n    mIn.skipBytes(1);\n\n    short screenWidth = mIn.readShort();\n    short screenHeight = mIn.readShort();\n\n    short sdkVersion = mIn.readShort();\n    /* minorVersion, now must always be 0 */\n    mIn.skipBytes(2);\n\n    byte screenLayout = 0;\n    byte uiMode = 0;\n    short smallestScreenWidthDp = 0;\n\n    if (size >= 32) {\n      screenLayout = mIn.readByte();\n      uiMode = mIn.readByte();\n      smallestScreenWidthDp = mIn.readShort();\n      read = 32;\n    }\n\n    short screenWidthDp = 0;\n    short screenHeightDp = 0;\n    if (size >= 36) {\n      screenWidthDp = mIn.readShort();\n      screenHeightDp = mIn.readShort();\n      read = 36;\n    }\n\n    char[] localeScript = null;\n    char[] localeVariant = null;\n    if (size >= 48) {\n      localeScript = readScriptOrVariantChar(4).toCharArray();\n      localeVariant = readScriptOrVariantChar(8).toCharArray();\n      read = 48;\n    }\n\n    byte screenLayout2 = 0;\n    if (size >= 52) {\n      screenLayout2 = mIn.readByte();\n      mIn.skipBytes(3); // reserved padding\n      read = 52;\n    }\n\n    if (size >= 56) {\n      mIn.skipBytes(4);\n      read = 56;\n    }\n\n    if (size >= 64) {\n      mIn.skipBytes(8);\n      read = 64;\n    }\n\n    int exceedingSize = size - KNOWN_CONFIG_BYTES;\n    if (exceedingSize > 0) {\n      byte[] buf = new byte[exceedingSize];\n      mIn.readFully(buf);\n      BigInteger exceedingBI = new BigInteger(1, buf);\n\n      if (exceedingBI.equals(BigInteger.ZERO)) {\n        LOGGER.fine(String.format(\"Config flags size > %d, but exceeding bytes are all zero, so it should be ok.\",\n            KNOWN_CONFIG_BYTES\n        ));\n      } else {\n        LOGGER.warning(String.format(\"Config flags size > %d. Exceeding bytes: 0x%X.\",\n            KNOWN_CONFIG_BYTES,\n            exceedingBI\n        ));\n      }\n    } else {\n      int remainingSize = size - read;\n      if (remainingSize > 0) {\n        mIn.skipBytes(remainingSize);\n      }\n    }\n  }\n\n  private String readScriptOrVariantChar(int length) throws AndrolibException, IOException {\n    StringBuilder string = new StringBuilder(16);\n\n    while (length-- != 0) {\n      short ch = mIn.readByte();\n      if (ch == 0) {\n        break;\n      }\n      string.append((char) ch);\n    }\n    mIn.skipBytes(length);\n\n    return string.toString();\n  }\n\n  private Header nextChunk() throws IOException {\n    return mHeader = Header.read(mIn, mCountIn);\n  }\n\n  private void checkChunkType(int expectedType) throws AndrolibException {\n    if (mHeader.type != expectedType) {\n      throw new AndrolibException(String.format(\"Invalid chunk type: expected=0x%08x, got=0x%08x\",\n          expectedType,\n          mHeader.type\n      ));\n    }\n  }\n\n  private void nextChunkCheckType(int expectedType) throws IOException, AndrolibException {\n    nextChunk();\n    checkChunkType(expectedType);\n  }\n\n  private void putTypeSpecNameStrings(int type, String name) {\n    Set<String> names = mExistTypeNames.get(type);\n    if (names == null) {\n      names = new HashSet<>();\n    }\n    names.add(name);\n    mExistTypeNames.put(type, names);\n  }\n\n  public static class Header {\n    public final static short TYPE_NONE = -1;\n    public final static short TYPE_TABLE = 0x0002;\n    public final static short TYPE_PACKAGE = 0x0200;\n    public final static short TYPE_TYPE = 0x0201;\n    public final static short TYPE_SPEC_TYPE = 0x0202;\n    public final static short TYPE_LIBRARY = 0x0203;\n    public final short type;\n    public final int headerSize;\n    public final int chunkSize;\n    public final int startPosition;\n    public final int endPosition;\n\n    public Header(short type, int headerSize, int chunkSize, int headerStart) {\n      this.type = type;\n      this.headerSize = headerSize;\n      this.chunkSize = chunkSize;\n      this.startPosition = headerStart;\n      this.endPosition = headerStart + chunkSize;\n    }\n\n    public static Header read(ExtDataInput in, CountingInputStream countIn) throws IOException {\n      short type;\n      int start = countIn.getCount();\n      try {\n        type = in.readShort();\n      } catch (EOFException ex) {\n        return new Header(TYPE_NONE, 0, 0, countIn.getCount());\n      }\n      return new Header(type, in.readShort(), in.readInt(), start);\n    }\n  }\n\n  public static class FlagsOffset {\n    public final int offset;\n    public final int count;\n\n    public FlagsOffset(int offset, int count) {\n      this.offset = offset;\n      this.count = count;\n    }\n  }\n}"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/decoder/StringBlock.java",
    "content": "/**\n * Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>\n * Copyright 2016 sim sun <sunsj1231@gmail.com>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.androlib.res.decoder;\n\nimport com.tencent.mm.androlib.AndrolibException;\nimport com.tencent.mm.util.ExtDataInput;\nimport com.tencent.mm.util.ExtDataOutput;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.CharacterCodingException;\nimport java.nio.charset.Charset;\nimport java.nio.charset.CharsetDecoder;\nimport java.util.Arrays;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\n/**\n * @author shwenzhang\n */\npublic class StringBlock {\n\n  private static final CharsetDecoder UTF16LE_DECODER = Charset.forName(\"UTF-16LE\").newDecoder();\n  private static final CharsetDecoder UTF8_DECODER = Charset.forName(\"UTF-8\").newDecoder();\n  private static final Logger LOGGER = Logger.getLogger(StringBlock.class.getName());\n\n  // ResChunk_header = header.type (0x0001) + header.headerSize (0x001C)\n  private static final int CHUNK_STRINGPOOL_TYPE = 0x001C0001;\n  private static final int UTF8_FLAG = 0x00000100;\n  private static final int CHUNK_NULL_TYPE = 0x00000000;\n  private static final byte NULL = 0;\n\n  private int[] m_stringOffsets;\n  private byte[] m_strings;\n  private int[] m_styleOffsets;\n  private int[] m_styles;\n  private boolean m_isUTF8;\n  private int[] m_stringOwns;\n\n  private StringBlock() {\n  }\n\n  /**\n   * Reads whole (including chunk type) string block from stream. Stream must\n   * be at the chunk type.\n   *\n   * @param reader reader\n   * @return stringblock\n   * @throws IOException ioexcetpion\n   */\n  public static StringBlock read(ExtDataInput reader) throws IOException {\n    reader.skipCheckChunkTypeInt(CHUNK_STRINGPOOL_TYPE, CHUNK_NULL_TYPE);\n    int chunkSize = reader.readInt();\n    int stringCount = reader.readInt();\n    int styleCount = reader.readInt();\n    int flags = reader.readInt();\n    int stringsOffset = reader.readInt();\n    int stylesOffset = reader.readInt();\n\n    StringBlock block = new StringBlock();\n    block.m_isUTF8 = (flags & UTF8_FLAG) != 0;\n    block.m_stringOffsets = reader.readIntArray(stringCount);\n    block.m_stringOwns = new int[stringCount];\n    Arrays.fill(block.m_stringOwns, -1);\n\n    if (styleCount != 0) {\n      block.m_styleOffsets = reader.readIntArray(styleCount);\n    }\n    {\n      int size = ((stylesOffset == 0) ? chunkSize : stylesOffset) - stringsOffset;\n\n      if ((size % 4) != 0) {\n        throw new IOException(\"String data size is not multiple of 4 (\" + size + \").\");\n      }\n      block.m_strings = new byte[size];\n\n      reader.readFully(block.m_strings);\n    }\n    if (stylesOffset != 0) {\n      int size = (chunkSize - stylesOffset);\n      if ((size % 4) != 0) {\n        throw new IOException(\"Style data size is not multiple of 4 (\" + size + \").\");\n      }\n      block.m_styles = reader.readIntArray(size / 4);\n    }\n    return block;\n  }\n\n  public static int writeSpecNameStringBlock(\n          ExtDataInput reader, ExtDataOutput out, Map<String, Set<String>> specNames, Map<String, Integer> curSpecNameToPos)\n      throws IOException, AndrolibException {\n    int type = reader.readInt();\n    int chunkSize = reader.readInt();\n    int stringCount = reader.readInt();\n    int styleOffsetCount = reader.readInt();\n\n    if (styleOffsetCount != 0) {\n      throw new AndrolibException(String.format(\"writeSpecNameStringBlock styleOffsetCount != 0  styleOffsetCount %d\",\n          styleOffsetCount\n      ));\n    }\n\n    int flags = reader.readInt();\n    boolean isUTF8 = (flags & UTF8_FLAG) != 0;\n    int stringsOffset = reader.readInt();\n    int stylesOffset = reader.readInt();\n    reader.readIntArray(stringCount);\n    int size = ((stylesOffset == 0) ? chunkSize : stylesOffset) - stringsOffset;\n\n    if ((size % 4) != 0) {\n      throw new IOException(\"String data size is not multiple of 4 (\" + size + \").\");\n    }\n    byte[] temp_strings = new byte[size];\n    reader.readFully(temp_strings);\n    int totalSize = 0;\n    out.writeCheckInt(type, CHUNK_STRINGPOOL_TYPE);\n    totalSize += 4;\n    stringCount = specNames.keySet().size();\n    System.out.println(\"String pool size: \" + stringCount);\n\n    totalSize += 6 * 4 + 4 * stringCount;\n    stringsOffset = totalSize;\n\n    int[] stringOffsets = new int[stringCount];\n    // make twice size buffer for avoiding out of bounds error\n    byte[] stringBytes = new byte[size * 2];\n    int offset = 0;\n    int i = 0;\n    curSpecNameToPos.clear();\n\n    for (Iterator<String> it = specNames.keySet().iterator(); it.hasNext(); ) {\n      stringOffsets[i] = offset;\n      String name = it.next();\n      for (String specName : specNames.get(name)) {\n        // N res entry item point to one string constant\n        curSpecNameToPos.put(specName, i);\n      }\n      if (isUTF8) {\n        stringBytes[offset++] = (byte) name.length();\n        stringBytes[offset++] = (byte) name.length();\n        totalSize += 2;\n        byte[] tempByte = name.getBytes(Charset.forName(\"UTF-8\"));\n        if (name.length() != tempByte.length) {\n          throw new AndrolibException(String.format(\n              \"writeSpecNameStringBlock %s UTF-8 length is different name %d, tempByte %d\\n\",\n              name,\n              name.length(),\n              tempByte.length\n          ));\n        }\n        System.arraycopy(tempByte, 0, stringBytes, offset, tempByte.length);\n        offset += name.length();\n        stringBytes[offset++] = NULL;\n        totalSize += name.length() + 1;\n      } else {\n        writeShort(stringBytes, offset, (short) name.length());\n        offset += 2;\n        totalSize += 2;\n        byte[] tempByte = name.getBytes(Charset.forName(\"UTF-16LE\"));\n        if ((name.length() * 2) != tempByte.length) {\n          throw new AndrolibException(String.format(\n              \"writeSpecNameStringBlock %s UTF-16LE length is different name %d, tempByte %d\\n\",\n              name,\n              name.length(),\n              tempByte.length\n          ));\n        }\n        System.arraycopy(tempByte, 0, stringBytes, offset, tempByte.length);\n        offset += tempByte.length;\n        stringBytes[offset++] = NULL;\n        stringBytes[offset++] = NULL;\n        totalSize += tempByte.length + 2;\n      }\n      i++;\n    }\n    //要保证string size 是4的倍数,要补零\n    size = totalSize - stringsOffset;\n    if ((size % 4) != 0) {\n      int add = 4 - (size % 4);\n      for (i = 0; i < add; i++) {\n        stringBytes[offset++] = NULL;\n        totalSize++;\n      }\n    }\n\n    out.writeInt(totalSize);\n    out.writeInt(stringCount);\n    out.writeInt(styleOffsetCount);\n    out.writeInt(flags);\n    out.writeInt(stringsOffset);\n    out.writeInt(stylesOffset);\n    out.writeIntArray(stringOffsets);\n    out.write(stringBytes, 0, offset);\n    return (chunkSize - totalSize);\n  }\n\n  public static int writeTableNameStringBlock(\n      ExtDataInput reader, ExtDataOutput out, Map<Integer, String> tableProguardMap)\n      throws IOException, AndrolibException {\n    int type = reader.readInt();\n    int chunkSize = reader.readInt();\n    int stringCount = reader.readInt();\n    int styleOffsetCount = reader.readInt();\n    int flags = reader.readInt();\n    int stringsOffset = reader.readInt();\n    int stylesOffset = reader.readInt();\n\n    StringBlock block = new StringBlock();\n    block.m_isUTF8 = (flags & UTF8_FLAG) != 0;\n    if (block.m_isUTF8) {\n      System.out.printf(\"resources.arsc Character Encoding: utf-8\\n\");\n    } else {\n      System.out.printf(\"resources.arsc Character Encoding: utf-16\\n\");\n    }\n\n    block.m_stringOffsets = reader.readIntArray(stringCount);\n    block.m_stringOwns = new int[stringCount];\n    for (int i = 0; i < stringCount; i++) {\n      block.m_stringOwns[i] = -1;\n    }\n    if (styleOffsetCount != 0) {\n      block.m_styleOffsets = reader.readIntArray(styleOffsetCount);\n    }\n    {\n      int size = ((stylesOffset == 0) ? chunkSize : stylesOffset) - stringsOffset;\n      if ((size % 4) != 0) {\n        throw new IOException(\"String data size is not multiple of 4 (\" + size + \").\");\n      }\n      block.m_strings = new byte[size];\n      reader.readFully(block.m_strings);\n    }\n    if (stylesOffset != 0) {\n      int size = (chunkSize - stylesOffset);\n      if ((size % 4) != 0) {\n        throw new IOException(\"Style data size is not multiple of 4 (\" + size + \").\");\n      }\n      block.m_styles = reader.readIntArray(size / 4);\n    }\n\n    int totalSize = 0;\n    out.writeCheckInt(type, CHUNK_STRINGPOOL_TYPE);\n    totalSize += 4;\n\n    totalSize += 6 * 4 + 4 * stringCount + 4 * styleOffsetCount;\n    stringsOffset = totalSize;\n\n    byte[] strings = new byte[block.m_strings.length];\n    int[] stringOffsets = new int[stringCount];\n    System.arraycopy(block.m_stringOffsets, 0, stringOffsets, 0, stringOffsets.length);\n\n    int offset = 0;\n    int i;\n    for (i = 0; i < stringCount; i++) {\n      stringOffsets[i] = offset;\n      //如果找不到即没混淆这一项,直接拷贝\n      if (tableProguardMap.get(i) == null) {\n        //需要区分是否是最后一项\n        int copyLen = (i == (stringCount - 1)) ? (block.m_strings.length - block.m_stringOffsets[i])\n            : (block.m_stringOffsets[i + 1] - block.m_stringOffsets[i]);\n        System.arraycopy(block.m_strings, block.m_stringOffsets[i], strings, offset, copyLen);\n        offset += copyLen;\n        totalSize += copyLen;\n      } else {\n        String name = tableProguardMap.get(i);\n        if (block.m_isUTF8) {\n          strings[offset++] = (byte) name.length();\n          strings[offset++] = (byte) name.length();\n          totalSize += 2;\n          byte[] tempByte = name.getBytes(Charset.forName(\"UTF-8\"));\n          if (name.length() != tempByte.length) {\n            throw new AndrolibException(String.format(\n                \"writeTableNameStringBlock UTF-8 length is different  name %d, tempByte %d\\n\",\n                name.length(),\n                tempByte.length\n            ));\n          }\n          System.arraycopy(tempByte, 0, strings, offset, tempByte.length);\n          offset += name.length();\n          strings[offset++] = NULL;\n          totalSize += name.length() + 1;\n        } else {\n          writeShort(strings, offset, (short) name.length());\n          offset += 2;\n          totalSize += 2;\n          byte[] tempByte = name.getBytes(Charset.forName(\"UTF-16LE\"));\n          if ((name.length() * 2) != tempByte.length) {\n            throw new AndrolibException(String.format(\n                \"writeTableNameStringBlock UTF-16LE length is different  name %d, tempByte %d\\n\",\n                name.length(),\n                tempByte.length\n            ));\n          }\n          System.arraycopy(tempByte, 0, strings, offset, tempByte.length);\n          offset += tempByte.length;\n          strings[offset++] = NULL;\n          strings[offset++] = NULL;\n          totalSize += tempByte.length + 2;\n        }\n      }\n    }\n    //要保证string size 是4的倍数,要补零\n    int size = totalSize - stringsOffset;\n    if ((size % 4) != 0) {\n      int add = 4 - (size % 4);\n      for (i = 0; i < add; i++) {\n        strings[offset++] = NULL;\n        totalSize++;\n      }\n    }\n    //因为是int的,如果之前的不为0\n    if (stylesOffset != 0) {\n      stylesOffset = totalSize;\n      totalSize += block.m_styles.length * 4;\n    }\n\n    out.writeInt(totalSize);\n    out.writeInt(stringCount);\n    out.writeInt(styleOffsetCount);\n    out.writeInt(flags);\n    out.writeInt(stringsOffset);\n    out.writeInt(stylesOffset);\n    out.writeIntArray(stringOffsets);\n    if (stylesOffset != 0) {\n      out.writeIntArray(block.m_styleOffsets);\n    }\n    out.write(strings, 0, offset);\n    if (stylesOffset != 0) {\n      out.writeIntArray(block.m_styles);\n    }\n    return (chunkSize - totalSize);\n  }\n\n  /**\n   * Reads whole (including chunk type) string block from stream. Stream must\n   * be at the chunk type.\n   *\n   * @param reader ExtDataInput reader\n   * @param out ExtDataOutput out\n   * @throws IOException ioexception\n   */\n  public static void writeAll(ExtDataInput reader, ExtDataOutput out) throws IOException {\n    out.writeCheckChunkTypeInt(reader, CHUNK_STRINGPOOL_TYPE, CHUNK_NULL_TYPE);\n    int chunkSize = reader.readInt();\n    out.writeInt(chunkSize);\n    out.writeBytes(reader, chunkSize - 8);\n  }\n\n  private static final int[] getUtf8(byte[] array, int offset) {\n    int val = array[offset];\n    int length;\n    // We skip the utf16 length of the string\n    if ((val & 0x80) != 0) {\n      offset += 2;\n    } else {\n      offset += 1;\n    }\n    // And we read only the utf-8 encoded length of the string\n    val = array[offset];\n    offset += 1;\n    if ((val & 0x80) != 0) {\n      int low = (array[offset] & 0xFF);\n      length = ((val & 0x7F) << 8) + low;\n      offset += 1;\n    } else {\n      length = val;\n    }\n    return new int[] { offset, length };\n  }\n\n  private static final int[] getUtf16(byte[] array, int offset) {\n    int val = ((array[offset + 1] & 0xFF) << 8 | array[offset] & 0xFF);\n\n    if ((val & 0x8000) != 0) {\n      int high = (array[offset + 3] & 0xFF) << 8;\n      int low = (array[offset + 2] & 0xFF);\n      int len_value = ((val & 0x7FFF) << 16) + (high + low);\n      return new int[] { 4, len_value * 2 };\n    }\n    return new int[] { 2, val * 2 };\n  }\n\n  private static final int getShort(byte[] array, int offset) {\n    return (array[offset + 1] & 0xff) << 8 | array[offset] & 0xff;\n  }\n\n  private static final void writeShort(byte[] array, int offset, short value) {\n    array[offset] = (byte) (0xFF & value);\n    array[offset + 1] = (byte) (0xFF & (value >> 8));\n  }\n\n  private static final int getShort(int[] array, int offset) {\n    int value = array[offset / 4];\n    if ((offset % 4) / 2 == 0) {\n      return (value & 0xFFFF);\n    } else {\n      return (value >>> 16);\n    }\n  }\n\n  /**\n   * Returns number of strings in block.\n   *\n   * @return int number of strings in block.\n   */\n  public int getCount() {\n    return m_stringOffsets != null ? m_stringOffsets.length : 0;\n  }\n\n  /**\n   * Returns raw string (without any styling information) at specified index.\n   *\n   * @param index index\n   * @return raw string\n   */\n  public String getString(int index) {\n    if (index < 0 || m_stringOffsets == null || index >= m_stringOffsets.length) {\n      return null;\n    }\n    int offset = m_stringOffsets[index];\n    int length;\n\n    if (m_isUTF8) {\n      int[] val = getUtf8(m_strings, offset);\n      offset = val[0];\n      length = val[1];\n    } else {\n      int[] val = getUtf16(m_strings, offset);\n      offset += val[0];\n      length = val[1];\n    }\n    return decodeString(offset, length);\n  }\n\n  /**\n   * Not yet implemented.\n   * <p>\n   * Returns string with style information (if any).\n   *\n   * @param index index\n   * @return string with style information (if any).\n   */\n  public CharSequence get(int index) {\n    return getString(index);\n  }\n\n  /**\n   * Finds index of the string. Returns -1 if the string was not found.\n   *\n   * @param string input string\n   * @return index of the string\n   */\n  public int find(String string) {\n    if (string == null) {\n      return -1;\n    }\n    for (int i = 0; i != m_stringOffsets.length; ++i) {\n      int offset = m_stringOffsets[i];\n      int length = getShort(m_strings, offset);\n      if (length != string.length()) {\n        continue;\n      }\n      int j = 0;\n      for (; j != length; ++j) {\n        offset += 2;\n        if (string.charAt(j) != getShort(m_strings, offset)) {\n          break;\n        }\n      }\n      if (j == length) {\n        return i;\n      }\n    }\n    return -1;\n  }\n\n  private String decodeString(int offset, int length) {\n    try {\n      return (m_isUTF8 ? UTF8_DECODER : UTF16LE_DECODER).decode(ByteBuffer.wrap(m_strings, offset, length)).toString();\n    } catch (CharacterCodingException ex) {\n      LOGGER.log(Level.WARNING, null, ex);\n      return null;\n    }\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/util/ExtFile.java",
    "content": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.androlib.res.util;\n\nimport com.tencent.mm.directory.Directory;\nimport com.tencent.mm.directory.DirectoryException;\nimport com.tencent.mm.directory.FileDirectory;\nimport com.tencent.mm.directory.ZipRODirectory;\nimport java.io.File;\nimport java.net.URI;\n\npublic class ExtFile extends File {\n  private Directory mDirectory;\n\n  public ExtFile(File file) {\n    super(file.getPath());\n  }\n\n  public ExtFile(URI uri) {\n    super(uri);\n  }\n\n  public ExtFile(File parent, String child) {\n    super(parent, child);\n  }\n\n  public ExtFile(String parent, String child) {\n    super(parent, child);\n  }\n\n  public ExtFile(String pathname) {\n    super(pathname);\n  }\n\n  public Directory getDirectory() throws DirectoryException {\n    if (mDirectory == null) {\n      if (isDirectory()) {\n        mDirectory = new FileDirectory(this);\n      } else {\n        mDirectory = new ZipRODirectory(this);\n      }\n    }\n    return mDirectory;\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/util/StringUtil.java",
    "content": "package com.tencent.mm.androlib.res.util;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\npublic class StringUtil {\n  public static boolean isPresent(final String string) {\n    return string != null && string.length() > 0;\n  }\n\n  public static boolean isBlank(final String string) {\n    return !isPresent(string);\n  }\n\n  public static String readInputStream(InputStream inputStream) throws IOException {\n    ByteArrayOutputStream result = new ByteArrayOutputStream();\n    byte[] buffer = new byte[4096];\n    int length;\n    while ((length = inputStream.read(buffer)) != -1) {\n      result.write(buffer, 0, length);\n    }\n    return result.toString(\"UTF-8\");\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/directory/AbstractDirectory.java",
    "content": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.directory;\n\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.Map;\nimport java.util.Set;\n\npublic abstract class AbstractDirectory implements Directory {\n  protected Set<String> mFiles;\n  protected Map<String, AbstractDirectory> mDirs;\n\n  @Override\n  public Set<String> getFiles() {\n    return getFiles(false);\n  }\n\n  @Override\n  public Set<String> getFiles(boolean recursive) {\n    if (mFiles == null) {\n      loadFiles();\n    }\n    if (!recursive) {\n      return mFiles;\n    }\n\n    Set<String> files = new LinkedHashSet<String>(mFiles);\n    for (Map.Entry<String, ? extends Directory> dir : getAbstractDirs().entrySet()) {\n      for (String path : dir.getValue().getFiles(true)) {\n        files.add(dir.getKey() + separator + path);\n      }\n    }\n    return files;\n  }\n\n  @Override\n  public boolean containsFile(String path) {\n    SubPath subpath;\n    try {\n      subpath = getSubPath(path);\n    } catch (PathNotExist e) {\n      return false;\n    }\n\n    if (subpath.dir != null) {\n      return subpath.dir.containsFile(subpath.path);\n    }\n    return getFiles().contains(subpath.path);\n  }\n\n  @Override\n  public boolean containsDir(String path) {\n    SubPath subpath;\n    try {\n      subpath = getSubPath(path);\n    } catch (PathNotExist e) {\n      return false;\n    }\n\n    if (subpath.dir != null) {\n      return subpath.dir.containsDir(subpath.path);\n    }\n    return getAbstractDirs().containsKey(subpath.path);\n  }\n\n  @Override\n  public Map<String, Directory> getDirs() throws UnsupportedOperationException {\n    return getDirs(false);\n  }\n\n  @Override\n  public Map<String, Directory> getDirs(boolean recursive) throws UnsupportedOperationException {\n    return new LinkedHashMap<String, Directory>(getAbstractDirs(recursive));\n  }\n\n  @Override\n  public InputStream getFileInput(String path) throws DirectoryException {\n    SubPath subpath = getSubPath(path);\n    if (subpath.dir != null) {\n      return subpath.dir.getFileInput(subpath.path);\n    }\n    if (!getFiles().contains(subpath.path)) {\n      throw new PathNotExist(path);\n    }\n    return getFileInputLocal(subpath.path);\n  }\n\n  @Override\n  public OutputStream getFileOutput(String path) throws DirectoryException {\n    ParsedPath parsed = parsePath(path);\n    if (parsed.dir == null) {\n      getFiles().add(parsed.subpath);\n      return getFileOutputLocal(parsed.subpath);\n    }\n\n    Directory dir;\n    // IMPOSSIBLE_EXCEPTION\n    try {\n      dir = createDir(parsed.dir);\n    } catch (PathAlreadyExists e) {\n      dir = getAbstractDirs().get(parsed.dir);\n    }\n    return dir.getFileOutput(parsed.subpath);\n  }\n\n  @Override\n  public Directory getDir(String path) throws PathNotExist {\n    SubPath subpath = getSubPath(path);\n    if (subpath.dir != null) {\n      return subpath.dir.getDir(subpath.path);\n    }\n    if (!getAbstractDirs().containsKey(subpath.path)) {\n      throw new PathNotExist(path);\n    }\n    return getAbstractDirs().get(subpath.path);\n  }\n\n  @Override\n  public Directory createDir(String path) throws DirectoryException {\n    ParsedPath parsed = parsePath(path);\n    AbstractDirectory dir;\n    if (parsed.dir == null) {\n      if (getAbstractDirs().containsKey(parsed.subpath)) {\n        throw new PathAlreadyExists(path);\n      }\n      dir = createDirLocal(parsed.subpath);\n      getAbstractDirs().put(parsed.subpath, dir);\n      return dir;\n    }\n\n    if (getAbstractDirs().containsKey(parsed.dir)) {\n      dir = getAbstractDirs().get(parsed.dir);\n    } else {\n      dir = createDirLocal(parsed.dir);\n      getAbstractDirs().put(parsed.dir, dir);\n    }\n    return dir.createDir(parsed.subpath);\n  }\n\n  @Override\n  public boolean removeFile(String path) {\n    SubPath subpath;\n    try {\n      subpath = getSubPath(path);\n    } catch (PathNotExist e) {\n      return false;\n    }\n\n    if (subpath.dir != null) {\n      return subpath.dir.removeFile(subpath.path);\n    }\n    if (!getFiles().contains(subpath.path)) {\n      return false;\n    }\n    removeFileLocal(subpath.path);\n    getFiles().remove(subpath.path);\n    return true;\n  }\n\n  protected Map<String, AbstractDirectory> getAbstractDirs() {\n    return getAbstractDirs(false);\n  }\n\n  protected Map<String, AbstractDirectory> getAbstractDirs(boolean recursive) {\n    if (mDirs == null) {\n      loadDirs();\n    }\n    if (!recursive) {\n      return mDirs;\n    }\n\n    Map<String, AbstractDirectory> dirs = new LinkedHashMap<String, AbstractDirectory>(mDirs);\n    for (Map.Entry<String, AbstractDirectory> dir : getAbstractDirs().entrySet()) {\n      for (Map.Entry<String, AbstractDirectory> subdir : dir.getValue().getAbstractDirs(true).entrySet()) {\n        dirs.put(dir.getKey() + separator + subdir.getKey(), subdir.getValue());\n      }\n    }\n    return dirs;\n  }\n\n  private SubPath getSubPath(String path) throws PathNotExist {\n    ParsedPath parsed = parsePath(path);\n    if (parsed.dir == null) {\n      return new SubPath(null, parsed.subpath);\n    }\n    if (!getAbstractDirs().containsKey(parsed.dir)) {\n      throw new PathNotExist(path);\n    }\n    return new SubPath(getAbstractDirs().get(parsed.dir), parsed.subpath);\n  }\n\n  private ParsedPath parsePath(String path) {\n    int pos = path.indexOf(separator);\n    if (pos == -1) {\n      return new ParsedPath(null, path);\n    }\n    return new ParsedPath(path.substring(0, pos), path.substring(pos + 1));\n  }\n\n  abstract protected void loadFiles();\n\n  abstract protected void loadDirs();\n\n  abstract protected InputStream getFileInputLocal(String name) throws DirectoryException;\n\n  abstract protected OutputStream getFileOutputLocal(String name) throws DirectoryException;\n\n  abstract protected AbstractDirectory createDirLocal(String name) throws DirectoryException;\n\n  abstract protected void removeFileLocal(String name);\n\n  private class ParsedPath {\n    public String dir;\n    public String subpath;\n\n    public ParsedPath(String dir, String subpath) {\n      this.dir = dir;\n      this.subpath = subpath;\n    }\n  }\n\n  private class SubPath {\n    public AbstractDirectory dir;\n    public String path;\n\n    public SubPath(AbstractDirectory dir, String path) {\n      this.dir = dir;\n      this.path = path;\n    }\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/directory/Directory.java",
    "content": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.directory;\n\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.Map;\nimport java.util.Set;\n\npublic interface Directory {\n  public final char separator = '/';\n\n  public Set<String> getFiles();\n\n  public Set<String> getFiles(boolean recursive);\n\n  public Map<String, Directory> getDirs();\n\n  public Map<String, Directory> getDirs(boolean recursive);\n\n  public boolean containsFile(String path);\n\n  public boolean containsDir(String path);\n\n  public InputStream getFileInput(String path) throws DirectoryException;\n\n  public OutputStream getFileOutput(String path) throws DirectoryException;\n\n  public Directory getDir(String path) throws PathNotExist;\n\n  public Directory createDir(String path) throws DirectoryException;\n\n  public boolean removeFile(String path);\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/directory/DirectoryException.java",
    "content": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.directory;\n\npublic class DirectoryException extends Exception {\n  private static final long serialVersionUID = -8871963042836625387L;\n\n  public DirectoryException(String detailMessage, Throwable throwable) {\n    super(detailMessage, throwable);\n  }\n\n  public DirectoryException(String detailMessage) {\n    super(detailMessage);\n  }\n\n  public DirectoryException(Throwable throwable) {\n    super(throwable);\n  }\n\n  public DirectoryException() {\n    super();\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/directory/FileDirectory.java",
    "content": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.directory;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedHashSet;\n\npublic class FileDirectory extends AbstractDirectory {\n  private File mDir;\n\n  public FileDirectory(String dir) throws DirectoryException {\n    this(new File(dir));\n  }\n\n  public FileDirectory(File dir) throws DirectoryException {\n    super();\n    if (!dir.isDirectory()) {\n      throw new DirectoryException(\"file must be a directory: \" + dir);\n    }\n    mDir = dir;\n  }\n\n  @Override\n  protected AbstractDirectory createDirLocal(String name) throws DirectoryException {\n    File dir = new File(generatePath(name));\n    dir.mkdir();\n    return new FileDirectory(dir);\n  }\n\n  @Override\n  protected InputStream getFileInputLocal(String name) throws DirectoryException {\n    try {\n      return new FileInputStream(generatePath(name));\n    } catch (FileNotFoundException e) {\n      throw new DirectoryException(e);\n    }\n  }\n\n  @Override\n  protected OutputStream getFileOutputLocal(String name) throws DirectoryException {\n    try {\n      return new FileOutputStream(generatePath(name));\n    } catch (FileNotFoundException e) {\n      throw new DirectoryException(e);\n    }\n  }\n\n  @Override\n  protected void loadDirs() {\n    loadAll();\n  }\n\n  @Override\n  protected void loadFiles() {\n    loadAll();\n  }\n\n  @Override\n  protected void removeFileLocal(String name) {\n    new File(generatePath(name)).delete();\n  }\n\n  private String generatePath(String name) {\n    return getDir().getPath() + separator + name;\n  }\n\n  private void loadAll() {\n    mFiles = new LinkedHashSet<String>();\n    mDirs = new LinkedHashMap<String, AbstractDirectory>();\n\n    File[] files = getDir().listFiles();\n    for (int i = 0; i < files.length; i++) {\n      File file = files[i];\n      if (file.isFile()) {\n        mFiles.add(file.getName());\n      } else {\n        // IMPOSSIBLE_EXCEPTION\n        try {\n          mDirs.put(file.getName(), new FileDirectory(file));\n        } catch (DirectoryException e) {\n        }\n      }\n    }\n  }\n\n  private File getDir() {\n    return mDir;\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/directory/PathAlreadyExists.java",
    "content": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.directory;\n\npublic class PathAlreadyExists extends DirectoryException {\n  private static final long serialVersionUID = 3776428251424428904L;\n\n  public PathAlreadyExists() {\n  }\n\n  public PathAlreadyExists(Throwable throwable) {\n    super(throwable);\n  }\n\n  public PathAlreadyExists(String detailMessage) {\n    super(detailMessage);\n  }\n\n  public PathAlreadyExists(String detailMessage, Throwable throwable) {\n    super(detailMessage, throwable);\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/directory/PathNotExist.java",
    "content": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.directory;\n\npublic class PathNotExist extends DirectoryException {\n  private static final long serialVersionUID = -6949242015506342032L;\n\n  public PathNotExist() {\n    super();\n  }\n\n  public PathNotExist(String detailMessage, Throwable throwable) {\n    super(detailMessage, throwable);\n  }\n\n  public PathNotExist(String detailMessage) {\n    super(detailMessage);\n  }\n\n  public PathNotExist(Throwable throwable) {\n    super(throwable);\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/directory/ZipRODirectory.java",
    "content": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.directory;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.Enumeration;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\npublic class ZipRODirectory extends AbstractDirectory {\n  private ZipFile mZipFile;\n  private String mPath;\n\n  public ZipRODirectory(String zipFileName) throws DirectoryException {\n    this(zipFileName, \"\");\n  }\n\n  public ZipRODirectory(File zipFile) throws DirectoryException {\n    this(zipFile, \"\");\n  }\n\n  public ZipRODirectory(ZipFile zipFile) {\n    this(zipFile, \"\");\n  }\n\n  public ZipRODirectory(String zipFileName, String path) throws DirectoryException {\n    this(new File(zipFileName), path);\n  }\n\n  public ZipRODirectory(File zipFile, String path) throws DirectoryException {\n    super();\n    try {\n      mZipFile = new ZipFile(zipFile);\n    } catch (IOException e) {\n      throw new DirectoryException(e);\n    }\n    mPath = path;\n  }\n\n  public ZipRODirectory(ZipFile zipFile, String path) {\n    super();\n    mZipFile = zipFile;\n    mPath = path;\n  }\n\n  @Override\n  protected AbstractDirectory createDirLocal(String name) throws DirectoryException {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  protected InputStream getFileInputLocal(String name) throws DirectoryException {\n    try {\n      return getZipFile().getInputStream(new ZipEntry(getPath() + name));\n    } catch (IOException e) {\n      throw new PathNotExist(name, e);\n    }\n  }\n\n  @Override\n  protected OutputStream getFileOutputLocal(String name) throws DirectoryException {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  protected void loadDirs() {\n    loadAll();\n  }\n\n  @Override\n  protected void loadFiles() {\n    loadAll();\n  }\n\n  @Override\n  protected void removeFileLocal(String name) {\n    throw new UnsupportedOperationException();\n  }\n\n  private void loadAll() {\n    mFiles = new LinkedHashSet<>();\n    mDirs = new LinkedHashMap<>();\n\n    int prefixLen = getPath().length();\n    Enumeration<? extends ZipEntry> entries = getZipFile().entries();\n    while (entries.hasMoreElements()) {\n      ZipEntry entry = entries.nextElement();\n      String name = entry.getName();\n\n      if (name.equals(getPath()) || !name.startsWith(getPath())) {\n        continue;\n      }\n\n      String subname = name.substring(prefixLen);\n      int pos = subname.indexOf(separator);\n      if (pos == -1) {\n        if (!entry.isDirectory()) {\n          mFiles.add(subname);\n          continue;\n        }\n      } else {\n        subname = subname.substring(0, pos);\n      }\n\n      if (!mDirs.containsKey(subname)) {\n        AbstractDirectory dir = new ZipRODirectory(getZipFile(), getPath() + subname + separator);\n        mDirs.put(subname, dir);\n      }\n    }\n  }\n\n  private String getPath() {\n    return mPath;\n  }\n\n  private ZipFile getZipFile() {\n    return mZipFile;\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/resourceproguard/Configuration.java",
    "content": "package com.tencent.mm.resourceproguard;\n\nimport com.tencent.mm.util.Utils;\nimport java.io.BufferedInputStream;\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.FileReader;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport javax.xml.parsers.ParserConfigurationException;\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.Node;\nimport org.w3c.dom.NodeList;\nimport org.xml.sax.InputSource;\nimport org.xml.sax.SAXException;\n\n/**\n * @author shwenzhang\n */\npublic class Configuration {\n\n  public static final String DEFAULT_DIGEST_ALG = \"SHA1\";\n  public static final String ASRC_FILE = \"resources.arsc\";\n  private static final String TAG_ISSUE = \"issue\";\n  private static final String ATTR_VALUE = \"value\";\n  private static final String ATTR_ID = \"id\";\n  private static final String ATTR_ACTIVE = \"isactive\";\n  private static final String PROPERTY_ISSUE = \"property\";\n  private static final String WHITELIST_ISSUE = \"whitelist\";\n  private static final String COMPRESS_ISSUE = \"compress\";\n  private static final String MAPPING_ISSUE = \"keepmapping\";\n  private static final String SIGN_ISSUE = \"sign\";\n  private static final String ATTR_7ZIP = \"seventzip\";\n  private static final String ATTR_KEEPROOT = \"keeproot\";\n  private static final String ATTR_SIGNFILE = \"metaname\";\n  private static final String MERGE_DUPLICATED_RES = \"mergeDuplicatedRes\";\n  private static final String ATTR_SIGNFILE_PATH = \"path\";\n  private static final String ATTR_SIGNFILE_KEYPASS = \"keypass\";\n  private static final String ATTR_SIGNFILE_STOREPASS = \"storepass\";\n  private static final String ATTR_SIGNFILE_ALIAS = \"alias\";\n  public final HashMap<String, HashMap<String, HashSet<Pattern>>> mWhiteList;\n  public final HashMap<String, HashMap<String, HashMap<String, String>>> mOldResMapping;\n  public final HashMap<String, String> mOldFileMapping;\n  public final HashSet<Pattern> mCompressPatterns;\n  public final String digestAlg;\n  private final Pattern MAP_PATTERN = Pattern.compile(\"\\\\s+(.*)->(.*)\");\n  public boolean mUse7zip = true;\n  public boolean mKeepRoot = false;\n  public boolean mMergeDuplicatedRes = false;\n  public String mMetaName = \"META-INF\";\n  public String mFixedResName = null;\n  public boolean mUseSignAPK = false;\n  public boolean mUseKeepMapping = false;\n  public File mSignatureFile;\n  public File mOldMappingFile;\n  public boolean mUseWhiteList;\n  public boolean mUseCompress;\n  public String mKeyPass;\n  public String mStorePass;\n  public String mStoreAlias;\n  public String m7zipPath;\n  public String mZipalignPath;\n\n  /**\n   * use by command line with xml config\n   *\n   * @param config        xml config file\n   * @param sevenzipPath  7zip bin file path\n   * @param zipAlignPath  zipalign bin file path\n   * @param mappingFile   mapping file\n   * @param signatureFile signature file\n   * @param keypass       signature key password\n   * @param storealias    signature store alias\n   * @param storepass     signature store password\n   * @throws IOException                  io exception\n   * @throws ParserConfigurationException parse exception\n   * @throws SAXException                 sax exception\n   */\n  public Configuration(\n      File config,\n      String sevenzipPath,\n      String zipAlignPath,\n      File mappingFile,\n      File signatureFile,\n      String keypass,\n      String storealias,\n      String storepass) throws IOException, ParserConfigurationException, SAXException {\n    mWhiteList = new HashMap<>();\n    mOldResMapping = new HashMap<>();\n    mOldFileMapping = new HashMap<>();\n    mCompressPatterns = new HashSet<>();\n    digestAlg = DEFAULT_DIGEST_ALG;\n    if (signatureFile != null) {\n      setSignData(signatureFile, keypass, storealias, storepass);\n    }\n    if (mappingFile != null) {\n      setKeepMappingData(mappingFile);\n    }\n    // setSignData and setKeepMappingData must before readXmlConfig or it will read\n    readXmlConfig(config);\n    this.m7zipPath = sevenzipPath;\n    this.mZipalignPath = zipAlignPath;\n  }\n\n  /**\n   * use by gradle\n   *\n   * @param param {@link InputParam} parameter\n   * @throws IOException io exception\n   */\n  public Configuration(InputParam param) throws IOException {\n    mWhiteList = new HashMap<>();\n    mOldResMapping = new HashMap<>();\n    mOldFileMapping = new HashMap<>();\n    mCompressPatterns = new HashSet<>();\n    this.digestAlg = param.digestAlg;\n    if (param.useSign) {\n      setSignData(param.signFile, param.keypass, param.storealias, param.storepass);\n    }\n    if (param.mappingFile != null) {\n      mUseKeepMapping = true;\n      setKeepMappingData(param.mappingFile);\n    }\n    for (String item : param.whiteList) {\n      mUseWhiteList = true;\n      addWhiteList(item);\n    }\n    mUse7zip = param.use7zip;\n    mKeepRoot = param.keepRoot;\n    mMergeDuplicatedRes = param.mergeDuplicatedRes;\n    mMetaName = param.metaName;\n    mFixedResName = param.fixedResName;\n    for (String item : param.compressFilePattern) {\n      mUseCompress = true;\n      addToCompressPatterns(item);\n    }\n    this.m7zipPath = param.sevenZipPath;\n    this.mZipalignPath = param.zipAlignPath;\n  }\n\n  private void setSignData(\n      File signatureFile, String keypass, String storealias, String storepass) throws IOException {\n    mUseSignAPK = true;\n    mSignatureFile = signatureFile;\n    if (!mSignatureFile.exists()) {\n      throw new IOException(String.format(\"the signature file do not exit, raw path= %s\\n\",\n          mSignatureFile.getAbsolutePath()\n      ));\n    }\n    mKeyPass = keypass;\n    mStoreAlias = storealias;\n    mStorePass = storepass;\n  }\n\n  private void setKeepMappingData(File mappingFile) throws IOException {\n    if (mUseKeepMapping) {\n      mOldMappingFile = mappingFile;\n\n      if (!mOldMappingFile.exists()) {\n        throw new IOException(String.format(\"the old mapping file do not exit, raw path= %s\",\n            mOldMappingFile.getAbsolutePath()\n        ));\n      }\n      processOldMappingFile();\n    }\n  }\n\n  private void readXmlConfig(File xmlConfigFile) throws IOException, ParserConfigurationException, SAXException {\n    if (!xmlConfigFile.exists()) {\n      return;\n    }\n\n    System.out.printf(\"reading config file, %s\\n\", xmlConfigFile.getAbsolutePath());\n    BufferedInputStream input = null;\n    try {\n      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n      input = new BufferedInputStream(new FileInputStream(xmlConfigFile));\n      InputSource source = new InputSource(input);\n      factory.setNamespaceAware(false);\n      factory.setValidating(false);\n      DocumentBuilder builder = factory.newDocumentBuilder();\n      Document document = builder.parse(source);\n      NodeList issues = document.getElementsByTagName(TAG_ISSUE);\n      for (int i = 0, count = issues.getLength(); i < count; i++) {\n        Node node = issues.item(i);\n\n        Element element = (Element) node;\n        String id = element.getAttribute(ATTR_ID);\n        String isActive = element.getAttribute(ATTR_ACTIVE);\n        if (id.length() == 0) {\n          System.err.println(\"Invalid config file: Missing required issue id attribute\");\n          continue;\n        }\n        boolean active = isActive != null && isActive.equals(\"true\");\n\n        switch (id) {\n          case PROPERTY_ISSUE:\n            readPropertyFromXml(node);\n            break;\n          case WHITELIST_ISSUE:\n            mUseWhiteList = active;\n            if (mUseWhiteList) {\n              readWhiteListFromXml(node);\n            }\n            break;\n          case COMPRESS_ISSUE:\n            mUseCompress = active;\n            if (mUseCompress) {\n              readCompressFromXml(node);\n            }\n            break;\n          case SIGN_ISSUE:\n            mUseSignAPK |= active;\n            if (mUseSignAPK) {\n              readSignFromXml(node, xmlConfigFile.getParentFile());\n            }\n            break;\n          case MAPPING_ISSUE:\n            mUseKeepMapping = active;\n            if (mUseKeepMapping) {\n              loadMappingFilesFromXml(node);\n            }\n            break;\n          default:\n            System.err.println(\"unknown issue \" + id);\n            break;\n        }\n      }\n    } finally {\n      if (input != null) {\n        try {\n          input.close();\n        } catch (IOException e) {\n          System.exit(-1);\n        }\n      }\n    }\n  }\n\n  private void readWhiteListFromXml(Node node) throws IOException {\n    NodeList childNodes = node.getChildNodes();\n    if (childNodes.getLength() > 0) {\n      for (int j = 0, n = childNodes.getLength(); j < n; j++) {\n        Node child = childNodes.item(j);\n        if (child.getNodeType() == Node.ELEMENT_NODE) {\n          Element check = (Element) child;\n          String vaule = check.getAttribute(ATTR_VALUE);\n          addWhiteList(vaule);\n        }\n      }\n    }\n  }\n\n  private void addWhiteList(String item) throws IOException {\n    if (item.length() == 0) {\n      throw new IOException(\"Invalid config file: Missing required attribute \" + ATTR_VALUE);\n    }\n\n    int packagePos = item.indexOf(\".R.\");\n    if (packagePos == -1) {\n\n      throw new IOException(String.format(\"please write the full package name,eg com.tencent.mm.R.drawable.dfdf, but yours %s\\n\",\n          item\n      ));\n    }\n    //先去掉空格\n    item = item.trim();\n    String packageName = item.substring(0, packagePos);\n    //不能通过lastDot\n    int nextDot = item.indexOf(\".\", packagePos + 3);\n    String typeName = item.substring(packagePos + 3, nextDot);\n    String name = item.substring(nextDot + 1);\n    HashMap<String, HashSet<Pattern>> typeMap;\n\n    if (mWhiteList.containsKey(packageName)) {\n      typeMap = mWhiteList.get(packageName);\n    } else {\n      typeMap = new HashMap<>();\n    }\n\n    HashSet<Pattern> patterns;\n    if (typeMap.containsKey(typeName)) {\n      patterns = typeMap.get(typeName);\n    } else {\n      patterns = new HashSet<>();\n    }\n\n    name = Utils.convertToPatternString(name);\n    Pattern pattern = Pattern.compile(name);\n    patterns.add(pattern);\n    typeMap.put(typeName, patterns);\n    System.out.println(String.format(\"convertToPatternString typeName %s format %s\", typeName, name));\n    mWhiteList.put(packageName, typeMap);\n  }\n\n  private void readSignFromXml(Node node, File xmlConfigFileParentFile) throws IOException {\n    if (mSignatureFile != null) {\n      System.err.println(\"already set the sign info from command line, ignore this\");\n      return;\n    }\n\n    NodeList childNodes = node.getChildNodes();\n\n    if (childNodes.getLength() > 0) {\n      for (int j = 0, n = childNodes.getLength(); j < n; j++) {\n        Node child = childNodes.item(j);\n        if (child.getNodeType() == Node.ELEMENT_NODE) {\n          Element check = (Element) child;\n          String tagName = check.getTagName();\n          String vaule = check.getAttribute(ATTR_VALUE);\n          if (vaule.length() == 0) {\n            throw new IOException(String.format(\"Invalid config file: Missing required attribute %s\\n\", ATTR_VALUE));\n          }\n\n          switch (tagName) {\n            case ATTR_SIGNFILE_PATH:\n              char ch = vaule.charAt(0);\n              switch (ch) {\n                // supports the writting style like ~/.android/debug.keystore. the symbol ~ represent the home directory of the current user.\n                case '~':\n                  mSignatureFile = new File(String.format(\"%s%s\", System.getProperty(\"user.home\"), vaule.substring(1)));\n                  break;\n                // relative to the directory of the xml config file.\n                case '.':\n                  mSignatureFile = new File(xmlConfigFileParentFile, vaule);\n                  break;\n                // keep the origin logical.\n                default:\n                  mSignatureFile = new File(vaule);\n              }\n              if (!mSignatureFile.isFile()) {\n                throw new IOException(String.format(\"the signature file do not exit. raw path= %s\\n\",\n                    mSignatureFile.getAbsolutePath()\n                ));\n              }\n              break;\n            case ATTR_SIGNFILE_STOREPASS:\n              mStorePass = vaule;\n              mStorePass = mStorePass.trim();\n              break;\n            case ATTR_SIGNFILE_KEYPASS:\n              mKeyPass = vaule;\n              mKeyPass = mKeyPass.trim();\n              break;\n            case ATTR_SIGNFILE_ALIAS:\n              mStoreAlias = vaule;\n              mStoreAlias = mStoreAlias.trim();\n              break;\n            default:\n              System.err.println(\"unknown tag \" + tagName);\n              break;\n          }\n        }\n      }\n    }\n  }\n\n  private void readCompressFromXml(Node node) throws IOException {\n    NodeList childNodes = node.getChildNodes();\n    if (childNodes.getLength() > 0) {\n      for (int j = 0, n = childNodes.getLength(); j < n; j++) {\n        Node child = childNodes.item(j);\n        if (child.getNodeType() == Node.ELEMENT_NODE) {\n          Element check = (Element) child;\n          String value = check.getAttribute(ATTR_VALUE);\n          addToCompressPatterns(value);\n        }\n      }\n    }\n  }\n\n  private void addToCompressPatterns(String value) throws IOException {\n    if (value.length() == 0) {\n      throw new IOException(String.format(\"Invalid config file: Missing required attribute %s\\n\", ATTR_VALUE));\n    }\n    value = Utils.convertToPatternString(value);\n    Pattern pattern = Pattern.compile(value);\n    mCompressPatterns.add(pattern);\n  }\n\n  private void loadMappingFilesFromXml(Node node) throws IOException {\n    if (mOldMappingFile != null) {\n      System.err.println(\"Mapping file already load from command line, ignore this config\");\n      return;\n    }\n    NodeList childNodes = node.getChildNodes();\n    if (childNodes.getLength() > 0) {\n      for (int j = 0, n = childNodes.getLength(); j < n; j++) {\n        Node child = childNodes.item(j);\n        if (child.getNodeType() == Node.ELEMENT_NODE) {\n          Element check = (Element) child;\n          String filePath = check.getAttribute(ATTR_VALUE);\n          if (filePath.length() == 0) {\n            throw new IOException(String.format(\"Invalid config file: Missing required attribute %s\\n\", ATTR_VALUE));\n          }\n          readOldMapping(filePath);\n        }\n      }\n    }\n  }\n\n  private void readPropertyFromXml(Node node) throws IOException {\n    NodeList childNodes = node.getChildNodes();\n    if (childNodes.getLength() > 0) {\n      for (int j = 0, n = childNodes.getLength(); j < n; j++) {\n        Node child = childNodes.item(j);\n        if (child.getNodeType() == Node.ELEMENT_NODE) {\n          Element check = (Element) child;\n          String tagName = check.getTagName();\n          String vaule = check.getAttribute(ATTR_VALUE);\n          if (vaule.length() == 0) {\n            throw new IOException(String.format(\"Invalid config file: Missing required attribute %s\\n\", ATTR_VALUE));\n          }\n\n          switch (tagName) {\n            case ATTR_7ZIP:\n              mUse7zip = vaule.equals(\"true\");\n              break;\n            case ATTR_KEEPROOT:\n              mKeepRoot = vaule.equals(\"true\");\n              System.out.println(\"mKeepRoot \" + mKeepRoot);\n              break;\n            case MERGE_DUPLICATED_RES:\n              mMergeDuplicatedRes = vaule.equals(\"true\");\n              System.out.println(\"mMergeDuplicatedRes \" + mMergeDuplicatedRes);\n              break;\n            case ATTR_SIGNFILE:\n              mMetaName = vaule.trim();\n              break;\n            default:\n              System.err.println(\"unknown tag \" + tagName);\n              break;\n          }\n        }\n      }\n    }\n  }\n\n  private void readOldMapping(String filePath) throws IOException {\n    mOldMappingFile = new File(filePath);\n    if (!mOldMappingFile.exists()) {\n      throw new IOException(String.format(\"the old mapping file do not exit, raw path= %s\\n\",\n          mOldMappingFile.getAbsolutePath()\n      ));\n    }\n    processOldMappingFile();\n    System.out.printf(\"you are using the keepmapping mode to proguard resouces: old mapping path:%s\\n\",\n        mOldMappingFile.getAbsolutePath()\n    );\n  }\n\n  private void processOldMappingFile() throws IOException {\n    mOldResMapping.clear();\n    mOldFileMapping.clear();\n\n    FileReader fr;\n    try {\n      fr = new FileReader(mOldMappingFile);\n    } catch (FileNotFoundException ex) {\n      throw new IOException(String.format(\"Could not find old mapping file %s\", mOldMappingFile.getAbsolutePath()));\n    }\n    BufferedReader br = new BufferedReader(fr);\n    try {\n      String line = br.readLine();\n\n      while (line != null) {\n        if (line.length() > 0) {\n          Matcher mat = MAP_PATTERN.matcher(line);\n\n          if (mat.find()) {\n            String nameAfter = mat.group(2);\n            String nameBefore = mat.group(1);\n            nameAfter = nameAfter.trim();\n            nameBefore = nameBefore.trim();\n\n            //如果有这个的话，那就是mOldFileMapping\n            if (line.contains(\"/\")) {\n              mOldFileMapping.put(nameBefore, nameAfter);\n            } else {\n              //这里是resid的mapping\n              int packagePos = nameBefore.indexOf(\".R.\");\n              if (packagePos == -1) {\n                throw new IOException(String.format(\"the old mapping file packagename is malformed, \"\n                                                    + \"it should be like com.tencent.mm.R.attr.test, yours %s\\n\",\n                    nameBefore\n                ));\n              }\n              String packageName = nameBefore.substring(0, packagePos);\n              int nextDot = nameBefore.indexOf(\".\", packagePos + 3);\n              String typeName = nameBefore.substring(packagePos + 3, nextDot);\n\n              String beforename = nameBefore.substring(nextDot + 1);\n              String aftername = nameAfter.substring(nameAfter.indexOf(\".\", packagePos + 3) + 1);\n\n              HashMap<String, HashMap<String, String>> typeMap;\n\n              if (mOldResMapping.containsKey(packageName)) {\n                typeMap = mOldResMapping.get(packageName);\n              } else {\n                typeMap = new HashMap<>();\n              }\n\n              HashMap<String, String> namesMap;\n              if (typeMap.containsKey(typeName)) {\n                namesMap = typeMap.get(typeName);\n              } else {\n                namesMap = new HashMap<>();\n              }\n              namesMap.put(beforename, aftername);\n\n              typeMap.put(typeName, namesMap);\n              mOldResMapping.put(packageName, typeMap);\n            }\n          }\n        }\n        line = br.readLine();\n      }\n    } catch (IOException ex) {\n      throw new RuntimeException(\"Error while mapping file\");\n    } finally {\n      try {\n        br.close();\n        fr.close();\n      } catch (IOException e) {\n        e.printStackTrace();\n      }\n    }\n  }\n}\n\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/resourceproguard/InputParam.java",
    "content": "package com.tencent.mm.resourceproguard;\n\nimport com.tencent.mm.androlib.res.util.StringUtil;\nimport java.io.File;\nimport java.util.ArrayList;\n\npublic class InputParam {\n\n  public final File mappingFile;\n  public final boolean use7zip;\n  public final boolean keepRoot;\n  public final boolean mergeDuplicatedRes;\n  public final boolean useSign;\n  public final String metaName;\n  public final String fixedResName;\n  public final ArrayList<String> whiteList;\n  public final ArrayList<String> compressFilePattern;\n  public final String apkPath;\n  public final String outFolder;\n  public final File signFile;\n  public final String keypass;\n  public final String storealias;\n  public final String storepass;\n  public final String zipAlignPath;\n  public final String sevenZipPath;\n  public final SignatureType signatureType;\n  public final String finalApkBackupPath;\n  public final String digestAlg;\n  public final int minSDKVersion;\n  public final int targetSDKVersion;\n\n  private InputParam(\n      File mappingFile,\n      boolean use7zip,\n      boolean useSign,\n      boolean keepRoot,\n      boolean mergeDuplicatedRes,\n      ArrayList<String> whiteList,\n      ArrayList<String> compressFilePattern,\n      String apkPath,\n      String outFolder,\n      File signFile,\n      String keypass,\n      String storealias,\n      String storepass,\n      String metaName,\n      String fixedResName,\n      String zipAlignPath,\n      String sevenZipPath,\n      SignatureType signatureType,\n      String finalApkBackupPath,\n      String digestAlg,\n      int minSDKVersion,\n      int targetSDKVersion) {\n\n    this.mappingFile = mappingFile;\n    this.use7zip = use7zip;\n    this.useSign = useSign;\n    this.keepRoot = keepRoot;\n    this.mergeDuplicatedRes = mergeDuplicatedRes;\n    this.whiteList = whiteList;\n    this.compressFilePattern = compressFilePattern;\n    this.apkPath = apkPath;\n    this.outFolder = outFolder;\n    this.signFile = signFile;\n    this.keypass = keypass;\n    this.storealias = storealias;\n    this.storepass = storepass;\n    this.metaName = metaName;\n    this.fixedResName = fixedResName;\n    this.zipAlignPath = zipAlignPath;\n    this.sevenZipPath = sevenZipPath;\n    this.signatureType = signatureType;\n    this.finalApkBackupPath = finalApkBackupPath;\n    this.digestAlg = digestAlg;\n    this.minSDKVersion = minSDKVersion;\n    this.targetSDKVersion = targetSDKVersion;\n  }\n\n  public enum SignatureType {\n    SchemaV1, SchemaV2, SchemaV3\n  }\n\n  public static class Builder {\n\n    private File mappingFile;\n    private boolean use7zip;\n    private boolean useSign;\n    private boolean keepRoot;\n    private boolean mergeDuplicatedRes;\n    private ArrayList<String> whiteList;\n    private ArrayList<String> compressFilePattern;\n    private String apkPath;\n    private String outFolder;\n    private File signFile;\n    private String keypass;\n    private String storealias;\n    private String storepass;\n    private String metaName;\n    private String fixedResName;\n    private String zipAlignPath;\n    private String sevenZipPath;\n    private SignatureType signatureType;\n    private String finalApkBackupPath;\n    private String digestAlg;\n    private int minSDKVersion;\n    private int targetSDKVersion;\n\n    public Builder() {\n      use7zip = false;\n      keepRoot = false;\n      signatureType = SignatureType.SchemaV1;\n    }\n\n    public Builder setMappingFile(File mappingFile) {\n      this.mappingFile = mappingFile;\n      return this;\n    }\n\n    public Builder setUse7zip(boolean use7zip) {\n      this.use7zip = use7zip;\n      return this;\n    }\n\n    public Builder setUseSign(boolean useSign) {\n      this.useSign = useSign;\n      return this;\n    }\n\n    public Builder setKeepRoot(boolean keepRoot) {\n      this.keepRoot = keepRoot;\n      return this;\n    }\n\n    public Builder setMergeDuplicatedRes(boolean mergeDuplicatedRes) {\n      this.mergeDuplicatedRes = mergeDuplicatedRes;\n      return this;\n    }\n\n    public Builder setWhiteList(ArrayList<String> whiteList) {\n      this.whiteList = whiteList;\n      return this;\n    }\n\n    public Builder setCompressFilePattern(ArrayList<String> compressFilePattern) {\n      if (compressFilePattern.contains(Configuration.ASRC_FILE)) {\n        System.out.printf(\"[Warning] compress %s will prevent optimization at runtime\",\n            Configuration.ASRC_FILE);\n      }\n      this.compressFilePattern = compressFilePattern;\n      return this;\n    }\n\n    public Builder setApkPath(String apkPath) {\n      this.apkPath = apkPath;\n      return this;\n    }\n\n    public Builder setOutBuilder(String outFolder) {\n      this.outFolder = outFolder;\n      return this;\n    }\n\n    public Builder setSignFile(File signFile) {\n      this.signFile = signFile;\n      return this;\n    }\n\n    public Builder setKeypass(String keypass) {\n      this.keypass = keypass;\n      return this;\n    }\n\n    public Builder setStorealias(String storealias) {\n      this.storealias = storealias;\n      return this;\n    }\n\n    public Builder setStorepass(String storepass) {\n      this.storepass = storepass;\n      return this;\n    }\n\n    public Builder setMetaName(String metaName) {\n      this.metaName = metaName;\n      return this;\n    }\n\n    public Builder setFixedResName(String fixedResName) {\n      this.fixedResName = fixedResName;\n      return this;\n    }\n\n    public Builder setZipAlign(String zipAlignPath) {\n      this.zipAlignPath = zipAlignPath;\n      return this;\n    }\n\n    public Builder setSevenZipPath(String sevenZipPath) {\n      this.sevenZipPath = sevenZipPath;\n      return this;\n    }\n\n    public Builder setSignatureType(SignatureType signatureType) {\n      this.signatureType = signatureType;\n      return this;\n    }\n\n    public Builder setFinalApkBackupPath(String finalApkBackupPath) {\n      this.finalApkBackupPath = finalApkBackupPath;\n      return this;\n    }\n\n    public Builder setDigestAlg(String digestAlg) {\n      if (StringUtil.isPresent(digestAlg)) {\n        this.digestAlg = digestAlg;\n      } else {\n        this.digestAlg = Configuration.DEFAULT_DIGEST_ALG;\n      }\n\n      return this;\n    }\n\n    public Builder setMinSDKVersion(int minSDKVersion) {\n      this.minSDKVersion = minSDKVersion;\n      return this;\n    }\n\n    public Builder setTargetSDKVersion(int targetSDKVersion) {\n      this.targetSDKVersion = targetSDKVersion;\n      return this;\n    }\n\n    public InputParam create() {\n      if (targetSDKVersion >= 30) {\n        // Targeting R+ (version 30 and above) requires the resources.arsc of installed APKs\n        // to be stored uncompressed and aligned on a 4-byte boundary\n        this.compressFilePattern.remove(Configuration.ASRC_FILE);\n        System.out.printf(\"[Warning] Remove resources.arsc from the compressPattern. (%s)\\n\",\n            this.compressFilePattern);\n      }\n\n      return new InputParam(mappingFile,\n          use7zip,\n          useSign,\n          keepRoot,\n          mergeDuplicatedRes,\n          whiteList,\n          compressFilePattern,\n          apkPath,\n          outFolder,\n          signFile,\n          keypass,\n          storealias,\n          storepass,\n          metaName,\n          fixedResName,\n          zipAlignPath,\n          sevenZipPath,\n          signatureType,\n          finalApkBackupPath,\n          digestAlg,\n          minSDKVersion,\n          targetSDKVersion\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/resourceproguard/Main.java",
    "content": "package com.tencent.mm.resourceproguard;\n\nimport com.tencent.mm.androlib.AndrolibException;\nimport com.tencent.mm.androlib.ApkDecoder;\nimport com.tencent.mm.androlib.ResourceApkBuilder;\nimport com.tencent.mm.androlib.res.decoder.ARSCDecoder;\nimport com.tencent.mm.androlib.res.util.StringUtil;\nimport com.tencent.mm.directory.DirectoryException;\nimport com.tencent.mm.util.FileOperation;\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * @author shwenzhang\n * @author simsun\n */\npublic class Main {\n\n  public static final int ERRNO_ERRORS = 1;\n  public static final int ERRNO_USAGE = 2;\n  protected static long mRawApkSize;\n  protected static String mRunningLocation;\n  protected static long mBeginTime;\n\n  /**\n   * 是否通过命令行方式设置\n   **/\n  public boolean mSetSignThroughCmd;\n  public boolean mSetMappingThroughCmd;\n  public String m7zipPath;\n  public String mZipalignPath;\n  public String mFinalApkBackPath;\n\n  protected Configuration config;\n  protected File mOutDir;\n\n  public static void gradleRun(InputParam inputParam) {\n    Main m = new Main();\n    m.run(inputParam);\n  }\n\n  private void run(InputParam inputParam) {\n    synchronized (Main.class) {\n      loadConfigFromGradle(inputParam);\n      this.mFinalApkBackPath = inputParam.finalApkBackupPath;\n      Thread currentThread = Thread.currentThread();\n      System.out.printf(\n          \"\\n-->AndResGuard starting! Current thread# id: %d, name: %s\\n\",\n          currentThread.getId(),\n          currentThread.getName()\n      );\n      File finalApkFile = StringUtil.isPresent(inputParam.finalApkBackupPath) ?\n          new File(inputParam.finalApkBackupPath)\n          : null;\n\n      resourceProguard(\n          new File(inputParam.outFolder),\n          finalApkFile,\n          inputParam.apkPath,\n          inputParam.signatureType,\n          inputParam.minSDKVersion\n      );\n      System.out.printf(\"<--AndResGuard Done! You can find the output in %s\\n\", mOutDir.getAbsolutePath());\n      clean();\n    }\n  }\n\n  protected void clean() {\n    config = null;\n    ARSCDecoder.mTableStringsResguard.clear();\n    ARSCDecoder.mMergeDuplicatedResCount = 0;\n  }\n\n  private void loadConfigFromGradle(InputParam inputParam) {\n    try {\n      config = new Configuration(inputParam);\n    } catch (IOException e) {\n      e.printStackTrace();\n    }\n  }\n\n  protected void resourceProguard(\n      File outputDir, File outputFile, String apkFilePath, InputParam.SignatureType signatureType) {\n    resourceProguard(outputDir, outputFile, apkFilePath, signatureType, 14 /*default min sdk*/);\n  }\n\n  protected void resourceProguard(\n      File outputDir, File outputFile, String apkFilePath, InputParam.SignatureType signatureType, int minSDKVersoin) {\n    File apkFile = new File(apkFilePath);\n    if (!apkFile.exists()) {\n      System.err.printf(\"The input apk %s does not exist\", apkFile.getAbsolutePath());\n      goToError();\n    }\n    mRawApkSize = FileOperation.getFileSizes(apkFile);\n    try {\n      ApkDecoder decoder = new ApkDecoder(config, apkFile);\n      /* 默认使用V1签名 */\n      decodeResource(outputDir, decoder, apkFile);\n      buildApk(decoder, apkFile, outputFile, signatureType, minSDKVersoin);\n    } catch (Exception e) {\n      e.printStackTrace();\n      goToError();\n    }\n  }\n\n  private void decodeResource(File outputFile, ApkDecoder decoder, File apkFile)\n      throws AndrolibException, IOException, DirectoryException {\n    if (outputFile == null) {\n      mOutDir = new File(mRunningLocation, apkFile.getName().substring(0, apkFile.getName().indexOf(\".apk\")));\n    } else {\n      mOutDir = outputFile;\n    }\n    decoder.setOutDir(mOutDir.getAbsoluteFile());\n    decoder.decode();\n  }\n\n  private void buildApk(\n      ApkDecoder decoder, File apkFile, File outputFile, InputParam.SignatureType signatureType, int minSDKVersion)\n      throws Exception {\n    ResourceApkBuilder builder = new ResourceApkBuilder(config);\n    String apkBasename = apkFile.getName();\n    apkBasename = apkBasename.substring(0, apkBasename.indexOf(\".apk\"));\n    builder.setOutDir(mOutDir, apkBasename, outputFile);\n    System.out.printf(\"[AndResGuard] buildApk signatureType: %s\\n\", signatureType);\n    switch (signatureType) {\n      case SchemaV1:\n        builder.buildApkWithV1sign(decoder.getCompressData());\n        break;\n      case SchemaV2:\n      case SchemaV3:\n        builder.buildApkWithV2V3Sign(decoder.getCompressData(), minSDKVersion, signatureType);\n        break;\n    }\n  }\n\n  protected void goToError() {\n    System.exit(ERRNO_USAGE);\n  }\n}"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/util/DataInputDelegate.java",
    "content": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.util;\n\nimport java.io.DataInput;\nimport java.io.IOException;\n\n/**\n * @author shwenzhang\n */\nabstract public class DataInputDelegate implements DataInput {\n  protected final DataInput mDelegate;\n\n  public DataInputDelegate(DataInput delegate) {\n    this.mDelegate = delegate;\n  }\n\n  public int skipBytes(int n) throws IOException {\n    return mDelegate.skipBytes(n);\n  }\n\n  public int readUnsignedShort() throws IOException {\n    return mDelegate.readUnsignedShort();\n  }\n\n  public int readUnsignedByte() throws IOException {\n    return mDelegate.readUnsignedByte();\n  }\n\n  public String readUTF() throws IOException {\n    return mDelegate.readUTF();\n  }\n\n  public short readShort() throws IOException {\n    return mDelegate.readShort();\n  }\n\n  public long readLong() throws IOException {\n    return mDelegate.readLong();\n  }\n\n  public String readLine() throws IOException {\n    return mDelegate.readLine();\n  }\n\n  public int readInt() throws IOException {\n    return mDelegate.readInt();\n  }\n\n  public void readFully(byte[] b, int off, int len) throws IOException {\n    mDelegate.readFully(b, off, len);\n  }\n\n  public void readFully(byte[] b) throws IOException {\n    mDelegate.readFully(b);\n  }\n\n  public float readFloat() throws IOException {\n    return mDelegate.readFloat();\n  }\n\n  public double readDouble() throws IOException {\n    return mDelegate.readDouble();\n  }\n\n  public char readChar() throws IOException {\n    return mDelegate.readChar();\n  }\n\n  public byte readByte() throws IOException {\n    return mDelegate.readByte();\n  }\n\n  public boolean readBoolean() throws IOException {\n    return mDelegate.readBoolean();\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/util/DataOutputDelegate.java",
    "content": "package com.tencent.mm.util;\n\nimport java.io.DataOutput;\nimport java.io.IOException;\n\npublic class DataOutputDelegate implements DataOutput {\n  protected final DataOutput mDelegate;\n\n  public DataOutputDelegate(DataOutput delegate) {\n    this.mDelegate = delegate;\n  }\n\n  @Override\n  public void write(int b) throws IOException {\n    // TODO Auto-generated method stub\n    this.mDelegate.write(b);\n  }\n\n  @Override\n  public void write(byte[] b) throws IOException {\n    // TODO Auto-generated method stub\n    this.mDelegate.write(b);\n  }\n\n  @Override\n  public void write(byte[] b, int off, int len) throws IOException {\n    // TODO Auto-generated method stub\n    this.mDelegate.write(b, off, len);\n  }\n\n  @Override\n  public void writeBoolean(boolean v) throws IOException {\n    // TODO Auto-generated method stub\n    this.mDelegate.writeBoolean(v);\n  }\n\n  @Override\n  public void writeByte(int v) throws IOException {\n    // TODO Auto-generated method stub\n    this.mDelegate.writeByte(v);\n  }\n\n  @Override\n  public void writeShort(int v) throws IOException {\n    // TODO Auto-generated method stub\n    this.mDelegate.writeShort(v);\n  }\n\n  @Override\n  public void writeChar(int v) throws IOException {\n    // TODO Auto-generated method stub\n    this.mDelegate.writeChar(v);\n  }\n\n  @Override\n  public void writeInt(int v) throws IOException {\n    // TODO Auto-generated method stub\n    this.mDelegate.writeInt(v);\n  }\n\n  @Override\n  public void writeLong(long v) throws IOException {\n    // TODO Auto-generated method stub\n    this.mDelegate.writeLong(v);\n  }\n\n  @Override\n  public void writeFloat(float v) throws IOException {\n    // TODO Auto-generated method stub\n    this.mDelegate.writeFloat(v);\n  }\n\n  @Override\n  public void writeDouble(double v) throws IOException {\n    // TODO Auto-generated method stub\n    this.mDelegate.writeDouble(v);\n  }\n\n  @Override\n  public void writeBytes(String s) throws IOException {\n    // TODO Auto-generated method stub\n    this.mDelegate.writeBytes(s);\n  }\n\n  @Override\n  public void writeChars(String s) throws IOException {\n    // TODO Auto-generated method stub\n    this.mDelegate.writeChars(s);\n  }\n\n  @Override\n  public void writeUTF(String s) throws IOException {\n    // TODO Auto-generated method stub\n    this.mDelegate.writeUTF(s);\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/util/ExtDataInput.java",
    "content": "/**\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.util;\n\nimport java.io.DataInput;\nimport java.io.DataInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * @author shwenzhang\n */\npublic class ExtDataInput extends DataInputDelegate {\n  public ExtDataInput(InputStream in) {\n    this((DataInput) new DataInputStream(in));\n  }\n\n  public ExtDataInput(DataInput delegate) {\n    super(delegate);\n  }\n\n  public int[] readIntArray(int length) throws IOException {\n    int[] array = new int[length];\n    for (int i = 0; i < length; i++) {\n      array[i] = readInt();\n    }\n    return array;\n  }\n\n  public void skipInt() throws IOException {\n    skipBytes(4);\n  }\n\n  public void skipCheckInt(int expected) throws IOException {\n    int got = readInt();\n    if (got != expected) {\n      throw new IOException(String.format(\"Expected: 0x%08x, got: 0x%08x\", expected, got));\n    }\n  }\n\n  public void skipCheckChunkTypeInt(int expected, int possible) throws IOException {\n    int got = readInt();\n\n    if (got == possible) {\n      skipCheckChunkTypeInt(expected, -1);\n    } else if (got != expected) {\n      throw new IOException(String.format(\"Expected: 0x%08x, got: 0x%08x\", expected, got));\n    }\n  }\n\n  public void skipCheckShort(short expected) throws IOException {\n    short got = readShort();\n    if (got != expected) {\n      throw new IOException(String.format(\"Expected: 0x%08x, got: 0x%08x\", expected, got));\n    }\n  }\n\n  public void skipCheckByte(byte expected) throws IOException {\n    byte got = readByte();\n    if (got != expected) {\n      throw new IOException(String.format(\"Expected: 0x%08x, got: 0x%08x\", expected, got));\n    }\n  }\n\n  public String readNullEndedString(int length, boolean fixed) throws IOException {\n    StringBuilder string = new StringBuilder(16);\n    while (length-- != 0) {\n      short ch = readShort();\n      if (ch == 0) {\n        break;\n      }\n      string.append((char) ch);\n    }\n    if (fixed) {\n      skipBytes(length * 2);\n    }\n\n    return string.toString();\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/util/ExtDataOutput.java",
    "content": "package com.tencent.mm.util;\n\nimport java.io.DataOutput;\nimport java.io.IOException;\n\npublic class ExtDataOutput extends DataOutputDelegate {\n\n  public ExtDataOutput(DataOutput delegate) {\n    super(delegate);\n    // TODO Auto-generated constructor stub\n  }\n\n  public void writeIntArray(int[] array) throws IOException {\n    int length = array.length;\n    for (int i = 0; i < length; i++) {\n      writeInt(array[i]);\n    }\n  }\n\n  public void writeBytes(ExtDataInput in, int length) throws IOException {\n    byte[] data = new byte[length];\n    in.readFully(data);\n    write(data);\n  }\n\n  public void writeCheckInt(int value, int expected) throws IOException {\n    writeInt(value);\n    if (value != expected) {\n      throw new IOException(String.format(\"Expected: 0x%08x, got: 0x%08x\", expected, value));\n    }\n  }\n\n  public void writeCheckChunkTypeInt(ExtDataInput reader, int expected, int possible) throws IOException {\n    int value = reader.readInt();\n    writeInt(value);\n    if (value == possible) {\n      writeCheckChunkTypeInt(reader, expected, -1);\n    } else if (value != expected) {\n      throw new IOException(String.format(\"Expected: 0x%08x, got: 0x%08x\", expected, value));\n    }\n  }\n\n  public void writeCheckShort(short value, short expected) throws IOException {\n    writeShort(value);\n    if (value != expected) {\n      throw new IOException(String.format(\"Expected: 0x%08x, got: 0x%08x\", expected, value));\n    }\n  }\n\n  public void writeCheckByte(byte value, byte expected) throws IOException {\n    writeByte(value);\n    if (value != expected) {\n      throw new IOException(String.format(\"Expected: 0x%08x, got: 0x%08x\", expected, value));\n    }\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/util/FileOperation.java",
    "content": "package com.tencent.mm.util;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.util.Collection;\nimport java.util.Enumeration;\nimport java.util.HashMap;\nimport java.util.zip.CRC32;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\nimport java.util.zip.ZipOutputStream;\n\npublic class FileOperation {\n  private static final int BUFFER = 8192;\n\n  public static boolean fileExists(String filePath) {\n    if (filePath == null) {\n      return false;\n    }\n\n    File file = new File(filePath);\n    if (file.exists()) return true;\n    return false;\n  }\n\n  public static boolean deleteFile(String filePath) {\n    if (filePath == null) {\n      return true;\n    }\n\n    File file = new File(filePath);\n    if (file.exists()) {\n      return file.delete();\n    }\n    return true;\n  }\n\n  public static long getlist(File f) {\n    if (f == null || (!f.exists())) {\n      return 0;\n    }\n    if (!f.isDirectory()) {\n      return 1;\n    }\n    long size;\n    File flist[] = f.listFiles();\n    size = flist.length;\n    for (int i = 0; i < flist.length; i++) {\n      if (flist[i].isDirectory()) {\n        size = size + getlist(flist[i]);\n        size--;\n      }\n    }\n    return size;\n  }\n\n  public static long getFileSizes(File f) {\n    long size = 0;\n    if (f.exists() && f.isFile()) {\n      FileInputStream fis = null;\n      try {\n        fis = new FileInputStream(f);\n        size = fis.available();\n      } catch (IOException e) {\n        e.printStackTrace();\n      } finally {\n        try {\n          if (fis != null) {\n            fis.close();\n          }\n        } catch (IOException e) {\n          e.printStackTrace();\n        }\n      }\n    }\n    return size;\n  }\n\n  public static boolean deleteDir(File file) {\n    if (file == null || (!file.exists())) {\n      return false;\n    }\n    if (file.isFile()) {\n      file.delete();\n    } else if (file.isDirectory()) {\n      File files[] = file.listFiles();\n      for (int i = 0; i < files.length; i++) {\n        deleteDir(files[i]);\n      }\n    }\n    file.delete();\n    return true;\n  }\n\n  public static void copyFileUsingStream(File source, File dest) throws IOException {\n    FileInputStream is = null;\n    FileOutputStream os = null;\n    File parent = dest.getParentFile();\n    if (parent != null && (!parent.exists())) {\n      parent.mkdirs();\n    }\n    try {\n      is = new FileInputStream(source);\n      os = new FileOutputStream(dest, false);\n\n      byte[] buffer = new byte[BUFFER];\n      int length;\n      while ((length = is.read(buffer)) > 0) {\n        os.write(buffer, 0, length);\n      }\n    } finally {\n      if (is != null) {\n        is.close();\n      }\n      if (os != null) {\n        os.close();\n      }\n    }\n  }\n\n  public static boolean checkDirectory(String dir) {\n    File dirObj = new File(dir);\n    deleteDir(dirObj);\n\n    if (!dirObj.exists()) {\n      dirObj.mkdirs();\n    }\n    return true;\n  }\n\n  public static File checkFile(String dir) {\n    deleteFile(dir);\n    File file = new File(dir);\n    try {\n      file.createNewFile();\n    } catch (IOException e) {\n      // TODO Auto-generated catch block\n      e.printStackTrace();\n    }\n    return file;\n  }\n\n  @SuppressWarnings(\"rawtypes\")\n  public static HashMap<String, Integer> unZipAPk(String fileName, String filePath) throws IOException {\n    checkDirectory(filePath);\n    ZipFile zipFile = new ZipFile(fileName);\n    Enumeration emu = zipFile.entries();\n    HashMap<String, Integer> compress = new HashMap<>();\n    try {\n      while (emu.hasMoreElements()) {\n        ZipEntry entry = (ZipEntry) emu.nextElement();\n        if (entry.isDirectory()) {\n          new File(filePath, entry.getName()).mkdirs();\n          continue;\n        }\n        BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(entry));\n\n        File file = new File(filePath + File.separator + entry.getName());\n\n        File parent = file.getParentFile();\n        if (parent != null && (!parent.exists())) {\n          parent.mkdirs();\n        }\n        //要用linux的斜杠\n        String compatibaleresult = entry.getName();\n        if (compatibaleresult.contains(\"\\\\\")) {\n          compatibaleresult = compatibaleresult.replace(\"\\\\\", \"/\");\n        }\n        compress.put(compatibaleresult, entry.getMethod());\n        FileOutputStream fos = new FileOutputStream(file);\n        BufferedOutputStream bos = new BufferedOutputStream(fos, BUFFER);\n\n        byte[] buf = new byte[BUFFER];\n        int len;\n        while ((len = bis.read(buf, 0, BUFFER)) != -1) {\n          fos.write(buf, 0, len);\n        }\n        bos.flush();\n        bos.close();\n        bis.close();\n      }\n    } finally {\n      zipFile.close();\n    }\n    return compress;\n  }\n\n  /**\n   * zip list of file\n   *\n   * @param resFileList file(dir) list\n   * @param baseFolder file(dir) base folder, we should calc relative path of resFile with base\n   * @param zipFile output zip file\n   * @param compressData compress data\n   * @throws IOException io exception\n   */\n  public static void zipFiles(\n      Collection<File> resFileList, File baseFolder, File zipFile, HashMap<String, Integer> compressData)\n      throws IOException {\n    ZipOutputStream zipOut = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile), BUFFER));\n    for (File resFile : resFileList) {\n      if (resFile.exists()) {\n        if (resFile.getAbsolutePath().contains(baseFolder.getAbsolutePath())) {\n          String relativePath = baseFolder.toURI().relativize(resFile.getParentFile().toURI()).getPath();\n          // remove slash at end of relativePath\n          if (relativePath.length() > 1) {\n            relativePath = relativePath.substring(0, relativePath.length() - 1);\n          } else {\n            relativePath = \"\";\n          }\n          zipFile(resFile, zipOut, relativePath, compressData);\n        } else {\n          zipFile(resFile, zipOut, \"\", compressData);\n        }\n      }\n    }\n    zipOut.close();\n  }\n\n  private static void zipFile(\n      File resFile, ZipOutputStream zipout, String rootpath, HashMap<String, Integer> compressData) throws IOException {\n    rootpath = rootpath + (rootpath.trim().length() == 0 ? \"\" : File.separator) + resFile.getName();\n    if (resFile.isDirectory()) {\n      File[] fileList = resFile.listFiles();\n      for (File file : fileList) {\n        zipFile(file, zipout, rootpath, compressData);\n      }\n    } else {\n      final byte[] fileContents = readContents(resFile);\n      //这里需要强转成linux格式，果然坑！！\n      if (rootpath.contains(\"\\\\\")) {\n        rootpath = rootpath.replace(\"\\\\\", \"/\");\n      }\n      if (!compressData.containsKey(rootpath)) {\n        System.err.printf(String.format(\"do not have the compress data path =%s in resource.asrc\\n\", rootpath));\n        //throw new IOException(String.format(\"do not have the compress data path=%s\", rootpath));\n        return;\n      }\n      int compressMethod = compressData.get(rootpath);\n      ZipEntry entry = new ZipEntry(rootpath);\n\n      if (compressMethod == ZipEntry.DEFLATED) {\n        entry.setMethod(ZipEntry.DEFLATED);\n      } else {\n        entry.setMethod(ZipEntry.STORED);\n        entry.setSize(fileContents.length);\n        final CRC32 checksumCalculator = new CRC32();\n        checksumCalculator.update(fileContents);\n        entry.setCrc(checksumCalculator.getValue());\n      }\n      zipout.putNextEntry(entry);\n      zipout.write(fileContents);\n      zipout.flush();\n      zipout.closeEntry();\n    }\n  }\n\n  private static byte[] readContents(final File file) throws IOException {\n    final ByteArrayOutputStream output = new ByteArrayOutputStream();\n    final int bufferSize = 4096;\n    try {\n      final FileInputStream in = new FileInputStream(file);\n      final BufferedInputStream bIn = new BufferedInputStream(in);\n      int length;\n      byte[] buffer = new byte[bufferSize];\n      byte[] bufferCopy;\n      while ((length = bIn.read(buffer, 0, bufferSize)) != -1) {\n        bufferCopy = new byte[length];\n        System.arraycopy(buffer, 0, bufferCopy, 0, length);\n        output.write(bufferCopy);\n      }\n      bIn.close();\n    } finally {\n      output.close();\n    }\n    return output.toByteArray();\n  }\n}\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/util/Md5Util.java",
    "content": "package com.tencent.mm.util;\n\n\nimport org.apache.commons.io.FileUtils;\n\nimport java.io.File;\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\n\n\n/**\n * @author ysbing\n */\npublic class Md5Util {\n\n    public static String getMD5Str(String str) {\n        MessageDigest digest;\n        try {\n            digest = MessageDigest.getInstance(\"MD5\");\n            digest.update(str.getBytes(StandardCharsets.UTF_8));\n        } catch (Exception e) {\n            return \"\";\n        }\n        return bytesToHexString(digest.digest());\n    }\n\n    public static String getMD5Str(File file) {\n        if (!file.isFile()) {\n            return \"\";\n        }\n        MessageDigest digest;\n        try {\n            digest = MessageDigest.getInstance(\"MD5\");\n            digest.update(FileUtils.readFileToByteArray(file));\n        } catch (Exception e) {\n            return \"\";\n        }\n        return bytesToHexString(digest.digest());\n    }\n\n    public static String bytesToHexString(byte[] src) {\n        if (src.length <= 0) {\n            return \"\";\n        }\n        StringBuilder stringBuilder = new StringBuilder(src.length);\n        for (byte b : src) {\n            int v = b & 0xFF;\n            String hv = Integer.toHexString(v);\n            if (hv.length() < 2) {\n                stringBuilder.append(0);\n            }\n            stringBuilder.append(hv);\n        }\n        return stringBuilder.toString();\n    }\n\n}"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/util/TypedValue.java",
    "content": "/*\n * Copyright (C) 2007 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.tencent.mm.util;\n\nimport java.util.zip.ZipEntry;\n\n/**\n * Container for a dynamically typed data value. Primarily used with\n */\npublic class TypedValue {\n  public static final String UNZIP_FILE_PATH = \"temp\";\n\n  public static final String COMMAND_7ZIP = \"7za\";\n  public static final String COMMAND_ZIPALIGIN = \"zipalign\";\n\n  public static final String OUT_7ZIP_FILE_PATH = \"out_7zip\";\n\n  /**\n   * 是7zip压缩使用，把制定为不压缩的拷到一起\n   */\n  public static final String STORED_FILE_PATH = \"storedfiles\";\n\n  public static final String RES_FILE_PATH = \"r\";\n\n  public static final String RES_MAPPING_FILE = \"resource_mapping_\";\n\n  public static final String MERGE_DUPLICATED_RES_MAPPING_FILE = \"merge_duplicated_res_mapping_\";\n\n  public static final int ZIP_STORED = ZipEntry.STORED;\n\n  public static final int ZIP_DEFLATED = ZipEntry.DEFLATED;\n\n  public static final int JDK_6 = 6;\n\n  public static final String TXT_FILE = \".txt\";\n\n  public static final String XML_FILE = \".xml\";\n\n  public static final String CONFIG_FILE = \"config.xml\";\n\n  /**\n   * The value contains no data.\n   */\n  public static final int TYPE_NULL = 0x00;\n\n  public static final int TYPE_STRING = 0x03;\n};\n"
  },
  {
    "path": "AndResGuard-core/src/main/java/com/tencent/mm/util/Utils.java",
    "content": "package com.tencent.mm.util;\n\nimport com.tencent.mm.androlib.res.util.StringUtil;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.LineNumberReader;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.regex.Pattern;\n\npublic class Utils {\n  public static boolean isPresent(String str) {\n    return str != null && str.length() > 0;\n  }\n\n  public static boolean isBlank(String str) {\n    return !isPresent(str);\n  }\n\n  public static boolean isPresent(Iterator iterator) {\n    return iterator != null && iterator.hasNext();\n  }\n\n  public static boolean isBlank(Iterator iterator) {\n    return !isPresent(iterator);\n  }\n\n  public static String convertToPatternString(String input) {\n    // ?\tZero or one character\n    // *\tZero or more of character\n    // +\tOne or more of character\n    final String[] searchList = new String[] { \".\", \"?\", \"*\", \"+\" };\n    final String[] replacementList = new String[] { \"\\\\.\", \".?\", \".*\", \".+\" };\n    return replaceEach(input, searchList, replacementList);\n  }\n\n  public static boolean match(String str, HashSet<Pattern> patterns) {\n    if (patterns == null) {\n      return false;\n    }\n    for(Pattern p : patterns) {\n      Boolean isMatch = p.matcher(str).matches();\n      if (isMatch) return true;\n    }\n    return false;\n  }\n\n  public static void cleanDir(File dir) {\n    if (dir.exists()) {\n      FileOperation.deleteDir(dir);\n      dir.mkdirs();\n    }\n  }\n\n  public static String runCmd(String... cmd) throws IOException, InterruptedException {\n    String output;\n    Process process = null;\n    try {\n      process = new ProcessBuilder(cmd).start();\n      output = StringUtil.readInputStream(process.getInputStream());\n      process.waitFor();\n      if (process.exitValue() != 0) {\n        System.err.println(String.format(\"%s Failed! Please check your signature file.\\n\", cmd[0]));\n        throw new RuntimeException(StringUtil.readInputStream(process.getErrorStream()));\n      }\n    } finally {\n      if (process != null) {\n        process.destroy();\n      }\n    }\n    return output;\n  }\n\n  public static String runExec(String[] argv) throws IOException, InterruptedException {\n    Process process = null;\n    String output;\n    try {\n      process = Runtime.getRuntime().exec(argv);\n      output = StringUtil.readInputStream(process.getInputStream());\n      process.waitFor();\n      if (process.exitValue() != 0) {\n        System.err.println(String.format(\"%s Failed! Please check your signature file.\\n\", argv[0]));\n        throw new RuntimeException(StringUtil.readInputStream(process.getErrorStream()));\n      }\n    } finally {\n      if (process != null) {\n        process.destroy();\n      }\n    }\n    return output;\n  }\n\n  private static void processOutputStreamInThread(Process process) throws IOException {\n    InputStreamReader ir = new InputStreamReader(process.getInputStream());\n    LineNumberReader input = new LineNumberReader(ir);\n    //如果不读会有问题，被阻塞\n    while (input.readLine() != null) {\n    }\n  }\n\n  private static String replaceEach(String text, String[] searchList, String[] replacementList) {\n    // TODO: throw new IllegalArgumentException() if any param doesn't make sense\n    //validateParams(text, searchList, replacementList);\n\n    SearchTracker tracker = new SearchTracker(text, searchList, replacementList);\n    if (!tracker.hasNextMatch(0)) {\n      return text;\n    }\n\n    StringBuilder buf = new StringBuilder(text.length() * 2);\n    int start = 0;\n\n    do {\n      SearchTracker.MatchInfo matchInfo = tracker.matchInfo;\n      int textIndex = matchInfo.textIndex;\n      String pattern = matchInfo.pattern;\n      String replacement = matchInfo.replacement;\n\n      buf.append(text.substring(start, textIndex));\n      buf.append(replacement);\n\n      start = textIndex + pattern.length();\n    } while (tracker.hasNextMatch(start));\n\n    return buf.append(text.substring(start)).toString();\n  }\n\n  static class SearchTracker {\n\n    final String text;\n\n    final Map<String, String> patternToReplacement = new HashMap<>();\n    final Set<String> pendingPatterns = new HashSet<>();\n\n    MatchInfo matchInfo = null;\n\n    SearchTracker(String text, String[] searchList, String[] replacementList) {\n      this.text = text;\n      for (int i = 0; i < searchList.length; ++i) {\n        String pattern = searchList[i];\n        patternToReplacement.put(pattern, replacementList[i]);\n        pendingPatterns.add(pattern);\n      }\n    }\n\n    boolean hasNextMatch(int start) {\n      int textIndex = -1;\n      String nextPattern = null;\n\n      for (String pattern : new ArrayList<>(pendingPatterns)) {\n        int matchIndex = text.indexOf(pattern, start);\n        if (matchIndex == -1) {\n          pendingPatterns.remove(pattern);\n        } else {\n          if (textIndex == -1 || matchIndex < textIndex) {\n            textIndex = matchIndex;\n            nextPattern = pattern;\n          }\n        }\n      }\n\n      if (nextPattern != null) {\n        matchInfo = new MatchInfo(nextPattern, patternToReplacement.get(nextPattern), textIndex);\n        return true;\n      }\n      return false;\n    }\n\n    private static class MatchInfo {\n      final String pattern;\n      final String replacement;\n      final int textIndex;\n\n      MatchInfo(String pattern, String replacement, int textIndex) {\n        this.pattern = pattern;\n        this.replacement = replacement;\n        this.textIndex = textIndex;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "AndResGuard-example/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n**/fabric.properties\n.gradletasknamecache\n"
  },
  {
    "path": "AndResGuard-example/app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "AndResGuard-example/app/build.gradle",
    "content": "apply plugin: 'AndResGuard'\napply plugin: 'com.android.application'\n\nbuildscript {\n  repositories {\n    mavenLocal()\n    google()\n    jcenter()\n  }\n  dependencies {\n    // The Fabric Gradle plugin uses an open ended version to react\n    // quickly to Android tooling updates\n    classpath(\"com.tencent.mm:AndResGuard-gradle-plugin:${ANDRESGUARD_VERSION}\") { changing = true }\n  }\n}\n\nandResGuard {\n  mappingFile = file(\"./resource_mapping.txt\")\n  use7zip = true\n  useSign = true\n  keepRoot = false\n  mergeDuplicatedRes = true\n  // add <yourpackagename>.R.drawable.icon into whitelist.\n  // because the launcher will get the icon with his name\n  whiteList = [\"R.mipmap.ic_launcher\",\n               //https://docs.fabric.io/android/crashlytics/build-tools.html\n               \"R.string.com.crashlytics.*\",\n               \"R.id.*\"]\n  compressFilePattern = [\"*.png\",\n                         \"*.jpg\",\n                         \"*.jpeg\",\n                         \"*.gif\",\n                         \"resources.arsc\"]\n  sevenzip {\n    artifact = \"com.tencent.mm:SevenZip:${ANDRESGUARD_SEVENZIP_VERSION}\"\n    //path = \"/usr/local/bin/7za\"\n  }\n\n  /**\n   * Optional: if finalApkBackupPath is null, AndResGuard will overwrite final apk\n   * to the path which assemble[Task] write to*/\n  finalApkBackupPath = \"${project.rootDir}/final.apk\"\n  digestalg = \"SHA-256\"\n}\n\nrepositories {\n  jcenter()\n  maven { url 'https://maven.fabric.io/public' }\n}\n\n\nandroid {\n  compileSdkVersion 30\n  buildToolsVersion \"29.0.2\"\n\n\n  signingConfigs {\n    release {\n      try {\n        storeFile file(\"../keystore test/testKey.jks\")\n        storePassword \"testtest\"\n        keyAlias \"test\"\n        keyPassword \"admin@test\"\n        v2SigningEnabled true\n      } catch (ex) {\n        throw new InvalidUserDataException(ex.toString())\n      }\n    }\n\n    debug {\n      storeFile file(\"../keystore test/debug.keystore\")\n    }\n  }\n\n  flavorDimensions \"default\", \"color\"\n  productFlavors {\n    flavor2 {\n      dimension \"default\"\n      println(\"@@@THIS IS FLAVOR2@@@\")\n      applicationId 'andresguard.tencent.com.andresguard_example.flavor2'\n    }\n\n    flavor1 {\n      dimension \"default\"\n      println(\"@@@THIS IS FLAVOR1@@@\")\n      applicationId 'andresguard.tencent.com.andresguard_example.flavor1'\n    }\n\n    aaa {\n      dimension \"default\"\n      println(\"@@@THIS IS aaa@@@\")\n      applicationId 'andresguard.tencent.com.andresguard_example.aaa'\n    }\n\n    red {\n      dimension \"color\"\n      println(\"@@@THIS IS red@@@\")\n    }\n\n    blue {\n      dimension \"color\"\n      println(\"@@@THIS IS blue@@@\")\n    }\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled true\n      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n      shrinkResources true\n      zipAlignEnabled true\n      pseudoLocalesEnabled true\n      signingConfig signingConfigs.release\n    }\n\n    releaseLog {\n      buildConfigField \"boolean\", \"DEBUG_MODE\", \"false\"\n      buildConfigField \"boolean\", \"LOG_DEBUG\", \"true\"\n      debuggable true\n      minifyEnabled true\n      zipAlignEnabled true\n      shrinkResources true\n      proguardFiles getDefaultProguardFile('proguard-android.txt'), \"proguard-rules.pro\"\n      signingConfig signingConfigs.release\n\n      matchingFallbacks = ['release']\n    }\n  }\n\n\n  defaultConfig {\n    applicationId \"andresguard.tencent.com.andresguard_example\"\n    minSdkVersion 24\n    targetSdkVersion 30\n    versionCode 3\n    versionName \"1.2\"\n  }\n}\n\ntask createVersionInformation {\n  def resDir = new File(buildDir, 'generated/buildinfo/')\n  def destDir = new File(resDir, 'META-INF/')\n\n  android {\n    sourceSets {\n      main.resources {\n        srcDir resDir\n      }\n    }\n  }\n\n  doLast {\n    destDir.mkdirs()\n    def vfile = new File(destDir, 'BUILD_INFO')\n\n    def stdout = new ByteArrayOutputStream()\n    exec {\n      commandLine 'git', 'log', '-1', '--format=%H'\n      standardOutput = stdout\n    }\n    def git_hash = stdout.toString().trim()\n\n    stdout.reset()\n    exec {\n      commandLine 'git', 'config', 'remote.origin.url'\n      standardOutput = stdout\n    }\n    def remote = stdout.toString().trim()\n\n    def dirty = exec {\n      commandLine 'git', 'diff-index', '--quiet', 'HEAD'\n      ignoreExitValue true\n    }\n    dirty = dirty.getExitValue() == 0 ? ' (Clean workspace)' : '-DIRTY using dirty workspace!'\n\n    def repo_info = \"$remote at $git_hash$dirty\"\n\n    vfile.text = \"$repo_info\\n\"\n  }\n}\n\nproject.afterEvaluate {\n  tasks.findAll { task -> task.name.startsWith('merge') && task.name.endsWith('Resources')\n  }.each { t -> t.dependsOn createVersionInformation }\n}\n\ndependencies {\n  implementation fileTree(dir: 'libs', include: ['*.jar'])\n  testImplementation 'junit:junit:4.12'\n  implementation project(':libres')\n  implementation 'com.android.support:appcompat-v7:26.1.0'\n  implementation 'com.android.support:design:26.1.0'\n  // Crashlytics Kit\n  implementation('com.crashlytics.sdk.android:crashlytics:2.7.1@aar') {\n    transitive = true\n  }\n  // NDK Kit\n  implementation('com.crashlytics.sdk.android:crashlytics-ndk:1.1.6@aar') {\n    transitive = true\n  }\n}\n"
  },
  {
    "path": "AndResGuard-example/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/sun/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "AndResGuard-example/app/resource_mapping.txt",
    "content": "res path mapping:\n    res/mipmap-hdpi-v4 -> res/mipmap-hdpi-v4\n    res/mipmap-mdpi-v4 -> res/mipmap-mdpi-v4\n    res/mipmap-xhdpi-v4 -> res/mipmap-xhdpi-v4\n    res/mipmap-xxhdpi-v4 -> res/mipmap-xxhdpi-v4\n    res/mipmap-xxxhdpi-v4 -> res/mipmap-xxxhdpi-v4\n"
  },
  {
    "path": "AndResGuard-example/app/src/androidTest/java/andresguard/tencent/com/andresguard_example/ApplicationTest.java",
    "content": "package andresguard.tencent.com.andresguard_example;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n  public ApplicationTest() {\n    super(Application.class);\n  }\n}"
  },
  {
    "path": "AndResGuard-example/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"andresguard.tencent.com.andresguard_example\"\n          xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:extractNativeLibs=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity\n            android:name=\".MainActivity\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/AppTheme.NoActionBar\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n        <meta-data\n            android:name=\"io.fabric.ApiKey\"\n            android:value=\"b7021b4d40a8f8af51c35fee23023646621364ac\" />\n    </application>\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n</manifest>\n"
  },
  {
    "path": "AndResGuard-example/app/src/main/java/andresguard/tencent/com/andresguard_example/MainActivity.java",
    "content": "package andresguard.tencent.com.andresguard_example;\n\nimport android.os.Bundle;\nimport android.support.design.widget.FloatingActionButton;\nimport android.support.design.widget.NavigationView;\nimport android.support.design.widget.Snackbar;\nimport android.support.v4.view.GravityCompat;\nimport android.support.v4.widget.DrawerLayout;\nimport android.support.v7.app.ActionBarDrawerToggle;\nimport android.support.v7.app.AppCompatActivity;\nimport android.support.v7.widget.Toolbar;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport com.crashlytics.android.Crashlytics;\nimport com.crashlytics.android.ndk.CrashlyticsNdk;\nimport io.fabric.sdk.android.Fabric;\n\npublic class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    Fabric.with(this, new Crashlytics(), new CrashlyticsNdk());\n\n    setContentView(R.layout.activity_main);\n    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);\n    setSupportActionBar(toolbar);\n\n    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);\n    fab.setOnClickListener(new View.OnClickListener() {\n      @Override\n      public void onClick(View view) {\n        Snackbar.make(view, \"Replace with your own action\", Snackbar.LENGTH_LONG).setAction(\"Action\", null).show();\n      }\n    });\n\n    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);\n    ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this,\n        drawer,\n        toolbar,\n        R.string.navigation_drawer_open,\n        R.string.navigation_drawer_close\n    );\n    drawer.setDrawerListener(toggle);\n    toggle.syncState();\n\n    NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);\n    navigationView.setNavigationItemSelectedListener(this);\n  }\n\n  @Override\n  public void onBackPressed() {\n    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);\n    if (drawer.isDrawerOpen(GravityCompat.START)) {\n      drawer.closeDrawer(GravityCompat.START);\n    } else {\n      super.onBackPressed();\n    }\n  }\n\n  @Override\n  public boolean onCreateOptionsMenu(Menu menu) {\n    // Inflate the menu; this adds items to the action bar if it is present.\n    getMenuInflater().inflate(R.menu.main, menu);\n    return true;\n  }\n\n  @Override\n  public boolean onOptionsItemSelected(MenuItem item) {\n    // Handle action bar item clicks here. The action bar will\n    // automatically handle clicks on the Home/Up button, so long\n    // as you specify a parent activity in AndroidManifest.xml.\n    int id = item.getItemId();\n\n    if (id == R.id.action_settings) {\n      return true;\n    }\n\n    return super.onOptionsItemSelected(item);\n  }\n\n  @SuppressWarnings(\"StatementWithEmptyBody\")\n  @Override\n  public boolean onNavigationItemSelected(MenuItem item) {\n    // Handle navigation view item clicks here.\n    int id = item.getItemId();\n\n    if (id == R.id.nav_camera) {\n      // Handle the camera action\n    } else if (id == R.id.nav_gallery) {\n\n    } else if (id == R.id.nav_slideshow) {\n\n    } else if (id == R.id.nav_manage) {\n\n    } else if (id == R.id.nav_share) {\n\n    } else if (id == R.id.nav_send) {\n\n    }\n\n    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);\n    drawer.closeDrawer(GravityCompat.START);\n    return true;\n  }\n}\n"
  },
  {
    "path": "AndResGuard-example/app/src/main/res/.keep",
    "content": ""
  },
  {
    "path": "AndResGuard-example/app/src/test/java/andresguard/tencent/com/andresguard_example/ExampleUnitTest.java",
    "content": "package andresguard.tencent.com.andresguard_example;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\n\n/**\n * To work on unit tests, switch the Test Artifact in the Build Variants view.\n */\npublic class ExampleUnitTest {\n  @Test\n  public void addition_isCorrect() throws Exception {\n    assertEquals(4, 2 + 2);\n  }\n}"
  },
  {
    "path": "AndResGuard-example/app1/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "AndResGuard-example/app1/build.gradle",
    "content": "apply plugin: 'AndResGuard'\napply plugin: 'com.android.application'\n\nbuildscript {\n  repositories {\n    mavenLocal()\n    jcenter()\n    google()\n  }\n  dependencies {\n    classpath(\"com.tencent.mm:AndResGuard-gradle-plugin:${ANDRESGUARD_VERSION}\") {\n      changing = true\n    }\n  }\n}\n\nandResGuard {\n  mappingFile = file(\"./resource_mapping.txt\")\n  use7zip = true\n  useSign = true\n  keepRoot = false\n  mergeDuplicatedRes = true\n  // add <yourpackagename>.R.drawable.icon into whitelist.\n  // because the launcher will get the icon with his name\n  whiteList = [\"R.mipmap.ic_launcher\",\n               //https://docs.fabric.io/android/crashlytics/build-tools.html\n               \"R.string.com.crashlytics.*\",\n               \"R.id.*\",\n               \"R.drawable.*\",\n               \"R.layout.*\"]\n  compressFilePattern = [\"*.png\",\n                         \"*.jpg\",\n                         \"*.jpeg\",\n                         \"*.gif\",\n                         \"resources.arsc\"]\n  sevenzip {\n    artifact = \"com.tencent.mm:SevenZip:${ANDRESGUARD_SEVENZIP_VERSION}\"\n    //path = \"/usr/local/bin/7za\"\n  }\n}\n\nrepositories {\n  jcenter()\n  maven { url 'https://maven.fabric.io/public' }\n}\n\nandroid {\n  compileSdkVersion 26\n  buildToolsVersion '29.0.2'\n\n\n  signingConfigs {\n    release {\n      try {\n        storeFile file(\"../keystore test/release.keystore\")\n        storePassword \"testres\"\n        keyAlias \"testres\"\n        keyPassword \"testres\"\n        v2SigningEnabled true\n      } catch (ex) {\n        throw new InvalidUserDataException(ex.toString())\n      }\n    }\n\n    debug {\n      storeFile file(\"../keystore test/debug.keystore\")\n    }\n  }\n\n  //    productFlavors {\n  //        flavor2 {\n  //            println(\"@@@THIS IS FLAVOR2@@@\")\n  //            applicationId 'andresguard.tencent.com.andresguard_example.flavor2'\n  //        }\n  //\n  //        flavor1 {\n  //            println(\"@@@THIS IS FLAVOR1@@@\")\n  //            applicationId 'andresguard.tencent.com.andresguard_example.flavor1'\n  //        }\n  //\n  //        aaa {\n  //            println(\"@@@THIS IS aaa@@@\")\n  //            applicationId 'andresguard.tencent.com.andresguard_example.aaa'\n  //        }\n  //    }\n\n  buildTypes {\n    release {\n      minifyEnabled true\n      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n      shrinkResources true\n      zipAlignEnabled true\n      pseudoLocalesEnabled true\n      signingConfig signingConfigs.release\n    }\n\n    releaseLog {\n      buildConfigField \"boolean\", \"DEBUG_MODE\", \"false\"\n      buildConfigField \"boolean\", \"LOG_DEBUG\", \"true\"\n      debuggable true\n      minifyEnabled true\n      zipAlignEnabled true\n      shrinkResources true\n      proguardFiles getDefaultProguardFile('proguard-android.txt'), \"proguard-rules.pro\"\n      signingConfig signingConfigs.release\n\n      matchingFallbacks = ['release']\n    }\n  }\n\n\n  defaultConfig {\n    applicationId \"andresguard.tencent.com.andresguard_example\"\n    minSdkVersion 14\n    targetSdkVersion 26\n    versionCode 2\n    versionName \"1.1\"\n  }\n}\n\ndependencies {\n  implementation fileTree(dir: 'libs', include: ['*.jar'])\n  testImplementation 'junit:junit:4.12'\n  implementation project(':libres')\n  implementation 'com.android.support:appcompat-v7:26.1.0'\n  implementation 'com.android.support:design:26.1.0'\n  // Crashlytics Kit\n  implementation('com.crashlytics.sdk.android:crashlytics:2.7.1@aar') {\n    transitive = true\n  }\n  // NDK Kit\n  implementation('com.crashlytics.sdk.android:crashlytics-ndk:1.1.6@aar') {\n    transitive = true\n  }\n}\n"
  },
  {
    "path": "AndResGuard-example/app1/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/sun/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "AndResGuard-example/app1/resource_mapping.txt",
    "content": "res path mapping:\n    res/mipmap-hdpi-v4 -> res/mipmap-hdpi-v4\n    res/mipmap-mdpi-v4 -> res/mipmap-mdpi-v4\n    res/mipmap-xhdpi-v4 -> res/mipmap-xhdpi-v4\n    res/mipmap-xxhdpi-v4 -> res/mipmap-xxhdpi-v4\n    res/mipmap-xxxhdpi-v4 -> res/mipmap-xxxhdpi-v4\n"
  },
  {
    "path": "AndResGuard-example/app1/src/androidTest/java/andresguard/tencent/com/andresguard_example/ApplicationTest.java",
    "content": "package andresguard.tencent.com.andresguard_example;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n  public ApplicationTest() {\n    super(Application.class);\n  }\n}"
  },
  {
    "path": "AndResGuard-example/app1/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"andresguard.tencent.com.andresguard_example\"\n          xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity\n            android:name=\".MainActivity\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/AppTheme.NoActionBar\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n        <meta-data\n            android:name=\"io.fabric.ApiKey\"\n            android:value=\"b7021b4d40a8f8af51c35fee23023646621364ac\" />\n    </application>\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n</manifest>\n"
  },
  {
    "path": "AndResGuard-example/app1/src/main/java/andresguard/tencent/com/andresguard_example/MainActivity.java",
    "content": "package andresguard.tencent.com.andresguard_example;\n\nimport android.os.Bundle;\nimport android.support.design.widget.FloatingActionButton;\nimport android.support.design.widget.NavigationView;\nimport android.support.design.widget.Snackbar;\nimport android.support.v4.view.GravityCompat;\nimport android.support.v4.widget.DrawerLayout;\nimport android.support.v7.app.ActionBarDrawerToggle;\nimport android.support.v7.app.AppCompatActivity;\nimport android.support.v7.widget.Toolbar;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport com.crashlytics.android.Crashlytics;\nimport com.crashlytics.android.ndk.CrashlyticsNdk;\nimport io.fabric.sdk.android.Fabric;\n\npublic class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    Fabric.with(this, new Crashlytics(), new CrashlyticsNdk());\n\n    setContentView(R.layout.activity_main);\n    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);\n    setSupportActionBar(toolbar);\n\n    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);\n    fab.setOnClickListener(new View.OnClickListener() {\n      @Override\n      public void onClick(View view) {\n        Snackbar.make(view, \"Replace with your own action\", Snackbar.LENGTH_LONG).setAction(\"Action\", null).show();\n      }\n    });\n\n    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);\n    ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this,\n        drawer,\n        toolbar,\n        R.string.navigation_drawer_open,\n        R.string.navigation_drawer_close\n    );\n    drawer.setDrawerListener(toggle);\n    toggle.syncState();\n\n    NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);\n    navigationView.setNavigationItemSelectedListener(this);\n  }\n\n  @Override\n  public void onBackPressed() {\n    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);\n    if (drawer.isDrawerOpen(GravityCompat.START)) {\n      drawer.closeDrawer(GravityCompat.START);\n    } else {\n      super.onBackPressed();\n    }\n  }\n\n  @Override\n  public boolean onCreateOptionsMenu(Menu menu) {\n    // Inflate the menu; this adds items to the action bar if it is present.\n    getMenuInflater().inflate(R.menu.main, menu);\n    return true;\n  }\n\n  @Override\n  public boolean onOptionsItemSelected(MenuItem item) {\n    // Handle action bar item clicks here. The action bar will\n    // automatically handle clicks on the Home/Up button, so long\n    // as you specify a parent activity in AndroidManifest.xml.\n    int id = item.getItemId();\n\n    if (id == R.id.action_settings) {\n      return true;\n    }\n\n    return super.onOptionsItemSelected(item);\n  }\n\n  @SuppressWarnings(\"StatementWithEmptyBody\")\n  @Override\n  public boolean onNavigationItemSelected(MenuItem item) {\n    // Handle navigation view item clicks here.\n    int id = item.getItemId();\n\n    if (id == R.id.nav_camera) {\n      // Handle the camera action\n    } else if (id == R.id.nav_gallery) {\n\n    } else if (id == R.id.nav_slideshow) {\n\n    } else if (id == R.id.nav_manage) {\n\n    } else if (id == R.id.nav_share) {\n\n    } else if (id == R.id.nav_send) {\n\n    }\n\n    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);\n    drawer.closeDrawer(GravityCompat.START);\n    return true;\n  }\n}\n"
  },
  {
    "path": "AndResGuard-example/app1/src/main/res/.keep",
    "content": ""
  },
  {
    "path": "AndResGuard-example/app1/src/test/java/andresguard/tencent/com/andresguard_example/ExampleUnitTest.java",
    "content": "package andresguard.tencent.com.andresguard_example;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\n\n/**\n * To work on unit tests, switch the Test Artifact in the Build Variants view.\n */\npublic class ExampleUnitTest {\n  @Test\n  public void addition_isCorrect() throws Exception {\n    assertEquals(4, 2 + 2);\n  }\n}"
  },
  {
    "path": "AndResGuard-example/app2/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "AndResGuard-example/app2/build.gradle",
    "content": "apply plugin: 'AndResGuard'\napply plugin: 'com.android.application'\n\nbuildscript {\n  repositories {\n    mavenLocal()\n    jcenter()\n    google()\n  }\n  dependencies {\n    classpath(\"com.tencent.mm:AndResGuard-gradle-plugin:${ANDRESGUARD_VERSION}\") {\n      changing = true\n    }\n  }\n}\n\nandResGuard {\n  mappingFile = file(\"./resource_mapping.txt\")\n  use7zip = true\n  useSign = true\n  keepRoot = false\n  mergeDuplicatedRes = true\n  // add <yourpackagename>.R.drawable.icon into whitelist.\n  // because the launcher will get the icon with his name\n  whiteList = [\"R.mipmap.ic_launcher\",\n               //https://docs.fabric.io/android/crashlytics/build-tools.html\n               \"R.string.com.crashlytics.*\",\n               \"R.id.*\"]\n  compressFilePattern = [\"*.png\",\n                         \"*.jpg\",\n                         \"*.jpeg\",\n                         \"*.gif\",\n                         \"resources.arsc\"]\n  sevenzip {\n    artifact = \"com.tencent.mm:SevenZip:${ANDRESGUARD_SEVENZIP_VERSION}\"\n    //path = \"/usr/local/bin/7za\"\n  }\n}\n\nrepositories {\n  jcenter()\n  maven { url 'https://maven.fabric.io/public' }\n}\n\nandroid {\n  compileSdkVersion 26\n  buildToolsVersion '29.0.2'\n\n\n  signingConfigs {\n    release {\n      try {\n        storeFile file(\"../keystore test/release.keystore\")\n        storePassword \"testres\"\n        keyAlias \"testres\"\n        keyPassword \"testres\"\n        v2SigningEnabled true\n      } catch (ex) {\n        throw new InvalidUserDataException(ex.toString())\n      }\n    }\n\n    debug {\n      storeFile file(\"../keystore test/debug.keystore\")\n    }\n  }\n\n  //    productFlavors {\n  //        flavor2 {\n  //            println(\"@@@THIS IS FLAVOR2@@@\")\n  //            applicationId 'andresguard.tencent.com.andresguard_example.flavor2'\n  //        }\n  //\n  //        flavor1 {\n  //            println(\"@@@THIS IS FLAVOR1@@@\")\n  //            applicationId 'andresguard.tencent.com.andresguard_example.flavor1'\n  //        }\n  //\n  //        aaa {\n  //            println(\"@@@THIS IS aaa@@@\")\n  //            applicationId 'andresguard.tencent.com.andresguard_example.aaa'\n  //        }\n  //    }\n\n  buildTypes {\n    release {\n      minifyEnabled true\n      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n      shrinkResources true\n      zipAlignEnabled true\n      pseudoLocalesEnabled true\n      signingConfig signingConfigs.release\n    }\n\n    releaseLog {\n      buildConfigField \"boolean\", \"DEBUG_MODE\", \"false\"\n      buildConfigField \"boolean\", \"LOG_DEBUG\", \"true\"\n      debuggable true\n      minifyEnabled true\n      zipAlignEnabled true\n      shrinkResources true\n      proguardFiles getDefaultProguardFile('proguard-android.txt'), \"proguard-rules.pro\"\n      signingConfig signingConfigs.release\n\n      matchingFallbacks = ['release']\n    }\n  }\n\n\n  defaultConfig {\n    applicationId \"andresguard.tencent.com.andresguard_example\"\n    minSdkVersion 14\n    targetSdkVersion 26\n    versionCode 2\n    versionName \"1.1\"\n  }\n}\n\ndependencies {\n  implementation fileTree(dir: 'libs', include: ['*.jar'])\n  testImplementation 'junit:junit:4.12'\n  implementation project(':libres')\n  implementation 'com.android.support:appcompat-v7:26.1.0'\n  implementation 'com.android.support:design:26.1.0'\n  // Crashlytics Kit\n  implementation('com.crashlytics.sdk.android:crashlytics:2.7.1@aar') {\n    transitive = true\n  }\n  // NDK Kit\n  implementation('com.crashlytics.sdk.android:crashlytics-ndk:1.1.6@aar') {\n    transitive = true\n  }\n}\n"
  },
  {
    "path": "AndResGuard-example/app2/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/sun/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "AndResGuard-example/app2/resource_mapping.txt",
    "content": "res path mapping:\n    res/mipmap-hdpi-v4 -> res/mipmap-hdpi-v4\n    res/mipmap-mdpi-v4 -> res/mipmap-mdpi-v4\n    res/mipmap-xhdpi-v4 -> res/mipmap-xhdpi-v4\n    res/mipmap-xxhdpi-v4 -> res/mipmap-xxhdpi-v4\n    res/mipmap-xxxhdpi-v4 -> res/mipmap-xxxhdpi-v4\n"
  },
  {
    "path": "AndResGuard-example/app2/src/androidTest/java/andresguard/tencent/com/andresguard_example/ApplicationTest.java",
    "content": "package andresguard.tencent.com.andresguard_example;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n  public ApplicationTest() {\n    super(Application.class);\n  }\n}"
  },
  {
    "path": "AndResGuard-example/app2/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"andresguard.tencent.com.andresguard_example\"\n          xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity\n            android:name=\".MainActivity\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/AppTheme.NoActionBar\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n        <meta-data\n            android:name=\"io.fabric.ApiKey\"\n            android:value=\"b7021b4d40a8f8af51c35fee23023646621364ac\" />\n    </application>\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n</manifest>\n"
  },
  {
    "path": "AndResGuard-example/app2/src/main/java/andresguard/tencent/com/andresguard_example/MainActivity.java",
    "content": "package andresguard.tencent.com.andresguard_example;\n\nimport android.os.Bundle;\nimport android.support.design.widget.FloatingActionButton;\nimport android.support.design.widget.NavigationView;\nimport android.support.design.widget.Snackbar;\nimport android.support.v4.view.GravityCompat;\nimport android.support.v4.widget.DrawerLayout;\nimport android.support.v7.app.ActionBarDrawerToggle;\nimport android.support.v7.app.AppCompatActivity;\nimport android.support.v7.widget.Toolbar;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport com.crashlytics.android.Crashlytics;\nimport com.crashlytics.android.ndk.CrashlyticsNdk;\nimport io.fabric.sdk.android.Fabric;\n\npublic class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    Fabric.with(this, new Crashlytics(), new CrashlyticsNdk());\n\n    setContentView(R.layout.activity_main);\n    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);\n    setSupportActionBar(toolbar);\n\n    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);\n    fab.setOnClickListener(new View.OnClickListener() {\n      @Override\n      public void onClick(View view) {\n        Snackbar.make(view, \"Replace with your own action\", Snackbar.LENGTH_LONG).setAction(\"Action\", null).show();\n      }\n    });\n\n    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);\n    ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this,\n        drawer,\n        toolbar,\n        R.string.navigation_drawer_open,\n        R.string.navigation_drawer_close\n    );\n    drawer.setDrawerListener(toggle);\n    toggle.syncState();\n\n    NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);\n    navigationView.setNavigationItemSelectedListener(this);\n  }\n\n  @Override\n  public void onBackPressed() {\n    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);\n    if (drawer.isDrawerOpen(GravityCompat.START)) {\n      drawer.closeDrawer(GravityCompat.START);\n    } else {\n      super.onBackPressed();\n    }\n  }\n\n  @Override\n  public boolean onCreateOptionsMenu(Menu menu) {\n    // Inflate the menu; this adds items to the action bar if it is present.\n    getMenuInflater().inflate(R.menu.main, menu);\n    return true;\n  }\n\n  @Override\n  public boolean onOptionsItemSelected(MenuItem item) {\n    // Handle action bar item clicks here. The action bar will\n    // automatically handle clicks on the Home/Up button, so long\n    // as you specify a parent activity in AndroidManifest.xml.\n    int id = item.getItemId();\n\n    if (id == R.id.action_settings) {\n      return true;\n    }\n\n    return super.onOptionsItemSelected(item);\n  }\n\n  @SuppressWarnings(\"StatementWithEmptyBody\")\n  @Override\n  public boolean onNavigationItemSelected(MenuItem item) {\n    // Handle navigation view item clicks here.\n    int id = item.getItemId();\n\n    if (id == R.id.nav_camera) {\n      // Handle the camera action\n    } else if (id == R.id.nav_gallery) {\n\n    } else if (id == R.id.nav_slideshow) {\n\n    } else if (id == R.id.nav_manage) {\n\n    } else if (id == R.id.nav_share) {\n\n    } else if (id == R.id.nav_send) {\n\n    }\n\n    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);\n    drawer.closeDrawer(GravityCompat.START);\n    return true;\n  }\n}\n"
  },
  {
    "path": "AndResGuard-example/app2/src/main/res/.keep",
    "content": ""
  },
  {
    "path": "AndResGuard-example/app2/src/test/java/andresguard/tencent/com/andresguard_example/ExampleUnitTest.java",
    "content": "package andresguard.tencent.com.andresguard_example;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\n\n/**\n * To work on unit tests, switch the Test Artifact in the Build Variants view.\n */\npublic class ExampleUnitTest {\n  @Test\n  public void addition_isCorrect() throws Exception {\n    assertEquals(4, 2 + 2);\n  }\n}"
  },
  {
    "path": "AndResGuard-example/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n  repositories {\n    google()\n    jcenter()\n  }\n  dependencies {\n    classpath 'com.android.tools.build:gradle:4.1.1'\n    // NOTE: Do not place your application dependencies here; they belong\n    // in the individual module build.gradle files\n  }\n}\n\nallprojects {\n  repositories {\n    google()\n    jcenter()\n  }\n}\n"
  },
  {
    "path": "AndResGuard-example/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Fri May 29 18:04:51 PDT 2020\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.6.1-all.zip\n"
  },
  {
    "path": "AndResGuard-example/gradle.properties",
    "content": "## Project-wide Gradle settings.\n#\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n#\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx1024m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n#\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n#Tue Sep 06 04:33:31 CST 2016\norg.gradle.jvmargs=-Xmx2584M\norg.gradle.parallel=true\norg.gradle.daemon=true\nANDRESGUARD_VERSION=1.2.20\nANDRESGUARD_SEVENZIP_VERSION=1.2.20\n"
  },
  {
    "path": "AndResGuard-example/gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "AndResGuard-example/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "AndResGuard-example/libres/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "AndResGuard-example/libres/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n  compileSdkVersion 26\n  buildToolsVersion '29.0.2'\n  defaultConfig {\n    minSdkVersion 14\n    targetSdkVersion 26\n    versionCode 1\n    versionName \"1.0\"\n\n    testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n  }\n  buildTypes {\n    release {\n      minifyEnabled false\n      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n    }\n  }\n}\n\ndependencies {\n  api fileTree(dir: 'libs', include: ['*.jar'])\n  androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {\n    exclude group: 'com.android.support', module: 'support-annotations'\n  })\n  api 'com.android.support:appcompat-v7:26.1.0'\n  api 'com.android.support:design:26.1.0'\n  testImplementation 'junit:junit:4.12'\n}\n"
  },
  {
    "path": "AndResGuard-example/libres/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/sun/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "AndResGuard-example/libres/src/androidTest/java/com/tinkerpatch/libres/ExampleInstrumentedTest.java",
    "content": "package com.tinkerpatch.libres;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationRegistry;\nimport android.support.test.runner.AndroidJUnit4;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n/**\n * Instrumentation test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n  @Test\n  public void useAppContext() throws Exception {\n    // Context of the app under test.\n    Context appContext = InstrumentationRegistry.getTargetContext();\n\n    assertEquals(\"com.tinkerpatch.libres.test\", appContext.getPackageName());\n  }\n}\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.tinkerpatch.libres\"\n>\n\n\n</manifest>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/drawable/side_nav_bar.xml",
    "content": "<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"rectangle\">\n    <gradient\n        android:angle=\"135\"\n        android:centerColor=\"#4CAF50\"\n        android:endColor=\"#2E7D32\"\n        android:startColor=\"#81C784\"\n        android:type=\"linear\"/>\n</shape>"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/drawable-v21/ic_menu_camera.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0\"/>\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z\"/>\n</vector>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/drawable-v21/ic_menu_gallery.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z\"/>\n</vector>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/drawable-v21/ic_menu_manage.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z\"/>\n</vector>"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/drawable-v21/ic_menu_send.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z\"/>\n</vector>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/drawable-v21/ic_menu_share.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z\"/>\n</vector>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/drawable-v21/ic_menu_slideshow.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-8,12.5v-9l6,4.5 -6,4.5z\"/>\n</vector>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.v4.widget.DrawerLayout android:id=\"@+id/drawer_layout\"\n                                        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                                        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n                                        xmlns:tools=\"http://schemas.android.com/tools\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"match_parent\"\n                                        android:fitsSystemWindows=\"true\"\n                                        tools:openDrawer=\"start\">\n\n    <include\n        layout=\"@layout/app_bar_main\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"/>\n\n    <android.support.design.widget.NavigationView\n        android:id=\"@+id/nav_view\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"start\"\n        android:fitsSystemWindows=\"true\"\n        app:headerLayout=\"@layout/nav_header_main\"\n        app:menu=\"@menu/activity_main_drawer\"/>\n\n</android.support.v4.widget.DrawerLayout>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/layout/app_bar_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.design.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                                                 xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n                                                 xmlns:tools=\"http://schemas.android.com/tools\"\n                                                 android:layout_width=\"match_parent\"\n                                                 android:layout_height=\"match_parent\"\n                                                 android:fitsSystemWindows=\"true\"\n                                                 tools:context=\"andresguard.tencent.com.andresguard_example.MainActivity\">\n\n    <android.support.design.widget.AppBarLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"@style/AppTheme.AppBarOverlay\">\n\n        <android.support.v7.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:background=\"?attr/colorPrimary\"\n            app:popupTheme=\"@style/AppTheme.PopupOverlay\"/>\n\n    </android.support.design.widget.AppBarLayout>\n\n    <include layout=\"@layout/content_main\"/>\n\n    <android.support.design.widget.FloatingActionButton\n        android:id=\"@+id/fab\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom|end\"\n        android:layout_margin=\"@dimen/fab_margin\"\n        android:src=\"@android:drawable/ic_dialog_email\"/>\n\n</android.support.design.widget.CoordinatorLayout>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/layout/content_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n                xmlns:tools=\"http://schemas.android.com/tools\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:paddingBottom=\"@dimen/activity_vertical_margin\"\n                android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n                android:paddingRight=\"@dimen/activity_horizontal_margin\"\n                android:paddingTop=\"@dimen/activity_vertical_margin\"\n                app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n                tools:context=\"andresguard.tencent.com.andresguard_example.MainActivity\"\n                tools:showIn=\"@layout/app_bar_main\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Hello World!\"/>\n</RelativeLayout>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/layout/nav_header_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"@dimen/nav_header_height\"\n              android:background=\"@drawable/side_nav_bar\"\n              android:gravity=\"bottom\"\n              android:orientation=\"vertical\"\n              android:paddingBottom=\"@dimen/activity_vertical_margin\"\n              android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n              android:paddingRight=\"@dimen/activity_horizontal_margin\"\n              android:paddingTop=\"@dimen/activity_vertical_margin\"\n              android:theme=\"@style/ThemeOverlay.AppCompat.Dark\">\n\n    <ImageView\n        android:id=\"@+id/imageView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:paddingTop=\"@dimen/nav_header_vertical_spacing\"\n        android:src=\"@android:drawable/sym_def_app_icon\"/>\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingTop=\"@dimen/nav_header_vertical_spacing\"\n        android:text=\"Android Studio\"\n        android:textAppearance=\"@style/TextAppearance.AppCompat.Body1\"/>\n\n    <TextView\n        android:id=\"@+id/textView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"android.studio@android.com\"/>\n\n</LinearLayout>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/menu/activity_main_drawer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <group android:checkableBehavior=\"single\">\n        <item\n            android:id=\"@+id/nav_camera\"\n            android:icon=\"@drawable/ic_menu_camera\"\n            android:title=\"Import\"/>\n        <item\n            android:id=\"@+id/nav_gallery\"\n            android:icon=\"@drawable/ic_menu_gallery\"\n            android:title=\"Gallery\"/>\n        <item\n            android:id=\"@+id/nav_slideshow\"\n            android:icon=\"@drawable/ic_menu_slideshow\"\n            android:title=\"Slideshow\"/>\n        <item\n            android:id=\"@+id/nav_manage\"\n            android:icon=\"@drawable/ic_menu_manage\"\n            android:title=\"Tools\"/>\n    </group>\n\n    <item android:title=\"Communicate\">\n        <menu>\n            <item\n                android:id=\"@+id/nav_share\"\n                android:icon=\"@drawable/ic_menu_share\"\n                android:title=\"Share\"/>\n            <item\n                android:id=\"@+id/nav_send\"\n                android:icon=\"@drawable/ic_menu_send\"\n                android:title=\"Send\"/>\n        </menu>\n    </item>\n\n</menu>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/menu/main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/action_settings\"\n        android:orderInCategory=\"100\"\n        android:title=\"@string/action_settings\"\n        app:showAsAction=\"never\"/>\n</menu>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n</resources>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"nav_header_vertical_spacing\">16dp</dimen>\n    <dimen name=\"nav_header_height\">160dp</dimen>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n    <dimen name=\"fab_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/values/drawables.xml",
    "content": "<resources>\n    <item name=\"ic_menu_camera\" type=\"drawable\">@android:drawable/ic_menu_camera</item>\n    <item name=\"ic_menu_gallery\" type=\"drawable\">@android:drawable/ic_menu_gallery</item>\n    <item name=\"ic_menu_slideshow\" type=\"drawable\">@android:drawable/ic_menu_slideshow</item>\n    <item name=\"ic_menu_manage\" type=\"drawable\">@android:drawable/ic_menu_manage</item>\n    <item name=\"ic_menu_share\" type=\"drawable\">@android:drawable/ic_menu_share</item>\n    <item name=\"ic_menu_send\" type=\"drawable\">@android:drawable/ic_menu_send</item>\n</resources>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">AndResGuard-example</string>\n\n    <string name=\"navigation_drawer_open\">Open navigation drawer</string>\n    <string name=\"navigation_drawer_close\">Close navigation drawer</string>\n\n    <string name=\"action_settings\">Settings</string>\n</resources>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <style name=\"AppTheme.NoActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n    </style>\n\n    <style name=\"AppTheme.AppBarOverlay\" parent=\"ThemeOverlay.AppCompat.Dark.ActionBar\"/>\n\n    <style name=\"AppTheme.PopupOverlay\" parent=\"ThemeOverlay.AppCompat.Light\"/>\n\n</resources>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/values-v21/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">AndResGuard-example-v21</string>\n\n    <string name=\"navigation_drawer_open\">Open navigation drawer v21</string>\n    <string name=\"navigation_drawer_close\">Close navigation drawer v21</string>\n\n    <string name=\"action_settings\">Settings v21</string>\n</resources>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/values-v21/styles.xml",
    "content": "<resources>\n\n    <style name=\"AppTheme.NoActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n        <item name=\"android:windowDrawsSystemBarBackgrounds\">true</item>\n        <item name=\"android:statusBarColor\">@android:color/transparent</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/values-w820dp/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">AndResGuard-example 820</string>\n\n    <string name=\"navigation_drawer_open\">Open navigation drawer 820</string>\n    <string name=\"navigation_drawer_close\">Close navigation drawer 820</string>\n\n    <string name=\"action_settings\">Settings 820</string>\n</resources>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/main/res/values-xxhdpi/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">AndResGuard-example xxh</string>\n\n    <string name=\"navigation_drawer_open\">Open navigation drawer xxh</string>\n    <string name=\"navigation_drawer_close\">Close navigation drawer xxh</string>\n\n    <string name=\"action_settings\">Settings xxh</string>\n</resources>\n"
  },
  {
    "path": "AndResGuard-example/libres/src/test/java/com/tinkerpatch/libres/ExampleUnitTest.java",
    "content": "package com.tinkerpatch.libres;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n  @Test\n  public void addition_isCorrect() throws Exception {\n    assertEquals(4, 2 + 2);\n  }\n}"
  },
  {
    "path": "AndResGuard-example/settings.gradle",
    "content": "include ':app', ':libres', ':app1', ':app2'\n"
  },
  {
    "path": "AndResGuard-gradle-plugin/build.gradle",
    "content": "apply plugin: 'groovy'\n\nversion rootProject.ext.VERSION_NAME\ngroup rootProject.ext.GROUP\n\ndependencies {\n  compile gradleApi()\n  compile localGroovy()\n  //compile group: 'com.tencent.mm', name: 'AndResGuard-core', version: version\n  compile 'com.google.gradle:osdetector-gradle-plugin:1.6.0'\n  compile project(':AndResGuard-core')\n}\n\nrepositories {\n  mavenCentral()\n}\n\napply from: rootProject.file('gradle/java-artifacts.gradle')\napply from: rootProject.file('gradle/gradle-mvn-push.gradle')"
  },
  {
    "path": "AndResGuard-gradle-plugin/gradle.properties",
    "content": "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",
    "content": "package com.tencent.gradle\n\n/**\n * The configuration properties.\n *\n * @author sim sun (sunsj1231@gmail.com)\n */\n\nclass AndResGuardExtension {\n\n  File mappingFile\n  boolean use7zip\n  boolean useSign\n  String metaName\n  String fixedResName\n  boolean keepRoot\n  boolean mergeDuplicatedRes\n  Iterable<String> whiteList\n  Iterable<String> compressFilePattern\n  String finalApkBackupPath\n  String digestalg\n  String sourceApk\n  String sourceBuildType\n  String sourceFlavor\n\n  AndResGuardExtension() {\n    use7zip = false\n    useSign = false\n    metaName = \"META-INF\"\n    fixedResName = null\n    keepRoot = false\n    mergeDuplicatedRes = false\n    whiteList = []\n    compressFilePattern = []\n    mappingFile = null\n    finalApkBackupPath = null\n    digestalg = null\n    sourceApk = null\n    sourceBuildType = null\n    sourceFlavor = null\n  }\n\n  Iterable<String> getCompressFilePattern() {\n    return compressFilePattern\n  }\n\n  File getMappingFile() {\n    return mappingFile\n  }\n\n  boolean getUse7zip() {\n    return use7zip\n  }\n\n  boolean getUseSign() {\n    return useSign\n  }\n\n  String getMetaName() {\n    return metaName\n  }\n\n  String getFixedResName() {\n    return fixedResName\n  }\n\n  boolean getKeepRoot() {\n    return keepRoot\n  }\n\n  boolean getMergeDuplicatedRes() {\n    return mergeDuplicatedRes\n  }\n\n  Iterable<String> getWhiteList() {\n    return whiteList\n  }\n\n  String getFinalApkBackupPath() {\n    return finalApkBackupPath\n  }\n\n  String getDigestalg() {\n    return digestalg\n  }\n\n  String getSourceApk() {\n    return sourceApk\n  }\n\n  String getSourceBuildType() {\n    return sourceBuildType\n  }\n\n  String getSourceFlavor() {\n    return sourceFlavor\n  }\n\n  @Override\n  String toString() {\n    \"\"\"| use7zip = ${use7zip}\n           | useSign = ${useSign}\n           | metaName = ${metaName}\n           | fixedResName = ${fixedResName}\n           | keepRoot = ${keepRoot}\n           | mergeDuplicatedRes = ${mergeDuplicatedRes}\n           | whiteList = ${whiteList}\n           | compressFilePattern = ${compressFilePattern}\n           | finalApkBackupPath = ${finalApkBackupPath}\n           | digstalg = ${digestalg}\n           | sourceApk = ${sourceApk}\n           | sourceBuildType = ${sourceBuildType}\n           | sourceFlavor = ${sourceFlavor}\n        \"\"\".stripMargin()\n  }\n}"
  },
  {
    "path": "AndResGuard-gradle-plugin/src/main/groovy/com/tencent/gradle/AndResGuardPlugin.groovy",
    "content": "package com.tencent.gradle\n\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\n\n/**\n * Registers the plugin's tasks.\n *\n * @author sim sun (sunsj1231@gmail.com)\n */\n\nclass AndResGuardPlugin implements Plugin<Project> {\n\n  public static final String USE_APK_TASK_NAME = \"UseApk\"\n\n  @Override\n  void apply(Project project) {\n    project.apply plugin: 'com.google.osdetector'\n    project.extensions.create('andResGuard', AndResGuardExtension)\n    project.extensions.add(\"sevenzip\", new ExecutorExtension(\"sevenzip\"))\n\n    project.afterEvaluate {\n      def android = project.extensions.android\n      createTask(project, USE_APK_TASK_NAME)\n\n      android.applicationVariants.all { variant ->\n        def variantName = variant.name.capitalize()\n        createTask(project, variantName)\n      }\n\n      android.buildTypes.all { buildType ->\n        def buildTypeName = buildType.name.capitalize()\n        createTask(project, buildTypeName)\n      }\n\n      android.productFlavors.all { flavor ->\n        def flavorName = flavor.name.capitalize()\n        createTask(project, flavorName)\n      }\n\n      project.extensions.findByName(\"sevenzip\").loadArtifact(project)\n    }\n  }\n\n  private static void createTask(Project project, variantName) {\n    def taskName = \"resguard${variantName}\"\n    if (project.tasks.findByPath(taskName) == null) {\n      def task = project.task(taskName, type: AndResGuardTask)\n      if (variantName != USE_APK_TASK_NAME) {\n        task.dependsOn \"assemble${variantName}\"\n      }\n    }\n  }\n}"
  },
  {
    "path": "AndResGuard-gradle-plugin/src/main/groovy/com/tencent/gradle/AndResGuardTask.groovy",
    "content": "package com.tencent.gradle\n\nimport com.tencent.mm.androlib.res.util.StringUtil\nimport com.tencent.mm.directory.PathNotExist\nimport com.tencent.mm.resourceproguard.InputParam\nimport com.tencent.mm.resourceproguard.Main\nimport org.gradle.api.DefaultTask\nimport org.gradle.api.GradleException\nimport org.gradle.api.provider.Property\nimport org.gradle.api.tasks.TaskAction\n\n/**\n * The configuration properties.\n *\n * @author Sim Sun (sunsj1231@gmail.com)\n */\nclass AndResGuardTask extends DefaultTask {\n  AndResGuardExtension configuration\n  def android\n  def buildConfigs = []\n\n  AndResGuardTask() {\n    description = 'Assemble Resource Proguard APK'\n    group = 'andresguard'\n    outputs.upToDateWhen { false }\n    android = project.extensions.android\n    configuration = project.andResGuard\n\n    if (StringUtil.isPresent(configuration.digestalg) && !configuration.digestalg.contains('-')) {\n      throw new RuntimeException(\"Plz add - in your digestalg, such as SHA-1 SHA-256\")\n    }\n\n    android.applicationVariants.all { variant ->\n      variant.outputs.each { output ->\n        // remove \"resguard\"\n        String variantName = this.name[\"resguard\".length()..-1]\n        if (variantName.equalsIgnoreCase(variant.buildType.name as String) || isTargetFlavor(variantName,\n            variant.productFlavors, variant.buildType.name) ||\n            variantName.equalsIgnoreCase(AndResGuardPlugin.USE_APK_TASK_NAME)) {\n\n          def outputFile = null\n          try {\n            if (variant.metaClass.respondsTo(variant, \"getPackageApplicationProvider\")) {\n              outputFile = new File(variant.packageApplicationProvider.get().outputDirectory, output.outputFileName)\n            }\n          } catch (Exception ignore) {\n            // no-op\n          } finally {\n            outputFile = outputFile ?: output.outputFile\n          }\n\n          def variantInfo\n          if (variant.variantData.hasProperty(\"variantConfiguration\")) {\n            variantInfo = variant.variantData.variantConfiguration\n          } else {\n            variantInfo = variant.variantData.variantDslInfo\n          }\n\n          def applicationId = variantInfo.applicationId instanceof Property\n              ? variantInfo.applicationId.get()\n              : variantInfo.applicationId\n\n          buildConfigs << new BuildInfo(\n              outputFile,\n              variantInfo.signingConfig,\n              applicationId,\n              variant.buildType.name,\n              variant.productFlavors,\n              variantName,\n              variant.mergedFlavor.minSdkVersion.apiLevel,\n              variant.mergedFlavor.targetSdkVersion.apiLevel,\n          )\n        }\n      }\n    }\n    if (!project.plugins.hasPlugin('com.android.application')) {\n      throw new GradleException('generateARGApk: Android Application plugin required')\n    }\n  }\n\n  static isTargetFlavor(variantName, flavors, buildType) {\n    if (flavors.size() > 0) {\n      String flavor = flavors.get(0).name\n      return variantName.equalsIgnoreCase(flavor) || variantName.equalsIgnoreCase([flavors.collect { it.name }.join(\"\"), buildType].join(\"\"))\n    }\n    return false\n  }\n\n  static useFolder(file) {\n    //remove .apk from filename\n    def fileName = file.name[0..-5]\n    return \"${file.parent}/AndResGuard_${fileName}/\"\n  }\n\n  def getZipAlignPath() {\n    return \"${android.getSdkDirectory().getAbsolutePath()}/build-tools/${android.buildToolsVersion}/zipalign\"\n  }\n\n  @TaskAction\n  run() {\n    project.logger.info(\"[AndResGuard] configuartion:$configuration\")\n    project.logger.info(\"[AndResGuard] BuildConfigs:$buildConfigs\")\n\n    buildConfigs.each { config ->\n      if (config.taskName == AndResGuardPlugin.USE_APK_TASK_NAME) {\n        if (StringUtil.isBlank(configuration.sourceApk) || !new File(configuration.sourceApk).exists()) {\n          throw new PathNotExist(\"Original APK not existed for \" + AndResGuardPlugin.USE_APK_TASK_NAME)\n        }\n        if (config.flavors.productFlavors.size() > 0 && StringUtil.isBlank(configuration.sourceFlavor)) {\n          throw new RuntimeException(\"Must setup sourceFlavor when flavors exist in build.gradle\")\n        }\n        if (StringUtil.isBlank(configuration.sourceBuildType)) {\n          throw new RuntimeException(\"Must setup sourceBuildType when flavors exist in build.gradle\")\n        }\n        if (config.buildType == configuration.sourceBuildType) {\n          if (StringUtil.isBlank(configuration.sourceFlavor) || (StringUtil.isPresent(configuration.sourceFlavor) &&\n              config.flavors.size() >\n              0 &&\n              config.flavors.get(0).name ==\n              configuration.sourceFlavor)) {\n            RunGradleTask(config, configuration.sourceApk, config.minSDKVersion, config.targetSDKVersion)\n          }\n        }\n      } else {\n        if (config.file == null || !config.file.exists()) {\n          throw new PathNotExist(\"Original APK not existed\")\n        }\n        RunGradleTask(config, config.file.getAbsolutePath(), config.minSDKVersion, config.targetSDKVersion)\n      }\n    }\n  }\n\n  def RunGradleTask(config, String absPath, int minSDKVersion, int targetSDKVersion) {\n    def signConfig = config.signConfig\n    String packageName = config.packageName\n    ArrayList<String> whiteListFullName = new ArrayList<>()\n    ExecutorExtension sevenzip = project.extensions.findByName(\"sevenzip\") as ExecutorExtension\n    configuration.whiteList.each { res ->\n      if (res.startsWith(\"R\")) {\n        whiteListFullName.add(packageName + \".\" + res)\n      } else {\n        whiteListFullName.add(res)\n      }\n    }\n\n    InputParam.Builder builder = new InputParam.Builder()\n        .setMappingFile(configuration.mappingFile)\n        .setWhiteList(whiteListFullName)\n        .setUse7zip(configuration.use7zip)\n        .setMetaName(configuration.metaName)\n        .setFixedResName(configuration.fixedResName)\n        .setKeepRoot(configuration.keepRoot)\n        .setMergeDuplicatedRes(configuration.mergeDuplicatedRes)\n        .setCompressFilePattern(configuration.compressFilePattern)\n        .setZipAlign(getZipAlignPath())\n        .setSevenZipPath(sevenzip.path)\n        .setOutBuilder(useFolder(config.file))\n        .setApkPath(absPath)\n        .setUseSign(configuration.useSign)\n        .setDigestAlg(configuration.digestalg)\n        .setMinSDKVersion(minSDKVersion)\n        .setTargetSDKVersion(targetSDKVersion)\n\n    if (configuration.finalApkBackupPath != null && configuration.finalApkBackupPath.length() > 0) {\n      builder.setFinalApkBackupPath(configuration.finalApkBackupPath)\n    } else {\n      builder.setFinalApkBackupPath(absPath)\n    }\n\n    if (configuration.useSign) {\n      if (signConfig == null) {\n        throw new GradleException(\"can't the get signConfig for release build\")\n      }\n      builder.setSignFile(signConfig.storeFile)\n          .setKeypass(signConfig.keyPassword)\n          .setStorealias(signConfig.keyAlias)\n          .setStorepass(signConfig.storePassword)\n      if (signConfig.hasProperty('v3SigningEnabled') && signConfig.v3SigningEnabled) {\n        builder.setSignatureType(InputParam.SignatureType.SchemaV3)\n      } else if (signConfig.hasProperty('v2SigningEnabled') && signConfig.v2SigningEnabled) {\n        builder.setSignatureType(InputParam.SignatureType.SchemaV2)\n      }\n    }\n    InputParam inputParam = builder.create()\n    Main.gradleRun(inputParam)\n  }\n}"
  },
  {
    "path": "AndResGuard-gradle-plugin/src/main/groovy/com/tencent/gradle/BuildInfo.groovy",
    "content": "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 packageName\n  def buildType\n  def flavors\n  def taskName\n  int minSDKVersion\n  int targetSDKVersion\n\n\n  BuildInfo(file, sign, packageName, buildType, flavors, taskName, minSDKVersion, targetSDKVersion) {\n    this.file = file\n    this.signConfig = sign\n    this.packageName = packageName\n    this.buildType = buildType\n    this.flavors = flavors\n    this.taskName = taskName\n    this.minSDKVersion = minSDKVersion\n    this.targetSDKVersion = targetSDKVersion\n  }\n\n  @Override\n  String toString() {\n    \"\"\"| file = ${file}\n       | packageName = ${packageName}\n       | buildType = ${buildType}\n       | flavors = ${flavors}\n       | taskname = ${taskName}\n       | minSDKVersion = ${minSDKVersion}\n       | targetSDKVersion = ${targetSDKVersion}\n    \"\"\".stripMargin()\n  }\n}\n"
  },
  {
    "path": "AndResGuard-gradle-plugin/src/main/groovy/com/tencent/gradle/ExecutorExtension.groovy",
    "content": "package com.tencent.gradle\n\nimport org.gradle.api.GradleException\nimport org.gradle.api.Named\nimport org.gradle.api.Project\nimport org.gradle.api.artifacts.Configuration\nimport org.gradle.api.artifacts.Dependency\n\nclass ExecutorExtension implements Named {\n\n  private final String name\n\n  private String artifact\n  private String path\n\n  ExecutorExtension(String name) {\n    this.name = name\n  }\n\n  @Override\n  String getName() {\n    return name\n  }\n\n  /**\n   * Specifies an artifact spec for downloading the executable from\n   * repositories. spec format: '<groupId>:<artifactId>:<version>'*/\n  def setArtifact(String spec) {\n    this.artifact = spec\n  }\n\n  /**\n   * Specifies a local path.*/\n  def setPath(String path) {\n    this.path = path\n  }\n\n  String getArtifact() {\n    return artifact\n  }\n\n  String getPath() {\n    return path\n  }\n\n  void loadArtifact(Project project) {\n    if (path == null && artifact != null) {\n      Configuration config = project.configurations.create(\"AndResGuardLocatorSevenZip\") {\n        visible = false\n        transitive = false\n        extendsFrom = []\n      }\n      def groupId, artifactId, version\n\n      (groupId, artifactId, version) = this.artifact.split(\":\")\n      def notation = [group     : groupId,\n                      name      : artifactId,\n                      version   : version,\n                      classifier: project.osdetector.classifier,\n                      ext       : 'exe']\n\n      project.logger.info(\"[AndResGuard]Resolving artifact: ${notation}\")\n      Dependency dep = project.dependencies.add(config.name, notation)\n      File file = config.fileCollection(dep).singleFile\n      if (!file.canExecute() && !file.setExecutable(true)) {\n        throw new GradleException(\"Cannot set ${file} as executable\")\n      }\n      project.logger.info(\"[AndResGuard]Resolved artifact: ${file}\")\n      this.path = file.path\n    }\n  }\n}\n\n"
  },
  {
    "path": "AndResGuard-gradle-plugin/src/main/resources/META-INF/gradle-plugins/AndResGuard.properties",
    "content": "implementation-class=com.tencent.gradle.AndResGuardPlugin"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "README.md",
    "content": "# AndResGuard\n\n[![Build Status](https://travis-ci.org/shwenzhang/AndResGuard.svg?branch=master)](https://travis-ci.org/shwenzhang/AndResGuard)\n[ ![Download](https://api.bintray.com/packages/wemobiledev/maven/com.tencent.mm%3AAndResGuard-core/images/download.svg) ](https://bintray.com/wemobiledev/maven/com.tencent.mm%3AAndResGuard-core/_latestVersion)\n[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-AndResGuard-green.svg?style=true)](https://android-arsenal.com/details/1/3034)\n\n*Read this in other languages: [English](README.md), [简体中文](README.zh-cn.md).*\n\n`AndResGuard` is a tooling for reducing your apk size, it works like the `ProGuard` for Java source code, but only aim at the resource files. It changes `res/drawable/wechat` to `r/d/a`, and renames the resource file `wechat.png` to `a.png`. Finally, it repackages the apk with 7zip, which can reduce the package size obviously.\n\n`AndResGuard` is fast, and it does **NOT** need the source codes. Input an Android apk, then we can get a 'ResGuard' apk in a few seconds.\n\nSome uses of `AndResGuard` are:\n\n1. Obfuscate android resources. It contains all the resource type(such as drawable、layout、string...). It can prevent your apk from being reversed by `Apktool`.\n\n2. Shrinking the apk size. It can reduce the `resources.arsc` and the package size obviously.\n\n3. Repackage with `7zip`. It supports repackage apk with `7zip`, and we can specify the compression method for each file.\n\n`AndResGuard` is a command-line tool, it supports Windows, Linux and Mac. We suggest you to use 7zip in Linux or Mac platform for a higher compression ratio.\n\n## How to use\n### With Gradle\nThis has been released on `Bintray`\n```gradle\napply plugin: 'AndResGuard'\n\nbuildscript {\n    repositories {\n        jcenter()\n        google()\n    }\n    dependencies {\n        classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.21'\n    }\n}\n\nandResGuard {\n    // mappingFile = file(\"./resource_mapping.txt\")\n    mappingFile = null\n    use7zip = true\n    useSign = true\n    // It will keep the origin path of your resources when it's true\n    keepRoot = false\n    // If set, name column in arsc those need to proguard will be kept to this value\n    fixedResName = \"arg\"\n    // It will merge the duplicated resources, but don't rely on this feature too much.\n    // it's always better to remove duplicated resource from repo\n    mergeDuplicatedRes = true\n    whiteList = [\n        // your icon\n        \"R.drawable.icon\",\n        // for fabric\n        \"R.string.com.crashlytics.*\",\n        // for google-services\n        \"R.string.google_app_id\",\n        \"R.string.gcm_defaultSenderId\",\n        \"R.string.default_web_client_id\",\n        \"R.string.ga_trackingId\",\n        \"R.string.firebase_database_url\",\n        \"R.string.google_api_key\",\n        \"R.string.google_crash_reporting_api_key\",\n        \"R.string.project_id\",\n    ]\n    compressFilePattern = [\n        \"*.png\",\n        \"*.jpg\",\n        \"*.jpeg\",\n        \"*.gif\",\n    ]\n    sevenzip {\n        artifact = 'com.tencent.mm:SevenZip:1.2.21'\n        //path = \"/usr/local/bin/7za\"\n    }\n\n    /**\n    * Optional: if finalApkBackupPath is null, AndResGuard will overwrite final apk\n    * to the path which assemble[Task] write to\n    **/\n    // finalApkBackupPath = \"${project.rootDir}/final.apk\"\n\n    /**\n    * Optional: Specifies the name of the message digest algorithm to user when digesting the entries of JAR file\n    * Only works in V1signing, default value is \"SHA-1\"\n    **/\n    // digestalg = \"SHA-256\"\n}\n```\n\n### Wildcard\nThe whiteList and compressFilePattern support wildcard include ? * +.\n\n```\n?\tZero or one character\n*\tZero or more of character\n+\tOne or more of character\n```\n\n### WhiteList\nYou need put all resource which access via `getIdentifier` into whiteList.\n**You can find more whitsList configs of third-part SDK in [white_list.md](doc/white_list.md). Welcome PR your configs which is not included in white_list.md**\n\nThe whiteList only works on the specsName of resources, it wouldn't keep the path of resource.\nIf you wanna keeping the path, please use `mappingFile` to implement it.\n\nFor example, we wanna keeping the path of icon, we need add below into our `mappingFile`.\n```\nres path mapping:\n    res/mipmap-hdpi-v4 -> res/mipmap-hdpi-v4\n    res/mipmap-mdpi-v4 -> res/mipmap-mdpi-v4\n    res/mipmap-xhdpi-v4 -> res/mipmap-xhdpi-v4\n    res/mipmap-xxhdpi-v4 -> res/mipmap-xxhdpi-v4\n    res/mipmap-xxxhdpi-v4 -> res/mipmap-xxxhdpi-v4\n```\n\n### How to Launch\nIf you are using `Android Studio`, you can find the generate task option in ```andresguard``` group.\nOr alternatively, you run ```./gradlew resguard[BuildType | Flavor]``` in your terminal. The format of task name is as same as `assemble`.\n\n### Sevenzip\nThe `sevenzip` in gradle file can be set by `path` or `artifact`. Multiple assignments are allowed, but the winner is **always** `path`.\n\n### Result\nIf finalApkBackupPath is null, AndResGuard will overwrite final APK to the path which assemble[Task] write. Otherwise, it will store in the path you assigned.\n\n### Other\n[Looking for more detail](doc/how_to_work.md)\n\n\n## Known Issue\n1. The first element of list which returned by `AssetManager#list(String path)` is empty string when you're using the APK which is compressed by 7zip. [#162](https://github.com/shwenzhang/AndResGuard/issues/162)\n\n## Best Practise\n1. Do **NOT** add `resources.arsc` into `compressFilePattern` unless the app size is really matter to you.([#84](https://github.com/shwenzhang/AndResGuard/issues/84) [#233](https://github.com/shwenzhang/AndResGuard/issues/233))\n2. Do **NOT** enable 7zip compression(`use7zip`) when you distribute your APP on Google Play. It'll prevent the file-by-file patch when updating your APP. ([#233](https://github.com/shwenzhang/AndResGuard/issues/233))\n\n\n## Thanks\n[Apktool](https://github.com/iBotPeaches/Apktool) Connor Tumbleson\n\n[v2sig](https://github.com/shwenzhang/AndResGuard/pull/133) @jonyChina162\n"
  },
  {
    "path": "README.zh-cn.md",
    "content": "#  Android资源混淆工具使用说明 #\n\n[![Build Status](https://travis-ci.org/shwenzhang/AndResGuard.svg?branch=master)](https://travis-ci.org/shwenzhang/AndResGuard)\n[ ![Download](https://api.bintray.com/packages/wemobiledev/maven/com.tencent.mm%3AAndResGuard-core/images/download.svg) ](https://bintray.com/wemobiledev/maven/com.tencent.mm%3AAndResGuard-core/_latestVersion)\n[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-AndResGuard-green.svg?style=true)](https://android-arsenal.com/details/1/3034)\n\n\n*其他语言版本: [English](README.md), [简体中文](README.zh-cn.md).*\n\n`AndResGuard`是一个帮助你缩小APK大小的工具，他的原理类似Java Proguard，但是只针对资源。他会将原本冗长的资源路径变短，例如将`res/drawable/wechat`变为`r/d/a`。\n\n`AndResGuard`不涉及编译过程，只需输入一个apk(无论签名与否，debug版，release版均可，在处理过程中会直接将原签名删除)，可得到一个实现资源混淆后的apk(若在配置文件中输入签名信息，可自动重签名并对齐，得到可直接发布的apk)以及对应资源ID的mapping文件。\n\n原理介绍：[详见WeMobileDev公众号文章](http://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=208135658&idx=1&sn=ac9bd6b4927e9e82f9fa14e396183a8f#rd)\n\n\n## 使用Gradle\n此工具已发布在Bintray\n```gradle\napply plugin: 'AndResGuard'\n\nbuildscript {\n    repositories {\n        jcenter()\n        google()\n    }\n    dependencies {\n        classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.21'\n    }\n}\n\n\nandResGuard {\n    // mappingFile = file(\"./resource_mapping.txt\")\n    mappingFile = null\n    use7zip = true\n    useSign = true\n    // 打开这个开关，会keep住所有资源的原始路径，只混淆资源的名字\n    keepRoot = false\n    // 设置这个值，会把arsc name列混淆成相同的名字，减少string常量池的大小\n    fixedResName = \"arg\"\n    // 打开这个开关会合并所有哈希值相同的资源，但请不要过度依赖这个功能去除去冗余资源\n    mergeDuplicatedRes = true\n    whiteList = [\n        // for your icon\n        \"R.drawable.icon\",\n        // for fabric\n        \"R.string.com.crashlytics.*\",\n        // for google-services\n        \"R.string.google_app_id\",\n        \"R.string.gcm_defaultSenderId\",\n        \"R.string.default_web_client_id\",\n        \"R.string.ga_trackingId\",\n        \"R.string.firebase_database_url\",\n        \"R.string.google_api_key\",\n        \"R.string.google_crash_reporting_api_key\"\n    ]\n    compressFilePattern = [\n        \"*.png\",\n        \"*.jpg\",\n        \"*.jpeg\",\n        \"*.gif\",\n    ]\n    sevenzip {\n         artifact = 'com.tencent.mm:SevenZip:1.2.21'\n         //path = \"/usr/local/bin/7za\"\n    }\n\n    /**\n    * 可选： 如果不设置则会默认覆盖assemble输出的apk\n    **/\n    // finalApkBackupPath = \"${project.rootDir}/final.apk\"\n\n    /**\n    * 可选: 指定v1签名时生成jar文件的摘要算法\n    * 默认值为“SHA-1”\n    **/\n    // digestalg = \"SHA-256\"\n}\n```\n\n### 文件通配符\n compressFilePattern和compressFilePattern中的通配符支持? + *\n\n```\n?\tZero or one character\n*\tZero or more of character\n+\tOne or more of character\n```\n\n### 白名单\n所有使用`getIdentifier`访问的资源都需要加入白名单。\n\n**请使用Umeng_social_sdk的同学特别留意将资源加入白名单，否则会出现Crash。可以在[white_list.md](doc/white_list.md)查看更多sdk的白名单配置，也欢迎大家PR自己的白名单**\n\n白名单机制只作用于资源的specsName，不会keep住资源的路径。如果想keep住资源原有的物理路径，可以使用`mappingFile`。\n例如我想keep住icon所有folder，可以在mappingFile指向的文件添加：\n\n```\nres path mapping:\n    res/mipmap-hdpi-v4 -> res/mipmap-hdpi-v4\n    res/mipmap-mdpi-v4 -> res/mipmap-mdpi-v4\n    res/mipmap-xhdpi-v4 -> res/mipmap-xhdpi-v4\n    res/mipmap-xxhdpi-v4 -> res/mipmap-xxhdpi-v4\n    res/mipmap-xxxhdpi-v4 -> res/mipmap-xxxhdpi-v4\n```\n\n### 如何启动\n使用Android Studio的同学可以再 `andresguard` 下找到相关的构建任务;\n命令行可直接运行```./gradlew resguard[BuildType | Flavor]```， 这里的任务命令规则和assemble一致。\n\n### 配置7Zip\n在设置`sevenzip`时, 你只需设置`artifact`或`path`. 支持同时设置,总以path的值为优先.\n\n### 结果\n如果没有配置`finalApkBackupPath`，最终结果会覆盖`assemble[BuildType | Flavor]`的输出APK。如果配置则输出至`finalApkBackupPath`配置路径。\n\n### 其他\n[点击查看更多细节和命令行使用方法](doc/how_to_work.zh-cn.md)\n\n## 已知问题\n\n1. 当时在使用7zip压缩的APK时，调用`AssetManager#list(String path)`返回结果的首个元素为空字符串. [#162](https://github.com/shwenzhang/AndResGuard/issues/162)\n\n## 最佳实践\n\n1. 如果不是对APK size有极致的需求，请不要把`resources.arsc`添加进`compressFilePattern`. ([#84](https://github.com/shwenzhang/AndResGuard/issues/84) [#233](https://github.com/shwenzhang/AndResGuard/issues/233))\n2. 对于发布于Google Play的APP，建议不要使用7Zip压缩，因为这个会导致Google Play的优化Patch算法失效. ([#233](https://github.com/shwenzhang/AndResGuard/issues/233))\n\n\n## 致谢\n\n[Apktool](https://github.com/iBotPeaches/Apktool) 使用了Apktool资源解码部分的代码\n\n[v2sig](https://github.com/shwenzhang/AndResGuard/pull/133) @jonyChina162\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nUse this section to tell people about which versions of your project are\ncurrently being supported with security updates.\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 5.1.x   | :white_check_mark: |\n| 5.0.x   | :x:                |\n| 4.0.x   | :white_check_mark: |\n| < 4.0   | :x:                |\n\n## Reporting a Vulnerability\n\nUse this section to tell people how to report a vulnerability.\n\nTell them where to go, how often they can expect to get an update on a\nreported vulnerability, what to expect if the vulnerability is accepted or\ndeclined, etc.\n"
  },
  {
    "path": "SevenZip/build.gradle",
    "content": "//apply plugin: 'com.google.osdetector'\napply plugin: 'maven'\napply plugin: 'maven-publish'\napply plugin: 'java'\n\nversion rootProject.ext.VERSION_NAME\ngroup rootProject.ext.GROUP\n\ntask sourcesJar(type: Jar) {\n  from sourceSets.main.java.srcDirs\n  classifier = 'sources'\n}\n\ntask javadocJar(type: Jar, dependsOn: javadoc) {\n  classifier = 'javadoc'\n  from javadoc.destinationDir\n}\n\nartifacts {\n  archives javadocJar\n  archives sourcesJar\n}\n\npublishing {\n  publications {\n    ResguardPub(MavenPublication) {\n      artifactId POM_ARTIFACT_ID\n      groupId group\n      artifact(\"executable/SevenZip-linux-x86_32.exe\") {\n        classifier \"linux-x86_32\"\n        extension \"exe\"\n      }\n      artifact(\"executable/SevenZip-linux-x86_64.exe\") {\n        classifier \"linux-x86_64\"\n        extension \"exe\"\n      }\n      artifact(\"executable/SevenZip-windows-x86_32.exe\") {\n        classifier \"windows-x86_32\"\n        extension \"exe\"\n      }\n      artifact(\"executable/SevenZip-windows-x86_64.exe\") {\n        classifier \"windows-x86_64\"\n        extension \"exe\"\n      }\n      artifact(\"executable/SevenZip-osx-x86_64.exe\") {\n        classifier \"osx-x86_64\"\n        extension \"exe\"\n      }\n    }\n  }\n}\n\napply from: rootProject.file('gradle/gradle-mvn-push.gradle')\n"
  },
  {
    "path": "SevenZip/gradle.properties",
    "content": "POM_ARTIFACT_ID=SevenZip\nPOM_NAME=Seven Zip\n"
  },
  {
    "path": "appveyol.yml",
    "content": "version: '{build}'\nskip_tags: true\nskip_commits:\n  message: /\\[ci skip\\]/\nclone_depth: 10\nenvironment:\n  TERM: dumb\n  matrix:\n    - JAVA_HOME: C:\\Program Files\\Java\\jdk1.7.0\n    - JAVA_HOME: C:\\Program Files\\Java\\jdk1.8.0\ninstall:\n  # prepend Java entry, remove Ruby entry (C:\\Ruby193\\bin;) from PATH\n  - SET PATH=%JAVA_HOME%\\bin;%PATH:C:\\Ruby193\\bin;=%\n  - echo %PATH%\n  - gradlew.bat --version\nbuild_script:\n  - gradlew.bat -u -i clean assemble\ntest_script:\n  - gradlew.bat -u -i -S check\ncache:\n  - .gradle\n  - C:\\Users\\appveyor\\.gradle\non_failure:\n  - echo Somebody setup us the bomb"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n  repositories {\n    google()\n    jcenter()\n  }\n  dependencies {\n    classpath 'com.android.tools.build:gradle:4.1.2'\n    classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'\n    classpath 'com.ofg:uptodate-gradle-plugin:1.6.2'\n  }\n}\n\nallprojects {\n  repositories {\n    google()\n    jcenter()\n  }\n  tasks.withType(JavaCompile) {\n    sourceCompatibility = rootProject.ext.javaVersion\n    targetCompatibility = rootProject.ext.javaVersion\n  }\n\n  tasks.withType(GroovyCompile) {\n    sourceCompatibility = rootProject.ext.javaVersion\n    targetCompatibility = rootProject.ext.javaVersion\n  }\n}\n\next {\n  javaVersion = JavaVersion.VERSION_1_8\n\n  GROUP = 'com.tencent.mm'\n  VERSION_NAME = \"${ANDRESGUARD_VESSION}\"\n\n  POM_PACKAGING = \"pom\"\n  POM_DESCRIPTION = \"Android Resource Proguard Core Lib\"\n\n  POM_URL = \"https://github.com/shwenzhang/AndResGuard\"\n  POM_SCM_URL = \"https://github.com/shwenzhang/AndResGuard.git\"\n  POM_ISSUE_URL = 'https://github.com/shwenzhang/AndResGuard/issues'\n\n  POM_LICENCE_NAME = \"Apache-2.0\"\n  POM_LICENCE_URL = \" http://www.apache.org/licenses/\"\n  POM_LICENCE_DIST = \"repo\"\n\n  POM_DEVELOPER_ID = \"Tencent Wechat\"\n  POM_DEVELOPER_NAME = \"Tencent Wechat, Inc.\"\n\n  BINTRAY_LICENCE = [\"Apache-2.0\"]\n  BINTRAY_ORGANIZATION = \"wemobiledev\"\n}"
  },
  {
    "path": "doc/how_to_work.md",
    "content": "### 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` folder.**\n\nwe can see the help description, The easiest way is : `java -jar andresguard.jar input.apk`. Then it would try to read the config,xml, and output the results to the directory with the name of input.apk.\n\n- -config,        set the config file yourself, if not, the default path is the running location with name config.xml.\n\n- -out,           set the output directory yourself, if not, the default directory is the running location with name of the input file\n\n- -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.\n\n- -mapping,       set keep mapping property, following by parameters: mapping_file_path. if you set these, the mapping data in the config file will be overlayed.\n\n- -7zip,          set the 7zip path, such as /home/shwenzhang/tools/7za, window will be end of 7za.exe.\n\n> Window:\n> set 7za to environment variables。Address: [http://sparanoid.com/lab/7z/download.html](http://sparanoid.com/lab/7z/download.html)\n>\n> linux: sudo apt-get install p7zip-full\n>\n> mac: brew install p7zip\n\n- -zipalign,      set the zipalign, such as /home/shwenzhang/sdk/tools/zipalign, window will be end of zipalign.exe.\n\n- -repackage,      usually, when we build the channeles apk, it may destroy the 7zip. so you may need to use 7zip to repackage the apk\n\n![](http://i.imgur.com/FssA59a.jpg)\n\n**2.samples**\n\n\tjava -jar resourceproguard.jar input.apk\n\nif you want to special the output path or config file path, you can input:\n\n\tjava -jar resourceproguard.jar input.apk -config yourconfig.xml -out output_directory\n\nif you want to special the sign or mapping data, you can input:\n\n\tjava -jar resourceproguard.jar input.apk -config yourconfig.xml\n\t\t-out output_directory -signature signature_file_path storepass_value\n\t\tkeypass_value storealias_value -mapping mapping_file_path\n\nif you want to special 7za or zipalign path, you can input:\n\n\tjava -jar resourceproguard.jar input.apk\n\t -7zip /shwenzhang/tool/7za  -zipalign /shwenzhang/sdk/tools/zipalign\n\nif you just want to repackage an apk compress with 7z:\n\n\tjava -jar resourceproguard.jar -repackage input.apk -out output_directory\n\t -7zip /shwenzhang/tool/7za  -zipalign /shwenzhang/sdk/tools/zipalign   \n\n### What we get\n\nNormally, we can get the following 7 useful files:\n\n![](http://i.imgur.com/LtzSGC4.png)\n\nDuring the process, we can see the cost time and  the reduce size.\n\n![](http://i.imgur.com/ICDkJCH.png)\n\n\n### How to write config.xml file\n\nThere are five main configurations:property, whitelist, keepmapping, compress, sign。\n\n**1. Property**\n\nCommon properties:\n\n- --sevenzip, whether use 7zip to repackage the signed apk, you must install the 7z command line version first.\n\n- --metaname, the sign data file name in your apk, default must be META-INF.\n\n- --keeproot, if keep root, res/drawable will be kept, it won't be changed to such as r/s.\n\n![](http://i.imgur.com/JfkZ09e.gif)\n\n**2. Whitelist**\n\nWhitelist property is used for keeping the resource you want. Because some resource id you can not proguard, such as throug method getIdentifier.\n\n- --isactive,  whether to use whitelist, you can set false to close it simply.\n\n- --path,  you must write the full package name, such as com.tencent.mm.R.drawable.icon. For some reason, we should keep our icon better, and it support *, ?, such as com.tencent.mm.R.drawable.emoji_* or com.tencent.mm.R.drawable.emoji_?   \n\nWarning:1. donot write the file format name,  such com.tencent.mm.R.drawable.emoji.png；2. * mean .+, a* would not match a；\n\n![](http://i.imgur.com/VZ4fOa2.gif)\n\n**3. Keepmapping**\n\nsometimes if we want to keep the last way of obfuscation, we can use keepmapping mode. It is just like applymapping in ProGuard.\n\n- --isactive, whether to use keepmapping, you can set false to close it simply.\n\n- --path,     the old mapping file, in window use \\, in linux use /, and the default path is the running location.\n\n![](http://i.imgur.com/y2LZRe9.gif)\n\n**4. Compress**\n\nCompress can specify the compression method for each file(Stored or Deflate). Generally, 1. blow 2.3 version, if the source file is larger than 1M, then is can not be compressed; 2, streaming media can not be compressed, such as .wav, .mpg.\n\n- --isactive,  whether to use compress, you can set false to close it simply.\n\n- --path,     you must use / separation, and it support *, ?, such as *.png, *.jpg, res/drawable-hdpi/welcome_?.png.\n\nThe maximum confusion will be:\n\n1. paths:*.png, *.jpg, *.jpeg, *.gif\n\n2. resources.arsc\n\n![](http://i.imgur.com/9lTPiPA.gif)\n\n\n**5. Sign**\n\nif you want to sign the apk, you should input following data, but if you want to use 7zip, you must fill them\n\n- --isactive,   whether to use sign, you can set false to close it simply.\n\n- --path,     the signature file path, in window use \\, in linux use /, and the default path is the running location.\n\n- --storepass, storepass value.\n\n- --keypass,   keypass value.\n\n- --alias,     alias value.\n\n![](http://i.imgur.com/21yO1jY.gif)\n\nWarning: if you use -signature mode。these setting in config.xml will be overlayed.\n\n## FAQ\n\n1. How to use compress flag\nIf you use compess flag with *.png、*.gif、*.jpg，it will help you decrease the size of file `resources.arsc`\nNOTE: If your app support Android2.2 and below, the size of file `resources.arsc` should be below 1M.\n\n2. keepmapping flag impact on the size of increasing package\nkeepmapping will help to keep your coherence in different version\n\n3. packages for different channel\nRepackage will make 7zip invalid，you should repackage all channel apk.\n\n4. wanna get resource with `getIdentifier`\nYou should add these resources to whitelist.\nNOTE: *You should add your icon to whitelist, because of some launchers' special implementation*\n\n5. Use umeng or other sdk\nYou should add umeng resource to whitelist\n```xml\n    <issue id=\"whitelist\" isactive=\"true\">\n        <path value =\"yourpackagename.R.string.umeng*\" />   \n        <path value =\"yourpackagename.R.layout.umeng*\" />\n        <path value =\"yourpackagename.R.drawable.umeng*\" />\n        <path value =\"yourpackagename.R.anim.umeng*\" />\n        <path value =\"yourpackagename.R.color.umeng*\" />\n        <path value =\"yourpackagename.R.style.*UM*\" />\n        <path value =\"yourpackagename.R.style.umeng*\" />\n        <path value =\"yourpackagename.R.id.umeng*\" />\n    </issue>\n```\n\n6. Use fabric\n\nThe Fabric Gradle plugin generates a unique identifier so that our backend can identify your builds. This identifier is used to deobfuscate crashes and distribute the correct versions of your beta test builds. The identifier will be added to your res/values and looks like:\n<string name=\"com.crashlytics.android.build_id\">RANDOM_UUID</string>\n\nThe Fabric will get this value with exact name, so we add it to whitelist.\n\n```\n    \"<your_package_name>.R.string.com.crashlytics.*\"\n```\n"
  },
  {
    "path": "doc/how_to_work.zh-cn.md",
    "content": "\n## 如何使用资源混淆工具 ##\n\n### 使用命令行###\n\n**`tools_output`文件夹有使用命令行工具的简单例子,可以参考**\n\n我们先看看它的help描述，最简单的使用方式是：java -jar andresguard.jar input.apk，此时会读取运行路径中的config.xml文件，并将结果输出到运行路径中的input(输入apk的名称)中。当然你也可以自己定义：\n\n-config,        指定具体config文件的路径；\n\n-out,           指定具体的输出路径；混淆的mapping会在输出文件夹中以resource_mapping_input(输入apk的名称).txt命名。\n\n-signature,     指定签名信息，若在命令行设置会覆盖config.xml中的签名信息，顺序为签名文件路径、storepass、keypass、storealias。\n\n-mapping,       指定旧的mapping文件，保证同一资源文件在不同版本混淆后的名称保持一致。若在命令行设置会覆盖config.xml中的信息。\n\n-7zip,          指定7zip的路径，若已添加到环境变量不需要设置。应是全路径例如linux: /shwenzhang/tool/7za, Window需要加上.exe      结尾。\n\n> window：\n> 对于window应下载命名行版本，若将7za指定到环境变量，即无须设置。地址：[http://sparanoid.com/lab/7z/download.html](http://sparanoid.com/lab/7z/download.html)\n>\n> linux：sudo apt-get install p7zip-full\n>\n> mac：brew install p7zip\n\n-zipalign,      指定zipalign的路径，若已添加到环境变量不需要设置。应是全路径例如linux: /shwenzhang/sdk/tools/zipalign, Window需要加上.exe结尾。\n\n-repackage,     如果想要出渠道包等需求，我们可能希望利用7zip直接重打包安装包。\n\n![](http://i.imgur.com/FssA59a.jpg)\n\n**2.简单用法**\n\n\tjava -jar andresguard.jar input.apk\n\n若想指定配置文件或输出目录：\n\n\tjava -jar andresguard.jar input.apk -config yourconfig.xml -out output_directory\n\n若想指定签名信息或mapping信息：\n\n\tjava -jar andresguard.jar input.apk -config yourconfig.xml\n\t\t-out output_directory -signature signature_file_path storepass_value\n\t\tkeypass_value storealias_value -mapping mapping_file_path\n\n若想指定7zip或zipalign的路径(若已设置环境变量，这两项不需要单独设置)：\n\n\tjava -jar andresguard.jar input.apk\n\t -7zip /shwenzhang/tool/7za  -zipalign /shwenzhang/sdk/tools/zipalign\n\n若想用7zip重打包安装包，同时也可指定output路径，指定7zip或zipalign的路径(此模式其他参数都不支持)：\n\n\tjava -jar andresguard.jar -repackage input.apk -out output_directory\n\t -7zip /shwenzhang/tool/7za  -zipalign /shwenzhang/sdk/tools/zipalign   \n\n\n###使用资源混淆工具会得到什么 ##\n\n正常来说，我们可得到以下output路径得到以下7个有用的文件：(需要把zipalign也加入环境变量)\n\n![](http://i.imgur.com/UDtxKqO.png)\n\n混淆过程中会输出log,主要是可看到耗费时间，以及相对输入apk减少的大小。\n\n![](http://i.imgur.com/ICDkJCH.png)\n\n##  如何写配置文件 ##\n\n配置文件中主要有五大项，即property，whitelist, keepmapping, compress,sign。\n\n\n**1. Property项**\n\nProperty主要设置一些通用属性：\n\n--sevenzip, 是否使用7z重新压缩签名后的apk包(这步一定要放在签名后，不然签名时会破坏效果)，需要我们安装7z命令行，同时加入环境变量中，同时要求输入签名信息(不然不会使用)。\n\n> Window：7z command line version, 即7za(http://www.7-zip.org/download.html)\n>  \n> Linux:  可直接sudo apt-get install p7zip-full。\n>  \n> 注意：效果很好，推荐使用，并且在Linux(Mac的高富帅也可)上。\n\n--metaname, 由于重打包时需要删除签名信息，考虑到这个文件名可能会被改变，所以使用者可手动输入签名信息对应的文件名。默认为META_INF。\n\n--keeproot, 是否将res/drawable混淆成r/s\n\n![](http://i.imgur.com/JfkZ09e.gif)\n\n**2. Whitelist项**\n\nWhitelist主要是用来设置白名单，由于我们代码中某些资源会通过getIdentifier(需要全局搜索所有用法并添加到白名单)或动态加载等方式，我们并不希望混淆这部分的资源ID：\n\n--isactive, 是否打开白名单功能；\n\n--path,     是白名单的项，格式为package_name.R.type.specname,由于一个resources.arsc中可能会有多个包，所以这里要求写全包名。同时支持*，？通配符，例如: com.tencent.mm.R.drawable.emoji_*、com.tencent.mm.R.drawable.emoji_？；    \n\n注意:1.不能写成com.tencent.mm.R.drawable.emoji.png，即带文件后缀名；2. *通配符代表.+,即a*,不能匹配到a；\n\n![](http://i.imgur.com/VZ4fOa2.gif)\n\n**3. Keepmapping项**\n\nKeepmapping主要用来指定旧的mapping文件，为了保持一致性，我们支持输入旧的mapping文件，可保证同一资源文件在不同版本混淆后的名称保持一致。另一方面由于我们需要支持增量下载方式，如果每次改动都导致所有文件名都会更改，这会导致增量文件增大，但测试证明影响并不大(后面有测试数据)。\n\n--isactive, 是否打开keepmapping模式；\n\n--path,     是旧mapping文件的位置，linux用/, window用 \\;\n\n![](http://i.imgur.com/y2LZRe9.gif)\n\n**4. Compress项**\n\nCompress主要用来指定文件重打包时是否压缩指定文件，默认我们重打包时是保持输入apk每个文件的压缩方式(即Stored或者Deflate)。一般来说，1、在2.3版本以下源文件大于1M不能压缩；2、流媒体不能压缩。对于.png、.jpg是可以压缩的，只是AssetManger读取时候的方式不同。\n\n--isactive, 是否打开compress模式；\n\n--path,     是需要被压缩文件的相对路径(相对于apk最顶层的位置)，这里明确一定要使用‘/’作为分隔符，同时支持通配符*，？，例如*.png(压缩所有.png文件)，res/drawable/emjio_?.png，resouces.arsc(压缩    resources.arsc)\n\n注意若想得到最大混淆：\n\n1. 输入四项个path:*.png, *.jpg, *.jpeg, *.gif\n\n2. 若你的resources.arsc原文件小于1M，可加入resourcs.arsc这一项！若不需要支持低版本，直接加入也可。\n\n![](http://i.imgur.com/9lTPiPA.gif)\n\n\n**5. Sign项**\n\nSign主要是对处理后的文件重签名，需要我们输入签名文件位置，密码等信息。若想使用7z功能就一定要填入相关信息。\n\n--isactive,  是否打开签名功能；\n\n--path,      是签名文件的位置，linux用/, window用 \\;\n\n--storepass, 是storepass的数值;\n\n--keypass,   是keypass的数值;\n\n--alias,     是alias的数值；\n\n![](http://i.imgur.com/21yO1jY.gif)\n\n 注意： 若出于保密不想写在config.xml，可用-signature命令行设置模式。config.xml中的签名信息会被命令行覆盖。\n\n## Android资源混淆工具需要注意的问题 ##\n\n1. compress参数对混淆效果的影响\n若指定compess 参数.png、.gif以及*.jpg，resources.arsc会大大减少安装包体积。若要支持Android2.2以及以下版本的设备，resources.arsc需保证压缩前小于1M。\n\n2. 操作系统对7z的影响\n实验证明，linux与mac的7z效果更好\n\n3. keepmapping方式对增量包大小的影响\n影响并不大，但使用keepmapping方式有利于保持所有版本混淆的一致性\n\n4. 渠道包的问题(**建议通过修改zip摘要的方式生产渠道包**)\n在出渠道包的时候，解压重压缩会破坏7zip的效果，通过repackage命令可用7zip重压缩。\n\n5. 若想通过getIdentifier方式获得资源，需要放置白名单中。\n部分手机桌面快捷图标的实现有问题，务必将程序桌面icon加入白名单。\n\n6. 对于一些第三方sdk,例如友盟，可能需要将部分资源添加到白名单中。\n\n```xml\n    <issue id=\"whitelist\" isactive=\"true\">\n        <path value =\"yourpackagename.R.string.umeng*\" />   \n        <path value =\"yourpackagename.R.layout.umeng*\" />\n        <path value =\"yourpackagename.R.drawable.umeng*\" />\n        <path value =\"yourpackagename.R.anim.umeng*\" />\n        <path value =\"yourpackagename.R.color.umeng*\" />\n        <path value =\"yourpackagename.R.style.*UM*\" />\n        <path value =\"yourpackagename.R.style.umeng*\" />\n        <path value =\"yourpackagename.R.id.umeng*\" />\n        <path value =\"yourpackagename.R.string.com.crashlytics.*\" />\n    </issue>\n```\n\n##  Androd资源混淆工具的耗时与效果 ##\n\n**1. 基本的耗时与效果**\n\n以微信的5.4为例，使用组件中的resoureproguard.jar进行资源混淆，具体的性能数据如下：\n\n其中时间指的是从最开始到该步骤完成的时间，而不是每步骤独立时间。\n\n![](http://i.imgur.com/8i62qbJ.jpg)\n\n**2. compres参数(下文有详细描述，是否压缩某些资源)对安装包大小的影响**\n\n若指定compess 参数*.png、*.gif以及*.jpg，resources.arsc对安装包大小影响如下：\n\n![](http://i.imgur.com/4kWgw6o.jpg)\n\n但是resources.arsc如果原文件大于1M，压缩后是不能在系统2.3以下运行的。\n\n**3. 操作系统对7z的影响**\n\n由于7z过程中使用的是极限压缩模式，所以遍历次数会增多(7次)，时间相对会比较长。假设不使用7z在单核的虚拟机中仅需10秒。\n\n同时我们需要注意是由于文件系统不一致，在window上面使用7z生成的安装包会较大，微信在window以及linux下7z的效果如下：\n\n![](http://i.imgur.com/t8SPz4Q.png)\n\n所以最后出包请使用Linux(Mac亦可)，具体原因应该与文件系统有关。\n\n**4. keepmapping方式(下文有详细描述，是否保持旧的mapping)对增量包大小的影响**\n\n我们一般使用bsdiff生成增量包，bsdiff差分的是二进制，利用LCS最长公共序列算法。假设分别使用正序与逆序混淆规则对微信5.4作资源混淆(即它们的混淆方式是完全相反的)。\n\n![](http://i.imgur.com/u7obKDt.png)\n\n事实上，它们的差分是不需要371kb,因为有比较大的文件格式，共同标记部分。\n\n现在我们做另外一个实验，首先对微信5.3.1作资源混淆得到安装包a，然后以keepmapping方式对微信5.4作资源混淆得到安装包b，最后以完全逆序的方式对微信5.4作资源混淆得到安装包c。\n\n分别用安装包b、c对安装包a生成增量文件d,e。比较增量文件d、e的大小，分别如下：\n\n![](http://i.imgur.com/MNY9AHr.png)\n\n\n所以增量文件的大小并不是我们采用keepmapping方式的主要考虑因素，保持混淆的一致性，便于查找问题或是更加重要的考虑。\n\n**5.安装包缩减的原因与影响因素**\n\n总结，安装包大小减少的原因以下四个：\n\n![](http://i.imgur.com/tkC2xr5.png)\n\n相对的，可得到影响效果的因素有以下几个：\n\n![](http://i.imgur.com/VEG9cP6.png)\n"
  },
  {
    "path": "doc/white_list.md",
    "content": "### 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.socialize_*\",\n\"R.layout.*messager*\",\n\"R.layout.tb_*\",\n\"R.color.umeng*\",\n\"R.color.tb_*\",\n\"R.style.*UM*\",\n\"R.style.umeng*\",\n\"R.drawable.umeng*\",\n\"R.drawable.tb_*\",\n\"R.drawable.sina*\",\n\"R.drawable.qq_*\",\n\"R.drawable.tb_*\",\n\"R.id.umeng*\",\n\"R.id.*messager*\",\n\"R.id.progress_bar_parent\",\n\"R.id.socialize_*\",\n\"R.id.webView\"\n```\n### google-services\n```\n\"R.string.google_app_id\",\n\"R.string.gcm_defaultSenderId\",\n\"R.string.default_web_client_id\",\n\"R.string.ga_trackingId\",\n\"R.string.firebase_database_url\",\n\"R.string.google_api_key\",\n\"R.string.google_crash_reporting_api_key\"\n```\n### getui(个推)\n```\n\"R.drawable.push\",\n\"R.drawable.push_small\",\n\"R.layout.getui_notification\"\n```\n\n### JPush(极光推送)\n```\n\"R.drawable.jpush_notification_icon\"\n```\n\n### GrowingIO\n```\n\"R.string.growingio_project_id\",\n\"R.string.growingio_url_scheme\",\n\"R.string.growingio_channel\"\n```\n\n### Firebase\n#### [firStore](https://firebase.google.cn/docs/firestore/)\n```\nR.string.project_id\n```\n\n### Huawei push\n```\n\"R.string.hms_*\",\n\"R.string.connect_server_fail_prompt_toast\",\n\"R.string.getting_message_fail_prompt_toast\",\n\"R.string.no_available_network_prompt_toast\",\n\"R.string.third_app_*\",\n\"R.string.upsdk_*\",\n\"R.style.upsdkDlDialog\",\n\"R.style.AppTheme\",\n\"R.style.AppBaseTheme\",\n\"R.dimen.upsdk_dialog_*\",\n\"R.color.upsdk_*\",\n\"R.layout.upsdk_*\",\n\"R.drawable.upsdk_*\",\n\"R.drawable.hms_*\",\n\"R.layout.hms_*\",\n\"R.id.hms_*\"\n```\n\n### Firebase Crashlytics\n```\n\"R.bool.com.crashlytics.useFirebaseAppId\",\n\"R.string.com.crashlytics.useFirebaseAppId\",\n\"R.string.google_app_id\",\n\"R.bool.com.crashlytics.CollectDeviceIdentifiers\",\n\"R.string.com.crashlytics.CollectDeviceIdentifiers\",\n\"R.bool.com.crashlytics.CollectUserIdentifiers\",\n\"R.string.com.crashlytics.CollectUserIdentifiers\",\n\"R.string.com.crashlytics.ApiEndpoint\",\n\"R.string.io.fabric.android.build_id\",\n\"R.string.com.crashlytics.android.build_id\",\n\"R.bool.com.crashlytics.RequireBuildId\",\n\"R.string.com.crashlytics.RequireBuildId\",\n\"R.bool.com.crashlytics.CollectCustomLogs\",\n\"R.string.com.crashlytics.CollectCustomLogs\",\n\"R.bool.com.crashlytics.Trace\",\n\"R.string.com.crashlytics.Trace\",\n\"R.string.com.crashlytics.CollectCustomKeys\"\n```\n\n### shareSDK\n\n```\n\"R.id.ssdk*\",\n\"R.string.mobcommon*\",\n\"R.string.ssdk*\",\n\"R.string.mobdemo*\",\n\"R.drawable.mobcommon*\",\n\"R.drawable.ssdk*\",\n\"R.layout.mob*\",\n\"R.style.mobcommon*\",\n        \n```\n\n### 穿山甲广告SDK\n\n```\n  \"R.string.tt_*\",\n  \"R.integer.tt_*\",\n  \"R.layout.tt_*\",\n  \"R.drawable.tt_*\",\n  \"R.style.tt_*\",\n  \"R.dimen.tt_*\",\n  \"R.anim.tt_*\",\n  \"R.color.tt_*\",\n  \"R.id.tt_*\"\n        \n```\n\n"
  },
  {
    "path": "gradle/gradle-mvn-push.gradle",
    "content": "apply plugin: 'maven'\napply plugin: 'com.jfrog.bintray'\n\ndef getBintrayUser() {\n  return hasProperty('BINTRAY_USER') ? BINTRAY_USER :\n      readPropertyFromLocalProperties('BINTRAY_USER')\n}\n\ndef getBintrayKey() {\n  return hasProperty('BINTRAY_APIKEY') ? BINTRAY_APIKEY :\n      readPropertyFromLocalProperties('BINTRAY_APIKEY')\n}\n\ndef readPropertyFromLocalProperties(String key) {\n  Properties properties = new Properties()\n  try {\n    properties.load(project.rootProject.file('local.properties').newDataInputStream())\n  } catch (Exception ignore) {\n  }\n  return properties.getProperty(key)\n}\n\nbintray {\n  user = getBintrayUser()\n  key = getBintrayKey()\n  configurations = ['archives']\n  publications = ['ResguardPub']\n\n  pkg {\n    repo = 'maven'\n    userOrg = BINTRAY_ORGANIZATION\n    name = \"${GROUP}:${POM_ARTIFACT_ID}\"\n    desc = POM_DESCRIPTION\n    licenses = BINTRAY_LICENCE\n    vcsUrl = POM_SCM_URL\n    websiteUrl = POM_URL\n    issueTrackerUrl = POM_ISSUE_URL\n    publicDownloadNumbers = true\n    publish = true\n    dryRun = false\n  }\n}\n\n\n\ntask buildAndPublishRepo(dependsOn: ['build', 'uploadArchives']) {\n  doLast {\n    println \"*published to repo: ${project.group}:${project.name}:${project.version}\"\n  }\n}"
  },
  {
    "path": "gradle/java-artifacts.gradle",
    "content": "apply plugin: 'maven-publish'\n\nsourceCompatibility = rootProject.ext.javaVersion\ntargetCompatibility = rootProject.ext.javaVersion\n\ntask sourcesJar(type: Jar) {\n  from sourceSets.main.java.srcDirs\n  classifier = 'sources'\n}\n\ntask javadocJar(type: Jar, dependsOn: javadoc) {\n  classifier = 'javadoc'\n  from javadoc.destinationDir\n}\n\nartifacts {\n  archives javadocJar\n  archives sourcesJar\n}\n\npublishing {\n  publications {\n    ResguardPub(MavenPublication) {\n      from components.java\n      groupId = group\n      artifactId = POM_ARTIFACT_ID\n      version = version\n    }\n  }\n}\n\ntask buildAndPublishLocalMaven(dependsOn: ['build', 'publishResguardPublicationToMavenLocal']) {}\n\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Fri Jan 01 00:19:05 CST 2016\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.10.1-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx10248m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\nANDRESGUARD_VESSION=1.2.21\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"AndResGuard\",\n  \"version\": \"1.0.0\",\n  \"description\": \"[![Join the chat at https://gitter.im/shwenzhang/AndResGuard](https://badges.gitter.im/shwenzhang/AndResGuard.svg)](https://gitter.im/shwenzhang/AndResGuard?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/shwenzhang/AndResGuard.svg?branch=master)](https://travis-ci.org/shwenzhang/AndResGuard) [![Jcenter Status](https://api.bintray.com/packages/simsun/maven/AndResGuard-gradle-plugin/images/download.svg)](https://bintray.com/simsun/maven/AndResGuard-gradle-plugin) [![license](http://img.shields.io/badge/license-apache_2.0-red.svg?style=flat)](https://raw.githubusercontent.com/shwenzhang/AndResGuard/master/LICENSE)\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/shwenzhang/AndResGuard.git\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"bugs\": {\n    \"url\": \"https://github.com/shwenzhang/AndResGuard/issues\"\n  },\n  \"homepage\": \"https://github.com/shwenzhang/AndResGuard#readme\",\n  \"devDependencies\": {\n    \"cz-conventional-changelog\": \"^1.1.6\"\n  },\n  \"config\": {\n    \"commitizen\": {\n      \"path\": \"./node_modules/cz-conventional-changelog\"\n    }\n  }\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':AndResGuard-core', ':AndResGuard-gradle-plugin', ':AndResGuard-cli'\ninclude 'SevenZip'\n"
  },
  {
    "path": "tool_output/build_apk.bat",
    "content": "set jdkpath=D:\\Program Files\\Java\\jdk1.7.0_79\\bin\\java.exe\nset storepath=release.keystore\nset storepass=testres\nset keypass=testres\nset alias=testres\nset zipalign=D:\\soft\\dev\\android\\sdk\\build-tools\\23.0.2\\zipalign.exe\n\"%jdkpath%\" -jar AndResGuard-cli-1.2.15.jar input.apk -config config.xml -out outapk -signature \"%storepath%\" \"%storepass%\" \"%keypass%\" \"%alias%\" -zipalign \"%zipalign%\"\npause\n"
  },
  {
    "path": "tool_output/build_apk.sh",
    "content": "#!/usr/bin/env bash\n\njava -jar AndResGuard-cli-1.2.15.jar input.apk -config config.xml -out outapk -signatureType v2 -signature release.keystore testres testres testres\n"
  },
  {
    "path": "tool_output/config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resproguard>\n  <!--defaut property to set  -->\n  <issue id=\"property\">\n    <!--whether use 7zip to repackage the signed apk, you must install the 7z command line version in window -->\n    <!--sudo apt-get install p7zip-full in linux -->\n    <!--and you must write the sign data fist, and i found that if we use linux, we can get a better result -->\n    <seventzip value=\"true\"/>\n    <!--the sign data file name in your apk, default must be META-INF-->\n    <!--generally, you do not need to change it if you dont change the meta file name in your apk-->\n    <metaname value=\"META-INF\"/>\n    <!--if keep root, res/drawable will be kept, it won't be changed to such as r/s-->\n    <keeproot value=\"false\"/>\n\t<!--filter duplicate resource files-->\n\t<mergeDuplicatedRes value=\"true\"/>\n  </issue>\n\n  <!--whitelist, some resource id you can not proguard, such as getIdentifier-->\n  <!--isactive, whether to use whitelist, you can set false to close it simply-->\n  <issue id=\"whitelist\" isactive=\"true\">\n    <!--you must write the full package name, such as com.tencent.mm.R -->\n    <!--for some reason, we should keep our icon better-->\n    <!--and it support *, ?, such as com.tencent.mm.R.drawable.emoji_*, com.tencent.mm.R.drawable.emoji_?-->\n    <!--<path value=\"<your_package_name>.R.drawable.icon\"/>-->\n    <!--<path value=\"<your_package_name>.R.string.com.crashlytics.*\"/>-->\n    <!--<path value=\"<your_package_name>.R.string.umeng*\"/>-->\n    <!--<path value=\"<your_package_name>.R.layout.umeng*\"/>-->\n    <!--<path value=\"<your_package_name>.R.drawable.umeng*\"/>-->\n    <!--<path value=\"<your_package_name>.R.anim.umeng*\"/>-->\n    <!--<path value=\"<your_package_name>.R.color.umeng*\"/>-->\n    <!--<path value=\"<your_package_name>.R.style.*UM*\"/>-->\n    <!--<path value=\"<your_package_name>.R.style.umeng*\"/>-->\n    <!--<path value=\"<your_package_name>.R.id.umeng*\"/>-->\n    <!--<path value=\"<your_package_name>.R.string.UM*\"/>-->\n    <!--<path value=\"<your_package_name>.R.string.tb_*\"/>-->\n    <!--<path value=\"<your_package_name>.R.layout.tb_*\"/>-->\n    <!--<path value=\"<your_package_name>.R.drawable.tb_*\"/>-->\n    <!--<path value=\"<your_package_name>.R.color.tb_*\"/>-->\n  </issue>\n\n  <!--keepmapping, sometimes if we need to support incremental upgrade, we should keep the old mapping-->\n  <!--isactive, whether to use keepmapping, you can set false to close it simply-->\n  <!--if you use -mapping to set keepmapping property in cammand line, these setting will be overlayed-->\n  <issue id=\"keepmapping\" isactive=\"false\">\n    <!--the old mapping path, in window use \\, in linux use /, and the default path is the running location-->\n    <path value=\"{your_mapping_path}\"/>\n  </issue>\n\n  <!--compress, if you want to compress the file, the name is relative path, such as resources.arsc, res/drawable-hdpi/welcome.png-->\n  <!--what can you compress? generally, if your resources.arsc less than 1m, you can compress it. and i think compress .png, .jpg is ok-->\n  <!--isactive, whether to use compress, you can set false to close it simply-->\n  <issue id=\"compress\" isactive=\"false\">\n    <!--you must use / separation, and it support *, ?, such as *.png, *.jpg, res/drawable-hdpi/welcome_?.png-->\n    <path value=\"*.png\"/>\n    <path value=\"*.jpg\"/>\n    <path value=\"*.jpeg\"/>\n    <path value=\"*.gif\"/>\n    <path value=\"resources.arsc\"/>\n  </issue>\n\n  <!--sign, if you want to sign the apk, and if you want to use 7zip, you must fill in the following data-->\n  <!--isactive, whether to use sign, you can set false to close it simply-->\n  <!--if you use -signature to set sign property in cammand line, these setting will be overlayed-->\n  <issue id=\"sign\" isactive=\"true\">\n    <!--the signature file path, in window use \\, in linux use /, and the default path is the running location-->\n    <path value=\"release.keystore\"/>\n    <!--storepass-->\n    <storepass value=\"testres\"/>\n    <!--keypass-->\n    <keypass value=\"testres\"/>\n    <!--alias-->\n    <alias value=\"testres\"/>\n  </issue>\n\n</resproguard>\n"
  }
]