diff --git a/smali/pom.xml b/smali/pom.xml
index 6073a0b3..63fe1ad5 100644
--- a/smali/pom.xml
+++ b/smali/pom.xml
@@ -78,5 +78,10 @@
dexlib
0.91-SNAPSHOT
+
+ commons-cli
+ commons-cli
+ 1.2
+
\ No newline at end of file
diff --git a/smali/src/main/java/org/jf/smali/UsageException.java b/smali/src/main/java/org/jf/smali/UsageException.java
deleted file mode 100644
index 90149c78..00000000
--- a/smali/src/main/java/org/jf/smali/UsageException.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jf.smali;
-
-/**
- * Simple exception class used to communicate that the command-line tool
- * should print the usage message.
- */
-public class UsageException extends RuntimeException {
- // This space intentionally left blank.
-}
diff --git a/smali/src/main/java/org/jf/smali/main.java b/smali/src/main/java/org/jf/smali/main.java
index 166803bb..8302d368 100644
--- a/smali/src/main/java/org/jf/smali/main.java
+++ b/smali/src/main/java/org/jf/smali/main.java
@@ -16,71 +16,158 @@
package org.jf.smali;
+import org.apache.commons.cli.*;
+import org.jf.dexlib.DexFile;
+import org.jf.dexlib.Util.ByteArrayAnnotatedOutput;
+import org.antlr.runtime.ANTLRInputStream;
+import org.antlr.runtime.CommonTokenStream;
+import org.antlr.runtime.tree.CommonTree;
+import org.antlr.runtime.tree.CommonTreeNodeStream;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.FileOutputStream;
+import java.io.FileInputStream;
+import java.util.Set;
+import java.util.LinkedHashSet;
+
/**
* Main class for smali. It recognizes enough options to be able to dispatch
* to the right "actual" main.
*/
public class main {
- public static final String VERSION = "0.5b";
+ public static final String VERSION = "0.91";
+
+ private final static Options options;
+
+ static {
+ options = new Options();
+ buildOptions();
+ }
- private static String USAGE_MESSAGE =
- "usage:\n" +
- " java -jar smali.jar --dex [--output=]\n" +
- " [--dump-to=] [--dump-width=]\n" +
- " [--] [ | | -]+\n" +
- " Convert a set of smali assembly files into a dex file.\n" +
- " - assemble the specified smali file\n" +
- " - recusively find and assemble all *.smali files in\n" +
- " the specified directory\n"+
- " - - additionally read a list of files and directories\n" +
- " from stdin\n" +
- " java -jar smali.jar --version\n" +
- " Print the version of this tool (" + VERSION +
- ").\n" +
- " java -jar smali.jar --help\n" +
- " Print this message.";
-
/**
* This class is uninstantiable.
*/
private main() {
- // This space intentionally left blank.
}
/**
* Run!
*/
public static void main(String[] args) {
- boolean gotCmd = false;
- boolean showUsage = false;
+ CommandLineParser parser = new PosixParser();
+ CommandLine commandLine;
try {
- for (int i = 0; i < args.length; i++) {
- String arg = args[i];
- if (arg.equals("--") || !arg.startsWith("--")) {
- gotCmd = false;
- showUsage = true;
- break;
- }
+ commandLine = parser.parse(options, args);
+ } catch (ParseException ex) {
+ usage();
+ return;
+ }
- gotCmd = true;
- if (arg.equals("--dex")) {
- smali.main(without(args, i));
- break;
- } else if (arg.equals("--version")) {
- version();
- break;
- } else if (arg.equals("--help")) {
- showUsage = true;
- break;
- } else {
- gotCmd = false;
+ boolean doDump = false;
+ boolean sort = false;
+
+ String outputDexFile = "out.dex";
+ String dumpFileName = null;
+
+ String[] remainingArgs = commandLine.getArgs();
+
+ if (commandLine.hasOption("v")) {
+ version();
+ return;
+ }
+
+ if (commandLine.hasOption("?")) {
+ usage();
+ return;
+ }
+
+ if (remainingArgs.length == 0) {
+ usage();
+ return;
+ }
+
+ if (commandLine.hasOption("o")) {
+ outputDexFile = commandLine.getOptionValue("o");
+ }
+
+ if (commandLine.hasOption("d")) {
+ doDump = true;
+ dumpFileName = commandLine.getOptionValue("d", outputDexFile + ".dump");
+ }
+
+ if (commandLine.hasOption("s")) {
+ sort = true;
+ }
+
+
+ try {
+
+ LinkedHashSet filesToProcess = new LinkedHashSet();
+
+ for (String arg: remainingArgs) {
+ File argFile = new File(arg);
+
+ if (!argFile.exists()) {
+ throw new RuntimeException("Cannot find file or directory \"" + arg + "\"");
+ }
+
+ if (argFile.isDirectory()) {
+ getSmaliFilesInDir(argFile, filesToProcess);
+ } else if (argFile.isFile()) {
+ filesToProcess.add(argFile);
+ }
+ }
+
+ DexFile dexFile = new DexFile();
+
+ boolean errors = false;
+
+ for (File file: filesToProcess) {
+ if (!assembleSmaliFile(file, dexFile)) {
+ errors = true;
}
}
- } catch (UsageException ex) {
- showUsage = true;
+
+ if (errors) {
+ System.exit(1);
+ }
+
+
+ if (sort) {
+ dexFile.setSortAllItems(true);
+ }
+
+ dexFile.place();
+
+ ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
+
+ if (dumpFileName != null) {
+ out.enableAnnotations(120, true);
+ }
+
+ dexFile.writeTo(out);
+
+ byte[] bytes = out.toByteArray();
+
+ DexFile.calcSignature(bytes);
+ DexFile.calcChecksum(bytes);
+
+ if (dumpFileName != null) {
+ out.finishAnnotating();
+
+ FileWriter fileWriter = new FileWriter(dumpFileName);
+ out.writeAnnotationsTo(fileWriter);
+ fileWriter.close();
+ }
+
+ FileOutputStream fileOutputStream = new FileOutputStream(outputDexFile);
+
+ fileOutputStream.write(bytes);
+ fileOutputStream.close();
} catch (RuntimeException ex) {
System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
ex.printStackTrace();
@@ -90,46 +177,104 @@ public class main {
ex.printStackTrace();
System.exit(3);
}
+ }
- if (!gotCmd) {
- System.err.println("error: no command specified");
- showUsage = true;
+
+ private static void getSmaliFilesInDir(File dir, Set smaliFiles) {
+ for(File file: dir.listFiles()) {
+ if (file.isDirectory()) {
+ getSmaliFilesInDir(file, smaliFiles);
+ } else if (file.getName().endsWith(".smali")) {
+ smaliFiles.add(file);
+ }
+ }
+ }
+
+ private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile)
+ throws Exception {
+ ANTLRInputStream input = new ANTLRInputStream(new FileInputStream(smaliFile));
+ input.name = smaliFile.getAbsolutePath();
+
+ smaliLexer lexer = new smaliLexer(input);
+
+ CommonTokenStream tokens = new CommonTokenStream(lexer);
+ smaliParser parser = new smaliParser(tokens);
+
+ smaliParser.smali_file_return result = parser.smali_file();
+
+ if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfLexerErrors() > 0) {
+ return false;
}
- if (showUsage) {
- usage();
- System.exit(1);
+ CommonTree t = (CommonTree) result.getTree();
+
+ CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
+ treeStream.setTokenStream(tokens);
+
+ smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
+
+ dexGen.dexFile = dexFile;
+ dexGen.smali_file();
+
+ if (dexGen.getNumberOfSyntaxErrors() > 0) {
+ return false;
}
+
+ dexFile.ClassDefsSection.intern(dexGen.classDefItem);
+ return true;
+ }
+
+
+ /**
+ * Prints the usage message.
+ */
+ private static void usage() {
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp("java -jar smali.jar [options] [--] [|folder]*",
+ "assembles a set of smali files into a dex file, and optionally generats an annotated dump of the output file", options, "");
}
/**
* Prints the version message.
*/
private static void version() {
- System.err.println("smali version " + VERSION);
+ System.out.println("smali " + VERSION + " (http://smali.googlecode.com)");
+ System.out.println("Copyright (C) 2009 Ben Gruver");
+ System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
System.exit(0);
}
- /**
- * Prints the usage message.
- */
- private static void usage() {
- System.err.println(USAGE_MESSAGE);
- }
+
- /**
- * Returns a copy of the given args array, but without the indicated
- * element.
- *
- * @param orig non-null; original array
- * @param n which element to omit
- * @return non-null; new array
- */
- private static String[] without(String[] orig, int n) {
- int len = orig.length - 1;
- String[] newa = new String[len];
- System.arraycopy(orig, 0, newa, 0, n);
- System.arraycopy(orig, n + 1, newa, n, len - n);
- return newa;
+ private static void buildOptions() {
+ Option versionOption = OptionBuilder.withLongOpt("version")
+ .withDescription("prints the version then exits")
+ .create("v");
+
+ Option helpOption = OptionBuilder.withLongOpt("help")
+ .withDescription("prints the help message then exits")
+ .create("?");
+
+ Option dumpOption = OptionBuilder.withLongOpt("dump-to")
+ .withDescription("additionally writes a dump of written dex file to FILE (.dump by default)")
+ .hasOptionalArg()
+ .withArgName("FILE")
+ .create("d");
+
+ Option outputOption = OptionBuilder.withLongOpt("output")
+ .withDescription("the directory where the disassembled files will be placed. The default is out.dex")
+ .hasArg()
+ .withArgName("FILE")
+ .create("o");
+
+ Option sortOption = OptionBuilder.withLongOpt("sort")
+ .withDescription("sort the items in the dex file into a canonical order before writing")
+ .create("s");
+
+ options.addOption(versionOption);
+ options.addOption(helpOption);
+ options.addOption(dumpOption);
+ options.addOption(outputOption);
+ options.addOption(sortOption);
}
}
\ No newline at end of file
diff --git a/smali/src/main/java/org/jf/smali/smali.java b/smali/src/main/java/org/jf/smali/smali.java
deleted file mode 100644
index 1745c73b..00000000
--- a/smali/src/main/java/org/jf/smali/smali.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * [The "BSD licence"]
- * Copyright (c) 2009 Ben Gruver
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.jf.smali;
-
-import org.antlr.runtime.ANTLRInputStream;
-import org.antlr.runtime.CommonTokenStream;
-import org.antlr.runtime.tree.CommonTree;
-import org.antlr.runtime.tree.CommonTreeNodeStream;
-import org.jf.dexlib.DexFile;
-import org.jf.dexlib.AnnotationItem;
-import org.jf.dexlib.Util.ByteArrayAnnotatedOutput;
-
-import java.io.*;
-import java.util.LinkedHashSet;
-import java.util.Set;
-import java.util.List;
-
-public class smali
-{
- public static void main(String[] args) throws Exception
- {
- LinkedHashSet filesToProcess = new LinkedHashSet();
-
- boolean getFilesFromStdin = false;
- String outputFilename = "classes.dex";
- String dumpFilename = null;
- int dumpWidth = 120;
-
- int i;
-
- for (i=0; i smaliFiles) {
- for(File file: dir.listFiles()) {
- if (file.isDirectory()) {
- getSmaliFilesInDir(file, smaliFiles);
- } else if (file.getName().endsWith(".smali")) {
- smaliFiles.add(file);
- }
- }
- }
-
- private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile)
- throws Exception {
- ANTLRInputStream input = new ANTLRInputStream(new FileInputStream(smaliFile));
- input.name = smaliFile.getAbsolutePath();
-
- smaliLexer lexer = new smaliLexer(input);
-
- CommonTokenStream tokens = new CommonTokenStream(lexer);
- smaliParser parser = new smaliParser(tokens);
-
- smaliParser.smali_file_return result = parser.smali_file();
-
- if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfLexerErrors() > 0) {
- return false;
- }
-
-
- CommonTree t = (CommonTree) result.getTree();
-
- CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
- treeStream.setTokenStream(tokens);
-
- smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
-
- dexGen.dexFile = dexFile;
- dexGen.smali_file();
-
- if (dexGen.getNumberOfSyntaxErrors() > 0) {
- return false;
- }
-
- dexFile.ClassDefsSection.intern(dexFile, dexGen.classDefItem);
- return true;
- }
-}
\ No newline at end of file