174c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com/* 200fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * [The "BSD licence"] 300fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * Copyright (c) 2010 Ben Gruver (JesusFreke) 400fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * All rights reserved. 574c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com * 600fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * Redistribution and use in source and binary forms, with or without 700fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * modification, are permitted provided that the following conditions 800fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * are met: 900fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * 1. Redistributions of source code must retain the above copyright 1000fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * notice, this list of conditions and the following disclaimer. 1100fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * 2. Redistributions in binary form must reproduce the above copyright 1200fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * notice, this list of conditions and the following disclaimer in the 1300fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * documentation and/or other materials provided with the distribution. 1400fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * 3. The name of the author may not be used to endorse or promote products 1500fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * derived from this software without specific prior written permission. 1674c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com * 1700fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 1800fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 1900fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 2000fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 2100fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 2200fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 2300fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 2400fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2500fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 2600fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2774c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com */ 2874c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com 29b09ba849b9ef031ed7b04c90b706bd5cd3b4c748JesusFreke@JesusFreke.compackage org.jf.smali; 3074c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com 314c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruverimport com.google.common.collect.Lists; 326c15046f2b744978bb3b03a0697d7865d132fe6eBen Gruverimport org.antlr.runtime.CommonTokenStream; 336c15046f2b744978bb3b03a0697d7865d132fe6eBen Gruverimport org.antlr.runtime.Token; 346c15046f2b744978bb3b03a0697d7865d132fe6eBen Gruverimport org.antlr.runtime.TokenSource; 352cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.comimport org.antlr.runtime.tree.CommonTree; 362cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.comimport org.antlr.runtime.tree.CommonTreeNodeStream; 376b3647e984d4a2b54bee5f5588c36f70a933613bJesusFreke@JesusFreke.comimport org.apache.commons.cli.*; 3842627b850c8f68a594f105e04b97c512b292b698Ben Gruverimport org.jf.dexlib2.writer.builder.DexBuilder; 3999b46173c5294d186ccf2e647b86346a22b247c8Ben Gruverimport org.jf.dexlib2.writer.io.FileDataStore; 406b3647e984d4a2b54bee5f5588c36f70a933613bJesusFreke@JesusFreke.comimport org.jf.util.ConsoleUtil; 4136c2f08ba064bce2a447f9280bcf80177cb4b9b9Ben Gruverimport org.jf.util.SmaliHelpFormatter; 422cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 43894327c7ed6c4ffc3a7b9fe61849a878b9a1e8cdBen Gruverimport javax.annotation.Nonnull; 44a53706985479f8e0a1019fdbd3731ed6063a1627JesusFreke@JesusFreke.comimport java.io.*; 454c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruverimport java.util.*; 464c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruverimport java.util.concurrent.Callable; 474c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruverimport java.util.concurrent.ExecutorService; 484c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruverimport java.util.concurrent.Executors; 494c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruverimport java.util.concurrent.Future; 502cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 5174c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com/** 5274c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com * Main class for smali. It recognizes enough options to be able to dispatch 5374c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com * to the right "actual" main. 5474c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com */ 5574c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.compublic class main { 5674c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com 57a53706985479f8e0a1019fdbd3731ed6063a1627JesusFreke@JesusFreke.com public static final String VERSION; 5874c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com 596d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com private final static Options basicOptions; 606d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com private final static Options debugOptions; 612cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com private final static Options options; 6274c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com 632cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com static { 646d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com basicOptions = new Options(); 656d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com debugOptions = new Options(); 662cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com options = new Options(); 672cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com buildOptions(); 68a53706985479f8e0a1019fdbd3731ed6063a1627JesusFreke@JesusFreke.com 69a53706985479f8e0a1019fdbd3731ed6063a1627JesusFreke@JesusFreke.com InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties"); 7049fa5f5f4438000c1a174ae88d394069bb46f826Ben Gruver if (templateStream != null) { 7149fa5f5f4438000c1a174ae88d394069bb46f826Ben Gruver Properties properties = new Properties(); 7249fa5f5f4438000c1a174ae88d394069bb46f826Ben Gruver String version = "(unknown)"; 7349fa5f5f4438000c1a174ae88d394069bb46f826Ben Gruver try { 7449fa5f5f4438000c1a174ae88d394069bb46f826Ben Gruver properties.load(templateStream); 7549fa5f5f4438000c1a174ae88d394069bb46f826Ben Gruver version = properties.getProperty("application.version"); 7649fa5f5f4438000c1a174ae88d394069bb46f826Ben Gruver } catch (IOException ex) { 7749fa5f5f4438000c1a174ae88d394069bb46f826Ben Gruver // just eat it 7849fa5f5f4438000c1a174ae88d394069bb46f826Ben Gruver } 7949fa5f5f4438000c1a174ae88d394069bb46f826Ben Gruver VERSION = version; 8049fa5f5f4438000c1a174ae88d394069bb46f826Ben Gruver } else { 8149fa5f5f4438000c1a174ae88d394069bb46f826Ben Gruver VERSION = "[unknown version]"; 82a53706985479f8e0a1019fdbd3731ed6063a1627JesusFreke@JesusFreke.com } 832cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com } 842cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 855867263eb588f4671400895d1e6b01c01535061bJesusFreke@JesusFreke.com 8674c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com /** 8774c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com * This class is uninstantiable. 8874c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com */ 8974c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com private main() { 9074c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com } 9174c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com 9274c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com /** 9374c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com * Run! 9474c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com */ 9574c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com public static void main(String[] args) { 967bb622e0b16201d0a50e4bdb1f133cbd6477a19aBen Gruver Locale locale = new Locale("en", "US"); 977bb622e0b16201d0a50e4bdb1f133cbd6477a19aBen Gruver Locale.setDefault(locale); 987bb622e0b16201d0a50e4bdb1f133cbd6477a19aBen Gruver 992cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com CommandLineParser parser = new PosixParser(); 1002cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com CommandLine commandLine; 10174c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com 10274c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com try { 1032cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com commandLine = parser.parse(options, args); 1042cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com } catch (ParseException ex) { 1052cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com usage(); 1062cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com return; 1072cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com } 1082cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 1094c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver int jobs = -1; 11094e5a39ad28f997442131a5aa3f574dfa80890a0Ben Gruver boolean allowOdex = false; 11186df593d85576290a631907f19fcab2656467bdfJesusFreke@JesusFreke.com boolean verboseErrors = false; 1125922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com boolean printTokens = false; 1132cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 11483f77f51aa888998486c0c9ad693047480b060b0Ben Gruver int apiLevel = 15; 115c2f08d5123c7cdbba3f449b07c46742ab3d7cacbBen Gruver 1162cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com String outputDexFile = "out.dex"; 1172cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 1182cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com String[] remainingArgs = commandLine.getArgs(); 1192cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 1206d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com Option[] options = commandLine.getOptions(); 1216d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com 1226d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com for (int i=0; i<options.length; i++) { 1236d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com Option option = options[i]; 1246d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com String opt = option.getOpt(); 1256d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com 1266d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com switch (opt.charAt(0)) { 1276d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com case 'v': 1286d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com version(); 1296d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com return; 1306d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com case '?': 1316d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com while (++i < options.length) { 1326d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com if (options[i].getOpt().charAt(0) == '?') { 1336d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com usage(true); 1346d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com return; 1356d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com } 1366d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com } 1376d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com usage(false); 1386d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com return; 1396d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com case 'o': 1406d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com outputDexFile = commandLine.getOptionValue("o"); 1416d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com break; 14294e5a39ad28f997442131a5aa3f574dfa80890a0Ben Gruver case 'x': 14394e5a39ad28f997442131a5aa3f574dfa80890a0Ben Gruver allowOdex = true; 14494e5a39ad28f997442131a5aa3f574dfa80890a0Ben Gruver break; 145c2f08d5123c7cdbba3f449b07c46742ab3d7cacbBen Gruver case 'a': 146c2f08d5123c7cdbba3f449b07c46742ab3d7cacbBen Gruver apiLevel = Integer.parseInt(commandLine.getOptionValue("a")); 147c2f08d5123c7cdbba3f449b07c46742ab3d7cacbBen Gruver break; 1484c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver case 'j': 1494c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver jobs = Integer.parseInt(commandLine.getOptionValue("j")); 1504c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver break; 15186df593d85576290a631907f19fcab2656467bdfJesusFreke@JesusFreke.com case 'V': 15286df593d85576290a631907f19fcab2656467bdfJesusFreke@JesusFreke.com verboseErrors = true; 15386df593d85576290a631907f19fcab2656467bdfJesusFreke@JesusFreke.com break; 1545922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com case 'T': 1555922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com printTokens = true; 1565922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com break; 1576d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com default: 1586d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com assert false; 1596d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com } 1602cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com } 1612cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 1622cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com if (remainingArgs.length == 0) { 1632cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com usage(); 1642cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com return; 1652cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com } 1662cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 1672cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com try { 1682cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>(); 1692cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 1702cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com for (String arg: remainingArgs) { 1712cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com File argFile = new File(arg); 1722cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 1732cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com if (!argFile.exists()) { 1742cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com throw new RuntimeException("Cannot find file or directory \"" + arg + "\""); 1752cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com } 1762cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 1772cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com if (argFile.isDirectory()) { 1782cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com getSmaliFilesInDir(argFile, filesToProcess); 1792cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com } else if (argFile.isFile()) { 1802cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com filesToProcess.add(argFile); 1812cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com } 1822cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com } 1832cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 1844c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver if (jobs <= 0) { 1854c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver jobs = Runtime.getRuntime().availableProcessors(); 1864c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver if (jobs > 6) { 1874c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver jobs = 6; 1884c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver } 1894c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver } 1904c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver 1912cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com boolean errors = false; 1922cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 193df8e4802115fadd6bb67ba405aba2db885abbd55Ben Gruver final DexBuilder dexBuilder = DexBuilder.makeDexBuilder(apiLevel); 1944c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver ExecutorService executor = Executors.newFixedThreadPool(jobs); 1954c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver List<Future<Boolean>> tasks = Lists.newArrayList(); 1964c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver 1974c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver final boolean finalVerboseErrors = verboseErrors; 1984c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver final boolean finalPrintTokens = printTokens; 1994c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver final boolean finalAllowOdex = allowOdex; 2004c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver final int finalApiLevel = apiLevel; 2014c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver for (final File file: filesToProcess) { 2024c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver tasks.add(executor.submit(new Callable<Boolean>() { 2034c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver @Override public Boolean call() throws Exception { 2044c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver return assembleSmaliFile(file, dexBuilder, finalVerboseErrors, finalPrintTokens, 2054c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver finalAllowOdex, finalApiLevel); 2064c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver } 2074c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver })); 2084c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver } 20942627b850c8f68a594f105e04b97c512b292b698Ben Gruver 2104c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver for (Future<Boolean> task: tasks) { 2114c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver while(true) { 2124c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver try { 2134c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver if (!task.get()) { 2144c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver errors = true; 2154c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver } 2164c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver } catch (InterruptedException ex) { 2174c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver continue; 2184c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver } 2194c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver break; 22074c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com } 22174c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com } 2222cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 2234c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver executor.shutdown(); 2244c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver 2252cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com if (errors) { 2262cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com System.exit(1); 2272cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com } 2282cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 22999b46173c5294d186ccf2e647b86346a22b247c8Ben Gruver dexBuilder.writeTo(new FileDataStore(new File(outputDexFile))); 23074c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com } catch (RuntimeException ex) { 23174c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); 23274c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com ex.printStackTrace(); 23374c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com System.exit(2); 23474c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com } catch (Throwable ex) { 23574c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:"); 23674c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com ex.printStackTrace(); 23774c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com System.exit(3); 23874c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com } 2392cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com } 24074c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com 241894327c7ed6c4ffc3a7b9fe61849a878b9a1e8cdBen Gruver private static void getSmaliFilesInDir(@Nonnull File dir, @Nonnull Set<File> smaliFiles) { 242894327c7ed6c4ffc3a7b9fe61849a878b9a1e8cdBen Gruver File[] files = dir.listFiles(); 243894327c7ed6c4ffc3a7b9fe61849a878b9a1e8cdBen Gruver if (files != null) { 244894327c7ed6c4ffc3a7b9fe61849a878b9a1e8cdBen Gruver for(File file: files) { 24599b46173c5294d186ccf2e647b86346a22b247c8Ben Gruver if (file.isDirectory()) { 24699b46173c5294d186ccf2e647b86346a22b247c8Ben Gruver getSmaliFilesInDir(file, smaliFiles); 24799b46173c5294d186ccf2e647b86346a22b247c8Ben Gruver } else if (file.getName().endsWith(".smali")) { 24899b46173c5294d186ccf2e647b86346a22b247c8Ben Gruver smaliFiles.add(file); 24999b46173c5294d186ccf2e647b86346a22b247c8Ben Gruver } 2502cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com } 25174c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com } 25242627b850c8f68a594f105e04b97c512b292b698Ben Gruver } 253311ee79fab06269ea6d9bd31ec1854a6ad036b23JesusFreke@JesusFreke.com 25442627b850c8f68a594f105e04b97c512b292b698Ben Gruver private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, boolean verboseErrors, 2559c67b1508e5318cb0bc6bef64bddfde592ddbe8dBen Gruver boolean printTokens, boolean allowOdex, int apiLevel) 2562cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com throws Exception { 2574c872e9a7cc5f389449c35f6aea49c8e4ed632d4JesusFreke@JesusFreke.com CommonTokenStream tokens; 2584c872e9a7cc5f389449c35f6aea49c8e4ed632d4JesusFreke@JesusFreke.com 25952ff693c8f257a2cc3f5f568e7c7e706802b2074JesusFreke@JesusFreke.com LexerErrorInterface lexer; 26052ff693c8f257a2cc3f5f568e7c7e706802b2074JesusFreke@JesusFreke.com 26148d5b730272ada20e5b8c0245d297b03dbbf6d02Ben Gruver FileInputStream fis = new FileInputStream(smaliFile.getAbsolutePath()); 26248d5b730272ada20e5b8c0245d297b03dbbf6d02Ben Gruver InputStreamReader reader = new InputStreamReader(fis, "UTF-8"); 2634c872e9a7cc5f389449c35f6aea49c8e4ed632d4JesusFreke@JesusFreke.com 26448d5b730272ada20e5b8c0245d297b03dbbf6d02Ben Gruver lexer = new smaliFlexLexer(reader); 26548d5b730272ada20e5b8c0245d297b03dbbf6d02Ben Gruver ((smaliFlexLexer)lexer).setSourceFile(smaliFile); 26648d5b730272ada20e5b8c0245d297b03dbbf6d02Ben Gruver tokens = new CommonTokenStream((TokenSource)lexer); 2672cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 2685922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com if (printTokens) { 2695922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com tokens.getTokens(); 2705922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com 2715922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com for (int i=0; i<tokens.size(); i++) { 2725922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com Token token = tokens.get(i); 27348d5b730272ada20e5b8c0245d297b03dbbf6d02Ben Gruver if (token.getChannel() == smaliParser.HIDDEN) { 2745922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com continue; 2755922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com } 2765922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com 2775922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText()); 2785922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com } 2795922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com } 2805922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com 2812cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com smaliParser parser = new smaliParser(tokens); 28286df593d85576290a631907f19fcab2656467bdfJesusFreke@JesusFreke.com parser.setVerboseErrors(verboseErrors); 28394e5a39ad28f997442131a5aa3f574dfa80890a0Ben Gruver parser.setAllowOdex(allowOdex); 2849c67b1508e5318cb0bc6bef64bddfde592ddbe8dBen Gruver parser.setApiLevel(apiLevel); 2852cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 2862cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com smaliParser.smali_file_return result = parser.smali_file(); 2872cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 28852ff693c8f257a2cc3f5f568e7c7e706802b2074JesusFreke@JesusFreke.com if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) { 2892cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com return false; 2902cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com } 2912cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 292894327c7ed6c4ffc3a7b9fe61849a878b9a1e8cdBen Gruver CommonTree t = result.getTree(); 2932cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 2942cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t); 2952cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com treeStream.setTokenStream(tokens); 2962cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 2972cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); 298d317a0679d983819f91994b19828e4072918ac4cBen Gruver dexGen.setVerboseErrors(verboseErrors); 29942627b850c8f68a594f105e04b97c512b292b698Ben Gruver dexGen.setDexBuilder(dexBuilder); 30042627b850c8f68a594f105e04b97c512b292b698Ben Gruver dexGen.smali_file(); 3012cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 302894327c7ed6c4ffc3a7b9fe61849a878b9a1e8cdBen Gruver return dexGen.getNumberOfSyntaxErrors() == 0; 30374c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com } 30474c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com 3052cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 30674c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com /** 30774c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com * Prints the usage message. 30874c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com */ 3096d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com private static void usage(boolean printDebugOptions) { 31036c2f08ba064bce2a447f9280bcf80177cb4b9b9Ben Gruver SmaliHelpFormatter formatter = new SmaliHelpFormatter(); 3116d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com 31236c2f08ba064bce2a447f9280bcf80177cb4b9b9Ben Gruver int consoleWidth = ConsoleUtil.getConsoleWidth(); 31336c2f08ba064bce2a447f9280bcf80177cb4b9b9Ben Gruver if (consoleWidth <= 0) { 31436c2f08ba064bce2a447f9280bcf80177cb4b9b9Ben Gruver consoleWidth = 80; 31536c2f08ba064bce2a447f9280bcf80177cb4b9b9Ben Gruver } 3166d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com 31736c2f08ba064bce2a447f9280bcf80177cb4b9b9Ben Gruver formatter.setWidth(consoleWidth); 3186d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com 31936c2f08ba064bce2a447f9280bcf80177cb4b9b9Ben Gruver formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*", 32036c2f08ba064bce2a447f9280bcf80177cb4b9b9Ben Gruver "assembles a set of smali files into a dex file", basicOptions, printDebugOptions?debugOptions:null); 3216d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com } 3226d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com 3236d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com private static void usage() { 3246d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com usage(false); 32574c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com } 32674c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com 32774c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com /** 3282cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com * Prints the version message. 32974c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com */ 3302cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com private static void version() { 3312cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com System.out.println("smali " + VERSION + " (http://smali.googlecode.com)"); 33200fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)"); 3332cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)"); 3342cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com System.exit(0); 3352cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com } 3362cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 337dd77ba20f44e0c40a603a28bd0a9bcb0b6f43dfcBen Gruver @SuppressWarnings("AccessStaticViaInstance") 3382cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com private static void buildOptions() { 3392cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com Option versionOption = OptionBuilder.withLongOpt("version") 3402cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com .withDescription("prints the version then exits") 3412cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com .create("v"); 3422cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 3432cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com Option helpOption = OptionBuilder.withLongOpt("help") 3446d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com .withDescription("prints the help message then exits. Specify twice for debug options") 3452cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com .create("?"); 3462cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 3472cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com Option outputOption = OptionBuilder.withLongOpt("output") 348613d7e6f69d2e1e6cdcb448795eb3e35b8014836JesusFreke@JesusFreke.com .withDescription("the name of the dex file that will be written. The default is out.dex") 3492cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com .hasArg() 3502cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com .withArgName("FILE") 3512cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com .create("o"); 3522cd924697658c4f5196365bc9efe98243b7e279dJesusFreke@JesusFreke.com 35394e5a39ad28f997442131a5aa3f574dfa80890a0Ben Gruver Option allowOdexOption = OptionBuilder.withLongOpt("allow-odex-instructions") 3544ba8cebf012c7b3f67d99be22283141d4cdd2216Ben Gruver .withDescription("allow odex instructions to be compiled into the dex file. Only a few" + 3554ba8cebf012c7b3f67d99be22283141d4cdd2216Ben Gruver " instructions are supported - the ones that can exist in a dead code path and not" + 3564ba8cebf012c7b3f67d99be22283141d4cdd2216Ben Gruver " cause dalvik to reject the class") 35794e5a39ad28f997442131a5aa3f574dfa80890a0Ben Gruver .create("x"); 35894e5a39ad28f997442131a5aa3f574dfa80890a0Ben Gruver 359c2f08d5123c7cdbba3f449b07c46742ab3d7cacbBen Gruver Option apiLevelOption = OptionBuilder.withLongOpt("api-level") 360c2f08d5123c7cdbba3f449b07c46742ab3d7cacbBen Gruver .withDescription("The numeric api-level of the file to generate, e.g. 14 for ICS. If not " + 36183f77f51aa888998486c0c9ad693047480b060b0Ben Gruver "specified, it defaults to 15 (ICS).") 362c2f08d5123c7cdbba3f449b07c46742ab3d7cacbBen Gruver .hasArg() 363c2f08d5123c7cdbba3f449b07c46742ab3d7cacbBen Gruver .withArgName("API_LEVEL") 364c2f08d5123c7cdbba3f449b07c46742ab3d7cacbBen Gruver .create("a"); 365c2f08d5123c7cdbba3f449b07c46742ab3d7cacbBen Gruver 3664c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver Option jobsOption = OptionBuilder.withLongOpt("jobs") 3674c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver .withDescription("The number of threads to use. Defaults to the number of cores available, up to a " + 3684c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver "maximum of 6") 3694c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver .hasArg() 3704c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver .withArgName("NUM_THREADS") 3714c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver .create("j"); 3724c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver 37386df593d85576290a631907f19fcab2656467bdfJesusFreke@JesusFreke.com Option verboseErrorsOption = OptionBuilder.withLongOpt("verbose-errors") 37486df593d85576290a631907f19fcab2656467bdfJesusFreke@JesusFreke.com .withDescription("Generate verbose error messages") 37586df593d85576290a631907f19fcab2656467bdfJesusFreke@JesusFreke.com .create("V"); 37686df593d85576290a631907f19fcab2656467bdfJesusFreke@JesusFreke.com 3775922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com Option printTokensOption = OptionBuilder.withLongOpt("print-tokens") 3785922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com .withDescription("Print the name and text of each token") 3795922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com .create("T"); 3804c872e9a7cc5f389449c35f6aea49c8e4ed632d4JesusFreke@JesusFreke.com 3816d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com basicOptions.addOption(versionOption); 3826d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com basicOptions.addOption(helpOption); 3836d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com basicOptions.addOption(outputOption); 38494e5a39ad28f997442131a5aa3f574dfa80890a0Ben Gruver basicOptions.addOption(allowOdexOption); 385c2f08d5123c7cdbba3f449b07c46742ab3d7cacbBen Gruver basicOptions.addOption(apiLevelOption); 3864c431a7ce774616dadb6ff14c064a954f4a1ef6fBen Gruver basicOptions.addOption(jobsOption); 3876d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com 38886df593d85576290a631907f19fcab2656467bdfJesusFreke@JesusFreke.com debugOptions.addOption(verboseErrorsOption); 3895922b466bf349c4214b5348ee706127a19e440feJesusFreke@JesusFreke.com debugOptions.addOption(printTokensOption); 3906d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com 3916d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com for (Object option: basicOptions.getOptions()) { 3926d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com options.addOption((Option)option); 3936d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com } 3946d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com 3956d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com for (Object option: debugOptions.getOptions()) { 3966d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com options.addOption((Option)option); 3976d8f7768380b6d08a255d0893e28c6019d9ff7ccJesusFreke@JesusFreke.com } 39874c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com } 39974c7bd560087b4a929bda47592fbb8dcc4ba84f6JesusFreke@JesusFreke.com}