diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b7db779
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+/.gradle
+/baksmali/build
+/dexlib/build
+/dexlib2/build
+/dexlib2/accessorTestGenerator/build
+/smali/build
+/util/build
+/smalidea/build
+*.iml
+*.ipr
+*.iws
+.idea
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..7f27c9f
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,85 @@
+The majority of smali/baksmali is written and copyrighted by me (Ben Gruver)
+and released under the following license:
+
+*******************************************************************************
+Copyright (c) 2010 Ben Gruver (JesusFreke)
+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.
+*******************************************************************************
+
+
+Unless otherwise stated in the code/commit message, any changes with the
+committer of bgruv@google.com or wkal@google.com is copyrighted by
+Google Inc. and released under the following license:
+
+*******************************************************************************
+Copyright 2011, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+    * 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.
+    * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"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 COPYRIGHT
+OWNER OR CONTRIBUTORS 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.
+*******************************************************************************
+
+
+Various portions of the code are taken from the Android Open Source Project,
+and are used in accordance with the following license:
+
+*******************************************************************************
+Copyright (C) 2007 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.
+*******************************************************************************
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a5169b4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+### About
+
+smali/baksmali is an assembler/disassembler for the dex format used by dalvik, Android's Java VM implementation. The syntax is loosely based on Jasmin's/dedexer's syntax, and supports the full functionality of the dex format (annotations, debug info, line info, etc.)
+
+Downloads are at  https://bitbucket.org/JesusFreke/smali/downloads/. If you are interested in submitting a patch, feel free to send me a pull request here.
+
+See [the wiki](https://github.com/JesusFreke/smali/wiki) for more info/news/release notes/etc.
+
+#### Support
+- [github Issue tracker](https://github.com/JesusFreke/smali/issues) - For any bugs/issues/feature requests
+- [#smali on freenode](http://webchat.freenode.net/?channels=smali) - Free free to drop by and ask a question. Don't expect an instant response, but if you hang around someone will respond.
+
+
+#### Some useful links for getting started with smali
+
+- [Official dex bytecode reference](https://source.android.com/devices/tech/dalvik/dalvik-bytecode.html)
+- [Registers wiki page](https://github.com/JesusFreke/smali/wiki/Registers)
+- [Types, Methods and Fields wiki page](https://github.com/JesusFreke/smali/wiki/TypesMethodsAndFields)
+- [Official dex format reference](https://source.android.com/devices/tech/dalvik/dex-format.html)
diff --git a/baksmali-2.4.0.jar b/baksmali-2.4.0.jar
deleted file mode 100644
index 5bc0188..0000000
Binary files a/baksmali-2.4.0.jar and /dev/null differ
diff --git a/baksmali/build.gradle b/baksmali/build.gradle
new file mode 100644
index 0000000..aae88a3
--- /dev/null
+++ b/baksmali/build.gradle
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.
+ */
+
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath depends.proguard_gradle
+    }
+}
+
+dependencies {
+    compile project(':util')
+    compile project(':dexlib2')
+    compile depends.guava
+    compile depends.jcommander
+
+    testCompile depends.junit
+    testCompile project(':smali')
+}
+
+processResources.inputs.property('version', version)
+processResources.expand('version': version)
+
+// Build a separate jar that contains all dependencies
+task fatJar(type: Jar) {
+    from sourceSets.main.output
+    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
+
+    classifier = 'fat'
+
+    manifest {
+        attributes('Main-Class': 'org.jf.baksmali.Main')
+    }
+
+    doLast {
+        if (!System.getProperty('os.name').toLowerCase().contains('windows')) {
+            ant.symlink(link: file("${destinationDir}/baksmali.jar"), resource: archivePath, overwrite: true)
+        }
+    }
+}
+tasks.getByPath('build').dependsOn(fatJar)
+
+uploadArchives {
+    repositories.mavenDeployer {
+        pom.project {
+            description 'baksmali is a disassembler for dalvik bytecode'
+            scm {
+                url 'https://github.com/JesusFreke/smali/tree/master/baksmali'
+            }
+        }
+    }
+}
+
+task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
+    def outFile = fatJar.destinationDir.getPath() + '/' + fatJar.baseName + '-' + fatJar.version + '-small' + '.' + fatJar.extension
+
+    injars fatJar.archivePath
+    outjars outFile
+
+    libraryjars "${System.properties['java.home']}/lib/rt.jar"
+
+    dontobfuscate
+    dontoptimize
+
+    keep 'public class org.jf.baksmali.Main { public static void main(java.lang.String[]); }'
+    keep 'public class org.jf.util.jcommander.ColonParameterSplitter'
+    keep 'class com.beust.jcommander.** { *; }'
+    keepclassmembers 'enum * { public static **[] values(); public static ** valueOf(java.lang.String); }'
+
+    dontwarn 'com.google.common.**'
+    dontnote 'com.google.common.**'
+}
+
+tasks.getByPath(':release').dependsOn(proguard)
+
+task fastbuild(dependsOn: build) {
+}
+
+task fb(dependsOn: fastbuild) {
+}
+
+tasks.getByPath('javadoc').onlyIf({
+    !gradle.taskGraph.hasTask(fastbuild)
+})
+
+tasks.getByPath('test').onlyIf({
+    !gradle.taskGraph.hasTask(fastbuild)
+})
\ No newline at end of file
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/AnnotationFormatter.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/AnnotationFormatter.java
new file mode 100644
index 0000000..1310f19
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/AnnotationFormatter.java
@@ -0,0 +1,69 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors;
+
+import org.jf.baksmali.Adaptors.EncodedValue.AnnotationEncodedValueAdaptor;
+import org.jf.dexlib2.AnnotationVisibility;
+import org.jf.dexlib2.iface.Annotation;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.util.Collection;
+
+public class AnnotationFormatter {
+
+    public static void writeTo(@Nonnull IndentingWriter writer,
+                               @Nonnull Collection<? extends Annotation> annotations,
+                               @Nullable String containingClass) throws IOException {
+        boolean first = true;
+        for (Annotation annotation: annotations) {
+            if (!first) {
+                writer.write('\n');
+            }
+            first = false;
+
+            writeTo(writer, annotation, containingClass);
+        }
+    }
+
+    public static void writeTo(@Nonnull IndentingWriter writer, @Nonnull Annotation annotation,
+                               @Nullable String containingClass) throws IOException {
+        writer.write(".annotation ");
+        writer.write(AnnotationVisibility.getVisibility(annotation.getVisibility()));
+        writer.write(' ');
+        writer.write(annotation.getType());
+        writer.write('\n');
+
+        AnnotationEncodedValueAdaptor.writeElementsTo(writer, annotation.getElements(), containingClass);
+
+        writer.write(".end annotation\n");
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/BlankMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/BlankMethodItem.java
new file mode 100644
index 0000000..d007849
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/BlankMethodItem.java
@@ -0,0 +1,48 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors;
+
+import org.jf.util.IndentingWriter;
+
+//a "spacer" between instructions
+public class BlankMethodItem extends MethodItem {
+    public BlankMethodItem(int codeAddress) {
+        super(codeAddress);
+    }
+
+    public double getSortOrder() {
+        return Integer.MAX_VALUE;
+    }
+
+    public boolean writeTo(IndentingWriter writer) {
+        //we didn't technically print something, but returning true indicates that a newline should be printed
+        //after this method item, which is the intended functionality
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/CatchMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/CatchMethodItem.java
new file mode 100644
index 0000000..4b545ee
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/CatchMethodItem.java
@@ -0,0 +1,97 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors;
+
+import org.jf.baksmali.BaksmaliOptions;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+
+public class CatchMethodItem extends MethodItem {
+    private final String exceptionType;
+
+    private final LabelMethodItem tryStartLabel;
+    private final LabelMethodItem tryEndLabel;
+    private final LabelMethodItem handlerLabel;
+
+    public CatchMethodItem(@Nonnull BaksmaliOptions options, @Nonnull MethodDefinition.LabelCache labelCache,
+                           int codeAddress, @Nullable String exceptionType, int startAddress, int endAddress,
+                           int handlerAddress) {
+        super(codeAddress);
+        this.exceptionType = exceptionType;
+
+        tryStartLabel = labelCache.internLabel(new LabelMethodItem(options, startAddress, "try_start_"));
+
+        //use the address from the last covered instruction, but make the label
+        //name refer to the address of the next instruction
+        tryEndLabel = labelCache.internLabel(new EndTryLabelMethodItem(options, codeAddress, endAddress));
+
+        if (exceptionType == null) {
+            handlerLabel = labelCache.internLabel(new LabelMethodItem(options, handlerAddress, "catchall_"));
+        } else {
+            handlerLabel = labelCache.internLabel(new LabelMethodItem(options, handlerAddress, "catch_"));
+        }
+    }
+
+    public LabelMethodItem getTryStartLabel() {
+        return tryStartLabel;
+    }
+
+    public LabelMethodItem getTryEndLabel() {
+        return tryEndLabel;
+    }
+
+    public LabelMethodItem getHandlerLabel() {
+        return handlerLabel;
+    }
+
+    public double getSortOrder() {
+        //sort after instruction and end_try label
+        return 102;
+    }
+
+    @Override
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        if (exceptionType == null) {
+            writer.write(".catchall");
+        } else {
+            writer.write(".catch ");
+            writer.write(exceptionType);
+        }
+        writer.write(" {");
+        tryStartLabel.writeTo(writer);
+        writer.write(" .. ");
+        tryEndLabel.writeTo(writer);
+        writer.write("} ");
+        handlerLabel.writeTo(writer);
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java
new file mode 100644
index 0000000..73603f1
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java
@@ -0,0 +1,331 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors;
+
+import org.jf.baksmali.BaksmaliOptions;
+import org.jf.dexlib2.AccessFlags;
+import org.jf.dexlib2.dexbacked.DexBackedClassDef;
+import org.jf.dexlib2.iface.*;
+import org.jf.dexlib2.iface.instruction.Instruction;
+import org.jf.dexlib2.iface.instruction.formats.Instruction21c;
+import org.jf.dexlib2.iface.reference.FieldReference;
+import org.jf.dexlib2.iface.reference.Reference;
+import org.jf.dexlib2.util.ReferenceUtil;
+import org.jf.util.IndentingWriter;
+import org.jf.util.StringUtils;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class ClassDefinition {
+    @Nonnull public final BaksmaliOptions options;
+    @Nonnull public final ClassDef classDef;
+    @Nonnull private final HashSet<String> fieldsSetInStaticConstructor;
+
+    protected boolean validationErrors;
+
+    public ClassDefinition(@Nonnull BaksmaliOptions options, @Nonnull ClassDef classDef) {
+        this.options = options;
+        this.classDef = classDef;
+        fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor(classDef);
+    }
+
+    public boolean hadValidationErrors() {
+        return validationErrors;
+    }
+
+    @Nonnull
+    private static HashSet<String> findFieldsSetInStaticConstructor(@Nonnull ClassDef classDef) {
+        HashSet<String> fieldsSetInStaticConstructor = new HashSet<String>();
+
+        for (Method method: classDef.getDirectMethods()) {
+            if (method.getName().equals("<clinit>")) {
+                MethodImplementation impl = method.getImplementation();
+                if (impl != null) {
+                    for (Instruction instruction: impl.getInstructions()) {
+                        switch (instruction.getOpcode()) {
+                            case SPUT:
+                            case SPUT_BOOLEAN:
+                            case SPUT_BYTE:
+                            case SPUT_CHAR:
+                            case SPUT_OBJECT:
+                            case SPUT_SHORT:
+                            case SPUT_WIDE: {
+                                Instruction21c ins = (Instruction21c)instruction;
+                                FieldReference fieldRef = (FieldReference)ins.getReference();
+                                try {
+                                    fieldRef.validateReference();
+                                    if (fieldRef.getDefiningClass().equals((classDef.getType()))) {
+                                        fieldsSetInStaticConstructor.add(ReferenceUtil.getShortFieldDescriptor(fieldRef));
+                                    }
+                                } catch (Reference.InvalidReferenceException ex) {
+                                    // Just ignore for now. We'll deal with it when processing the instruction
+                                }
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return fieldsSetInStaticConstructor;
+    }
+
+    public void writeTo(IndentingWriter writer) throws IOException {
+        writeClass(writer);
+        writeSuper(writer);
+        writeSourceFile(writer);
+        writeInterfaces(writer);
+        writeAnnotations(writer);
+        Set<String> staticFields = writeStaticFields(writer);
+        writeInstanceFields(writer, staticFields);
+        Set<String> directMethods = writeDirectMethods(writer);
+        writeVirtualMethods(writer, directMethods);
+    }
+
+    private void writeClass(IndentingWriter writer) throws IOException {
+        writer.write(".class ");
+        writeAccessFlags(writer);
+        writer.write(classDef.getType());
+        writer.write('\n');
+    }
+
+    private void writeAccessFlags(IndentingWriter writer) throws IOException {
+        for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForClass(classDef.getAccessFlags())) {
+            writer.write(accessFlag.toString());
+            writer.write(' ');
+        }
+    }
+
+    private void writeSuper(IndentingWriter writer) throws IOException {
+        String superClass = classDef.getSuperclass();
+        if (superClass != null) {
+            writer.write(".super ");
+            writer.write(superClass);
+            writer.write('\n');
+        }
+    }
+
+    private void writeSourceFile(IndentingWriter writer) throws IOException {
+        String sourceFile = classDef.getSourceFile();
+        if (sourceFile != null) {
+            writer.write(".source \"");
+            StringUtils.writeEscapedString(writer, sourceFile);
+            writer.write("\"\n");
+        }
+    }
+
+    private void writeInterfaces(IndentingWriter writer) throws IOException {
+        List<String> interfaces = classDef.getInterfaces();
+
+        if (interfaces.size() != 0) {
+            writer.write('\n');
+            writer.write("# interfaces\n");
+            for (String interfaceName: interfaces) {
+                writer.write(".implements ");
+                writer.write(interfaceName);
+                writer.write('\n');
+            }
+        }
+    }
+
+    private void writeAnnotations(IndentingWriter writer) throws IOException {
+        Collection<? extends Annotation> classAnnotations = classDef.getAnnotations();
+        if (classAnnotations.size() != 0) {
+            writer.write("\n\n");
+            writer.write("# annotations\n");
+
+            String containingClass = null;
+            if (options.implicitReferences) {
+                containingClass = classDef.getType();
+            }
+
+            AnnotationFormatter.writeTo(writer, classAnnotations, containingClass);
+        }
+    }
+
+    private Set<String> writeStaticFields(IndentingWriter writer) throws IOException {
+        boolean wroteHeader = false;
+        Set<String> writtenFields = new HashSet<String>();
+
+        Iterable<? extends Field> staticFields;
+        if (classDef instanceof DexBackedClassDef) {
+            staticFields = ((DexBackedClassDef)classDef).getStaticFields(false);
+        } else {
+            staticFields = classDef.getStaticFields();
+        }
+
+        for (Field field: staticFields) {
+            if (!wroteHeader) {
+                writer.write("\n\n");
+                writer.write("# static fields");
+                wroteHeader = true;
+            }
+            writer.write('\n');
+
+            boolean setInStaticConstructor;
+            IndentingWriter fieldWriter = writer;
+            String fieldString = ReferenceUtil.getShortFieldDescriptor(field);
+            if (!writtenFields.add(fieldString)) {
+                writer.write("# duplicate field ignored\n");
+                fieldWriter = new CommentingIndentingWriter(writer);
+                System.err.println(String.format("Ignoring duplicate field: %s->%s", classDef.getType(), fieldString));
+                setInStaticConstructor = false;
+            } else {
+                setInStaticConstructor = fieldsSetInStaticConstructor.contains(fieldString);
+            }
+            FieldDefinition.writeTo(options, fieldWriter, field, setInStaticConstructor);
+        }
+        return writtenFields;
+    }
+
+    private void writeInstanceFields(IndentingWriter writer, Set<String> staticFields) throws IOException {
+        boolean wroteHeader = false;
+        Set<String> writtenFields = new HashSet<String>();
+
+        Iterable<? extends Field> instanceFields;
+        if (classDef instanceof DexBackedClassDef) {
+            instanceFields = ((DexBackedClassDef)classDef).getInstanceFields(false);
+        } else {
+            instanceFields = classDef.getInstanceFields();
+        }
+
+        for (Field field: instanceFields) {
+            if (!wroteHeader) {
+                writer.write("\n\n");
+                writer.write("# instance fields");
+                wroteHeader = true;
+            }
+            writer.write('\n');
+
+            IndentingWriter fieldWriter = writer;
+            String fieldString = ReferenceUtil.getShortFieldDescriptor(field);
+            if (!writtenFields.add(fieldString)) {
+                writer.write("# duplicate field ignored\n");
+                fieldWriter = new CommentingIndentingWriter(writer);
+                System.err.println(String.format("Ignoring duplicate field: %s->%s", classDef.getType(), fieldString));
+            } else if (staticFields.contains(fieldString)) {
+                System.err.println(String.format("Duplicate static+instance field found: %s->%s",
+                        classDef.getType(), fieldString));
+                System.err.println("You will need to rename one of these fields, including all references.");
+
+                writer.write("# There is both a static and instance field with this signature.\n" +
+                             "# You will need to rename one of these fields, including all references.\n");
+            }
+            FieldDefinition.writeTo(options, fieldWriter, field, false);
+        }
+    }
+
+    private Set<String> writeDirectMethods(IndentingWriter writer) throws IOException {
+        boolean wroteHeader = false;
+        Set<String> writtenMethods = new HashSet<String>();
+
+        Iterable<? extends Method> directMethods;
+        if (classDef instanceof DexBackedClassDef) {
+            directMethods = ((DexBackedClassDef)classDef).getDirectMethods(false);
+        } else {
+            directMethods = classDef.getDirectMethods();
+        }
+
+        for (Method method: directMethods) {
+            if (!wroteHeader) {
+                writer.write("\n\n");
+                writer.write("# direct methods");
+                wroteHeader = true;
+            }
+            writer.write('\n');
+
+            // TODO: check for method validation errors
+            String methodString = ReferenceUtil.getMethodDescriptor(method, true);
+
+            IndentingWriter methodWriter = writer;
+            if (!writtenMethods.add(methodString)) {
+                writer.write("# duplicate method ignored\n");
+                methodWriter = new CommentingIndentingWriter(writer);
+            }
+
+            MethodImplementation methodImpl = method.getImplementation();
+            if (methodImpl == null) {
+                MethodDefinition.writeEmptyMethodTo(methodWriter, method, options);
+            } else {
+                MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl);
+                methodDefinition.writeTo(methodWriter);
+            }
+        }
+        return writtenMethods;
+    }
+
+    private void writeVirtualMethods(IndentingWriter writer, Set<String> directMethods) throws IOException {
+        boolean wroteHeader = false;
+        Set<String> writtenMethods = new HashSet<String>();
+
+        Iterable<? extends Method> virtualMethods;
+        if (classDef instanceof DexBackedClassDef) {
+            virtualMethods = ((DexBackedClassDef)classDef).getVirtualMethods(false);
+        } else {
+            virtualMethods = classDef.getVirtualMethods();
+        }
+
+        for (Method method: virtualMethods) {
+            if (!wroteHeader) {
+                writer.write("\n\n");
+                writer.write("# virtual methods");
+                wroteHeader = true;
+            }
+            writer.write('\n');
+
+            // TODO: check for method validation errors
+            String methodString = ReferenceUtil.getMethodDescriptor(method, true);
+
+            IndentingWriter methodWriter = writer;
+            if (!writtenMethods.add(methodString)) {
+                writer.write("# duplicate method ignored\n");
+                methodWriter = new CommentingIndentingWriter(writer);
+            } else if (directMethods.contains(methodString)) {
+                writer.write("# There is both a direct and virtual method with this signature.\n" +
+                             "# You will need to rename one of these methods, including all references.\n");
+                System.err.println(String.format("Duplicate direct+virtual method found: %s->%s",
+                        classDef.getType(), methodString));
+                System.err.println("You will need to rename one of these methods, including all references.");
+            }
+
+            MethodImplementation methodImpl = method.getImplementation();
+            if (methodImpl == null) {
+                MethodDefinition.writeEmptyMethodTo(methodWriter, method, options);
+            } else {
+                MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl);
+                methodDefinition.writeTo(methodWriter);
+            }
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/CommentMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/CommentMethodItem.java
new file mode 100644
index 0000000..8ac4396
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/CommentMethodItem.java
@@ -0,0 +1,55 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors;
+
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+
+public class CommentMethodItem extends MethodItem {
+    //private final StringTemplate template;
+    private final String comment;
+    private final double sortOrder;
+
+    public CommentMethodItem(String comment, int codeAddress, double sortOrder) {
+        super(codeAddress);
+        this.comment = comment;
+        this.sortOrder = sortOrder;
+    }
+
+    public double getSortOrder() {
+        return sortOrder;
+    }
+
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        writer.write('#');
+        writer.write(comment);
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/CommentedOutMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/CommentedOutMethodItem.java
new file mode 100644
index 0000000..aaeb6a6
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/CommentedOutMethodItem.java
@@ -0,0 +1,52 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors;
+
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+
+public class CommentedOutMethodItem extends MethodItem {
+    private final MethodItem commentedOutMethodItem;
+
+    public CommentedOutMethodItem(MethodItem commentedOutMethodItem) {
+        super(commentedOutMethodItem.getCodeAddress());
+        this.commentedOutMethodItem = commentedOutMethodItem;
+    }
+
+    public double getSortOrder() {
+        return commentedOutMethodItem.getSortOrder() + .001;
+    }
+
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        writer.write('#');
+        commentedOutMethodItem.writeTo(writer);
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/CommentingIndentingWriter.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/CommentingIndentingWriter.java
new file mode 100644
index 0000000..c63da1c
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/CommentingIndentingWriter.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali.Adaptors;
+
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+import java.io.Writer;
+
+public class CommentingIndentingWriter extends IndentingWriter {
+    public CommentingIndentingWriter(Writer writer) {
+        super(writer);
+    }
+
+    @Override protected void writeIndent() throws IOException {
+        writer.write("# ");
+        super.writeIndent();
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/BeginEpilogueMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/BeginEpilogueMethodItem.java
new file mode 100644
index 0000000..a1294fc
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/BeginEpilogueMethodItem.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali.Adaptors.Debug;
+
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+
+public class BeginEpilogueMethodItem extends DebugMethodItem {
+    public BeginEpilogueMethodItem(int codeAddress, int sortOrder) {
+        super(codeAddress, sortOrder);
+    }
+
+    @Override
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        writer.write(".prologue");
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/DebugMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/DebugMethodItem.java
new file mode 100644
index 0000000..86d30f9
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/DebugMethodItem.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali.Adaptors.Debug;
+
+import org.jf.baksmali.Adaptors.MethodItem;
+import org.jf.baksmali.Adaptors.RegisterFormatter;
+import org.jf.dexlib2.DebugItemType;
+import org.jf.dexlib2.iface.debug.*;
+import org.jf.util.ExceptionWithContext;
+
+public abstract class DebugMethodItem extends MethodItem {
+    private final int sortOrder;
+
+    protected DebugMethodItem(int codeAddress, int sortOrder) {
+        super(codeAddress);
+        this.sortOrder = sortOrder;
+    }
+
+    @Override public double getSortOrder() { return sortOrder; }
+
+    public static DebugMethodItem build(RegisterFormatter registerFormatter, DebugItem debugItem) {
+        int codeAddress = debugItem.getCodeAddress();
+        switch (debugItem.getDebugItemType()) {
+            case DebugItemType.START_LOCAL:
+                return new StartLocalMethodItem(codeAddress, -1, registerFormatter, (StartLocal)debugItem);
+            case DebugItemType.END_LOCAL:
+                return new EndLocalMethodItem(codeAddress, -1, registerFormatter, (EndLocal)debugItem);
+            case DebugItemType.RESTART_LOCAL:
+                return new RestartLocalMethodItem(codeAddress, -1, registerFormatter, (RestartLocal)debugItem);
+            case DebugItemType.EPILOGUE_BEGIN:
+                return new BeginEpilogueMethodItem(codeAddress, -4);
+            case DebugItemType.PROLOGUE_END:
+                return new EndPrologueMethodItem(codeAddress, -4);
+            case DebugItemType.SET_SOURCE_FILE:
+                return new SetSourceFileMethodItem(codeAddress, -3, (SetSourceFile)debugItem);
+            case DebugItemType.LINE_NUMBER:
+                return new LineNumberMethodItem(codeAddress, -2, (LineNumber)debugItem);
+            default:
+                throw new ExceptionWithContext("Invalid debug item type: %d", debugItem.getDebugItemType());
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/EndLocalMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/EndLocalMethodItem.java
new file mode 100644
index 0000000..231e049
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/EndLocalMethodItem.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali.Adaptors.Debug;
+
+import org.jf.baksmali.Adaptors.RegisterFormatter;
+import org.jf.dexlib2.iface.debug.EndLocal;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+
+public class EndLocalMethodItem extends DebugMethodItem {
+    @Nonnull private final EndLocal endLocal;
+    @Nonnull private final RegisterFormatter registerFormatter;
+
+    public EndLocalMethodItem(int codeAddress, int sortOrder, @Nonnull RegisterFormatter registerFormatter,
+                                @Nonnull EndLocal endLocal) {
+        super(codeAddress, sortOrder);
+        this.endLocal = endLocal;
+        this.registerFormatter = registerFormatter;
+    }
+
+    @Override
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        writer.write(".end local ");
+        registerFormatter.writeTo(writer, endLocal.getRegister());
+
+        String name = endLocal.getName();
+        String type = endLocal.getType();
+        String signature = endLocal.getSignature();
+        if (name != null || type != null || signature != null) {
+            writer.write("    # ");
+            LocalFormatter.writeLocal(writer, name, type, signature);
+        }
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/EndPrologueMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/EndPrologueMethodItem.java
new file mode 100644
index 0000000..369c38f
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/EndPrologueMethodItem.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali.Adaptors.Debug;
+
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+
+public class EndPrologueMethodItem extends DebugMethodItem {
+    public EndPrologueMethodItem(int codeAddress, int sortOrder) {
+        super(codeAddress, sortOrder);
+    }
+
+    @Override
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        writer.write(".prologue");
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/LineNumberMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/LineNumberMethodItem.java
new file mode 100644
index 0000000..91473bd
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/LineNumberMethodItem.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali.Adaptors.Debug;
+
+import org.jf.dexlib2.iface.debug.LineNumber;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+
+public class LineNumberMethodItem extends DebugMethodItem {
+    private final int lineNumber;
+
+    public LineNumberMethodItem(int codeAddress, int sortOrder, @Nonnull LineNumber lineNumber) {
+        super(codeAddress, sortOrder);
+        this.lineNumber = lineNumber.getLineNumber();
+    }
+
+    @Override
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        writer.write(".line ");
+        writer.printUnsignedIntAsDec(lineNumber);
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/LocalFormatter.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/LocalFormatter.java
new file mode 100644
index 0000000..62ed995
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/LocalFormatter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali.Adaptors.Debug;
+
+import org.jf.baksmali.Adaptors.ReferenceFormatter;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+
+public class LocalFormatter {
+    /**
+     * Writes out the given local info
+     *
+     * The written string will be something like:
+     *
+     * "localVar":Ljava/lang/String;, "SomeSignature"
+     * "localVar":Ljava/lang/String;
+     * "localVar":V, "SomeSignature"
+     * null:Ljava/lang/String;, "SomeSignature"
+     * null:V, "SomeSignature"
+     *
+     * One of name, type or signature must be non-null
+     */
+    public static void writeLocal(@Nonnull IndentingWriter writer, @Nullable String name, @Nullable String type,
+                                  @Nullable String signature) throws IOException {
+        if (name != null) {
+            ReferenceFormatter.writeStringReference(writer, name);
+        } else {
+            writer.write("null");
+        }
+        writer.write(':');
+        if (type != null) {
+            writer.write(type);
+        } else {
+            writer.write("V");
+        }
+        if (signature != null) {
+            writer.write(", ");
+            ReferenceFormatter.writeStringReference(writer, signature);
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/RestartLocalMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/RestartLocalMethodItem.java
new file mode 100644
index 0000000..4461719
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/RestartLocalMethodItem.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali.Adaptors.Debug;
+
+import org.jf.baksmali.Adaptors.RegisterFormatter;
+import org.jf.dexlib2.iface.debug.RestartLocal;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+
+public class RestartLocalMethodItem extends DebugMethodItem {
+    @Nonnull private final RestartLocal restartLocal;
+    @Nonnull private final RegisterFormatter registerFormatter;
+
+    public RestartLocalMethodItem(int codeAddress, int sortOrder, @Nonnull RegisterFormatter registerFormatter,
+                              @Nonnull RestartLocal restartLocal) {
+        super(codeAddress, sortOrder);
+        this.restartLocal = restartLocal;
+        this.registerFormatter = registerFormatter;
+    }
+
+    @Override
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        writer.write(".restart local ");
+        registerFormatter.writeTo(writer, restartLocal.getRegister());
+
+        String name = restartLocal.getName();
+        String type = restartLocal.getType();
+        String signature = restartLocal.getSignature();
+        if (name != null || type != null || signature != null) {
+            writer.write("    # ");
+            LocalFormatter.writeLocal(writer, name, type, signature);
+        }
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/SetSourceFileMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/SetSourceFileMethodItem.java
new file mode 100644
index 0000000..faccfdf
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/SetSourceFileMethodItem.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali.Adaptors.Debug;
+
+import org.jf.dexlib2.iface.debug.SetSourceFile;
+import org.jf.util.IndentingWriter;
+import org.jf.util.StringUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+
+public class SetSourceFileMethodItem extends DebugMethodItem {
+    @Nullable private final String sourceFile;
+
+    public SetSourceFileMethodItem(int codeAddress, int sortOrder, @Nonnull SetSourceFile setSourceFile) {
+        super(codeAddress, sortOrder);
+        this.sourceFile = setSourceFile.getSourceFile();
+    }
+
+    @Override
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        writer.write(".source");
+
+        if (sourceFile != null) {
+            writer.write(" \"");
+            StringUtils.writeEscapedString(writer, sourceFile);
+            writer.write('"');
+        }
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/StartLocalMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/StartLocalMethodItem.java
new file mode 100644
index 0000000..0cd2d2b
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Debug/StartLocalMethodItem.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali.Adaptors.Debug;
+
+import org.jf.baksmali.Adaptors.RegisterFormatter;
+import org.jf.dexlib2.iface.debug.StartLocal;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+
+public class StartLocalMethodItem extends DebugMethodItem {
+    @Nonnull private final StartLocal startLocal;
+    @Nonnull private final RegisterFormatter registerFormatter;
+
+    public StartLocalMethodItem(int codeAddress, int sortOrder, @Nonnull RegisterFormatter registerFormatter,
+                                @Nonnull StartLocal startLocal) {
+        super(codeAddress, sortOrder);
+        this.startLocal = startLocal;
+        this.registerFormatter = registerFormatter;
+    }
+
+    @Override
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        writer.write(".local ");
+        registerFormatter.writeTo(writer, startLocal.getRegister());
+
+        String name = startLocal.getName();
+        String type = startLocal.getType();
+        String signature = startLocal.getSignature();
+
+        if (name != null || type != null || signature != null) {
+            writer.write(", ");
+            LocalFormatter.writeLocal(writer, name, type, signature);
+        }
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/AnnotationEncodedValueAdaptor.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/AnnotationEncodedValueAdaptor.java
new file mode 100644
index 0000000..e8f12a2
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/AnnotationEncodedValueAdaptor.java
@@ -0,0 +1,65 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors.EncodedValue;
+
+import org.jf.dexlib2.iface.AnnotationElement;
+import org.jf.dexlib2.iface.value.AnnotationEncodedValue;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.util.Collection;
+
+public abstract class AnnotationEncodedValueAdaptor {
+
+    public static void writeTo(@Nonnull IndentingWriter writer,
+                               @Nonnull AnnotationEncodedValue annotationEncodedValue,
+                               @Nullable String containingClass) throws IOException {
+        writer.write(".subannotation ");
+        writer.write(annotationEncodedValue.getType());
+        writer.write('\n');
+
+        writeElementsTo(writer, annotationEncodedValue.getElements(), containingClass);
+        writer.write(".end subannotation");
+    }
+
+    public static void writeElementsTo(@Nonnull IndentingWriter writer,
+                                       @Nonnull Collection<? extends AnnotationElement> annotationElements,
+                                       @Nullable String containingClass) throws IOException {
+        writer.indent(4);
+        for (AnnotationElement annotationElement: annotationElements) {
+            writer.write(annotationElement.getName());
+            writer.write(" = ");
+            EncodedValueAdaptor.writeTo(writer, annotationElement.getValue(), containingClass);
+            writer.write('\n');
+        }
+        writer.deindent(4);
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/ArrayEncodedValueAdaptor.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/ArrayEncodedValueAdaptor.java
new file mode 100644
index 0000000..eb079b3
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/ArrayEncodedValueAdaptor.java
@@ -0,0 +1,65 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors.EncodedValue;
+
+import org.jf.dexlib2.iface.value.ArrayEncodedValue;
+import org.jf.dexlib2.iface.value.EncodedValue;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.util.Collection;
+
+public class ArrayEncodedValueAdaptor {
+    public static void writeTo(@Nonnull IndentingWriter writer,
+                               @Nonnull ArrayEncodedValue arrayEncodedValue,
+                               @Nullable String containingClass) throws IOException {
+        writer.write('{');
+        Collection<? extends EncodedValue> values = arrayEncodedValue.getValue();
+        if (values.size() == 0) {
+            writer.write('}');
+            return;
+        }
+
+        writer.write('\n');
+        writer.indent(4);
+        boolean first = true;
+        for (EncodedValue encodedValue: values) {
+            if (!first) {
+                writer.write(",\n");
+            }
+            first = false;
+
+            EncodedValueAdaptor.writeTo(writer, encodedValue, containingClass);
+        }
+        writer.deindent(4);
+        writer.write("\n}");
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/EncodedValueAdaptor.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/EncodedValueAdaptor.java
new file mode 100644
index 0000000..880c760
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/EncodedValueAdaptor.java
@@ -0,0 +1,124 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors.EncodedValue;
+
+import org.jf.baksmali.Adaptors.ReferenceFormatter;
+import org.jf.baksmali.Renderers.*;
+import org.jf.dexlib2.ReferenceType;
+import org.jf.dexlib2.ValueType;
+import org.jf.dexlib2.iface.value.*;
+import org.jf.dexlib2.util.ReferenceUtil;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+
+public abstract class EncodedValueAdaptor {
+    public static void writeTo(@Nonnull IndentingWriter writer, @Nonnull EncodedValue encodedValue,
+                               @Nullable String containingClass)
+            throws IOException {
+        switch (encodedValue.getValueType()) {
+            case ValueType.ANNOTATION:
+                AnnotationEncodedValueAdaptor.writeTo(writer, (AnnotationEncodedValue)encodedValue, containingClass);
+                return;
+            case ValueType.ARRAY:
+                ArrayEncodedValueAdaptor.writeTo(writer, (ArrayEncodedValue)encodedValue, containingClass);
+                return;
+            case ValueType.BOOLEAN:
+                BooleanRenderer.writeTo(writer, ((BooleanEncodedValue)encodedValue).getValue());
+                return;
+            case ValueType.BYTE:
+                ByteRenderer.writeTo(writer, ((ByteEncodedValue)encodedValue).getValue());
+                return;
+            case ValueType.CHAR:
+                CharRenderer.writeTo(writer, ((CharEncodedValue)encodedValue).getValue());
+                return;
+            case ValueType.DOUBLE:
+                DoubleRenderer.writeTo(writer, ((DoubleEncodedValue)encodedValue).getValue());
+                return;
+            case ValueType.ENUM:
+                EnumEncodedValue enumEncodedValue = (EnumEncodedValue)encodedValue;
+                boolean useImplicitReference = false;
+                if (enumEncodedValue.getValue().getDefiningClass().equals(containingClass)) {
+                    useImplicitReference = true;
+                }
+                writer.write(".enum ");
+                ReferenceUtil.writeFieldDescriptor(writer, enumEncodedValue.getValue(), useImplicitReference);
+                return;
+            case ValueType.FIELD:
+                FieldEncodedValue fieldEncodedValue = (FieldEncodedValue)encodedValue;
+                useImplicitReference = false;
+                if (fieldEncodedValue.getValue().getDefiningClass().equals(containingClass)) {
+                    useImplicitReference = true;
+                }
+                ReferenceUtil.writeFieldDescriptor(writer, fieldEncodedValue.getValue(), useImplicitReference);
+                return;
+            case ValueType.FLOAT:
+                FloatRenderer.writeTo(writer, ((FloatEncodedValue)encodedValue).getValue());
+                return;
+            case ValueType.INT:
+                IntegerRenderer.writeTo(writer, ((IntEncodedValue)encodedValue).getValue());
+                return;
+            case ValueType.LONG:
+                LongRenderer.writeTo(writer, ((LongEncodedValue)encodedValue).getValue());
+                return;
+            case ValueType.METHOD:
+                MethodEncodedValue methodEncodedValue = (MethodEncodedValue)encodedValue;
+                useImplicitReference = false;
+                if (methodEncodedValue.getValue().getDefiningClass().equals(containingClass)) {
+                    useImplicitReference = true;
+                }
+                ReferenceUtil.writeMethodDescriptor(writer, methodEncodedValue.getValue(), useImplicitReference);
+                return;
+            case ValueType.NULL:
+                writer.write("null");
+                return;
+            case ValueType.SHORT:
+                ShortRenderer.writeTo(writer, ((ShortEncodedValue)encodedValue).getValue());
+                return;
+            case ValueType.STRING:
+                ReferenceFormatter.writeStringReference(writer, ((StringEncodedValue)encodedValue).getValue());
+                return;
+            case ValueType.TYPE:
+                writer.write(((TypeEncodedValue)encodedValue).getValue());
+                return;
+            case ValueType.METHOD_TYPE:
+                ReferenceFormatter.writeReference(writer, ReferenceType.METHOD_PROTO,
+                        ((MethodTypeEncodedValue)encodedValue).getValue());
+                return;
+            case ValueType.METHOD_HANDLE:
+                ReferenceFormatter.writeReference(writer, ReferenceType.METHOD_HANDLE,
+                        ((MethodHandleEncodedValue)encodedValue).getValue());
+                return;
+            default:
+                throw new IllegalArgumentException("Unknown encoded value type: " + encodedValue.getValueType());
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/EndTryLabelMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EndTryLabelMethodItem.java
new file mode 100644
index 0000000..2680704
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EndTryLabelMethodItem.java
@@ -0,0 +1,51 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors;
+
+import org.jf.baksmali.BaksmaliOptions;
+
+import javax.annotation.Nonnull;
+
+public class EndTryLabelMethodItem extends LabelMethodItem {
+    private int endTryAddress;
+
+    public EndTryLabelMethodItem(@Nonnull BaksmaliOptions options, int codeAddress, int endTryAddress) {
+        super(options, codeAddress, "try_end_");
+        this.endTryAddress = endTryAddress;
+    }
+
+    public double getSortOrder() {
+        //sort after instruction, but before catch directive
+        return 101;
+    }
+
+    public int getLabelAddress() {
+        return endTryAddress;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java
new file mode 100644
index 0000000..8b5f920
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java
@@ -0,0 +1,109 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors;
+
+import org.jf.baksmali.Adaptors.EncodedValue.EncodedValueAdaptor;
+import org.jf.baksmali.BaksmaliOptions;
+import org.jf.dexlib2.AccessFlags;
+import org.jf.dexlib2.HiddenApiRestriction;
+import org.jf.dexlib2.iface.Annotation;
+import org.jf.dexlib2.iface.Field;
+import org.jf.dexlib2.iface.value.EncodedValue;
+import org.jf.dexlib2.util.EncodedValueUtils;
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Set;
+
+public class FieldDefinition {
+    public static void writeTo(BaksmaliOptions options, IndentingWriter writer, Field field,
+                               boolean setInStaticConstructor) throws IOException {
+        EncodedValue initialValue = field.getInitialValue();
+        int accessFlags = field.getAccessFlags();
+
+        if (setInStaticConstructor &&
+                AccessFlags.STATIC.isSet(accessFlags) &&
+                AccessFlags.FINAL.isSet(accessFlags) &&
+                initialValue != null) {
+            if (!EncodedValueUtils.isDefaultValue(initialValue)) {
+                writer.write("# The value of this static final field might be set in the static constructor\n");
+            } else {
+                // don't write out the default initial value for static final fields that get set in the static
+                // constructor
+                initialValue = null;
+            }
+        }
+
+        writer.write(".field ");
+        writeAccessFlagsAndRestrictions(writer, field.getAccessFlags(), field.getHiddenApiRestrictions());
+        writer.write(field.getName());
+        writer.write(':');
+        writer.write(field.getType());
+        if (initialValue != null) {
+            writer.write(" = ");
+
+            String containingClass = null;
+            if (options.implicitReferences) {
+                containingClass = field.getDefiningClass();
+            }
+
+            EncodedValueAdaptor.writeTo(writer, initialValue, containingClass);
+        }
+
+        writer.write('\n');
+
+        Collection<? extends Annotation> annotations = field.getAnnotations();
+        if (annotations.size() > 0) {
+            writer.indent(4);
+
+            String containingClass = null;
+            if (options.implicitReferences) {
+                containingClass = field.getDefiningClass();
+            }
+
+            AnnotationFormatter.writeTo(writer, annotations, containingClass);
+            writer.deindent(4);
+            writer.write(".end field\n");
+        }
+    }
+
+    private static void writeAccessFlagsAndRestrictions(
+            IndentingWriter writer, int accessFlags, Set<HiddenApiRestriction> hiddenApiRestrictions)
+            throws IOException {
+        for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForField(accessFlags)) {
+            writer.write(accessFlag.toString());
+            writer.write(' ');
+        }
+        for (HiddenApiRestriction hiddenApiRestriction : hiddenApiRestrictions) {
+            writer.write(hiddenApiRestriction.toString());
+            writer.write(' ');
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/ArrayDataMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/ArrayDataMethodItem.java
new file mode 100644
index 0000000..9c7b658
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/ArrayDataMethodItem.java
@@ -0,0 +1,81 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors.Format;
+
+import org.jf.baksmali.Adaptors.MethodDefinition;
+import org.jf.baksmali.Renderers.LongRenderer;
+import org.jf.dexlib2.iface.instruction.formats.ArrayPayload;
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+import java.util.List;
+
+public class ArrayDataMethodItem extends InstructionMethodItem<ArrayPayload> {
+    public ArrayDataMethodItem(MethodDefinition methodDef, int codeAddress, ArrayPayload instruction) {
+        super(methodDef, codeAddress, instruction);
+    }
+
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        int elementWidth = instruction.getElementWidth();
+
+        writer.write(".array-data ");
+        writer.printSignedIntAsDec(instruction.getElementWidth());
+        writer.write('\n');
+
+        writer.indent(4);
+
+        List<Number> elements = instruction.getArrayElements();
+
+        String suffix = "";
+        switch (elementWidth) {
+            case 1:
+                suffix = "t";
+                break;
+            case 2:
+                suffix = "s";
+                break;
+        }
+
+        for (Number number: elements) {
+            LongRenderer.writeSignedIntOrLongTo(writer, number.longValue());
+            writer.write(suffix);
+            if (elementWidth == 8) {
+                writeCommentIfLikelyDouble(writer, number.longValue());
+            } else if (elementWidth == 4) {
+                int value = number.intValue();
+                boolean isResourceId = writeCommentIfResourceId(writer, value);
+                if (!isResourceId) writeCommentIfLikelyFloat(writer, value);
+            }
+            writer.write("\n");
+        }
+        writer.deindent(4);
+        writer.write(".end array-data");
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java
new file mode 100644
index 0000000..e5ef926
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java
@@ -0,0 +1,587 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors.Format;
+
+import org.jf.baksmali.Adaptors.MethodDefinition;
+import org.jf.baksmali.Adaptors.MethodDefinition.InvalidSwitchPayload;
+import org.jf.baksmali.Adaptors.MethodItem;
+import org.jf.baksmali.Adaptors.ReferenceFormatter;
+import org.jf.baksmali.BaksmaliOptions;
+import org.jf.baksmali.Renderers.LongRenderer;
+import org.jf.dexlib2.Opcode;
+import org.jf.dexlib2.VerificationError;
+import org.jf.dexlib2.iface.instruction.*;
+import org.jf.dexlib2.iface.instruction.formats.Instruction20bc;
+import org.jf.dexlib2.iface.instruction.formats.Instruction31t;
+import org.jf.dexlib2.iface.instruction.formats.UnknownInstruction;
+import org.jf.dexlib2.iface.reference.CallSiteReference;
+import org.jf.dexlib2.iface.reference.Reference;
+import org.jf.dexlib2.util.ReferenceUtil;
+import org.jf.util.ExceptionWithContext;
+import org.jf.util.IndentingWriter;
+import org.jf.util.NumberUtils;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.Map;
+
+public class InstructionMethodItem<T extends Instruction> extends MethodItem {
+    @Nonnull protected final MethodDefinition methodDef;
+    @Nonnull protected final T instruction;
+
+    public InstructionMethodItem(@Nonnull MethodDefinition methodDef, int codeAddress, @Nonnull T instruction) {
+        super(codeAddress);
+        this.methodDef = methodDef;
+        this.instruction = instruction;
+    }
+
+    public double getSortOrder() {
+        //instructions should appear after everything except an "end try" label and .catch directive
+        return 100;
+    }
+
+    private boolean isAllowedOdex(@Nonnull Opcode opcode) {
+        BaksmaliOptions options = methodDef.classDef.options;
+        if (options.allowOdex) {
+            return true;
+        }
+
+        if (methodDef.classDef.options.apiLevel >= 14) {
+            return false;
+        }
+
+        return opcode.isVolatileFieldAccessor() || opcode == Opcode.THROW_VERIFICATION_ERROR;
+    }
+
+    private interface Writable {
+        void writeTo(IndentingWriter writer) throws IOException;
+    }
+
+    @Override
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        Opcode opcode = instruction.getOpcode();
+        String verificationErrorName = null;
+        Writable referenceWritable = null;
+        Writable referenceWritable2 = null;
+
+        boolean commentOutInstruction = false;
+
+        if (instruction instanceof Instruction20bc) {
+            int verificationError = ((Instruction20bc)instruction).getVerificationError();
+            verificationErrorName = VerificationError.getVerificationErrorName(verificationError);
+            if (verificationErrorName == null) {
+                writer.write("#was invalid verification error type: ");
+                writer.printSignedIntAsDec(verificationError);
+                writer.write("\n");
+                verificationErrorName = "generic-error";
+            }
+        }
+
+        if (instruction instanceof ReferenceInstruction) {
+            ReferenceInstruction referenceInstruction = (ReferenceInstruction)instruction;
+            final String classContext;
+            if (methodDef.classDef.options.implicitReferences) {
+                classContext = methodDef.method.getDefiningClass();
+            } else {
+                classContext = null;
+            }
+
+            Reference reference = referenceInstruction.getReference();
+
+            try {
+                reference.validateReference();
+
+                if (reference instanceof CallSiteReference) {
+                    referenceWritable = new Writable() {
+                        @Override
+                        public void writeTo(IndentingWriter indentingWriter) throws IOException {
+                            ReferenceFormatter.writeCallSiteReference(indentingWriter, (CallSiteReference)reference);
+                        }
+                    };
+                } else {
+                    referenceWritable = new Writable() {
+                        @Override
+                        public void writeTo(IndentingWriter indentingWriter) throws IOException {
+                            indentingWriter.write(ReferenceUtil.getReferenceString(reference, classContext));
+                        }
+                    };
+                }
+            } catch (Reference.InvalidReferenceException ex) {
+                commentOutInstruction = true;
+                writer.write("#");
+                writer.write(ex.getMessage());
+                writer.write("\n");
+                referenceWritable = indentingWriter -> {
+                    indentingWriter.write(ex.getInvalidReferenceRepresentation());
+                };
+            }
+
+            if (instruction instanceof DualReferenceInstruction) {
+                DualReferenceInstruction dualReferenceInstruction =
+                        (DualReferenceInstruction) instruction;
+                try {
+                    Reference reference2 = dualReferenceInstruction.getReference2();
+                    reference2.validateReference();
+
+                    referenceWritable2 = indentingWriter -> {
+                        indentingWriter.write(ReferenceUtil.getReferenceString(reference2, classContext));
+                    };
+                } catch (Reference.InvalidReferenceException ex) {
+                    commentOutInstruction = true;
+                    writer.write("#");
+                    writer.write(ex.getMessage());
+                    writer.write("\n");
+                    referenceWritable = indentingWriter -> {
+                        indentingWriter.write(ex.getInvalidReferenceRepresentation());
+                    };
+                }
+            }
+        }
+
+        if (instruction instanceof Instruction31t) {
+            boolean validPayload = true;
+
+            switch (instruction.getOpcode()) {
+                case PACKED_SWITCH:
+                    int baseAddress = methodDef.getPackedSwitchBaseAddress(
+                            this.codeAddress + ((Instruction31t)instruction).getCodeOffset());
+                    if (baseAddress == -1) {
+                        validPayload = false;
+                    }
+                    break;
+                case SPARSE_SWITCH:
+                    baseAddress = methodDef.getSparseSwitchBaseAddress(
+                            this.codeAddress + ((Instruction31t)instruction).getCodeOffset());
+                    if (baseAddress == -1) {
+                        validPayload = false;
+                    }
+                    break;
+                case FILL_ARRAY_DATA:
+                    try {
+                        methodDef.findPayloadOffset(this.codeAddress + ((Instruction31t)instruction).getCodeOffset(),
+                                Opcode.ARRAY_PAYLOAD);
+                    } catch (InvalidSwitchPayload ex) {
+                        validPayload = false;
+                    }
+                    break;
+                default:
+                    throw new ExceptionWithContext("Invalid 31t opcode: %s", instruction.getOpcode());
+            }
+
+            if (!validPayload) {
+                writer.write("#invalid payload reference\n");
+                commentOutInstruction = true;
+            }
+        }
+
+        if (opcode.odexOnly()) {
+            if (!isAllowedOdex(opcode)) {
+                writer.write("#disallowed odex opcode\n");
+                commentOutInstruction = true;
+            }
+        }
+
+        if (commentOutInstruction) {
+            writer.write("#");
+        }
+
+        switch (instruction.getOpcode().format) {
+            case Format10t:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeTargetLabel(writer);
+                break;
+            case Format10x:
+                if (instruction instanceof UnknownInstruction) {
+                    writer.write("#unknown opcode: 0x");
+                    writer.printUnsignedLongAsHex(((UnknownInstruction)instruction).getOriginalOpcode());
+                    writer.write('\n');
+                }
+                writeOpcode(writer);
+                break;
+            case Format11n:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeFirstRegister(writer);
+                writer.write(", ");
+                writeLiteral(writer);
+                break;
+            case Format11x:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeFirstRegister(writer);
+                break;
+            case Format12x:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeFirstRegister(writer);
+                writer.write(", ");
+                writeSecondRegister(writer);
+                break;
+            case Format20bc:
+                writeOpcode(writer);
+                writer.write(' ');
+                writer.write(verificationErrorName);
+                writer.write(", ");
+                referenceWritable.writeTo(writer);
+                break;
+            case Format20t:
+            case Format30t:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeTargetLabel(writer);
+                break;
+            case Format21c:
+            case Format31c:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeFirstRegister(writer);
+                writer.write(", ");
+                referenceWritable.writeTo(writer);
+                break;
+            case Format21ih:
+            case Format21lh:
+            case Format21s:
+            case Format31i:
+            case Format51l:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeFirstRegister(writer);
+                writer.write(", ");
+                writeLiteral(writer);
+                if (instruction.getOpcode().setsWideRegister()) {
+                    writeCommentIfLikelyDouble(writer);
+                } else {
+                    boolean isResourceId = writeCommentIfResourceId(writer);
+                    if (!isResourceId) writeCommentIfLikelyFloat(writer);
+                }
+                break;
+            case Format21t:
+            case Format31t:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeFirstRegister(writer);
+                writer.write(", ");
+                writeTargetLabel(writer);
+                break;
+            case Format22b:
+            case Format22s:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeFirstRegister(writer);
+                writer.write(", ");
+                writeSecondRegister(writer);
+                writer.write(", ");
+                writeLiteral(writer);
+                break;
+            case Format22c:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeFirstRegister(writer);
+                writer.write(", ");
+                writeSecondRegister(writer);
+                writer.write(", ");
+                referenceWritable.writeTo(writer);
+                break;
+            case Format22cs:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeFirstRegister(writer);
+                writer.write(", ");
+                writeSecondRegister(writer);
+                writer.write(", ");
+                writeFieldOffset(writer);
+                break;
+            case Format22t:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeFirstRegister(writer);
+                writer.write(", ");
+                writeSecondRegister(writer);
+                writer.write(", ");
+                writeTargetLabel(writer);
+                break;
+            case Format22x:
+            case Format32x:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeFirstRegister(writer);
+                writer.write(", ");
+                writeSecondRegister(writer);
+                break;
+            case Format23x:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeFirstRegister(writer);
+                writer.write(", ");
+                writeSecondRegister(writer);
+                writer.write(", ");
+                writeThirdRegister(writer);
+                break;
+            case Format35c:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeInvokeRegisters(writer);
+                writer.write(", ");
+                referenceWritable.writeTo(writer);
+                break;
+            case Format35mi:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeInvokeRegisters(writer);
+                writer.write(", ");
+                writeInlineIndex(writer);
+                break;
+            case Format35ms:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeInvokeRegisters(writer);
+                writer.write(", ");
+                writeVtableIndex(writer);
+                break;
+            case Format3rc:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeInvokeRangeRegisters(writer);
+                writer.write(", ");
+                referenceWritable.writeTo(writer);
+                break;
+            case Format3rmi:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeInvokeRangeRegisters(writer);
+                writer.write(", ");
+                writeInlineIndex(writer);
+                break;
+            case Format3rms:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeInvokeRangeRegisters(writer);
+                writer.write(", ");
+                writeVtableIndex(writer);
+                break;
+            case Format45cc:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeInvokeRegisters(writer);
+                writer.write(", ");
+                referenceWritable.writeTo(writer);
+                writer.write(", ");
+                referenceWritable2.writeTo(writer);
+                break;
+            case Format4rcc:
+                writeOpcode(writer);
+                writer.write(' ');
+                writeInvokeRangeRegisters(writer);
+                writer.write(", ");
+                referenceWritable.writeTo(writer);
+                writer.write(", ");
+                referenceWritable2.writeTo(writer);
+                break;
+            default:
+                assert false;
+                return false;
+        }
+
+        if (commentOutInstruction) {
+            writer.write("\nnop");
+        }
+
+        return true;
+    }
+
+    protected void writeOpcode(IndentingWriter writer) throws IOException {
+        writer.write(instruction.getOpcode().name);
+    }
+
+    protected void writeTargetLabel(IndentingWriter writer) throws IOException {
+        //this method is overridden by OffsetInstructionMethodItem, and should only be called for the formats that
+        //have a target
+        throw new RuntimeException();
+    }
+
+    protected void writeRegister(IndentingWriter writer, int registerNumber) throws IOException {
+        methodDef.registerFormatter.writeTo(writer, registerNumber);
+    }
+
+    protected void writeFirstRegister(IndentingWriter writer) throws IOException {
+        writeRegister(writer, ((OneRegisterInstruction)instruction).getRegisterA());
+    }
+
+    protected void writeSecondRegister(IndentingWriter writer) throws IOException {
+        writeRegister(writer, ((TwoRegisterInstruction)instruction).getRegisterB());
+    }
+
+    protected void writeThirdRegister(IndentingWriter writer) throws IOException {
+        writeRegister(writer, ((ThreeRegisterInstruction) instruction).getRegisterC());
+    }
+
+    protected void writeInvokeRegisters(IndentingWriter writer) throws IOException {
+        FiveRegisterInstruction instruction = (FiveRegisterInstruction)this.instruction;
+        final int regCount = instruction.getRegisterCount();
+
+        writer.write('{');
+        switch (regCount) {
+            case 1:
+                writeRegister(writer, instruction.getRegisterC());
+                break;
+            case 2:
+                writeRegister(writer, instruction.getRegisterC());
+                writer.write(", ");
+                writeRegister(writer, instruction.getRegisterD());
+                break;
+            case 3:
+                writeRegister(writer, instruction.getRegisterC());
+                writer.write(", ");
+                writeRegister(writer, instruction.getRegisterD());
+                writer.write(", ");
+                writeRegister(writer, instruction.getRegisterE());
+                break;
+            case 4:
+                writeRegister(writer, instruction.getRegisterC());
+                writer.write(", ");
+                writeRegister(writer, instruction.getRegisterD());
+                writer.write(", ");
+                writeRegister(writer, instruction.getRegisterE());
+                writer.write(", ");
+                writeRegister(writer, instruction.getRegisterF());
+                break;
+            case 5:
+                writeRegister(writer, instruction.getRegisterC());
+                writer.write(", ");
+                writeRegister(writer, instruction.getRegisterD());
+                writer.write(", ");
+                writeRegister(writer, instruction.getRegisterE());
+                writer.write(", ");
+                writeRegister(writer, instruction.getRegisterF());
+                writer.write(", ");
+                writeRegister(writer, instruction.getRegisterG());
+                break;
+        }
+        writer.write('}');
+    }
+
+    protected void writeInvokeRangeRegisters(IndentingWriter writer) throws IOException {
+        RegisterRangeInstruction instruction = (RegisterRangeInstruction)this.instruction;
+
+        int regCount = instruction.getRegisterCount();
+        if (regCount == 0) {
+            writer.write("{}");
+        } else {
+            int startRegister = instruction.getStartRegister();
+            methodDef.registerFormatter.writeRegisterRange(writer, startRegister, startRegister+regCount-1);
+        }
+    }
+
+    protected void writeLiteral(IndentingWriter writer) throws IOException {
+        LongRenderer.writeSignedIntOrLongTo(writer, ((WideLiteralInstruction)instruction).getWideLiteral());
+    }
+
+    protected void writeCommentIfLikelyFloat(IndentingWriter writer) throws IOException {
+        writeCommentIfLikelyFloat(writer, ((NarrowLiteralInstruction)instruction).getNarrowLiteral());
+    }
+
+    protected void writeCommentIfLikelyFloat(IndentingWriter writer, int val) throws IOException {
+        if (NumberUtils.isLikelyFloat(val)) {
+            writer.write("    # ");
+            float fval = Float.intBitsToFloat(val);
+            if (fval == Float.POSITIVE_INFINITY)
+                writer.write("Float.POSITIVE_INFINITY");
+            else if (fval == Float.NEGATIVE_INFINITY)
+                writer.write("Float.NEGATIVE_INFINITY");
+            else if (Float.isNaN(fval))
+                writer.write("Float.NaN");
+            else if (fval == Float.MAX_VALUE)
+                writer.write("Float.MAX_VALUE");
+            else if (fval == (float)Math.PI)
+                writer.write("(float)Math.PI");
+            else if (fval == (float)Math.E)
+                writer.write("(float)Math.E");
+            else {
+                writer.write(Float.toString(fval));
+                writer.write('f');
+            }
+        }
+    }
+
+    protected void writeCommentIfLikelyDouble(IndentingWriter writer) throws IOException {
+        writeCommentIfLikelyDouble(writer, ((WideLiteralInstruction)instruction).getWideLiteral());
+    }
+
+    protected void writeCommentIfLikelyDouble(IndentingWriter writer, long val) throws IOException {
+        if (NumberUtils.isLikelyDouble(val)) {
+            writer.write("    # ");
+            double dval = Double.longBitsToDouble(val);
+            if (dval == Double.POSITIVE_INFINITY)
+                writer.write("Double.POSITIVE_INFINITY");
+            else if (dval == Double.NEGATIVE_INFINITY)
+                writer.write("Double.NEGATIVE_INFINITY");
+            else if (Double.isNaN(dval))
+                writer.write("Double.NaN");
+            else if (dval == Double.MAX_VALUE)
+                writer.write("Double.MAX_VALUE");
+            else if (dval == Math.PI)
+                writer.write("Math.PI");
+            else if (dval == Math.E)
+                writer.write("Math.E");
+            else
+                writer.write(Double.toString(dval));
+        }
+    }
+
+    protected boolean writeCommentIfResourceId(IndentingWriter writer) throws IOException {
+        return writeCommentIfResourceId(writer, ((NarrowLiteralInstruction)instruction).getNarrowLiteral());
+    }
+
+    protected boolean writeCommentIfResourceId(IndentingWriter writer, int val) throws IOException {
+        Map<Integer,String> resourceIds = methodDef.classDef.options.resourceIds;
+        String resource = resourceIds.get(Integer.valueOf(val));
+        if (resource != null) {
+            writer.write("    # ");
+            writer.write(resource);
+            return true;
+        }
+        return false;
+    }
+
+    protected void writeFieldOffset(IndentingWriter writer) throws IOException {
+        writer.write("field@0x");
+        writer.printUnsignedLongAsHex(((FieldOffsetInstruction)instruction).getFieldOffset());
+    }
+
+    protected void writeInlineIndex(IndentingWriter writer) throws IOException {
+        writer.write("inline@");
+        writer.printSignedIntAsDec(((InlineIndexInstruction)instruction).getInlineIndex());
+    }
+
+    protected void writeVtableIndex(IndentingWriter writer) throws IOException {
+        writer.write("vtable@");
+        writer.printSignedIntAsDec(((VtableIndexInstruction)instruction).getVtableIndex());
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItemFactory.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItemFactory.java
new file mode 100644
index 0000000..429cb69
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItemFactory.java
@@ -0,0 +1,67 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors.Format;
+
+import org.jf.baksmali.Adaptors.MethodDefinition;
+import org.jf.dexlib2.analysis.UnresolvedOdexInstruction;
+import org.jf.dexlib2.iface.instruction.Instruction;
+import org.jf.dexlib2.iface.instruction.OffsetInstruction;
+import org.jf.dexlib2.iface.instruction.formats.ArrayPayload;
+import org.jf.dexlib2.iface.instruction.formats.PackedSwitchPayload;
+import org.jf.dexlib2.iface.instruction.formats.SparseSwitchPayload;
+
+public class InstructionMethodItemFactory {
+    private InstructionMethodItemFactory() {
+    }
+
+    public static InstructionMethodItem makeInstructionFormatMethodItem(
+            MethodDefinition methodDef, int codeAddress, Instruction instruction) {
+
+        if (instruction instanceof OffsetInstruction) {
+            return new OffsetInstructionFormatMethodItem(methodDef.classDef.options, methodDef, codeAddress,
+                    (OffsetInstruction)instruction);
+        }
+
+        if (instruction instanceof UnresolvedOdexInstruction) {
+            return new UnresolvedOdexInstructionMethodItem(methodDef, codeAddress,
+                    (UnresolvedOdexInstruction)instruction);
+        }
+
+        switch (instruction.getOpcode().format) {
+            case ArrayPayload:
+                return new ArrayDataMethodItem(methodDef, codeAddress, (ArrayPayload)instruction);
+            case PackedSwitchPayload:
+                return new PackedSwitchMethodItem(methodDef, codeAddress, (PackedSwitchPayload)instruction);
+            case SparseSwitchPayload:
+                return new SparseSwitchMethodItem(methodDef, codeAddress, (SparseSwitchPayload)instruction);
+            default:
+                return new InstructionMethodItem<Instruction>(methodDef, codeAddress, instruction);
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OffsetInstructionFormatMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OffsetInstructionFormatMethodItem.java
new file mode 100644
index 0000000..be76edf
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OffsetInstructionFormatMethodItem.java
@@ -0,0 +1,85 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors.Format;
+
+import org.jf.baksmali.Adaptors.LabelMethodItem;
+import org.jf.baksmali.Adaptors.MethodDefinition;
+import org.jf.baksmali.BaksmaliOptions;
+import org.jf.dexlib2.Opcode;
+import org.jf.dexlib2.iface.instruction.OffsetInstruction;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+
+public class OffsetInstructionFormatMethodItem extends InstructionMethodItem<OffsetInstruction> {
+    protected LabelMethodItem label;
+
+    public OffsetInstructionFormatMethodItem(@Nonnull BaksmaliOptions options, @Nonnull MethodDefinition methodDef,
+                                             int codeAddress, OffsetInstruction instruction) {
+        super(methodDef, codeAddress, instruction);
+
+        label = new LabelMethodItem(options, codeAddress + instruction.getCodeOffset(), getLabelPrefix());
+        label = methodDef.getLabelCache().internLabel(label);
+    }
+
+    @Override
+    protected void writeTargetLabel(IndentingWriter writer) throws IOException {
+        label.writeTo(writer);
+    }
+
+    public LabelMethodItem getLabel() {
+        return label;
+    }
+
+    private String getLabelPrefix() {
+        Opcode opcode = instruction.getOpcode();
+        switch (opcode.format) {
+            case Format10t:
+            case Format20t:
+            case Format30t:
+                return "goto_";
+            case Format21t:
+            case Format22t:
+                return "cond_";
+            case Format31t:
+                if (opcode == Opcode.FILL_ARRAY_DATA) {
+                    return "array_";
+                }
+                if (opcode == Opcode.PACKED_SWITCH) {
+                    return "pswitch_data_";
+                }
+                // Opcode.SPARSE_SWITCH;
+                return "sswitch_data_";
+        }
+
+        assert false;
+        return null;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/PackedSwitchMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/PackedSwitchMethodItem.java
new file mode 100644
index 0000000..30edfcd
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/PackedSwitchMethodItem.java
@@ -0,0 +1,130 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors.Format;
+
+import org.jf.baksmali.Adaptors.CommentingIndentingWriter;
+import org.jf.baksmali.Adaptors.LabelMethodItem;
+import org.jf.baksmali.Adaptors.MethodDefinition;
+import org.jf.dexlib2.iface.instruction.SwitchElement;
+import org.jf.dexlib2.iface.instruction.formats.PackedSwitchPayload;
+import org.jf.util.IndentingWriter;
+import org.jf.baksmali.Renderers.IntegerRenderer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PackedSwitchMethodItem extends InstructionMethodItem<PackedSwitchPayload> {
+    private final List<PackedSwitchTarget> targets;
+    private final int firstKey;
+
+    // Whether this sparse switch instruction should be commented out because it is never referenced
+    private boolean commentedOut;
+
+    public PackedSwitchMethodItem(MethodDefinition methodDef, int codeAddress, PackedSwitchPayload instruction) {
+        super(methodDef, codeAddress, instruction);
+
+        int baseCodeAddress = methodDef.getPackedSwitchBaseAddress(codeAddress);
+
+        targets = new ArrayList<PackedSwitchTarget>();
+
+        boolean first = true;
+        int firstKey = 0;
+        if (baseCodeAddress >= 0) {
+            for (SwitchElement switchElement: instruction.getSwitchElements()) {
+                if (first) {
+                    firstKey = switchElement.getKey();
+                    first = false;
+                }
+                LabelMethodItem label = methodDef.getLabelCache().internLabel(
+                        new LabelMethodItem(methodDef.classDef.options, baseCodeAddress + switchElement.getOffset(),
+                                "pswitch_"));
+                targets.add(new PackedSwitchLabelTarget(label));
+            }
+        } else {
+            commentedOut = true;
+            for (SwitchElement switchElement: instruction.getSwitchElements()) {
+                if (first) {
+                    firstKey = switchElement.getKey();
+                    first = false;
+                }
+                targets.add(new PackedSwitchOffsetTarget(switchElement.getOffset()));
+            }
+        }
+        this.firstKey = firstKey;
+    }
+
+    @Override
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        if (commentedOut) {
+            writer = new CommentingIndentingWriter(writer);
+        }
+        writer.write(".packed-switch ");
+        IntegerRenderer.writeTo(writer, firstKey);
+        writer.indent(4);
+        writer.write('\n');
+        int key = firstKey;
+        for (PackedSwitchTarget target: targets) {
+            target.writeTargetTo(writer);
+            writeCommentIfResourceId(writer, key);
+            writer.write('\n');
+            key++;
+        }
+        writer.deindent(4);
+        writer.write(".end packed-switch");
+        return true;
+    }
+
+    private static abstract class PackedSwitchTarget {
+        public abstract void writeTargetTo(IndentingWriter writer) throws IOException;
+    }
+
+    private static class PackedSwitchLabelTarget extends PackedSwitchTarget {
+        private final LabelMethodItem target;
+        public PackedSwitchLabelTarget(LabelMethodItem target) {
+            this.target = target;
+        }
+        public void writeTargetTo(IndentingWriter writer) throws IOException {
+            target.writeTo(writer);
+        }
+    }
+
+    private static class PackedSwitchOffsetTarget extends PackedSwitchTarget {
+        private final int target;
+        public PackedSwitchOffsetTarget(int target) {
+            this.target = target;
+        }
+        public void writeTargetTo(IndentingWriter writer) throws IOException {
+            if (target >= 0) {
+                writer.write('+');
+            }
+            writer.printSignedIntAsDec(target);
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/SparseSwitchMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/SparseSwitchMethodItem.java
new file mode 100644
index 0000000..68d7e92
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/SparseSwitchMethodItem.java
@@ -0,0 +1,126 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors.Format;
+
+import org.jf.baksmali.Adaptors.CommentingIndentingWriter;
+import org.jf.baksmali.Adaptors.LabelMethodItem;
+import org.jf.baksmali.Adaptors.MethodDefinition;
+import org.jf.dexlib2.iface.instruction.SwitchElement;
+import org.jf.dexlib2.iface.instruction.formats.SparseSwitchPayload;
+import org.jf.util.IndentingWriter;
+import org.jf.baksmali.Renderers.IntegerRenderer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SparseSwitchMethodItem extends InstructionMethodItem<SparseSwitchPayload> {
+    private final List<SparseSwitchTarget> targets;
+
+    // Whether this sparse switch instruction should be commented out because it is never referenced
+    private boolean commentedOut;
+
+    public SparseSwitchMethodItem(MethodDefinition methodDef, int codeAddress, SparseSwitchPayload instruction) {
+        super(methodDef, codeAddress, instruction);
+
+        int baseCodeAddress = methodDef.getSparseSwitchBaseAddress(codeAddress);
+
+        targets = new ArrayList<SparseSwitchTarget>();
+        if (baseCodeAddress >= 0) {
+            for (SwitchElement switchElement: instruction.getSwitchElements()) {
+                LabelMethodItem label = methodDef.getLabelCache().internLabel(
+                        new LabelMethodItem( methodDef.classDef.options, baseCodeAddress + switchElement.getOffset(),
+                                "sswitch_"));
+                targets.add(new SparseSwitchLabelTarget(switchElement.getKey(), label));
+            }
+        } else {
+            commentedOut = true;
+            //if we couldn't determine a base address, just use relative offsets rather than labels
+            for (SwitchElement switchElement: instruction.getSwitchElements()) {
+                targets.add(new SparseSwitchOffsetTarget(switchElement.getKey(), switchElement.getOffset()));
+            }
+        }
+    }
+
+    @Override
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        if (commentedOut) {
+            writer = new CommentingIndentingWriter(writer);
+        }
+
+        writer.write(".sparse-switch\n");
+        writer.indent(4);
+        for (SparseSwitchTarget target: targets) {
+            IntegerRenderer.writeTo(writer, target.getKey());
+            writer.write(" -> ");
+            target.writeTargetTo(writer);
+            writeCommentIfResourceId(writer, target.getKey());
+            writer.write('\n');
+        }
+        writer.deindent(4);
+        writer.write(".end sparse-switch");
+        return true;
+    }
+
+    private static abstract class SparseSwitchTarget {
+        private final int key;
+        public SparseSwitchTarget(int key) {
+            this.key = key;
+        }
+        public int getKey() { return key; }
+        public abstract void writeTargetTo(IndentingWriter writer) throws IOException;
+    }
+
+    private static class SparseSwitchLabelTarget extends SparseSwitchTarget {
+        private final LabelMethodItem target;
+        public SparseSwitchLabelTarget(int key, LabelMethodItem target) {
+            super(key);
+            this.target = target;
+        }
+
+        public void writeTargetTo(IndentingWriter writer) throws IOException {
+            target.writeTo(writer);
+        }
+    }
+
+    private static class SparseSwitchOffsetTarget extends SparseSwitchTarget {
+        private final int target;
+        public SparseSwitchOffsetTarget(int key, int target) {
+            super(key);
+            this.target = target;
+        }
+
+        public void writeTargetTo(IndentingWriter writer) throws IOException {
+            if (target >= 0) {
+                writer.write('+');
+            }
+            writer.printSignedIntAsDec(target);
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/UnresolvedOdexInstructionMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/UnresolvedOdexInstructionMethodItem.java
new file mode 100644
index 0000000..a776881
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/UnresolvedOdexInstructionMethodItem.java
@@ -0,0 +1,54 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors.Format;
+
+import org.jf.baksmali.Adaptors.MethodDefinition;
+import org.jf.dexlib2.analysis.UnresolvedOdexInstruction;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+
+public class UnresolvedOdexInstructionMethodItem extends InstructionMethodItem<UnresolvedOdexInstruction> {
+    public UnresolvedOdexInstructionMethodItem(@Nonnull MethodDefinition methodDef, int codeAddress,
+                                               @Nonnull UnresolvedOdexInstruction instruction) {
+        super(methodDef, codeAddress, instruction);
+    }
+
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        writeThrowTo(writer);
+        return true;
+    }
+
+    private void writeThrowTo(IndentingWriter writer) throws IOException {
+        writer.write("#Replaced unresolvable odex instruction with a throw\n");
+        writer.write("throw ");
+        writeRegister(writer, instruction.objectRegisterNum);
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/LabelMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/LabelMethodItem.java
new file mode 100644
index 0000000..268d643
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/LabelMethodItem.java
@@ -0,0 +1,102 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors;
+
+import org.jf.baksmali.BaksmaliOptions;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+
+public class LabelMethodItem extends MethodItem {
+    private final BaksmaliOptions options;
+    private final String labelPrefix;
+    private int labelSequence;
+
+    public LabelMethodItem(@Nonnull BaksmaliOptions options, int codeAddress, @Nonnull String labelPrefix) {
+        super(codeAddress);
+        this.options = options;
+        this.labelPrefix = labelPrefix;
+    }
+
+    public double getSortOrder() {
+        return 0;
+    }
+
+    public int compareTo(MethodItem methodItem) {
+        int result = super.compareTo(methodItem);
+
+        if (result == 0) {
+            if (methodItem instanceof LabelMethodItem) {
+                result = labelPrefix.compareTo(((LabelMethodItem)methodItem).labelPrefix);
+            }
+        }
+        return result;
+    }
+
+    public int hashCode() {
+        //force it to call equals when two labels are at the same address
+        return getCodeAddress();
+    }
+
+    public boolean equals(Object o) {
+        if (!(o instanceof LabelMethodItem)) {
+            return false;
+        }
+        return this.compareTo((MethodItem)o) == 0;
+    }
+
+
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        writer.write(':');
+        writer.write(labelPrefix);
+        if (options.sequentialLabels) {
+            writer.printUnsignedLongAsHex(labelSequence);
+        } else {
+            writer.printUnsignedLongAsHex(this.getLabelAddress());
+        }
+        return true;
+    }
+
+    public String getLabelPrefix() {
+        return labelPrefix;
+    }
+
+    public int getLabelAddress() {
+        return this.getCodeAddress();
+    }
+
+    public int getLabelSequence() {
+        return labelSequence;
+    }
+
+    public void setLabelSequence(int labelSequence) {
+        this.labelSequence = labelSequence;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java
new file mode 100644
index 0000000..8197ee1
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java
@@ -0,0 +1,642 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.jf.baksmali.Adaptors.Debug.DebugMethodItem;
+import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory;
+import org.jf.baksmali.BaksmaliOptions;
+import org.jf.dexlib2.*;
+import org.jf.dexlib2.analysis.AnalysisException;
+import org.jf.dexlib2.analysis.AnalyzedInstruction;
+import org.jf.dexlib2.analysis.MethodAnalyzer;
+import org.jf.dexlib2.iface.*;
+import org.jf.dexlib2.iface.debug.DebugItem;
+import org.jf.dexlib2.iface.instruction.Instruction;
+import org.jf.dexlib2.iface.instruction.OffsetInstruction;
+import org.jf.dexlib2.iface.instruction.ReferenceInstruction;
+import org.jf.dexlib2.iface.instruction.formats.Instruction31t;
+import org.jf.dexlib2.iface.reference.MethodReference;
+import org.jf.dexlib2.iface.reference.Reference;
+import org.jf.dexlib2.immutable.instruction.ImmutableInstruction31t;
+import org.jf.dexlib2.util.InstructionOffsetMap;
+import org.jf.dexlib2.util.InstructionOffsetMap.InvalidInstructionOffset;
+import org.jf.dexlib2.util.ReferenceUtil;
+import org.jf.dexlib2.util.SyntheticAccessorResolver;
+import org.jf.dexlib2.util.SyntheticAccessorResolver.AccessedMember;
+import org.jf.dexlib2.util.TypeUtils;
+import org.jf.util.ExceptionWithContext;
+import org.jf.util.IndentingWriter;
+import org.jf.util.SparseIntArray;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.util.*;
+
+public class MethodDefinition {
+    @Nonnull public final ClassDefinition classDef;
+    @Nonnull public final Method method;
+    @Nonnull public final MethodImplementation methodImpl;
+    @Nonnull public final ImmutableList<Instruction> instructions;
+    @Nonnull public final List<Instruction> effectiveInstructions;
+
+    @Nonnull public final ImmutableList<MethodParameter> methodParameters;
+    public RegisterFormatter registerFormatter;
+
+    @Nonnull private final LabelCache labelCache = new LabelCache();
+
+    @Nonnull private final SparseIntArray packedSwitchMap;
+    @Nonnull private final SparseIntArray sparseSwitchMap;
+    @Nonnull private final InstructionOffsetMap instructionOffsetMap;
+
+    public MethodDefinition(@Nonnull ClassDefinition classDef, @Nonnull Method method,
+                            @Nonnull MethodImplementation methodImpl) {
+        this.classDef = classDef;
+        this.method = method;
+        this.methodImpl = methodImpl;
+
+        try {
+            //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh.
+
+            instructions = ImmutableList.copyOf(methodImpl.getInstructions());
+            methodParameters = ImmutableList.copyOf(method.getParameters());
+
+            effectiveInstructions = Lists.newArrayList(instructions);
+
+            packedSwitchMap = new SparseIntArray(0);
+            sparseSwitchMap = new SparseIntArray(0);
+            instructionOffsetMap = new InstructionOffsetMap(instructions);
+
+            int endOffset = instructionOffsetMap.getInstructionCodeOffset(instructions.size()-1) +
+                    instructions.get(instructions.size()-1).getCodeUnits();
+
+            for (int i=0; i<instructions.size(); i++) {
+                Instruction instruction = instructions.get(i);
+
+                Opcode opcode = instruction.getOpcode();
+                if (opcode == Opcode.PACKED_SWITCH) {
+                    boolean valid = true;
+                    int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i);
+                    int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset();
+                    try {
+                        targetOffset = findPayloadOffset(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD);
+                    } catch (InvalidSwitchPayload ex) {
+                        valid = false;
+                    }
+                    if (valid) {
+                        if (packedSwitchMap.get(targetOffset, -1) != -1) {
+                            Instruction payloadInstruction =
+                                    findSwitchPayload(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD);
+                            targetOffset = endOffset;
+                            effectiveInstructions.set(i, new ImmutableInstruction31t(opcode,
+                                    ((Instruction31t)instruction).getRegisterA(), targetOffset-codeOffset));
+                            effectiveInstructions.add(payloadInstruction);
+                            endOffset += payloadInstruction.getCodeUnits();
+                        }
+                        packedSwitchMap.append(targetOffset, codeOffset);
+                    }
+                } else if (opcode == Opcode.SPARSE_SWITCH) {
+                    boolean valid = true;
+                    int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i);
+                    int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset();
+                    try {
+                        targetOffset = findPayloadOffset(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD);
+                    } catch (InvalidSwitchPayload ex) {
+                        valid = false;
+                        // The offset to the payload instruction was invalid. Nothing to do, except that we won't
+                        // add this instruction to the map.
+                    }
+                    if (valid) {
+                        if (sparseSwitchMap.get(targetOffset, -1) != -1) {
+                            Instruction payloadInstruction =
+                                    findSwitchPayload(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD);
+                            targetOffset = endOffset;
+                            effectiveInstructions.set(i, new ImmutableInstruction31t(opcode,
+                                    ((Instruction31t)instruction).getRegisterA(), targetOffset-codeOffset));
+                            effectiveInstructions.add(payloadInstruction);
+                            endOffset += payloadInstruction.getCodeUnits();
+                        }
+                        sparseSwitchMap.append(targetOffset, codeOffset);
+                    }
+                }
+            }
+        } catch (Exception ex) {
+            String methodString;
+            try {
+                methodString = ReferenceUtil.getMethodDescriptor(method);
+            } catch (Exception ex2) {
+                throw ExceptionWithContext.withContext(ex, "Error while processing method");
+            }
+            throw ExceptionWithContext.withContext(ex, "Error while processing method %s", methodString);
+        }
+    }
+
+    public static void writeEmptyMethodTo(IndentingWriter writer, Method method,
+                                          BaksmaliOptions options) throws IOException {
+        writer.write(".method ");
+        writeAccessFlagsAndRestrictions(writer, method.getAccessFlags(), method.getHiddenApiRestrictions());
+        writer.write(method.getName());
+        writer.write("(");
+        ImmutableList<MethodParameter> methodParameters = ImmutableList.copyOf(method.getParameters());
+        for (MethodParameter parameter: methodParameters) {
+            writer.write(parameter.getType());
+        }
+        writer.write(")");
+        writer.write(method.getReturnType());
+        writer.write('\n');
+
+        writer.indent(4);
+        writeParameters(writer, method, methodParameters, options);
+
+        String containingClass = null;
+        if (options.implicitReferences) {
+            containingClass = method.getDefiningClass();
+        }
+        AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass);
+
+        writer.deindent(4);
+        writer.write(".end method\n");
+    }
+
+    public void writeTo(IndentingWriter writer) throws IOException {
+        int parameterRegisterCount = 0;
+        if (!AccessFlags.STATIC.isSet(method.getAccessFlags())) {
+            parameterRegisterCount++;
+        }
+
+        writer.write(".method ");
+        writeAccessFlagsAndRestrictions(writer, method.getAccessFlags(), method.getHiddenApiRestrictions());
+        writer.write(method.getName());
+        writer.write("(");
+        for (MethodParameter parameter: methodParameters) {
+            String type = parameter.getType();
+            writer.write(type);
+            parameterRegisterCount++;
+            if (TypeUtils.isWideType(type)) {
+                parameterRegisterCount++;
+            }
+        }
+        writer.write(")");
+        writer.write(method.getReturnType());
+        writer.write('\n');
+
+        writer.indent(4);
+        if (classDef.options.localsDirective) {
+            writer.write(".locals ");
+            writer.printSignedIntAsDec(methodImpl.getRegisterCount() - parameterRegisterCount);
+        } else {
+            writer.write(".registers ");
+            writer.printSignedIntAsDec(methodImpl.getRegisterCount());
+        }
+        writer.write('\n');
+        writeParameters(writer, method, methodParameters, classDef.options);
+
+        if (registerFormatter == null) {
+            registerFormatter = new RegisterFormatter(classDef.options, methodImpl.getRegisterCount(),
+                    parameterRegisterCount);
+        }
+
+        String containingClass = null;
+        if (classDef.options.implicitReferences) {
+            containingClass = method.getDefiningClass();
+        }
+        AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass);
+
+        writer.write('\n');
+
+        List<MethodItem> methodItems = getMethodItems();
+        for (MethodItem methodItem: methodItems) {
+            if (methodItem.writeTo(writer)) {
+                writer.write('\n');
+            }
+        }
+        writer.deindent(4);
+        writer.write(".end method\n");
+    }
+
+    public Instruction findSwitchPayload(int targetOffset, Opcode type) {
+        int targetIndex;
+        try {
+            targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset);
+        } catch (InvalidInstructionOffset ex) {
+            throw new InvalidSwitchPayload(targetOffset);
+        }
+
+        //TODO: does dalvik let you pad with multiple nops?
+        //TODO: does dalvik let a switch instruction point to a non-payload instruction?
+
+        Instruction instruction = instructions.get(targetIndex);
+        if (instruction.getOpcode() != type) {
+            // maybe it's pointing to a NOP padding instruction. Look at the next instruction
+            if (instruction.getOpcode() == Opcode.NOP) {
+                targetIndex += 1;
+                if (targetIndex < instructions.size()) {
+                    instruction = instructions.get(targetIndex);
+                    if (instruction.getOpcode() == type) {
+                        return instruction;
+                    }
+                }
+            }
+            throw new InvalidSwitchPayload(targetOffset);
+        } else {
+            return instruction;
+        }
+    }
+
+    public int findPayloadOffset(int targetOffset, Opcode type) {
+        int targetIndex;
+        try {
+            targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset);
+        } catch (InvalidInstructionOffset ex) {
+            throw new InvalidSwitchPayload(targetOffset);
+        }
+
+        //TODO: does dalvik let you pad with multiple nops?
+        //TODO: does dalvik let a switch instruction point to a non-payload instruction?
+
+        Instruction instruction = instructions.get(targetIndex);
+        if (instruction.getOpcode() != type) {
+            // maybe it's pointing to a NOP padding instruction. Look at the next instruction
+            if (instruction.getOpcode() == Opcode.NOP) {
+                targetIndex += 1;
+                if (targetIndex < instructions.size()) {
+                    instruction = instructions.get(targetIndex);
+                    if (instruction.getOpcode() == type) {
+                        return instructionOffsetMap.getInstructionCodeOffset(targetIndex);
+                    }
+                }
+            }
+            throw new InvalidSwitchPayload(targetOffset);
+        } else {
+            return targetOffset;
+        }
+    }
+
+    private static void writeAccessFlagsAndRestrictions(
+            IndentingWriter writer, int accessFlags, Set<HiddenApiRestriction> hiddenApiRestrictions)
+            throws IOException {
+        for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(accessFlags)) {
+            writer.write(accessFlag.toString());
+            writer.write(' ');
+        }
+        for (HiddenApiRestriction hiddenApiRestriction : hiddenApiRestrictions) {
+            writer.write(hiddenApiRestriction.toString());
+            writer.write(' ');
+        }
+    }
+
+    private static void writeParameters(IndentingWriter writer, Method method,
+                                        List<? extends MethodParameter> parameters,
+                                        BaksmaliOptions options) throws IOException {
+        boolean isStatic = AccessFlags.STATIC.isSet(method.getAccessFlags());
+        int registerNumber = isStatic?0:1;
+        for (MethodParameter parameter: parameters) {
+            String parameterType = parameter.getType();
+            String parameterName = parameter.getName();
+            Collection<? extends Annotation> annotations = parameter.getAnnotations();
+            if ((options.debugInfo && parameterName != null) || annotations.size() != 0) {
+                writer.write(".param p");
+                writer.printSignedIntAsDec(registerNumber);
+
+                if (parameterName != null && options.debugInfo) {
+                    writer.write(", ");
+                    ReferenceFormatter.writeStringReference(writer, parameterName);
+                }
+                writer.write("    # ");
+                writer.write(parameterType);
+                writer.write("\n");
+                if (annotations.size() > 0) {
+                    writer.indent(4);
+
+                    String containingClass = null;
+                    if (options.implicitReferences) {
+                        containingClass = method.getDefiningClass();
+                    }
+                    AnnotationFormatter.writeTo(writer, annotations, containingClass);
+                    writer.deindent(4);
+                    writer.write(".end param\n");
+                }
+            }
+
+            registerNumber++;
+            if (TypeUtils.isWideType(parameterType)) {
+                registerNumber++;
+            }
+        }
+    }
+
+    @Nonnull public LabelCache getLabelCache() {
+        return labelCache;
+    }
+
+    public int getPackedSwitchBaseAddress(int packedSwitchPayloadCodeOffset) {
+        return packedSwitchMap.get(packedSwitchPayloadCodeOffset, -1);
+    }
+
+    public int getSparseSwitchBaseAddress(int sparseSwitchPayloadCodeOffset) {
+        return sparseSwitchMap.get(sparseSwitchPayloadCodeOffset, -1);
+    }
+
+    private List<MethodItem> getMethodItems() {
+        ArrayList<MethodItem> methodItems = new ArrayList<MethodItem>();
+
+        if ((classDef.options.registerInfo != 0) || (classDef.options.normalizeVirtualMethods) ||
+                (classDef.options.deodex && needsAnalyzed())) {
+            addAnalyzedInstructionMethodItems(methodItems);
+        } else {
+            addInstructionMethodItems(methodItems);
+        }
+
+        addTries(methodItems);
+        if (classDef.options.debugInfo) {
+            addDebugInfo(methodItems);
+        }
+
+        if (classDef.options.sequentialLabels) {
+            setLabelSequentialNumbers();
+        }
+
+        for (LabelMethodItem labelMethodItem: labelCache.getLabels()) {
+            methodItems.add(labelMethodItem);
+        }
+
+        Collections.sort(methodItems);
+
+        return methodItems;
+    }
+
+    private boolean needsAnalyzed() {
+        for (Instruction instruction: methodImpl.getInstructions()) {
+            if (instruction.getOpcode().odexOnly()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void addInstructionMethodItems(List<MethodItem> methodItems) {
+        int currentCodeAddress = 0;
+
+        for (int i=0; i<effectiveInstructions.size(); i++) {
+            Instruction instruction = effectiveInstructions.get(i);
+
+            MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
+                    currentCodeAddress, instruction);
+
+            methodItems.add(methodItem);
+
+            if (i != effectiveInstructions.size() - 1) {
+                methodItems.add(new BlankMethodItem(currentCodeAddress));
+            }
+
+            if (classDef.options.codeOffsets) {
+                methodItems.add(new MethodItem(currentCodeAddress) {
+
+                    @Override
+                    public double getSortOrder() {
+                        return -1000;
+                    }
+
+                    @Override
+                    public boolean writeTo(IndentingWriter writer) throws IOException {
+                        writer.write("#@");
+                        writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFFL);
+                        return true;
+                    }
+                });
+            }
+
+            if (classDef.options.accessorComments && classDef.options.syntheticAccessorResolver != null &&
+                    (instruction instanceof ReferenceInstruction)) {
+                Opcode opcode = instruction.getOpcode();
+
+                if (opcode.referenceType == ReferenceType.METHOD) {
+                    MethodReference methodReference =
+                            (MethodReference)((ReferenceInstruction)instruction).getReference();
+
+                    try {
+                        methodReference.validateReference();
+
+                        if (SyntheticAccessorResolver.looksLikeSyntheticAccessor(methodReference.getName())) {
+                            AccessedMember accessedMember =
+                                    classDef.options.syntheticAccessorResolver.getAccessedMember(methodReference);
+                            if (accessedMember != null) {
+                                methodItems.add(new SyntheticAccessCommentMethodItem(accessedMember, currentCodeAddress));
+                            }
+                        }
+                    } catch (Reference.InvalidReferenceException e) {
+                        // Just ignore for now. We'll deal with it when processing the instruction
+                    }
+                }
+            }
+
+            currentCodeAddress += instruction.getCodeUnits();
+        }
+    }
+
+    private void addAnalyzedInstructionMethodItems(List<MethodItem> methodItems) {
+        MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classDef.options.classPath, method,
+                classDef.options.inlineResolver, classDef.options.normalizeVirtualMethods);
+
+        AnalysisException analysisException = methodAnalyzer.getAnalysisException();
+        if (analysisException != null) {
+            // TODO: need to keep track of whether any errors occurred, so we can exit with a non-zero result
+            methodItems.add(new CommentMethodItem(
+                    String.format("AnalysisException: %s", analysisException.getMessage()),
+                    analysisException.codeAddress, Integer.MIN_VALUE));
+            analysisException.printStackTrace(System.err);
+        }
+
+        List<AnalyzedInstruction> instructions = methodAnalyzer.getAnalyzedInstructions();
+
+        int currentCodeAddress = 0;
+        for (int i=0; i<instructions.size(); i++) {
+            AnalyzedInstruction instruction = instructions.get(i);
+
+            MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(
+                    this, currentCodeAddress, instruction.getInstruction());
+
+            methodItems.add(methodItem);
+
+            if (instruction.getInstruction().getOpcode().format == Format.UnresolvedOdexInstruction) {
+                methodItems.add(new CommentedOutMethodItem(
+                        InstructionMethodItemFactory.makeInstructionFormatMethodItem(
+                                this, currentCodeAddress, instruction.getOriginalInstruction())));
+            }
+
+            if (i != instructions.size() - 1) {
+                methodItems.add(new BlankMethodItem(currentCodeAddress));
+            }
+
+            if (classDef.options.codeOffsets) {
+                methodItems.add(new MethodItem(currentCodeAddress) {
+
+                    @Override
+                    public double getSortOrder() {
+                        return -1000;
+                    }
+
+                    @Override
+                    public boolean writeTo(IndentingWriter writer) throws IOException {
+                        writer.write("#@");
+                        writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFFL);
+                        return true;
+                    }
+                });
+            }
+
+            if (classDef.options.registerInfo != 0 &&
+                    !instruction.getInstruction().getOpcode().format.isPayloadFormat) {
+                methodItems.add(
+                        new PreInstructionRegisterInfoMethodItem(classDef.options.registerInfo,
+                                methodAnalyzer, registerFormatter, instruction, currentCodeAddress));
+
+                methodItems.add(
+                        new PostInstructionRegisterInfoMethodItem(registerFormatter, instruction, currentCodeAddress));
+            }
+
+            currentCodeAddress += instruction.getInstruction().getCodeUnits();
+        }
+    }
+
+    private void addTries(List<MethodItem> methodItems) {
+        List<? extends TryBlock<? extends ExceptionHandler>> tryBlocks = methodImpl.getTryBlocks();
+        if (tryBlocks.size() == 0) {
+            return;
+        }
+
+        int lastInstructionAddress = instructionOffsetMap.getInstructionCodeOffset(instructions.size() - 1);
+        int codeSize = lastInstructionAddress + instructions.get(instructions.size() - 1).getCodeUnits();
+
+        for (TryBlock<? extends ExceptionHandler> tryBlock: tryBlocks) {
+            int startAddress = tryBlock.getStartCodeAddress();
+            int endAddress = startAddress + tryBlock.getCodeUnitCount();
+
+            if (startAddress >= codeSize) {
+                throw new RuntimeException(String.format("Try start offset %d is past the end of the code block.",
+                        startAddress));
+            }
+            // Note: not >=. endAddress == codeSize is valid, when the try covers the last instruction
+            if (endAddress > codeSize) {
+                throw new RuntimeException(String.format("Try end offset %d is past the end of the code block.",
+                        endAddress));
+            }
+
+            /**
+             * The end address points to the address immediately after the end of the last
+             * instruction that the try block covers. We want the .catch directive and end_try
+             * label to be associated with the last covered instruction, so we need to get
+             * the address for that instruction
+             */
+
+            int lastCoveredIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(endAddress - 1, false);
+            int lastCoveredAddress = instructionOffsetMap.getInstructionCodeOffset(lastCoveredIndex);
+
+            for (ExceptionHandler handler: tryBlock.getExceptionHandlers()) {
+                int handlerAddress = handler.getHandlerCodeAddress();
+                if (handlerAddress >= codeSize) {
+                    throw new ExceptionWithContext(
+                            "Exception handler offset %d is past the end of the code block.", handlerAddress);
+                }
+
+                //use the address from the last covered instruction
+                CatchMethodItem catchMethodItem = new CatchMethodItem(classDef.options, labelCache, lastCoveredAddress,
+                        handler.getExceptionType(), startAddress, endAddress, handlerAddress);
+                methodItems.add(catchMethodItem);
+            }
+        }
+    }
+
+    private void addDebugInfo(final List<MethodItem> methodItems) {
+        for (DebugItem debugItem: methodImpl.getDebugItems()) {
+            methodItems.add(DebugMethodItem.build(registerFormatter, debugItem));
+        }
+    }
+
+    private void setLabelSequentialNumbers() {
+        HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>();
+        ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels());
+
+        //sort the labels by their location in the method
+        Collections.sort(sortedLabels);
+
+        for (LabelMethodItem labelMethodItem: sortedLabels) {
+            Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix());
+            if (labelSequence == null) {
+                labelSequence = 0;
+            }
+            labelMethodItem.setLabelSequence(labelSequence);
+            nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1);
+        }
+    }
+
+    @Nullable
+    private String getContainingClassForImplicitReference() {
+        if (classDef.options.implicitReferences) {
+            return classDef.classDef.getType();
+        }
+        return null;
+    }
+
+    public static class LabelCache {
+        protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>();
+
+        public LabelCache() {
+        }
+
+        public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) {
+            LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem);
+            if (internedLabelMethodItem != null) {
+                return internedLabelMethodItem;
+            }
+            labels.put(labelMethodItem, labelMethodItem);
+            return labelMethodItem;
+        }
+
+
+        public Collection<LabelMethodItem> getLabels() {
+            return labels.values();
+        }
+    }
+
+    public static class InvalidSwitchPayload extends ExceptionWithContext {
+        private final int payloadOffset;
+
+        public InvalidSwitchPayload(int payloadOffset) {
+            super("No switch payload at offset: %d", payloadOffset);
+            this.payloadOffset = payloadOffset;
+        }
+
+        public int getPayloadOffset() {
+            return payloadOffset;
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodItem.java
new file mode 100644
index 0000000..60358c5
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodItem.java
@@ -0,0 +1,59 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors;
+
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+
+public abstract class MethodItem implements Comparable<MethodItem> {
+    protected final int codeAddress;
+
+    protected MethodItem(int codeAddress) {
+        this.codeAddress = codeAddress;
+    }
+
+    public int getCodeAddress() {
+        return codeAddress;
+    }
+
+    //return an arbitrary double that determines how this item will be sorted with others at the same address
+    public abstract double getSortOrder();
+
+    public int compareTo(MethodItem methodItem) {
+        int result = ((Integer) codeAddress).compareTo(methodItem.codeAddress);
+
+        if (result == 0){
+            return ((Double)getSortOrder()).compareTo(methodItem.getSortOrder());
+        }
+        return result;
+    }
+
+    public abstract boolean writeTo(IndentingWriter writer) throws IOException;
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/PostInstructionRegisterInfoMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/PostInstructionRegisterInfoMethodItem.java
new file mode 100644
index 0000000..62826b1
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/PostInstructionRegisterInfoMethodItem.java
@@ -0,0 +1,102 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 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.baksmali.Adaptors;
+
+import org.jf.baksmali.BaksmaliOptions;
+import org.jf.dexlib2.analysis.AnalyzedInstruction;
+import org.jf.dexlib2.analysis.RegisterType;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.BitSet;
+
+public class PostInstructionRegisterInfoMethodItem extends MethodItem {
+    @Nonnull private final RegisterFormatter registerFormatter;
+    @Nonnull private final AnalyzedInstruction analyzedInstruction;
+
+    public PostInstructionRegisterInfoMethodItem(@Nonnull RegisterFormatter registerFormatter,
+                                                 @Nonnull AnalyzedInstruction analyzedInstruction,
+                                                 int codeAddress) {
+        super(codeAddress);
+        this.registerFormatter = registerFormatter;
+        this.analyzedInstruction = analyzedInstruction;
+    }
+
+    @Override
+    public double getSortOrder() {
+        return 100.1;
+    }
+
+    @Override
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        int registerInfo = registerFormatter.options.registerInfo;
+        int registerCount = analyzedInstruction.getRegisterCount();
+        BitSet registers = new BitSet(registerCount);
+
+        if ((registerInfo & BaksmaliOptions.ALL) != 0) {
+            registers.set(0, registerCount);
+        } else {
+            if ((registerInfo & BaksmaliOptions.ALLPOST) != 0) {
+                registers.set(0, registerCount);
+            } else if ((registerInfo & BaksmaliOptions.DEST) != 0) {
+                addDestRegs(registers, registerCount);
+            }
+        }
+
+        return writeRegisterInfo(writer, registers);
+    }
+
+    private void addDestRegs(BitSet printPostRegister, int registerCount) {
+        for (int registerNum=0; registerNum<registerCount; registerNum++) {
+            if (!analyzedInstruction.getPreInstructionRegisterType(registerNum).equals(
+                    analyzedInstruction.getPostInstructionRegisterType(registerNum))) {
+                printPostRegister.set(registerNum);
+            }
+        }
+    }
+
+    private boolean writeRegisterInfo(IndentingWriter writer, BitSet registers) throws IOException {
+        int registerNum = registers.nextSetBit(0);
+        if (registerNum < 0) {
+            return false;
+        }
+
+        writer.write('#');
+        for (; registerNum >= 0; registerNum = registers.nextSetBit(registerNum + 1)) {
+            RegisterType registerType = analyzedInstruction.getPostInstructionRegisterType(registerNum);
+
+            registerFormatter.writeTo(writer, registerNum);
+            writer.write('=');
+            registerType.writeTo(writer);
+            writer.write(';');
+        }
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/PreInstructionRegisterInfoMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/PreInstructionRegisterInfoMethodItem.java
new file mode 100644
index 0000000..f934edd
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/PreInstructionRegisterInfoMethodItem.java
@@ -0,0 +1,244 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 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.baksmali.Adaptors;
+
+import org.jf.baksmali.BaksmaliOptions;
+import org.jf.dexlib2.analysis.AnalyzedInstruction;
+import org.jf.dexlib2.analysis.MethodAnalyzer;
+import org.jf.dexlib2.analysis.RegisterType;
+import org.jf.dexlib2.iface.instruction.*;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.BitSet;
+
+public class PreInstructionRegisterInfoMethodItem extends MethodItem {
+    private final int registerInfo;
+    @Nonnull private final MethodAnalyzer methodAnalyzer;
+    @Nonnull private final RegisterFormatter registerFormatter;
+    @Nonnull private final AnalyzedInstruction analyzedInstruction;
+
+    public PreInstructionRegisterInfoMethodItem(int registerInfo,
+                                                @Nonnull MethodAnalyzer methodAnalyzer,
+                                                @Nonnull RegisterFormatter registerFormatter,
+                                                @Nonnull AnalyzedInstruction analyzedInstruction,
+                                                int codeAddress) {
+        super(codeAddress);
+        this.registerInfo = registerInfo;
+        this.methodAnalyzer = methodAnalyzer;
+        this.registerFormatter = registerFormatter;
+        this.analyzedInstruction = analyzedInstruction;
+    }
+
+    @Override
+    public double getSortOrder() {
+        return 99.9;
+    }
+
+    @Override
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        int registerCount = analyzedInstruction.getRegisterCount();
+        BitSet registers = new BitSet(registerCount);
+        BitSet mergeRegisters = null;
+
+        if ((registerInfo & BaksmaliOptions.ALL) != 0) {
+            registers.set(0, registerCount);
+        } else {
+            if ((registerInfo & BaksmaliOptions.ALLPRE) != 0) {
+                registers.set(0, registerCount);
+            } else {
+                if ((registerInfo & BaksmaliOptions.ARGS) != 0) {
+                    addArgsRegs(registers);
+                }
+                if ((registerInfo & BaksmaliOptions.MERGE) != 0) {
+                    if (analyzedInstruction.isBeginningInstruction()) {
+                        addParamRegs(registers, registerCount);
+                    }
+                    mergeRegisters = new BitSet(registerCount);
+                    addMergeRegs(mergeRegisters, registerCount);
+                } else if ((registerInfo & BaksmaliOptions.FULLMERGE) != 0 &&
+                        (analyzedInstruction.isBeginningInstruction())) {
+                    addParamRegs(registers, registerCount);
+                }
+            }
+        }
+
+        if ((registerInfo & BaksmaliOptions.FULLMERGE) != 0) {
+            if (mergeRegisters == null) {
+                mergeRegisters = new BitSet(registerCount);
+                addMergeRegs(mergeRegisters, registerCount);
+            }
+            registers.or(mergeRegisters);
+        } else if (mergeRegisters != null) {
+            registers.or(mergeRegisters);
+            mergeRegisters = null;
+        }
+
+        return writeRegisterInfo(writer, registers, mergeRegisters);
+    }
+
+    private void addArgsRegs(BitSet registers) {
+        if (analyzedInstruction.getInstruction() instanceof RegisterRangeInstruction) {
+            RegisterRangeInstruction instruction = (RegisterRangeInstruction)analyzedInstruction.getInstruction();
+
+            registers.set(instruction.getStartRegister(),
+                    instruction.getStartRegister() + instruction.getRegisterCount());
+        } else if (analyzedInstruction.getInstruction() instanceof FiveRegisterInstruction) {
+            FiveRegisterInstruction instruction = (FiveRegisterInstruction)analyzedInstruction.getInstruction();
+            int regCount = instruction.getRegisterCount();
+            switch (regCount) {
+                case 5:
+                    registers.set(instruction.getRegisterG());
+                    //fall through
+                case 4:
+                    registers.set(instruction.getRegisterF());
+                    //fall through
+                case 3:
+                    registers.set(instruction.getRegisterE());
+                    //fall through
+                case 2:
+                    registers.set(instruction.getRegisterD());
+                    //fall through
+                case 1:
+                    registers.set(instruction.getRegisterC());
+            }
+        } else if (analyzedInstruction.getInstruction() instanceof ThreeRegisterInstruction) {
+            ThreeRegisterInstruction instruction = (ThreeRegisterInstruction)analyzedInstruction.getInstruction();
+            registers.set(instruction.getRegisterA());
+            registers.set(instruction.getRegisterB());
+            registers.set(instruction.getRegisterC());
+        } else if (analyzedInstruction.getInstruction() instanceof TwoRegisterInstruction) {
+            TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.getInstruction();
+            registers.set(instruction.getRegisterA());
+            registers.set(instruction.getRegisterB());
+        } else if (analyzedInstruction.getInstruction() instanceof OneRegisterInstruction) {
+            OneRegisterInstruction instruction = (OneRegisterInstruction)analyzedInstruction.getInstruction();
+            registers.set(instruction.getRegisterA());
+        }
+    }
+
+    private void addMergeRegs(BitSet registers, int registerCount) {
+        if (analyzedInstruction.getPredecessorCount() <= 1) {
+            //in the common case of an instruction that only has a single predecessor which is the previous
+            //instruction, the pre-instruction registers will always match the previous instruction's
+            //post-instruction registers
+            return;
+        }
+
+        for (int registerNum=0; registerNum<registerCount; registerNum++) {
+            RegisterType mergedRegisterType = analyzedInstruction.getPreInstructionRegisterType(registerNum);
+
+            for (AnalyzedInstruction predecessor: analyzedInstruction.getPredecessors()) {
+                RegisterType predecessorRegisterType = analyzedInstruction.getPredecessorRegisterType(
+                        predecessor, registerNum);
+                if (predecessorRegisterType.category != RegisterType.UNKNOWN &&
+                        !predecessorRegisterType.equals(mergedRegisterType)) {
+                    registers.set(registerNum);
+                }
+            }
+        }
+    }
+
+    private void addParamRegs(BitSet registers, int registerCount) {
+        int parameterRegisterCount = methodAnalyzer.getParamRegisterCount();
+        registers.set(registerCount-parameterRegisterCount, registerCount);
+    }
+
+    private void writeFullMerge(IndentingWriter writer, int registerNum) throws IOException {
+        registerFormatter.writeTo(writer, registerNum);
+        writer.write('=');
+        analyzedInstruction.getPreInstructionRegisterType(registerNum).writeTo(writer);
+        writer.write(":merge{");
+
+        boolean first = true;
+
+        for (AnalyzedInstruction predecessor: analyzedInstruction.getPredecessors()) {
+            RegisterType predecessorRegisterType = analyzedInstruction.getPredecessorRegisterType(
+                    predecessor, registerNum);
+
+            if (!first) {
+                writer.write(',');
+            }
+
+            if (predecessor.getInstructionIndex() == -1) {
+                //the fake "StartOfMethod" instruction
+                writer.write("Start:");
+            } else {
+                writer.write("0x");
+                writer.printUnsignedLongAsHex(methodAnalyzer.getInstructionAddress(predecessor));
+                writer.write(':');
+            }
+            predecessorRegisterType.writeTo(writer);
+
+            first = false;
+        }
+        writer.write('}');
+    }
+
+    private boolean writeRegisterInfo(IndentingWriter writer, BitSet registers,
+                                      BitSet fullMergeRegisters) throws IOException {
+        boolean firstRegister = true;
+        boolean previousWasFullMerge = false;
+        int registerNum = registers.nextSetBit(0);
+        if (registerNum < 0) {
+            return false;
+        }
+
+        writer.write('#');
+        for (; registerNum >= 0; registerNum = registers.nextSetBit(registerNum + 1)) {
+            boolean fullMerge = fullMergeRegisters!=null && fullMergeRegisters.get(registerNum);
+            if (fullMerge) {
+                if (!firstRegister) {
+                    writer.write('\n');
+                    writer.write('#');
+                }
+                writeFullMerge(writer, registerNum);
+                previousWasFullMerge = true;
+            } else {
+                if (previousWasFullMerge) {
+                    writer.write('\n');
+                    writer.write('#');
+                    previousWasFullMerge = false;
+                }
+
+                RegisterType registerType = analyzedInstruction.getPreInstructionRegisterType(registerNum);
+
+                registerFormatter.writeTo(writer, registerNum);
+                writer.write('=');
+
+                registerType.writeTo(writer);
+                writer.write(';');
+            }
+
+            firstRegister = false;
+        }
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/ReferenceFormatter.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/ReferenceFormatter.java
new file mode 100644
index 0000000..bfb71e8
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/ReferenceFormatter.java
@@ -0,0 +1,99 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 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.baksmali.Adaptors;
+
+import org.jf.baksmali.Adaptors.EncodedValue.EncodedValueAdaptor;
+import org.jf.dexlib2.MethodHandleType;
+import org.jf.dexlib2.ReferenceType;
+import org.jf.dexlib2.iface.reference.*;
+import org.jf.dexlib2.iface.value.EncodedValue;
+import org.jf.dexlib2.util.ReferenceUtil;
+import org.jf.util.IndentingWriter;
+import org.jf.util.StringUtils;
+
+import java.io.IOException;
+
+public class ReferenceFormatter {
+    public static void writeStringReference(IndentingWriter writer, String item) throws IOException {
+        writer.write('"');
+        StringUtils.writeEscapedString(writer, item);
+        writer.write('"');
+    }
+
+    public static void writeCallSiteReference(IndentingWriter writer, CallSiteReference callSite) throws IOException {
+        writer.write(callSite.getName());
+        writer.write('(');
+        writer.write('"');
+        StringUtils.writeEscapedString(writer, callSite.getMethodName());
+        writer.write("\", ");
+        writeReference(writer, ReferenceType.METHOD_PROTO, callSite.getMethodProto());
+
+        for (EncodedValue encodedValue : callSite.getExtraArguments()) {
+            writer.write(", ");
+            EncodedValueAdaptor.writeTo(writer, encodedValue, null);
+        }
+        writer.write(")@");
+        MethodHandleReference methodHandle = callSite.getMethodHandle();
+        if (methodHandle.getMethodHandleType() != MethodHandleType.INVOKE_STATIC) {
+            throw new IllegalArgumentException("The linker method handle for a call site must be of type invoke-static");
+        }
+        writeReference(writer, ReferenceType.METHOD, callSite.getMethodHandle().getMemberReference());
+    }
+
+    public static void writeReference(IndentingWriter writer, int referenceType,
+                                      Reference reference) throws IOException {
+        switch (referenceType) {
+            case ReferenceType.STRING:
+                writeStringReference(writer, ((StringReference)reference).getString());
+                return;
+            case ReferenceType.TYPE:
+                writer.write(((TypeReference)reference).getType());
+                return;
+            case ReferenceType.METHOD:
+                ReferenceUtil.writeMethodDescriptor(writer, (MethodReference)reference);
+                return;
+            case ReferenceType.FIELD:
+                ReferenceUtil.writeFieldDescriptor(writer, (FieldReference)reference);
+                return;
+            case ReferenceType.METHOD_PROTO:
+                ReferenceUtil.writeMethodProtoDescriptor(writer, (MethodProtoReference)reference);
+                return;
+            case ReferenceType.METHOD_HANDLE:
+                ReferenceUtil.writeMethodHandle(writer, (MethodHandleReference)reference);
+                return;
+            case ReferenceType.CALL_SITE:
+                // We can't use ReferenceUtil.writeCallSite here, because it doesn't write encoded values out in the
+                // exact format we need here.
+                writeCallSiteReference(writer, (CallSiteReference)reference);
+                return;
+            default:
+                throw new IllegalStateException("Unknown reference type");
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/RegisterFormatter.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/RegisterFormatter.java
new file mode 100644
index 0000000..3d72f46
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/RegisterFormatter.java
@@ -0,0 +1,99 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Adaptors;
+
+import org.jf.baksmali.BaksmaliOptions;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+
+/**
+ * This class contains the logic used for formatting registers
+ */
+public class RegisterFormatter {
+    @Nonnull public final BaksmaliOptions options;
+    public final int registerCount;
+    public final int parameterRegisterCount;
+
+    public RegisterFormatter(@Nonnull BaksmaliOptions options, int registerCount, int parameterRegisterCount) {
+        this.options = options;
+        this.registerCount = registerCount;
+        this.parameterRegisterCount = parameterRegisterCount;
+    }
+
+    /**
+     * Write out the register range value used by Format3rc. If baksmali.noParameterRegisters is true, it will always
+     * output the registers in the v<n> format. But if false, then it will check if *both* registers are parameter
+     * registers, and if so, use the p<n> format for both. If only the last register is a parameter register, it will
+     * use the v<n> format for both, otherwise it would be confusing to have something like {v20 .. p1}
+     * @param writer the <code>IndentingWriter</code> to write to
+     * @param startRegister the first register in the range
+     * @param lastRegister the last register in the range
+     */
+    public void writeRegisterRange(IndentingWriter writer, int startRegister, int lastRegister) throws IOException {
+        if (options.parameterRegisters) {
+            assert startRegister <= lastRegister;
+
+            if (startRegister >= registerCount - parameterRegisterCount) {
+                writer.write("{p");
+                writer.printSignedIntAsDec(startRegister - (registerCount - parameterRegisterCount));
+                writer.write(" .. p");
+                writer.printSignedIntAsDec(lastRegister - (registerCount - parameterRegisterCount));
+                writer.write('}');
+                return;
+            }
+        }
+        writer.write("{v");
+        writer.printSignedIntAsDec(startRegister);
+        writer.write(" .. v");
+        writer.printSignedIntAsDec(lastRegister);
+        writer.write('}');
+    }
+
+    /**
+     * Writes a register with the appropriate format. If baksmali.noParameterRegisters is true, then it will always
+     * output a register in the v<n> format. If false, then it determines if the register is a parameter register,
+     * and if so, formats it in the p<n> format instead.
+     *
+     * @param writer the <code>IndentingWriter</code> to write to
+     * @param register the register number
+     */
+    public void writeTo(IndentingWriter writer, int register) throws IOException {
+        if (options.parameterRegisters) {
+            if (register >= registerCount - parameterRegisterCount) {
+                writer.write('p');
+                writer.printSignedIntAsDec((register - (registerCount - parameterRegisterCount)));
+                return;
+            }
+        }
+        writer.write('v');
+        writer.printSignedIntAsDec(register);
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/SyntheticAccessCommentMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/SyntheticAccessCommentMethodItem.java
new file mode 100644
index 0000000..ef0abb7
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/SyntheticAccessCommentMethodItem.java
@@ -0,0 +1,121 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2011 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.baksmali.Adaptors;
+
+import org.jf.dexlib2.ReferenceType;
+import org.jf.dexlib2.util.SyntheticAccessorResolver;
+import org.jf.util.ExceptionWithContext;
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+
+public class SyntheticAccessCommentMethodItem extends MethodItem {
+    private final SyntheticAccessorResolver.AccessedMember accessedMember;
+
+    public SyntheticAccessCommentMethodItem(SyntheticAccessorResolver.AccessedMember accessedMember, int codeAddress) {
+        super(codeAddress);
+        this.accessedMember = accessedMember;
+    }
+
+    public double getSortOrder() {
+        //just before the pre-instruction register information, if any
+        return 99.8;
+    }
+
+    public boolean writeTo(IndentingWriter writer) throws IOException {
+        writer.write("# ");
+        switch (accessedMember.accessedMemberType) {
+            case SyntheticAccessorResolver.METHOD:
+                writer.write("invokes: ");
+                break;
+            case SyntheticAccessorResolver.GETTER:
+                writer.write("getter for: ");
+                break;
+            case SyntheticAccessorResolver.SETTER:
+                writer.write("setter for: ");
+                break;
+            case SyntheticAccessorResolver.PREFIX_INCREMENT:
+                writer.write("++operator for: ");
+                break;
+            case SyntheticAccessorResolver.POSTFIX_INCREMENT:
+                writer.write("operator++ for: ");
+                break;
+            case SyntheticAccessorResolver.PREFIX_DECREMENT:
+                writer.write("--operator for: ");
+                break;
+            case SyntheticAccessorResolver.POSTFIX_DECREMENT:
+                writer.write("operator-- for: ");
+                break;
+            case SyntheticAccessorResolver.ADD_ASSIGNMENT:
+                writer.write("+= operator for: ");
+                break;
+            case SyntheticAccessorResolver.SUB_ASSIGNMENT:
+                writer.write("-= operator for: ");
+                break;
+            case SyntheticAccessorResolver.MUL_ASSIGNMENT:
+                writer.write("*= operator for: ");
+                break;
+            case SyntheticAccessorResolver.DIV_ASSIGNMENT:
+                writer.write("/= operator for: ");
+                break;
+            case SyntheticAccessorResolver.REM_ASSIGNMENT:
+                writer.write("%= operator for: ");
+                break;
+            case SyntheticAccessorResolver.AND_ASSIGNMENT:
+                writer.write("&= operator for: ");
+                break;
+            case SyntheticAccessorResolver.OR_ASSIGNMENT:
+                writer.write("|= operator for: ");
+                break;
+            case SyntheticAccessorResolver.XOR_ASSIGNMENT:
+                writer.write("^= operator for: ");
+                break;
+            case SyntheticAccessorResolver.SHL_ASSIGNMENT:
+                writer.write("<<= operator for: ");
+                break;
+            case SyntheticAccessorResolver.SHR_ASSIGNMENT:
+                writer.write(">>= operator for: ");
+                break;
+            case SyntheticAccessorResolver.USHR_ASSIGNMENT:
+                writer.write(">>>= operator for: ");
+                break;
+            default:
+                throw new ExceptionWithContext("Unknown access type: %d", accessedMember.accessedMemberType);
+        }
+
+        int referenceType;
+        if (accessedMember.accessedMemberType == SyntheticAccessorResolver.METHOD) {
+            referenceType = ReferenceType.METHOD;
+        } else {
+            referenceType = ReferenceType.FIELD;
+        }
+        ReferenceFormatter.writeReference(writer, referenceType, accessedMember.accessedMember);
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/AnalysisArguments.java b/baksmali/src/main/java/org/jf/baksmali/AnalysisArguments.java
new file mode 100644
index 0000000..d1e1684
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/AnalysisArguments.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.Parameter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.jf.dexlib2.VersionMap;
+import org.jf.dexlib2.analysis.ClassPath;
+import org.jf.dexlib2.analysis.ClassPathResolver;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.dexbacked.OatFile;
+import org.jf.dexlib2.iface.MultiDexContainer;
+import org.jf.util.jcommander.ColonParameterSplitter;
+import org.jf.util.jcommander.ExtendedParameter;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import static org.jf.dexlib2.analysis.ClassPath.NOT_SPECIFIED;
+
+public class AnalysisArguments {
+    @Parameter(names = {"-b", "--bootclasspath", "--bcp"},
+            description = "A colon separated list of the files to include in the bootclasspath when analyzing the " +
+                    "dex file. If not specified, baksmali will attempt to choose an " +
+                    "appropriate default. When analyzing oat files, this can simply be the path to the device's " +
+                    "boot.oat file. A single empty string can be used to specify that an empty bootclasspath should " +
+                    "be used. (e.g. --bootclasspath \"\") See baksmali help classpath for more information.",
+            splitter = ColonParameterSplitter.class)
+    @ExtendedParameter(argumentNames = "classpath")
+    public List<String> bootClassPath = null;
+
+    @Parameter(names = {"-c", "--classpath", "--cp"},
+            description = "A colon separated list of additional files to include in the classpath when analyzing the " +
+                    "dex file. These will be added to the classpath after any bootclasspath entries.",
+            splitter = ColonParameterSplitter.class)
+    @ExtendedParameter(argumentNames = "classpath")
+    public List<String> classPath = Lists.newArrayList();
+
+    @Parameter(names = {"-d", "--classpath-dir", "--cpd", "--dir"},
+            description = "A directory to search for classpath files. This option can be used multiple times to " +
+                    "specify multiple directories to search. They will be searched in the order they are provided.")
+    @ExtendedParameter(argumentNames = "dir")
+    public List<String> classPathDirectories = null;
+
+    public static class CheckPackagePrivateArgument {
+        @Parameter(names = {"--check-package-private-access", "--package-private", "--checkpp", "--pp"},
+                description = "Use the package-private access check when calculating vtable indexes. This is enabled " +
+                        "by default for oat files. For odex files, this is only needed for odexes from 4.2.0. It " +
+                        "was reverted in 4.2.1.")
+        public boolean checkPackagePrivateAccess = false;
+    }
+
+    @Nonnull
+    public ClassPath loadClassPathForDexFile(@Nonnull File dexFileDir,
+                                             @Nonnull MultiDexContainer.DexEntry<? extends DexBackedDexFile> dexEntry,
+                                             boolean checkPackagePrivateAccess) throws IOException {
+        return loadClassPathForDexFile(dexFileDir, dexEntry, checkPackagePrivateAccess, NOT_SPECIFIED);
+    }
+
+    @Nonnull
+    public ClassPath loadClassPathForDexFile(@Nonnull File dexFileDir,
+                                             @Nonnull MultiDexContainer.DexEntry<? extends DexBackedDexFile> dexEntry,
+                                             boolean checkPackagePrivateAccess, int oatVersion)
+            throws IOException {
+        ClassPathResolver resolver;
+
+        MultiDexContainer<? extends DexBackedDexFile> container = dexEntry.getContainer();
+
+        if (oatVersion == NOT_SPECIFIED) {
+            if (container instanceof OatFile) {
+                checkPackagePrivateAccess = true;
+                oatVersion = ((OatFile) container).getOatVersion();
+            } else {
+                oatVersion = VersionMap.mapApiToArtVersion(dexEntry.getDexFile().getOpcodes().api);
+            }
+        } else {
+            // this should always be true for ART
+            checkPackagePrivateAccess = true;
+        }
+
+        if (classPathDirectories == null || classPathDirectories.size() == 0) {
+            classPathDirectories = Lists.newArrayList(dexFileDir.getPath());
+        }
+
+        List<String> filteredClassPathDirectories = Lists.newArrayList();
+        if (classPathDirectories != null) {
+            for (String dir: classPathDirectories) {
+                File file = new File(dir);
+                if (!file.exists()) {
+                    System.err.println(String.format("Warning: directory %s does not exist. Ignoring.", dir));
+                } else if (!file.isDirectory()) {
+                    System.err.println(String.format("Warning: %s is not a directory. Ignoring.", dir));
+                } else {
+                    filteredClassPathDirectories.add(dir);
+                }
+            }
+        }
+
+        if (bootClassPath == null) {
+            // TODO: we should be able to get the api from the Opcodes object associated with the dexFile..
+            // except that the oat version -> api mapping doesn't fully work yet
+            resolver = new ClassPathResolver(filteredClassPathDirectories, classPath, dexEntry);
+        }  else if (bootClassPath.size() == 1 && bootClassPath.get(0).length() == 0) {
+            // --bootclasspath "" is a special case, denoting that no bootclasspath should be used
+            resolver = new ClassPathResolver(
+                    ImmutableList.<String>of(), ImmutableList.<String>of(), classPath, dexEntry);
+        } else {
+            resolver = new ClassPathResolver(filteredClassPathDirectories, bootClassPath, classPath, dexEntry);
+        }
+
+        if (oatVersion == 0 && container instanceof OatFile) {
+            oatVersion = ((OatFile) container).getOatVersion();
+        }
+        return new ClassPath(resolver.getResolvedClassProviders(), checkPackagePrivateAccess, oatVersion);
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Baksmali.java b/baksmali/src/main/java/org/jf/baksmali/Baksmali.java
new file mode 100644
index 0000000..1c0231b
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Baksmali.java
@@ -0,0 +1,173 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+import org.jf.baksmali.Adaptors.ClassDefinition;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.DexFile;
+import org.jf.util.ClassFileNameHandler;
+import org.jf.util.IndentingWriter;
+
+import javax.annotation.Nullable;
+import java.io.*;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.*;
+
+public class Baksmali {
+    public static boolean disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options) {
+        return disassembleDexFile(dexFile, outputDir, jobs, options, null);
+    }
+
+    public static boolean disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options,
+                                             @Nullable List<String> classes) {
+
+        //sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file
+        //name collisions, then we'll use the same name for each class, if the dex file goes through multiple
+        //baksmali/smali cycles for some reason. If a class with a colliding name is added or removed, the filenames
+        //may still change of course
+        List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());
+
+        final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDir, ".smali");
+
+        ExecutorService executor = Executors.newFixedThreadPool(jobs);
+        List<Future<Boolean>> tasks = Lists.newArrayList();
+
+        Set<String> classSet = null;
+        if (classes != null) {
+            classSet = new HashSet<String>(classes);
+        }
+
+        for (final ClassDef classDef: classDefs) {
+            if (classSet != null && !classSet.contains(classDef.getType())) {
+                continue;
+            }
+            tasks.add(executor.submit(new Callable<Boolean>() {
+                @Override public Boolean call() throws Exception {
+                    return disassembleClass(classDef, fileNameHandler, options);
+                }
+            }));
+        }
+
+        boolean errorOccurred = false;
+        try {
+            for (Future<Boolean> task: tasks) {
+                while(true) {
+                    try {
+                        if (!task.get()) {
+                            errorOccurred = true;
+                        }
+                    } catch (InterruptedException ex) {
+                        continue;
+                    } catch (ExecutionException ex) {
+                        throw new RuntimeException(ex);
+                    }
+                    break;
+                }
+            }
+        } finally {
+            executor.shutdown();
+        }
+        return !errorOccurred;
+    }
+
+    private static boolean disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler,
+                                            BaksmaliOptions options) {
+        /**
+         * The path for the disassembly file is based on the package name
+         * The class descriptor will look something like:
+         * Ljava/lang/Object;
+         * Where the there is leading 'L' and a trailing ';', and the parts of the
+         * package name are separated by '/'
+         */
+        String classDescriptor = classDef.getType();
+
+        //validate that the descriptor is formatted like we expect
+        if (classDescriptor.charAt(0) != 'L' ||
+                classDescriptor.charAt(classDescriptor.length()-1) != ';') {
+            System.err.println("Unrecognized class descriptor - " + classDescriptor + " - skipping class");
+            return false;
+        }
+
+        File smaliFile = fileNameHandler.getUniqueFilenameForClass(classDescriptor);
+
+        //create and initialize the top level string template
+        ClassDefinition classDefinition = new ClassDefinition(options, classDef);
+
+        //write the disassembly
+        Writer writer = null;
+        try
+        {
+            File smaliParent = smaliFile.getParentFile();
+            if (!smaliParent.exists()) {
+                if (!smaliParent.mkdirs()) {
+                    // check again, it's likely it was created in a different thread
+                    if (!smaliParent.exists()) {
+                        System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class");
+                        return false;
+                    }
+                }
+            }
+
+            if (!smaliFile.exists()){
+                if (!smaliFile.createNewFile()) {
+                    System.err.println("Unable to create file " + smaliFile.toString() + " - skipping class");
+                    return false;
+                }
+            }
+
+            BufferedWriter bufWriter = new BufferedWriter(new OutputStreamWriter(
+                    new FileOutputStream(smaliFile), "UTF8"));
+
+            writer = new IndentingWriter(bufWriter);
+            classDefinition.writeTo((IndentingWriter)writer);
+        } catch (Exception ex) {
+            System.err.println("\n\nError occurred while disassembling class " + classDescriptor.replace('/', '.') + " - skipping class");
+            ex.printStackTrace();
+            // noinspection ResultOfMethodCallIgnored
+            smaliFile.delete();
+            return false;
+        }
+        finally
+        {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (Throwable ex) {
+                    System.err.println("\n\nError occurred while closing file " + smaliFile.toString());
+                    ex.printStackTrace();
+                }
+            }
+        }
+        return true;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/BaksmaliOptions.java b/baksmali/src/main/java/org/jf/baksmali/BaksmaliOptions.java
new file mode 100644
index 0000000..7ad5124
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/BaksmaliOptions.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.jf.dexlib2.analysis.ClassPath;
+import org.jf.dexlib2.analysis.InlineMethodResolver;
+import org.jf.dexlib2.util.SyntheticAccessorResolver;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class BaksmaliOptions {
+    public int apiLevel = 15;
+
+    public boolean parameterRegisters = true;
+    public boolean localsDirective = false;
+    public boolean sequentialLabels = false;
+    public boolean debugInfo = true;
+    public boolean codeOffsets = false;
+    public boolean accessorComments = true;
+    public boolean allowOdex = false;
+    public boolean deodex = false;
+    public boolean implicitReferences = false;
+    public boolean normalizeVirtualMethods = false;
+
+    // register info values
+    public static final int ALL = 1;
+    public static final int ALLPRE = 2;
+    public static final int ALLPOST = 4;
+    public static final int ARGS = 8;
+    public static final int DEST = 16;
+    public static final int MERGE = 32;
+    public static final int FULLMERGE = 64;
+
+    public int registerInfo = 0;
+
+    public Map<Integer,String> resourceIds = new HashMap<Integer,String>();
+    public InlineMethodResolver inlineResolver = null;
+    public ClassPath classPath = null;
+    public SyntheticAccessorResolver syntheticAccessorResolver = null;
+
+    /**
+     * Load the resource ids from a set of public.xml files.
+     *
+     * @param resourceFiles A map of resource prefixes -> public.xml files
+     */
+    public void loadResourceIds(Map<String, File> resourceFiles) throws SAXException, IOException {
+        for (Map.Entry<String, File> entry: resourceFiles.entrySet()) {
+            try {
+                SAXParser saxp = SAXParserFactory.newInstance().newSAXParser();
+                final String prefix = entry.getKey();
+                saxp.parse(entry.getValue(), new DefaultHandler() {
+                    @Override
+                    public void startElement(String uri, String localName, String qName,
+                                             Attributes attr) throws SAXException {
+                        if (qName.equals("public")) {
+                            String resourceType = attr.getValue("type");
+                            String resourceName = attr.getValue("name").replace('.', '_');
+                            Integer resourceId = Integer.decode(attr.getValue("id"));
+                            String qualifiedResourceName =
+                                    String.format("%s.%s.%s", prefix, resourceType, resourceName);
+                            resourceIds.put(resourceId, qualifiedResourceName);
+                        }
+                    }
+                });
+            } catch (ParserConfigurationException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java b/baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java
new file mode 100644
index 0000000..3ded479
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.beust.jcommander.ParametersDelegate;
+import org.jf.baksmali.AnalysisArguments.CheckPackagePrivateArgument;
+import org.jf.dexlib2.analysis.CustomInlineMethodResolver;
+import org.jf.dexlib2.analysis.InlineMethodResolver;
+import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
+import org.jf.util.jcommander.ExtendedParameter;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+@Parameters(commandDescription = "Deodexes an odex/oat file")
+@ExtendedParameters(
+        commandName = "deodex",
+        commandAliases = { "de", "x" })
+public class DeodexCommand extends DisassembleCommand {
+
+    @ParametersDelegate
+    protected CheckPackagePrivateArgument checkPackagePrivateArgument = new CheckPackagePrivateArgument();
+
+    @Parameter(names = {"--inline-table", "--inline", "--it"},
+            description = "Specify a file containing a custom inline method table to use. See the " +
+                    "\"deodexerant\" tool in the smali github repository to dump the inline method table from a " +
+                    "device that uses dalvik.")
+    @ExtendedParameter(argumentNames = "file")
+    private String inlineTable;
+
+    public DeodexCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors);
+    }
+
+    @Override protected BaksmaliOptions getOptions() {
+        BaksmaliOptions options = super.getOptions();
+
+        options.deodex = true;
+
+        if (dexFile instanceof DexBackedOdexFile) {
+            if (inlineTable == null) {
+                options.inlineResolver = InlineMethodResolver.createInlineMethodResolver(
+                        ((DexBackedOdexFile)dexFile).getOdexVersion());
+            } else {
+                File inlineTableFile = new File(inlineTable);
+                if (!inlineTableFile.exists()) {
+                    System.err.println(String.format("Could not find file: %s", inlineTable));
+                    System.exit(-1);
+                }
+                try {
+                    options.inlineResolver = new CustomInlineMethodResolver(options.classPath, inlineTableFile);
+                } catch (IOException ex) {
+                    System.err.println(String.format("Error while reading file: %s", inlineTableFile));
+                    ex.printStackTrace(System.err);
+                    System.exit(-1);
+                }
+            }
+        }
+
+        return options;
+    }
+
+    @Override protected boolean shouldCheckPackagePrivateAccess() {
+        return checkPackagePrivateArgument.checkPackagePrivateAccess;
+    }
+
+    @Override protected boolean needsClassPath() {
+        return true;
+    }
+
+    @Override protected boolean showDeodexWarning() {
+        return false;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/DexInputCommand.java b/baksmali/src/main/java/org/jf/baksmali/DexInputCommand.java
new file mode 100644
index 0000000..4904e89
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/DexInputCommand.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.jf.dexlib2.DexFileFactory;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.iface.MultiDexContainer;
+import org.jf.util.jcommander.Command;
+import org.jf.util.jcommander.ExtendedParameter;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * This class implements common functionality for commands that need to load a dex file based on
+ * command line input
+ */
+public abstract class DexInputCommand extends Command {
+
+    @Parameter(names = {"-a", "--api"},
+            description = "The numeric api level of the file being disassembled.")
+    @ExtendedParameter(argumentNames = "api")
+    public int apiLevel = -1;
+
+    @Parameter(description = "A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " +
+            "files, you can specify the specific entry to use as if the apk/oat file was a directory. " +
+            "e.g. \"app.apk/classes2.dex\". For more information, see \"baksmali help input\".")
+    @ExtendedParameter(argumentNames = "file")
+    protected List<String> inputList = Lists.newArrayList();
+
+    protected File inputFile;
+    protected String inputEntry;
+    protected MultiDexContainer.DexEntry<? extends DexBackedDexFile> dexEntry;
+    protected DexBackedDexFile dexFile;
+
+    public DexInputCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors);
+    }
+
+    /**
+     * Parses a dex file input from the user and loads the given dex file.
+     *
+     * In some cases, the input file can contain multiple dex files. If this is the case, you can refer to a specific
+     * dex file with a slash, followed by the entry name, optionally in quotes.
+     *
+     * If the entry name is enclosed in quotes, then it will strip the first and last quote and look for an entry with
+     * exactly that name. Otherwise, it will perform a partial filename match against the entry to find any candidates.
+     * If there is a single matching candidate, it will be used. Otherwise, an error will be generated.
+     *
+     * For example, to refer to the "/system/framework/framework.jar:classes2.dex" entry within the
+     * "framework/arm/framework.oat" oat file, you could use any of:
+     *
+     * framework/arm/framework.oat/"/system/framework/framework.jar:classes2.dex"
+     * framework/arm/framework.oat/system/framework/framework.jar:classes2.dex
+     * framework/arm/framework.oat/framework/framework.jar:classes2.dex
+     * framework/arm/framework.oat/framework.jar:classes2.dex
+     * framework/arm/framework.oat/classes2.dex
+     *
+     * The last option is the easiest, but only works if the oat file doesn't contain another entry with the
+     * "classes2.dex" name. e.g. "/system/framework/blah.jar:classes2.dex"
+     *
+     * It's technically possible (although unlikely) for an oat file to contain 2 entries like:
+     * /system/framework/framework.jar:classes2.dex
+     * system/framework/framework.jar:classes2.dex
+     *
+     * In this case, the "framework/arm/framework.oat/system/framework/framework.jar:classes2.dex" syntax will generate
+     * an error because both entries match the partial entry name. Instead, you could use the following for the
+     * first and second entry respectively:
+     *
+     * framework/arm/framework.oat/"/system/framework/framework.jar:classes2.dex"
+     * framework/arm/framework.oat/"system/framework/framework.jar:classes2.dex"
+     *
+     * @param input The name of a dex, apk, odex or oat file/entry.
+     */
+    protected void loadDexFile(@Nonnull String input) {
+        File file = new File(input);
+
+        while (file != null && !file.exists()) {
+            file = file.getParentFile();
+        }
+
+        if (file == null || !file.exists() || file.isDirectory()) {
+            System.err.println("Can't find file: " + input);
+            System.exit(1);
+        }
+
+        inputFile = file;
+
+        String dexEntryName = null;
+        if (file.getPath().length() < input.length()) {
+            dexEntryName = input.substring(file.getPath().length() + 1);
+        }
+
+        Opcodes opcodes = null;
+        if (apiLevel != -1) {
+            opcodes = Opcodes.forApi(apiLevel);
+        }
+
+        if (!Strings.isNullOrEmpty(dexEntryName)) {
+            boolean exactMatch = false;
+            if (dexEntryName.length() > 2 && dexEntryName.charAt(0) == '"' && dexEntryName.charAt(dexEntryName.length() - 1) == '"') {
+                dexEntryName = dexEntryName.substring(1, dexEntryName.length() - 1);
+                exactMatch = true;
+            }
+
+            inputEntry = dexEntryName;
+
+            try {
+                dexEntry = DexFileFactory.loadDexEntry(file, dexEntryName, exactMatch, opcodes);
+                dexFile = dexEntry.getDexFile();
+            } catch (IOException ex) {
+                throw new RuntimeException(ex);
+            }
+        } else {
+            try {
+                MultiDexContainer<? extends DexBackedDexFile> container =
+                        DexFileFactory.loadDexContainer(file, opcodes);
+
+                if (container.getDexEntryNames().size() == 1) {
+                    dexEntry = container.getEntry(container.getDexEntryNames().get(0));
+                    assert dexEntry != null;
+                    dexFile = dexEntry.getDexFile();
+                } else if (container.getDexEntryNames().size() > 1) {
+                    dexEntry = container.getEntry("classes.dex");
+                    if (dexEntry == null) {
+                        dexEntry = container.getEntry(container.getDexEntryNames().get(0));
+                    }
+                    assert dexEntry != null;
+                    dexFile = dexEntry.getDexFile();
+                } else {
+                    throw new RuntimeException(String.format("\"%s\" has no dex files", input));
+                }
+            } catch (IOException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java b/baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java
new file mode 100644
index 0000000..368fa72
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.beust.jcommander.ParametersDelegate;
+import com.beust.jcommander.validators.PositiveInteger;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.jf.dexlib2.util.SyntheticAccessorResolver;
+import org.jf.util.ConsoleUtil;
+import org.jf.util.StringWrapper;
+import org.jf.util.jcommander.ExtendedParameter;
+import org.jf.util.jcommander.ExtendedParameters;
+import org.xml.sax.SAXException;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+@Parameters(commandDescription = "Disassembles a dex file.")
+@ExtendedParameters(
+        commandName = "disassemble",
+        commandAliases = { "dis", "d" })
+public class DisassembleCommand extends DexInputCommand {
+
+    @Parameter(names = {"-h", "-?", "--help"}, help = true,
+            description = "Show usage information for this command.")
+    private boolean help;
+
+    @ParametersDelegate
+    protected AnalysisArguments analysisArguments = new AnalysisArguments();
+
+    @Parameter(names = {"--debug-info", "--di"}, arity = 1,
+            description = "Whether to include debug information in the output (.local, .param, .line, etc.). True " +
+                    "by default, use --debug-info=false to disable.")
+    @ExtendedParameter(argumentNames = "boolean")
+    private boolean debugInfo = true;
+
+    @Parameter(names = {"--code-offsets", "--offsets", "--off"},
+            description = "Add a comment before each instruction with it's code offset within the method.")
+    private boolean codeOffsets = false;
+
+    @Parameter(names = {"--resolve-resources", "--rr"}, arity = 2,
+            description = "This will attempt to find any resource id references within the bytecode and add a " +
+                    "comment with the name of the resource being referenced. The parameter accepts 2 values:" +
+                    "an arbitrary resource prefix and the path to a public.xml file. For example: " +
+                    "--resolve-resources android.R framework/res/values/public.xml. This option can be specified " +
+                    "multiple times to provide resources from multiple packages.")
+    @ExtendedParameter(argumentNames = {"resource prefix", "public.xml file"})
+    private List<String> resourceIdFiles = Lists.newArrayList();
+
+    @Parameter(names = {"-j", "--jobs"},
+            description = "The number of threads to use. Defaults to the number of cores available.",
+            validateWith = PositiveInteger.class)
+    @ExtendedParameter(argumentNames = "n")
+    private int jobs = Runtime.getRuntime().availableProcessors();
+
+    @Parameter(names = {"-l", "--use-locals"},
+            description = "When disassembling, output the .locals directive with the number of non-parameter " +
+                    "registers instead of the .registers directive with the total number of registers.")
+    private boolean localsDirective = false;
+
+    @Parameter(names = {"--accessor-comments", "--ac"}, arity = 1,
+            description = "Generate helper comments for synthetic accessors. True by default, use " +
+                    "--accessor-comments=false to disable.")
+    @ExtendedParameter(argumentNames = "boolean")
+    private boolean accessorComments = true;
+
+    @Parameter(names = {"--normalize-virtual-methods", "--norm", "--nvm"},
+            description = "Normalize virtual method references to use the base class where the method is " +
+                    "originally declared.")
+    private boolean normalizeVirtualMethods = false;
+
+    @Parameter(names = {"-o", "--output"},
+            description = "The directory to write the disassembled files to.")
+    @ExtendedParameter(argumentNames = "dir")
+    private String outputDir = "out";
+
+    @Parameter(names = {"--parameter-registers", "--preg", "--pr"}, arity = 1,
+            description = "Use the pNN syntax for registers that refer to a method parameter on method entry. True " +
+                    "by default, use --parameter-registers=false to disable.")
+    @ExtendedParameter(argumentNames = "boolean")
+    private boolean parameterRegisters = true;
+
+    @Parameter(names = {"-r", "--register-info"},
+            description = "Add comments before/after each instruction with information about register types. " +
+                    "The value is a comma-separated list of any of ALL, ALLPRE, ALLPOST, ARGS, DEST, MERGE and " +
+                    "FULLMERGE. See \"baksmali help register-info\" for more information.")
+    @ExtendedParameter(argumentNames = "register info specifier")
+    private List<String> registerInfoTypes = Lists.newArrayList();
+
+    @Parameter(names = {"--sequential-labels", "--seq", "--sl"},
+            description = "Create label names using a sequential numbering scheme per label type, rather than " +
+                    "using the bytecode address.")
+    private boolean sequentialLabels = false;
+
+    @Parameter(names = {"--implicit-references", "--implicit", "--ir"},
+            description = "Use implicit method and field references (without the class name) for methods and " +
+                    "fields from the current class.")
+    private boolean implicitReferences = false;
+
+    @Parameter(names = "--allow-odex-opcodes",
+            description = "Allows odex opcodes to be disassembled, even if the result won't be able to be reassembled.")
+    private boolean allowOdex = false;
+
+    @Parameter(names = "--classes",
+            description = "A comma separated list of classes. Only disassemble these classes")
+    @ExtendedParameter(argumentNames = "classes")
+    private List<String> classes = null;
+
+    public DisassembleCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors);
+    }
+
+    public void run() {
+        if (help || inputList == null || inputList.isEmpty()) {
+            usage();
+            return;
+        }
+
+        if (inputList.size() > 1) {
+            System.err.println("Too many files specified");
+            usage();
+            return;
+        }
+
+        String input = inputList.get(0);
+        loadDexFile(input);
+
+        if (showDeodexWarning() && dexFile.supportsOptimizedOpcodes()) {
+            StringWrapper.printWrappedString(System.err,
+                    "Warning: You are disassembling an odex/oat file without deodexing it. You won't be able to " +
+                            "re-assemble the results unless you deodex it. See \"baksmali help deodex\"",
+                    ConsoleUtil.getConsoleWidth());
+        }
+
+        File outputDirectoryFile = new File(outputDir);
+        if (!outputDirectoryFile.exists()) {
+            if (!outputDirectoryFile.mkdirs()) {
+                System.err.println("Can't create the output directory " + outputDir);
+                System.exit(-1);
+            }
+        }
+
+        if (analysisArguments.classPathDirectories == null || analysisArguments.classPathDirectories.isEmpty()) {
+            analysisArguments.classPathDirectories = Lists.newArrayList(inputFile.getAbsoluteFile().getParent());
+        }
+
+        if (!Baksmali.disassembleDexFile(dexFile, outputDirectoryFile, jobs, getOptions(), classes)) {
+            System.exit(-1);
+        }
+    }
+
+    protected boolean needsClassPath() {
+        return !registerInfoTypes.isEmpty() || normalizeVirtualMethods;
+    }
+
+    protected boolean shouldCheckPackagePrivateAccess() {
+        return false;
+    }
+
+    protected boolean showDeodexWarning() {
+        return true;
+    }
+
+    protected BaksmaliOptions getOptions() {
+        if (dexFile == null) {
+            throw new IllegalStateException("You must call loadDexFile first");
+        }
+
+        final BaksmaliOptions options = new BaksmaliOptions();
+
+        if (needsClassPath()) {
+            try {
+                options.classPath = analysisArguments.loadClassPathForDexFile(
+                        inputFile.getAbsoluteFile().getParentFile(), dexEntry, shouldCheckPackagePrivateAccess());
+            } catch (Exception ex) {
+                System.err.println("\n\nError occurred while loading class path files. Aborting.");
+                ex.printStackTrace(System.err);
+                System.exit(-1);
+            }
+        }
+
+        if (!resourceIdFiles.isEmpty()) {
+            Map<String, File> resourceFiles = Maps.newHashMap();
+
+            assert (resourceIdFiles.size() % 2) == 0;
+            for (int i=0; i<resourceIdFiles.size(); i+=2) {
+                String resourcePrefix = resourceIdFiles.get(i);
+                String publicXml = resourceIdFiles.get(i+1);
+
+                File publicXmlFile = new File(publicXml);
+
+                if (!publicXmlFile.exists()) {
+                    System.err.println(String.format("Can't find file: %s", publicXmlFile));
+                    System.exit(-1);
+                }
+
+                resourceFiles.put(resourcePrefix, publicXmlFile);
+            }
+
+            try {
+                options.loadResourceIds(resourceFiles);
+            } catch (IOException ex) {
+                System.err.println("Error while loading resource files:");
+                ex.printStackTrace(System.err);
+                System.exit(-1);
+            } catch (SAXException ex) {
+                System.err.println("Error while loading resource files:");
+                ex.printStackTrace(System.err);
+                System.exit(-1);
+            }
+        }
+
+        options.parameterRegisters = parameterRegisters;
+        options.localsDirective = localsDirective;
+        options.sequentialLabels = sequentialLabels;
+        options.debugInfo = debugInfo;
+        options.codeOffsets = codeOffsets;
+        options.accessorComments = accessorComments;
+        options.implicitReferences = implicitReferences;
+        options.normalizeVirtualMethods = normalizeVirtualMethods;
+
+        options.registerInfo = 0;
+
+        for (String registerInfoType: registerInfoTypes) {
+            if (registerInfoType.equalsIgnoreCase("ALL")) {
+                options.registerInfo  |= BaksmaliOptions.ALL;
+            } else if (registerInfoType.equalsIgnoreCase("ALLPRE")) {
+                options.registerInfo  |= BaksmaliOptions.ALLPRE;
+            } else if (registerInfoType.equalsIgnoreCase("ALLPOST")) {
+                options.registerInfo  |= BaksmaliOptions.ALLPOST;
+            } else if (registerInfoType.equalsIgnoreCase("ARGS")) {
+                options.registerInfo  |= BaksmaliOptions.ARGS;
+            } else if (registerInfoType.equalsIgnoreCase("DEST")) {
+                options.registerInfo  |= BaksmaliOptions.DEST;
+            } else if (registerInfoType.equalsIgnoreCase("MERGE")) {
+                options.registerInfo  |= BaksmaliOptions.MERGE;
+            } else if (registerInfoType.equalsIgnoreCase("FULLMERGE")) {
+                options.registerInfo  |= BaksmaliOptions.FULLMERGE;
+            } else {
+                System.err.println(String.format("Invalid register info type: %s", registerInfoType));
+                usage();
+                System.exit(-1);
+            }
+
+            if ((options.registerInfo & BaksmaliOptions.FULLMERGE) != 0) {
+                options.registerInfo &= ~BaksmaliOptions.MERGE;
+            }
+        }
+
+        if (accessorComments) {
+            options.syntheticAccessorResolver = new SyntheticAccessorResolver(dexFile.getOpcodes(),
+                    dexFile.getClasses());
+        }
+
+        if (allowOdex) {
+            options.allowOdex = true;
+        }
+
+        return options;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/DumpCommand.java b/baksmali/src/main/java/org/jf/baksmali/DumpCommand.java
new file mode 100644
index 0000000..101bc41
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/DumpCommand.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.dexbacked.raw.util.DexAnnotator;
+import org.jf.util.ConsoleUtil;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.io.*;
+import java.util.List;
+
+@Parameters(commandDescription = "Prints an annotated hex dump for the given dex file")
+@ExtendedParameters(
+        commandName = "dump",
+        commandAliases = "du")
+public class DumpCommand extends DexInputCommand {
+
+    @Parameter(names = {"-h", "-?", "--help"}, help = true,
+            description = "Show usage information for this command.")
+    private boolean help;
+
+    public DumpCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors);
+    }
+
+    public void run() {
+        if (help || inputList == null || inputList.isEmpty()) {
+            usage();
+            return;
+        }
+
+        if (inputList.size() > 1) {
+            System.err.println("Too many files specified");
+            usage();
+            return;
+        }
+
+        String input = inputList.get(0);
+        loadDexFile(input);
+
+        try {
+            dump(dexFile, System.out);
+        } catch (IOException ex) {
+            System.err.println("There was an error while dumping the dex file");
+            ex.printStackTrace(System.err);
+        }
+    }
+
+    /**
+     * Writes an annotated hex dump of the given dex file to output.
+     *
+     * @param dexFile The dex file to dump
+     * @param output An OutputStream to write the annotated hex dump to. The caller is responsible for closing this
+     *               when needed.
+     *
+     * @throws IOException
+     */
+    public static void dump(@Nonnull DexBackedDexFile dexFile, @Nonnull OutputStream output)
+            throws IOException {
+        Writer writer = new BufferedWriter(new OutputStreamWriter(output));
+
+        try {
+            int consoleWidth = ConsoleUtil.getConsoleWidth();
+            if (consoleWidth <= 0) {
+                consoleWidth = 120;
+            }
+
+            DexAnnotator annotator = new DexAnnotator(dexFile, consoleWidth);
+            annotator.writeAnnotations(writer);
+        } finally {
+            writer.close();
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/HelpCommand.java b/baksmali/src/main/java/org/jf/baksmali/HelpCommand.java
new file mode 100644
index 0000000..149ac63
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/HelpCommand.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.google.common.collect.Lists;
+import org.jf.util.ConsoleUtil;
+import org.jf.util.StringWrapper;
+import org.jf.util.jcommander.*;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Shows usage information")
+@ExtendedParameters(
+        commandName = "help",
+        commandAliases = "h")
+public class HelpCommand extends Command {
+
+    public HelpCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors);
+    }
+
+    @Parameter(description = "If specified, show the detailed usage information for the given commands")
+    @ExtendedParameter(argumentNames = "commands")
+    private List<String> commands = Lists.newArrayList();
+
+    public void run() {
+        JCommander parentJc = commandAncestors.get(commandAncestors.size() - 1);
+
+        if (commands == null || commands.isEmpty()) {
+            System.out.println(new HelpFormatter()
+                    .width(ConsoleUtil.getConsoleWidth())
+                    .format(commandAncestors));
+        } else {
+            boolean printedHelp = false;
+            for (String cmd : commands) {
+                if (cmd.equals("register-info")) {
+                    printedHelp = true;
+                    String registerInfoHelp = "The --register-info parameter will cause baksmali to generate " +
+                            "comments before and after every instruction containing register type " +
+                            "information about some subset of registers. This parameter accepts a comma-separated list" +
+                            "of values specifying which registers and how much information to include.\n" +
+                            "    ALL: all pre- and post-instruction registers\n" +
+                            "    ALLPRE: all pre-instruction registers\n" +
+                            "    ALLPOST: all post-instruction registers\n" +
+                            "    ARGS: any pre-instruction registers used as arguments to the instruction\n" +
+                            "    DEST: the post-instruction register used as the output of the instruction\n" +
+                            "    MERGE: any pre-instruction register that has been merged from multiple " +
+                            "incoming code paths\n" +
+                            "    FULLMERGE: an extended version of MERGE that also includes a list of all " +
+                            "the register types from incoming code paths that were merged";
+
+                    Iterable<String> lines = StringWrapper.wrapStringOnBreaks(registerInfoHelp,
+                            ConsoleUtil.getConsoleWidth());
+                    for (String line : lines) {
+                        System.out.println(line);
+                    }
+                } else if (cmd.equals("input")) {
+                    printedHelp = true;
+                    String registerInfoHelp = "Apks and oat files can contain multiple dex files. In order to " +
+                            "specify a particular dex file, the basic syntax is to treat the apk/oat file as a " +
+                            "directory. For example, to load the \"classes2.dex\" entry from \"app.apk\", you can " +
+                            "use \"app.apk/classes2.dex\".\n" +
+                            "\n" +
+                            "For ease of use, you can also specify a partial path to the dex file to load. For " +
+                            "example, to load a entry named \"/system/framework/framework.jar:classes2.dex\" from " +
+                            "\"framework.oat\", you can use any of the following:\n" +
+                            "\"framework.oat/classes2.dex\"\n" +
+                            "\"framework.oat/framework.jar:classes2.dex\"\n" +
+                            "\"framework.oat/framework/framework.jar:classes2.dex\"\n" +
+                            "\"framework.oat/system/framework/framework.jar:classes2.dex\"\n" +
+                            "\n" +
+                            "In some rare cases, an oat file could have entries that can't be differentiated with " +
+                            "the above syntax. For example \"/blah/blah.dex\" and \"blah/blah.dex\". In this case, " +
+                            "the \"blah.oat/blah/blah.dex\" would match both entries and generate an error. To get " +
+                            "around this, you can add double quotes around the entry name to specify an exact entry " +
+                            "name. E.g. blah.oat/\"/blah/blah.dex\" or blah.oat/\"blah/blah.dex\" respectively.";
+
+                    Iterable<String> lines = StringWrapper.wrapStringOnBreaks(registerInfoHelp,
+                            ConsoleUtil.getConsoleWidth());
+                    for (String line : lines) {
+                        System.out.println(line);
+                    }
+                } else if (cmd.equals("classpath")) {
+                    printedHelp = true;
+                    String registerInfoHelp = "When deodexing odex/oat files or when using the --register-info " +
+                            "option, baksmali needs to load all classes from the framework files on the device " +
+                            "in order to fully understand the class hierarchy. There are several options that " +
+                            "control how baksmali finds and loads the classpath entries.\n" +
+                            "\n"+
+                            "L+ devices (ART):\n" +
+                            "When deodexing or disassembling a file from an L+ device using ART, you generally " +
+                            "just need to specify the path to the boot.oat file via the --bootclasspath/-b " +
+                            "parameter. On pre-N devices, the boot.oat file is self-contained and no other files are " +
+                            "needed. In N, boot.oat was split into multiple files. In this case, the other " +
+                            "files should be in the same directory as the boot.oat file, but you still only need to " +
+                            "specify the boot.oat file in the --bootclasspath/-b option. The other files will be " +
+                            "automatically loaded from the same directory.\n" +
+                            "\n" +
+                            "Pre-L devices (dalvik):\n" +
+                            "When deodexing odex files from a pre-L device using dalvik, you " +
+                            "generally just need to specify the path to a directory containing the framework files " +
+                            "from the device via the --classpath-dir/-d option. odex files contain a list of " +
+                            "framework files they depend on and baksmali will search for these dependencies in the " +
+                            "directory that you specify.\n" +
+                            "\n" +
+                            "Dex files don't contain a list of dependencies like odex files, so when disassembling a " +
+                            "dex file using the --register-info option, and using the framework files from a " +
+                            "pre-L device, baksmali will attempt to use a reasonable default list of classpath files " +
+                            "based on the api level set via the -a option. If this default list is incorrect, you " +
+                            "can override the classpath using the --bootclasspath/-b option. This option accepts a " +
+                            "colon separated list of classpath entries. Each entry can be specified in a few " +
+                            "different ways.\n" +
+                            " - A simple filename like \"framework.jar\"\n" +
+                            " - A device path like \"/system/framework/framework.jar\"\n" +
+                            " - A local relative or absolute path like \"/tmp/framework/framework.jar\"\n" +
+                            "When using the first or second formats, you should also specify the directory " +
+                            "containing the framework files via the --classpath-dir/-d option. When using the third " +
+                            "format, this option is not needed.\n" +
+                            "It's worth noting that the second format matches the format used by Android for the " +
+                            "BOOTCLASSPATH environment variable, so you can simply grab the value of that variable " +
+                            "from the device and use it as-is.\n" +
+                            "\n" +
+                            "Examples:\n" +
+                            "  For an M device:\n" +
+                            "    adb pull /system/framework/arm/boot.oat /tmp/boot.oat\n" +
+                            "    baksmali deodex blah.oat -b /tmp/boot.oat\n" +
+                            "  For an N+ device:\n" +
+                            "    adb pull /system/framework/arm /tmp/framework\n" +
+                            "    baksmali deodex blah.oat -b /tmp/framework/boot.oat\n" +
+                            "  For a pre-L device:\n" +
+                            "    adb pull /system/framework /tmp/framework\n" +
+                            "    baksmali deodex blah.odex -d /tmp/framework\n" +
+                            "  Using the BOOTCLASSPATH on a pre-L device:\n" +
+                            "    adb pull /system/framework /tmp/framework\n" +
+                            "    export BOOTCLASSPATH=`adb shell \"echo \\\\$BOOTCLASPATH\"`\n" +
+                            "    baksmali disassemble --register-info ARGS,DEST blah.apk -b $BOOTCLASSPATH -d " +
+                            "/tmp/framework";
+
+                    Iterable<String> lines = StringWrapper.wrapStringOnBreaks(registerInfoHelp,
+                            ConsoleUtil.getConsoleWidth());
+                    for (String line : lines) {
+                        System.out.println(line);
+                    }
+                } else {
+                    JCommander command = ExtendedCommands.getSubcommand(parentJc, cmd);
+                    if (command == null) {
+                        System.err.println("No such command: " + cmd);
+                    } else {
+                        printedHelp = true;
+                        System.out.println(new HelpFormatter()
+                                .width(ConsoleUtil.getConsoleWidth())
+                                .format(((Command)command.getObjects().get(0)).getCommandHierarchy()));
+                    }
+                }
+            }
+            if (!printedHelp) {
+                System.out.println(new HelpFormatter()
+                        .width(ConsoleUtil.getConsoleWidth())
+                        .format(commandAncestors));
+            }
+        }
+    }
+
+    @Parameters(hidden =  true)
+    @ExtendedParameters(commandName = "hlep")
+    public static class HlepCommand extends HelpCommand {
+        public HlepCommand(@Nonnull List<JCommander> commandAncestors) {
+            super(commandAncestors);
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListClassesCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListClassesCommand.java
new file mode 100644
index 0000000..fb172bd
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListClassesCommand.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the classes in a dex file.")
+@ExtendedParameters(
+        commandName = "classes",
+        commandAliases = { "class", "c" })
+public class ListClassesCommand extends DexInputCommand {
+
+    @Parameter(names = {"-h", "-?", "--help"}, help = true,
+            description = "Show usage information")
+    private boolean help;
+
+    public ListClassesCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors);
+    }
+
+    @Override public void run() {
+        if (help || inputList == null || inputList.isEmpty()) {
+            usage();
+            return;
+        }
+
+        if (inputList.size() > 1) {
+            System.err.println("Too many files specified");
+            usage();
+            return;
+        }
+
+        String input = inputList.get(0);
+        loadDexFile(input);
+
+        for (ClassDef classDef: dexFile.getClasses()) {
+            System.out.println(classDef.getType());
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListCommand.java
new file mode 100644
index 0000000..9547620
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListCommand.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import org.jf.baksmali.ListHelpCommand.ListHlepCommand;
+import org.jf.util.jcommander.Command;
+import org.jf.util.jcommander.ExtendedCommands;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists various objects in a dex file.")
+@ExtendedParameters(
+        commandName = "list",
+        commandAliases = "l")
+public class ListCommand extends Command {
+
+    @Parameter(names = {"-h", "-?", "--help"}, help = true,
+            description = "Show usage information")
+    private boolean help;
+
+    public ListCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors);
+    }
+
+    @Override protected void setupCommand(JCommander jc) {
+        List<JCommander> hierarchy = getCommandHierarchy();
+
+        ExtendedCommands.addExtendedCommand(jc, new ListStringsCommand(hierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new ListMethodsCommand(hierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new ListFieldsCommand(hierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new ListTypesCommand(hierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new ListClassesCommand(hierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new ListDexCommand(hierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new ListVtablesCommand(hierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new ListFieldOffsetsCommand(hierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new ListDependenciesCommand(hierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new ListHelpCommand(hierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new ListHlepCommand(hierarchy));
+    }
+
+    @Override public void run() {
+        JCommander jc = getJCommander();
+        if (help || jc.getParsedCommand() == null) {
+            usage();
+            return;
+        }
+
+        Command command = (Command)jc.getCommands().get(jc.getParsedCommand()).getObjects().get(0);
+        command.run();
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListDependenciesCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListDependenciesCommand.java
new file mode 100644
index 0000000..636a87c
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListDependenciesCommand.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.google.common.collect.Lists;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
+import org.jf.dexlib2.dexbacked.OatFile;
+import org.jf.util.jcommander.Command;
+import org.jf.util.jcommander.ExtendedParameter;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.io.*;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the stored dependencies in an odex/oat file.")
+@ExtendedParameters(
+        commandName = "dependencies",
+        commandAliases = { "deps", "dep" })
+public class ListDependenciesCommand extends Command {
+
+    @Parameter(names = {"-h", "-?", "--help"}, help = true,
+            description = "Show usage information")
+    private boolean help;
+
+    @Parameter(description = "An oat/odex file")
+    @ExtendedParameter(argumentNames = "file")
+    private List<String> inputList = Lists.newArrayList();
+
+    public ListDependenciesCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors);
+    }
+
+    @Override public void run() {
+        if (help || inputList == null || inputList.isEmpty()) {
+            usage();
+            return;
+        }
+
+        if (inputList.size() > 1) {
+            System.err.println("Too many files specified");
+            usage();
+            return;
+        }
+
+        String input = inputList.get(0);
+        InputStream inputStream = null;
+        try {
+            inputStream = new BufferedInputStream(new FileInputStream(input));
+        } catch (FileNotFoundException ex) {
+            System.err.println("Could not find file: " + input);
+            System.exit(-1);
+        }
+
+        try {
+            OatFile oatFile = OatFile.fromInputStream(inputStream);
+            for (String entry: oatFile.getBootClassPath()) {
+                System.out.println(entry);
+            }
+            return;
+        } catch (OatFile.NotAnOatFileException ex) {
+            // ignore
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+
+        try {
+            DexBackedOdexFile odexFile = DexBackedOdexFile.fromInputStream(Opcodes.getDefault(), inputStream);
+            for (String entry: odexFile.getDependencies()) {
+                System.out.println(entry);
+            }
+            return;
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        } catch (DexBackedOdexFile.NotAnOdexFile ex) {
+            // handled below
+        } catch (DexBackedDexFile.NotADexFile ex) {
+            // handled below
+        }
+
+        System.err.println(input + " is not an odex or oat file.");
+        System.exit(-1);
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListDexCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListDexCommand.java
new file mode 100644
index 0000000..d5862eb
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListDexCommand.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.google.common.collect.Lists;
+import org.jf.dexlib2.DexFileFactory;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.iface.MultiDexContainer;
+import org.jf.util.jcommander.Command;
+import org.jf.util.jcommander.ExtendedParameter;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the dex files in an apk/oat file.")
+@ExtendedParameters(
+        commandName = "dex",
+        commandAliases = "d")
+public class ListDexCommand extends Command {
+
+    @Parameter(names = {"-h", "-?", "--help"}, help = true,
+            description = "Show usage information")
+    private boolean help;
+
+    @Parameter(description = "An apk or oat file.")
+    @ExtendedParameter(argumentNames = "file")
+    private List<String> inputList = Lists.newArrayList();
+
+    public ListDexCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors);
+    }
+
+    @Override public void run() {
+        if (help || inputList == null || inputList.isEmpty()) {
+            usage();
+            return;
+        }
+
+        if (inputList.size() > 1) {
+            System.err.println("Too many files specified");
+            usage();
+            return;
+        }
+
+        String input = inputList.get(0);
+        File file = new File(input);
+
+        if (!file.exists()) {
+            System.err.println(String.format("Could not find the file: %s", input));
+            System.exit(-1);
+        }
+
+        List<String> entries;
+        try {
+            MultiDexContainer<? extends DexBackedDexFile> container =
+                    DexFileFactory.loadDexContainer(file, Opcodes.getDefault());
+            entries = container.getDexEntryNames();
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+
+        for (String entry: entries) {
+            System.out.println(entry);
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListFieldOffsetsCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListFieldOffsetsCommand.java
new file mode 100644
index 0000000..41f9fe8
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListFieldOffsetsCommand.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.beust.jcommander.ParametersDelegate;
+import org.jf.dexlib2.analysis.ClassProto;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.reference.FieldReference;
+import org.jf.util.SparseArray;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the instance field offsets for classes in a dex file.")
+@ExtendedParameters(
+        commandName = "fieldoffsets",
+        commandAliases = { "fieldoffset", "fo" })
+public class ListFieldOffsetsCommand extends DexInputCommand {
+
+    @Parameter(names = {"-h", "-?", "--help"}, help = true,
+            description = "Show usage information")
+    private boolean help;
+
+    @ParametersDelegate
+    private AnalysisArguments analysisArguments = new AnalysisArguments();
+
+    public ListFieldOffsetsCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors);
+    }
+
+    @Override public void run() {
+        if (help || inputList == null || inputList.isEmpty()) {
+            usage();
+            return;
+        }
+
+        if (inputList.size() > 1) {
+            System.err.println("Too many files specified");
+            usage();
+            return;
+        }
+
+        String input = inputList.get(0);
+        loadDexFile(input);
+        BaksmaliOptions options = getOptions();
+
+        try {
+            for (ClassDef classDef: dexFile.getClasses()) {
+                ClassProto classProto = (ClassProto) options.classPath.getClass(classDef);
+                SparseArray<FieldReference> fields = classProto.getInstanceFields();
+                String className = "Class "  + classDef.getType() + " : " + fields.size() + " instance fields\n";
+                System.out.write(className.getBytes());
+                for (int i=0;i<fields.size();i++) {
+                    String field = fields.keyAt(i) + ":" + fields.valueAt(i).getType() + " " + fields.valueAt(i).getName() + "\n";
+                    System.out.write(field.getBytes());
+                }
+                System.out.write("\n".getBytes());
+            }
+            System.out.close();
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Nonnull
+    private BaksmaliOptions getOptions() {
+        if (dexFile == null) {
+            throw new IllegalStateException("You must call loadDexFile first");
+        }
+
+        final BaksmaliOptions options = new BaksmaliOptions();
+
+        options.apiLevel = apiLevel;
+
+        try {
+            options.classPath = analysisArguments.loadClassPathForDexFile(
+                    inputFile.getAbsoluteFile().getParentFile(), dexEntry, false);
+        } catch (Exception ex) {
+            System.err.println("Error occurred while loading class path files.");
+            ex.printStackTrace(System.err);
+            System.exit(-1);
+        }
+
+        return options;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListFieldsCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListFieldsCommand.java
new file mode 100644
index 0000000..c4d090d
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListFieldsCommand.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameters;
+import org.jf.dexlib2.ReferenceType;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the fields in a dex file's field table.")
+@ExtendedParameters(
+        commandName = "fields",
+        commandAliases = { "field", "f" })
+public class ListFieldsCommand extends ListReferencesCommand {
+    public ListFieldsCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors, ReferenceType.FIELD);
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListHelpCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListHelpCommand.java
new file mode 100644
index 0000000..2e64286
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListHelpCommand.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.google.common.collect.Iterables;
+import org.jf.util.ConsoleUtil;
+import org.jf.util.jcommander.*;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Shows usage information")
+@ExtendedParameters(
+        commandName = "help",
+        commandAliases = "h")
+public class ListHelpCommand extends Command {
+
+    @Parameter(description = "If specified, show the detailed usage information for the given commands")
+    @ExtendedParameter(argumentNames = "commands")
+    private List<String> commands;
+
+    public ListHelpCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors);
+    }
+
+    public void run() {
+        if (commands == null || commands.isEmpty()) {
+            System.out.println(new HelpFormatter()
+                    .width(ConsoleUtil.getConsoleWidth())
+                    .format(commandAncestors));
+        } else {
+            boolean printedHelp = false;
+            JCommander parentJc = Iterables.getLast(commandAncestors);
+            for (String cmd : commands) {
+                JCommander command = ExtendedCommands.getSubcommand(parentJc, cmd);
+                if (command == null) {
+                    System.err.println("No such command: " + cmd);
+                } else {
+                    printedHelp = true;
+                    System.out.println(new HelpFormatter()
+                            .width(ConsoleUtil.getConsoleWidth())
+                            .format(((Command)command.getObjects().get(0)).getCommandHierarchy()));
+                }
+            }
+            if (!printedHelp) {
+                System.out.println(new HelpFormatter()
+                        .width(ConsoleUtil.getConsoleWidth())
+                        .format(commandAncestors));
+            }
+        }
+    }
+
+    @Parameters(hidden =  true)
+    @ExtendedParameters(commandName = "hlep")
+    public static class ListHlepCommand extends ListHelpCommand {
+        public ListHlepCommand(@Nonnull List<JCommander> commandAncestors) {
+            super(commandAncestors);
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListMethodsCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListMethodsCommand.java
new file mode 100644
index 0000000..603e764
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListMethodsCommand.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameters;
+import org.jf.dexlib2.ReferenceType;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the methods in a dex file's method table.")
+@ExtendedParameters(
+        commandName = "methods",
+        commandAliases = { "method", "m" })
+public class ListMethodsCommand extends ListReferencesCommand {
+    public ListMethodsCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors, ReferenceType.METHOD);
+    }
+}
\ No newline at end of file
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListReferencesCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListReferencesCommand.java
new file mode 100644
index 0000000..da9c3e3
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListReferencesCommand.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import org.jf.dexlib2.iface.reference.Reference;
+import org.jf.dexlib2.util.ReferenceUtil;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+public abstract class ListReferencesCommand extends DexInputCommand {
+
+    private final int referenceType;
+
+    @Parameter(names = {"-h", "-?", "--help"}, help = true,
+            description = "Show usage information")
+    private boolean help;
+
+    public ListReferencesCommand(@Nonnull List<JCommander> commandAncestors, int referenceType) {
+        super(commandAncestors);
+        this.referenceType = referenceType;
+    }
+
+    @Override public void run() {
+        if (help || inputList == null || inputList.isEmpty()) {
+            usage();
+            return;
+        }
+
+        if (inputList.size() > 1) {
+            System.err.println("Too many files specified");
+            usage();
+            return;
+        }
+
+        String input = inputList.get(0);
+        loadDexFile(input);
+
+        for (Reference reference: dexFile.getReferences(referenceType)) {
+            System.out.println(ReferenceUtil.getReferenceString(reference));
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListStringsCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListStringsCommand.java
new file mode 100644
index 0000000..8694f91
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListStringsCommand.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameters;
+import org.jf.dexlib2.ReferenceType;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the strings in a dex file's string table.")
+@ExtendedParameters(
+        commandName = "strings",
+        commandAliases = { "string", "str", "s" })
+public class ListStringsCommand extends ListReferencesCommand {
+    public ListStringsCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors, ReferenceType.STRING);
+    }
+}
\ No newline at end of file
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListTypesCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListTypesCommand.java
new file mode 100644
index 0000000..fbff2f2
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListTypesCommand.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameters;
+import org.jf.dexlib2.ReferenceType;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the type ids in a dex file's type table.")
+@ExtendedParameters(
+        commandName = "types",
+        commandAliases = { "type", "t" })
+public class ListTypesCommand extends ListReferencesCommand {
+    public ListTypesCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors, ReferenceType.TYPE);
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListVtablesCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListVtablesCommand.java
new file mode 100644
index 0000000..84928d2
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListVtablesCommand.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.beust.jcommander.ParametersDelegate;
+import org.jf.baksmali.AnalysisArguments.CheckPackagePrivateArgument;
+import org.jf.dexlib2.AccessFlags;
+import org.jf.dexlib2.analysis.ClassProto;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.Method;
+import org.jf.util.jcommander.ExtendedParameter;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the virtual method tables for classes in a dex file.")
+@ExtendedParameters(
+        commandName = "vtables",
+        commandAliases = { "vtable", "v" })
+public class ListVtablesCommand extends DexInputCommand {
+
+    @Parameter(names = {"-h", "-?", "--help"}, help = true,
+            description = "Show usage information")
+    private boolean help;
+
+    @ParametersDelegate
+    private AnalysisArguments analysisArguments = new AnalysisArguments();
+
+    @ParametersDelegate
+    private CheckPackagePrivateArgument checkPackagePrivateArgument = new CheckPackagePrivateArgument();
+
+    @Parameter(names = "--classes",
+            description = "A comma separated list of classes. Only print the vtable for these classes")
+    @ExtendedParameter(argumentNames = "classes")
+    private List<String> classes = null;
+
+    @Parameter(names = "--override-oat-version",
+            description = "Uses a classpath for the given oat version, regardless of the actual oat version. This " +
+                    "can be used, e.g. to list vtables from a dex file, as if they were in an oat file of the given " +
+                    "version.")
+    private int oatVersion = 0;
+
+    public ListVtablesCommand(@Nonnull List<JCommander> commandAncestors) {
+        super(commandAncestors);
+    }
+
+    @Override public void run() {
+        if (help || inputList == null || inputList.isEmpty()) {
+            usage();
+            return;
+        }
+
+        if (inputList.size() > 1) {
+            System.err.println("Too many files specified");
+            usage();
+            return;
+        }
+
+        String input = inputList.get(0);
+        loadDexFile(input);
+
+        BaksmaliOptions options = getOptions();
+        if (options == null) {
+            return;
+        }
+
+        try {
+            if (classes != null && !classes.isEmpty()) {
+                for (String cls: classes) {
+                    listClassVtable((ClassProto)options.classPath.getClass(cls));
+                }
+                return;
+            }
+
+            for (ClassDef classDef : dexFile.getClasses()) {
+                if (!AccessFlags.INTERFACE.isSet(classDef.getAccessFlags())) {
+                    listClassVtable((ClassProto)options.classPath.getClass(classDef));
+                }
+            }
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private void listClassVtable(ClassProto classProto) throws IOException {
+        List<Method> methods = classProto.getVtable();
+        String className = "Class " + classProto.getType() + " extends " + classProto.getSuperclass() +
+                " : " + methods.size() + " methods\n";
+        System.out.write(className.getBytes());
+        for (int i = 0; i < methods.size(); i++) {
+            Method method = methods.get(i);
+
+            String methodString = i + ":" + method.getDefiningClass() + "->" + method.getName() + "(";
+            for (CharSequence parameter : method.getParameterTypes()) {
+                methodString += parameter;
+            }
+            methodString += ")" + method.getReturnType() + "\n";
+            System.out.write(methodString.getBytes());
+        }
+        System.out.write("\n".getBytes());
+    }
+
+    protected BaksmaliOptions getOptions() {
+        if (dexFile == null) {
+            throw new IllegalStateException("You must call loadDexFile first");
+        }
+
+        final BaksmaliOptions options = new BaksmaliOptions();
+
+        options.apiLevel = apiLevel;
+
+        try {
+            options.classPath = analysisArguments.loadClassPathForDexFile(inputFile.getAbsoluteFile().getParentFile(),
+                    dexEntry, checkPackagePrivateArgument.checkPackagePrivateAccess, oatVersion);
+        } catch (Exception ex) {
+            System.err.println("Error occurred while loading class path files.");
+            ex.printStackTrace(System.err);
+            return null;
+        }
+
+        return options;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Main.java b/baksmali/src/main/java/org/jf/baksmali/Main.java
new file mode 100644
index 0000000..66d9b4f
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Main.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.google.common.collect.Lists;
+import org.jf.baksmali.HelpCommand.HlepCommand;
+import org.jf.util.jcommander.Command;
+import org.jf.util.jcommander.ExtendedCommands;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Properties;
+
+@ExtendedParameters(
+        includeParametersInUsage = true,
+        commandName = "baksmali",
+        postfixDescription = "See baksmali help <command> for more information about a specific command")
+public class Main extends Command {
+    public static final String VERSION = loadVersion();
+
+    @Parameter(names = {"--help", "-h", "-?"}, help = true,
+            description = "Show usage information")
+    private boolean help;
+
+    @Parameter(names = {"--version", "-v"}, help = true,
+            description = "Print the version of baksmali and then exit")
+    public boolean version;
+
+    private JCommander jc;
+
+    public Main() {
+        super(Lists.<JCommander>newArrayList());
+    }
+
+    @Override public void run() {
+    }
+
+    @Override protected JCommander getJCommander() {
+        return jc;
+    }
+
+    public static void main(String[] args) {
+        Main main = new Main();
+
+        JCommander jc = new JCommander(main);
+        main.jc = jc;
+        jc.setProgramName("baksmali");
+        List<JCommander> commandHierarchy = main.getCommandHierarchy();
+
+        ExtendedCommands.addExtendedCommand(jc, new DisassembleCommand(commandHierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new DeodexCommand(commandHierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new DumpCommand(commandHierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new HelpCommand(commandHierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new HlepCommand(commandHierarchy));
+        ExtendedCommands.addExtendedCommand(jc, new ListCommand(commandHierarchy));
+
+        jc.parse(args);
+
+        if (main.version) {
+            version();
+        }
+
+        if (jc.getParsedCommand() == null || main.help) {
+            main.usage();
+            return;
+        }
+
+        Command command = (Command)jc.getCommands().get(jc.getParsedCommand()).getObjects().get(0);
+        command.run();
+    }
+
+    protected static void version() {
+        System.out.println("baksmali " + VERSION + " (http://smali.org)");
+        System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
+        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
+        System.exit(0);
+    }
+
+    private static String loadVersion() {
+        InputStream propertiesStream = Baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties");
+        String version = "[unknown version]";
+        if (propertiesStream != null) {
+            Properties properties = new Properties();
+            try {
+                properties.load(propertiesStream);
+                version = properties.getProperty("application.version");
+            } catch (IOException ex) {
+                // ignore
+            }
+        }
+        return version;
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Renderers/BooleanRenderer.java b/baksmali/src/main/java/org/jf/baksmali/Renderers/BooleanRenderer.java
new file mode 100644
index 0000000..f181bb4
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Renderers/BooleanRenderer.java
@@ -0,0 +1,43 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 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.baksmali.Renderers;
+
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+
+public class BooleanRenderer {
+    public static void writeTo(IndentingWriter writer, boolean val) throws IOException {
+        if (val) {
+            writer.write("true");
+        } else {
+            writer.write("false");
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Renderers/ByteRenderer.java b/baksmali/src/main/java/org/jf/baksmali/Renderers/ByteRenderer.java
new file mode 100644
index 0000000..9c060fd
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Renderers/ByteRenderer.java
@@ -0,0 +1,53 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Renderers;
+
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+
+public class ByteRenderer  {
+    public static void writeTo(IndentingWriter writer, byte val) throws IOException {
+        if (val<0) {
+            writer.write("-0x");
+            writer.printUnsignedLongAsHex(-val);
+            writer.write('t');
+        } else {
+            writer.write("0x");
+            writer.printUnsignedLongAsHex(val);
+            writer.write('t');
+        }
+    }
+
+    public static void writeUnsignedTo(IndentingWriter writer, byte val) throws IOException {
+        writer.write("0x");
+        writer.printUnsignedLongAsHex(val & 0xFF);
+        writer.write('t');
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Renderers/CharRenderer.java b/baksmali/src/main/java/org/jf/baksmali/Renderers/CharRenderer.java
new file mode 100644
index 0000000..daf7634
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Renderers/CharRenderer.java
@@ -0,0 +1,42 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Renderers;
+
+import org.jf.util.IndentingWriter;
+import org.jf.util.StringUtils;
+
+import java.io.IOException;
+
+public class CharRenderer {
+    public static void writeTo(IndentingWriter writer, char val) throws IOException {
+        writer.write('\'');
+        StringUtils.writeEscapedChar(writer, val);
+        writer.write('\'');
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Renderers/DoubleRenderer.java b/baksmali/src/main/java/org/jf/baksmali/Renderers/DoubleRenderer.java
new file mode 100644
index 0000000..03fff64
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Renderers/DoubleRenderer.java
@@ -0,0 +1,39 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 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.baksmali.Renderers;
+
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+
+public class DoubleRenderer {
+    public static void writeTo(IndentingWriter writer, double val) throws IOException {
+        writer.write(Double.toString(val));
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Renderers/FloatRenderer.java b/baksmali/src/main/java/org/jf/baksmali/Renderers/FloatRenderer.java
new file mode 100644
index 0000000..a1de2b9
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Renderers/FloatRenderer.java
@@ -0,0 +1,40 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Renderers;
+
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+
+public class FloatRenderer {
+    public static void writeTo(IndentingWriter writer, float val) throws IOException {
+        writer.write(Float.toString(val));
+        writer.write('f');
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Renderers/IntegerRenderer.java b/baksmali/src/main/java/org/jf/baksmali/Renderers/IntegerRenderer.java
new file mode 100644
index 0000000..22beaac
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Renderers/IntegerRenderer.java
@@ -0,0 +1,50 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Renderers;
+
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+
+public class IntegerRenderer {
+    public static void writeTo(IndentingWriter writer, int val) throws IOException {
+        if (val<0) {
+            writer.write("-0x");
+            writer.printUnsignedLongAsHex(-((long) val));
+        } else {
+            writer.write("0x");
+            writer.printUnsignedLongAsHex(val);
+        }
+    }
+
+    public static void writeUnsignedTo(IndentingWriter writer, int val) throws IOException {
+        writer.write("0x");
+        writer.printUnsignedLongAsHex(val & 0xFFFFFFFFL);
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Renderers/LongRenderer.java b/baksmali/src/main/java/org/jf/baksmali/Renderers/LongRenderer.java
new file mode 100644
index 0000000..83076e4
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Renderers/LongRenderer.java
@@ -0,0 +1,63 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Renderers;
+
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+
+public class LongRenderer {
+    public static void writeTo(IndentingWriter writer, long val) throws IOException {
+        if (val<0) {
+            writer.write("-0x");
+            writer.printUnsignedLongAsHex(-val);
+            writer.write('L');
+        } else {
+            writer.write("0x");
+            writer.printUnsignedLongAsHex(val);
+            writer.write('L');
+        }
+    }
+
+    public static void writeSignedIntOrLongTo(IndentingWriter writer, long val) throws IOException {
+        if (val<0) {
+            writer.write("-0x");
+            writer.printUnsignedLongAsHex(-val);
+            if (val < Integer.MIN_VALUE) {
+                writer.write('L');
+            }
+        } else {
+            writer.write("0x");
+            writer.printUnsignedLongAsHex(val);
+            if (val > Integer.MAX_VALUE) {
+                writer.write('L');
+            }
+        }
+    }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Renderers/ShortRenderer.java b/baksmali/src/main/java/org/jf/baksmali/Renderers/ShortRenderer.java
new file mode 100644
index 0000000..3385c6b
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Renderers/ShortRenderer.java
@@ -0,0 +1,47 @@
+/*
+ * [The "BSD licence"]
+ * Copyright (c) 2010 Ben Gruver (JesusFreke)
+ * 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.baksmali.Renderers;
+
+import org.jf.util.IndentingWriter;
+
+import java.io.IOException;
+
+public class ShortRenderer {
+    public static void writeTo(IndentingWriter writer, short val) throws IOException {
+        if (val < 0) {
+            writer.write("-0x");
+            writer.printUnsignedLongAsHex(-val);
+            writer.write('s');
+        } else {
+            writer.write("0x");
+            writer.printUnsignedLongAsHex(val);
+            writer.write('s');
+        }
+    }
+}
diff --git a/baksmali/src/main/resources/baksmali.properties b/baksmali/src/main/resources/baksmali.properties
new file mode 100644
index 0000000..df22408
--- /dev/null
+++ b/baksmali/src/main/resources/baksmali.properties
@@ -0,0 +1 @@
+application.version=${version}
\ No newline at end of file
diff --git a/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java b/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java
new file mode 100644
index 0000000..80a54b7
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Resources;
+import junit.framework.Assert;
+import org.jf.baksmali.Adaptors.ClassDefinition;
+import org.jf.dexlib2.DexFileFactory;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.analysis.ClassPath;
+import org.jf.dexlib2.analysis.ClassProvider;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.DexFile;
+import org.jf.util.IndentingWriter;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+
+public class AnalysisTest {
+
+    @Test
+    public void ConstructorTest() throws IOException, URISyntaxException {
+        runTest("ConstructorTest", true);
+    }
+
+    @Test
+    public void RegisterEqualityOnMergeTest() throws IOException, URISyntaxException {
+        runTest("RegisterEqualityOnMergeTest", true);
+    }
+
+    @Test
+    public void UninitRefIdentityTest() throws IOException, URISyntaxException {
+        runTest("UninitRefIdentityTest", true);
+    }
+
+    @Test
+    public void InstanceOfTest() throws IOException, URISyntaxException {
+        runTest("InstanceOfTest", true, true);
+    }
+
+    @Test
+    public void MultipleStartInstructionsTest() throws IOException, URISyntaxException {
+        runTest("MultipleStartInstructionsTest", true);
+    }
+
+    @Test
+    public void DuplicateTest() throws IOException, URISyntaxException {
+        runTest("DuplicateTest", false);
+    }
+
+    @Test
+    public void LocalTest() throws IOException, URISyntaxException {
+        runTest("LocalTest", false);
+    }
+
+    public void runTest(String test, boolean registerInfo) throws IOException, URISyntaxException {
+        runTest(test, registerInfo, false);
+    }
+
+    public void runTest(String test, boolean registerInfo, boolean isArt) throws IOException, URISyntaxException {
+        String dexFilePath = String.format("%s%sclasses.dex", test, File.separatorChar);
+
+        DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), Opcodes.getDefault());
+
+        BaksmaliOptions options = new BaksmaliOptions();
+        if (registerInfo) {
+            options.registerInfo = BaksmaliOptions.ALL | BaksmaliOptions.FULLMERGE;
+            if (isArt) {
+                options.classPath = new ClassPath(new ArrayList<ClassProvider>(), true, 56);
+            } else {
+                options.classPath = new ClassPath();
+            }
+        }
+        options.implicitReferences = false;
+
+        for (ClassDef classDef: dexFile.getClasses()) {
+            StringWriter stringWriter = new StringWriter();
+            IndentingWriter writer = new IndentingWriter(stringWriter);
+            ClassDefinition classDefinition = new ClassDefinition(options, classDef);
+            classDefinition.writeTo(writer);
+            writer.close();
+
+            String className = classDef.getType();
+            String smaliPath = String.format("%s%s%s.smali", test, File.separatorChar,
+                    className.substring(1, className.length() - 1));
+            String smaliContents = readResource(smaliPath);
+
+            Assert.assertEquals(BaksmaliTestUtils.normalizeWhitespace(smaliContents),
+                    BaksmaliTestUtils.normalizeWhitespace((stringWriter.toString())));
+        }
+    }
+
+    @Nonnull
+    private File findResource(String resource) throws URISyntaxException {
+        URL resUrl = Resources.getResource(resource);
+        return new File(resUrl.toURI());
+    }
+
+    @Nonnull
+    private String readResource(String resource) throws URISyntaxException, IOException {
+        URL url = Resources.getResource(resource);
+        return Resources.toString(url, Charsets.UTF_8);
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java b/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java
new file mode 100644
index 0000000..e6406fb
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.google.common.io.ByteStreams;
+import junit.framework.Assert;
+import org.antlr.runtime.RecognitionException;
+import org.jf.baksmali.Adaptors.ClassDefinition;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.smali.SmaliTestUtils;
+import org.jf.util.IndentingWriter;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class BaksmaliTestUtils {
+
+    private static String newline = System.getProperty("line.separator");
+
+    public static void assertSmaliCompiledEquals(String source, String expected,
+                                                 BaksmaliOptions options, boolean stripComments) throws IOException,
+            RecognitionException {
+        ClassDef classDef = SmaliTestUtils.compileSmali(source, options.apiLevel);
+
+        // Remove unnecessary whitespace and optionally strip all comments from smali file
+        String normalizedActual = getNormalizedSmali(classDef, options, stripComments);
+        String normalizedExpected = normalizeSmali(expected, stripComments);
+
+        // Assert that normalized strings are now equal
+        Assert.assertEquals(normalizedExpected, normalizedActual);
+    }
+
+    public static void assertSmaliCompiledEquals(String source, String expected,
+            BaksmaliOptions options) throws IOException, RecognitionException {
+        assertSmaliCompiledEquals(source, expected, options, false);
+    }
+
+    public static void assertSmaliCompiledEquals(String source, String expected)
+            throws IOException, RecognitionException {
+        BaksmaliOptions options = new BaksmaliOptions();
+        assertSmaliCompiledEquals(source, expected, options);
+    }
+
+    @Nonnull
+    public static String normalizeSmali(@Nonnull String smaliText, boolean stripComments) {
+        if (stripComments) {
+            smaliText = stripComments(smaliText);
+        }
+        return normalizeWhitespace(smaliText);
+    }
+
+    @Nonnull
+    public static String getNormalizedSmali(@Nonnull ClassDef classDef, @Nonnull BaksmaliOptions options,
+                                            boolean stripComments)
+            throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        IndentingWriter writer = new IndentingWriter(stringWriter);
+        ClassDefinition classDefinition = new ClassDefinition(options, classDef);
+        classDefinition.writeTo(writer);
+        writer.close();
+        return normalizeSmali(stringWriter.toString(), stripComments);
+    }
+
+    @Nonnull
+    public static byte[] readResourceBytesFully(@Nonnull String fileName) throws IOException {
+        InputStream smaliStream = RoundtripTest.class.getClassLoader().
+                getResourceAsStream(fileName);
+        if (smaliStream == null) {
+            org.junit.Assert.fail("Could not load " + fileName);
+        }
+
+        return ByteStreams.toByteArray(smaliStream);
+    }
+
+    @Nonnull
+    public static String readResourceFully(@Nonnull String fileName) throws IOException {
+        return readResourceFully(fileName, "UTF-8");
+    }
+
+    @Nonnull
+    public static String readResourceFully(@Nonnull String fileName, @Nonnull String encoding)
+            throws IOException {
+        return new String(readResourceBytesFully(fileName), encoding);
+    }
+
+    @Nonnull
+    public static String normalizeNewlines(@Nonnull String source) {
+        return normalizeNewlines(source, newline);
+    }
+
+    @Nonnull
+    public static String normalizeNewlines(@Nonnull String source, String newlineValue) {
+        return source.replace("\r", "").replace("\n", newlineValue);
+    }
+
+    @Nonnull
+    public static String normalizeWhitespace(@Nonnull String source) {
+        // Go to native system new lines so that ^/$ work correctly
+        source = normalizeNewlines(source);
+
+        // Remove all suffix/prefix whitespace
+        Pattern pattern = Pattern.compile("((^[ \t]+)|([ \t]+$))", Pattern.MULTILINE);
+        Matcher matcher = pattern.matcher(source);
+        source = matcher.replaceAll("");
+
+        // Remove all empty lines
+        Pattern pattern2 = Pattern.compile("^\r?\n?", Pattern.MULTILINE);
+        Matcher matcher2 = pattern2.matcher(source);
+        source = matcher2.replaceAll("");
+
+        // Remove a trailing new line, if present
+        Pattern pattern3 = Pattern.compile("\r?\n?$");
+        Matcher matcher3 = pattern3.matcher(source);
+        source = matcher3.replaceAll("");
+
+        // Go back to unix-style \n newlines
+        source = normalizeNewlines(source, "\n");
+        return source;
+    }
+
+    @Nonnull
+    public static String stripComments(@Nonnull String source) {
+        Pattern pattern = Pattern.compile("#(.*)");
+        Matcher matcher = pattern.matcher(source);
+        return matcher.replaceAll("");
+    }
+
+    @Test
+    public void testStripComments() {
+        Assert.assertEquals("", stripComments("#world"));
+        Assert.assertEquals("hello", stripComments("hello#world"));
+        Assert.assertEquals("multi\nline", stripComments("multi#hello world\nline#world"));
+    }
+
+    @Test
+    public void testNormalizeWhitespace() {
+        Assert.assertEquals("", normalizeWhitespace(" "));
+        Assert.assertEquals("hello", normalizeWhitespace("hello "));
+        Assert.assertEquals("hello", normalizeWhitespace(" hello"));
+        Assert.assertEquals("hello", normalizeWhitespace(" hello "));
+        Assert.assertEquals("hello\nworld", normalizeWhitespace("hello \n \n world"));
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/DexTest.java b/baksmali/src/test/java/org/jf/baksmali/DexTest.java
new file mode 100644
index 0000000..f9f5562
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/DexTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.junit.Assert;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A base test class for performing a test using a dex file as input
+ */
+/**
+ * A base test class for performing a disassembly on a dex file and verifying the results
+ *
+ * The test accepts a single-class dex file as input. By default, the input dex file should be a resource at
+ * [testDir]/[testName]Input.dex
+ */
+public abstract class DexTest {
+    protected final String testDir;
+
+    protected DexTest(@Nonnull String testDir) {
+        this.testDir = testDir;
+    }
+
+    protected DexTest() {
+        this.testDir = this.getClass().getSimpleName();
+    }
+
+    @Nonnull
+    protected String getInputFilename(@Nonnull String testName) {
+        return String.format("%s%s%sInput.dex", testDir, File.separatorChar, testName);
+    }
+
+    @Nonnull
+    protected DexBackedDexFile getInputDexFile(@Nonnull String testName, @Nonnull BaksmaliOptions options) {
+        try {
+            // Load file from resources as a stream
+            byte[] inputBytes = BaksmaliTestUtils.readResourceBytesFully(getInputFilename(testName));
+            return new DexBackedDexFile(Opcodes.forApi(options.apiLevel), inputBytes);
+        } catch (IOException ex) {
+            Assert.fail();
+        }
+        return null;
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java b/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java
new file mode 100644
index 0000000..f8ebe91
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.google.common.collect.Iterables;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.iface.ClassDef;
+import org.junit.Assert;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A base test class for performing a disassembly on a dex file and verifying the results
+ *
+ * The test accepts a single-class dex file as input, disassembles it, and  verifies that
+ * the result equals a known-good output smali file.
+ *
+ * By default, the input and output files should be resources at [testDir]/[testName]Input.dex
+ * and [testDir]/[testName]Output.smali respectively
+ */
+public class DisassemblyTest extends DexTest {
+
+    @Nonnull
+    protected String getOutputFilename(@Nonnull String testName) {
+        return String.format("%s%s%sOutput.smali", testDir, File.separatorChar, testName);
+    }
+
+    protected void runTest(@Nonnull String testName) {
+        runTest(testName, new BaksmaliOptions());
+    }
+
+    protected void runTest(@Nonnull String testName, @Nonnull BaksmaliOptions options) {
+        try {
+            DexBackedDexFile inputDex = getInputDexFile(testName, options);
+            Assert.assertEquals(1, inputDex.getClassSection().size());
+            ClassDef inputClass = Iterables.getFirst(inputDex.getClasses(), null);
+            Assert.assertNotNull(inputClass);
+            String input = BaksmaliTestUtils.getNormalizedSmali(inputClass, options, true);
+
+            String output = BaksmaliTestUtils.readResourceFully(getOutputFilename(testName));
+            output = BaksmaliTestUtils.normalizeSmali(output, true);
+
+            // Run smali, baksmali, and then compare strings are equal (minus comments/whitespace)
+            Assert.assertEquals(output, input);
+        } catch (IOException ex) {
+            Assert.fail();
+        }
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java b/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java
new file mode 100644
index 0000000..ad2aad5
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.google.common.collect.Lists;
+import org.jf.dexlib2.analysis.ClassPath;
+import org.jf.dexlib2.analysis.ClassProto;
+import org.jf.dexlib2.analysis.DexClassProvider;
+import org.jf.dexlib2.iface.DexFile;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class FieldGapOrderTest extends DexTest {
+    @Test
+    public void testOldOrder() {
+        DexFile dexFile = getInputDexFile("FieldGapOrder", new BaksmaliOptions());
+        Assert.assertEquals(3, dexFile.getClasses().size());
+
+        ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), false, 66);
+        ClassProto classProto = (ClassProto)classPath.getClass("LGapOrder;");
+        Assert.assertEquals("r1", classProto.getFieldByOffset(12).getName());
+        Assert.assertEquals("r2", classProto.getFieldByOffset(16).getName());
+        Assert.assertEquals("d", classProto.getFieldByOffset(24).getName());
+        Assert.assertEquals("s", classProto.getFieldByOffset(36).getName());
+        Assert.assertEquals("i", classProto.getFieldByOffset(32).getName());
+    }
+
+    @Test
+    public void testNewOrder() {
+        DexFile dexFile = getInputDexFile("FieldGapOrder", new BaksmaliOptions());
+        Assert.assertEquals(3, dexFile.getClasses().size());
+
+        ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), false, 67);
+        ClassProto classProto = (ClassProto)classPath.getClass("LGapOrder;");
+        Assert.assertEquals("s", classProto.getFieldByOffset(10).getName());
+        Assert.assertEquals("r1", classProto.getFieldByOffset(12).getName());
+        Assert.assertEquals("r2", classProto.getFieldByOffset(16).getName());
+        Assert.assertEquals("i", classProto.getFieldByOffset(20).getName());
+        Assert.assertEquals("d", classProto.getFieldByOffset(24).getName());
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/HiddenApiRestrictionsRoundtripTest.java b/baksmali/src/test/java/org/jf/baksmali/HiddenApiRestrictionsRoundtripTest.java
new file mode 100644
index 0000000..a6ed181
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/HiddenApiRestrictionsRoundtripTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.junit.Test;
+
+public class HiddenApiRestrictionsRoundtripTest extends RoundtripTest {
+    @Test
+    public void testHiddenApiRestrictions() {
+        BaksmaliOptions options = new BaksmaliOptions();
+        options.apiLevel = 29;
+        runTest("HiddenApiRestrictions", options);
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/HiddenApiRestrictionsTest.java b/baksmali/src/test/java/org/jf/baksmali/HiddenApiRestrictionsTest.java
new file mode 100644
index 0000000..2b4726c
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/HiddenApiRestrictionsTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2020, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.antlr.runtime.RecognitionException;
+import org.jf.dexlib2.dexbacked.DexBackedClassDef;
+import org.jf.dexlib2.dexbacked.raw.ItemType;
+import org.jf.smali.SmaliTestUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class HiddenApiRestrictionsTest {
+
+    @Test
+    public void testNoHiddenApiRestrictions() throws IOException, RecognitionException {
+        String source = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                ".method public static main([Ljava/lang/String;)V\n" +
+                "    .registers 1\n" +
+                "    return-void\n" +
+                ".end method";
+
+        DexBackedClassDef classDef = SmaliTestUtils.compileSmali(source, 29);
+
+        Assert.assertNull(classDef.dexFile.getMapItemForSection(ItemType.HIDDENAPI_CLASS_DATA_ITEM));
+    }
+
+    @Test
+    public void testWithHiddenApiRestrictions() throws IOException, RecognitionException {
+        String source = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                ".method public whitelist static main([Ljava/lang/String;)V\n" +
+                "    .registers 1\n" +
+                "    return-void\n" +
+                ".end method";
+
+        DexBackedClassDef classDef = SmaliTestUtils.compileSmali(source, 29);
+
+        Assert.assertNotNull(classDef.dexFile.getMapItemForSection(ItemType.HIDDENAPI_CLASS_DATA_ITEM));
+    }
+
+    @Test
+    public void testWithHiddenApiRestrictionsWithLowerApi() throws IOException, RecognitionException {
+        String source = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                ".method public whitelist static main([Ljava/lang/String;)V\n" +
+                "    .registers 1\n" +
+                "    return-void\n" +
+                ".end method";
+
+        try {
+            SmaliTestUtils.compileSmali(source, 28);
+            Assert.fail();
+        } catch (RuntimeException ex) {
+            // expected exception
+        }
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/IdenticalRoundtripTest.java b/baksmali/src/test/java/org/jf/baksmali/IdenticalRoundtripTest.java
new file mode 100644
index 0000000..e636ee1
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/IdenticalRoundtripTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+
+/**
+ * A base test class for performing a roundtrip assembly/disassembly where the input and output
+ * should be identical.
+ *
+ * By default, the input/output file should be a resource at [testDir]/[testName].smali
+ */
+public abstract class IdenticalRoundtripTest extends RoundtripTest {
+
+    public IdenticalRoundtripTest(@Nonnull String testDir) {
+        super(testDir);
+    }
+
+    public IdenticalRoundtripTest() {
+    }
+
+    @Nonnull @Override protected String getInputFilename(@Nonnull String testName) {
+        return String.format("%s%s%s.smali", testDir, File.separatorChar, testName);
+    }
+
+    @Nonnull @Override protected String getOutputFilename(@Nonnull String testName) {
+        return getInputFilename(testName);
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java b/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java
new file mode 100644
index 0000000..962a6be
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.antlr.runtime.RecognitionException;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class ImplicitReferenceTest {
+    @Test
+    public void testImplicitMethodReferences() throws IOException, RecognitionException {
+        String source = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                ".method public static main([Ljava/lang/String;)V\n" +
+                "    .registers 1\n" +
+                "    invoke-static {p0}, LHelloWorld;->toString()V\n" +
+                "    invoke-static {p0}, LHelloWorld;->V()V\n" +
+                "    invoke-static {p0}, LHelloWorld;->I()V\n" +
+                "    return-void\n" +
+                ".end method";
+
+        String expected = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                "# direct methods\n" +
+                ".method public static main([Ljava/lang/String;)V\n" +
+                ".registers 1\n" +
+                "invoke-static {p0}, toString()V\n" +
+                "invoke-static {p0}, V()V\n" +
+                "invoke-static {p0}, I()V\n" +
+                "return-void\n" +
+                ".end method\n";
+
+        BaksmaliOptions options = new BaksmaliOptions();
+        options.implicitReferences = true;
+
+        BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
+    }
+
+    @Test
+    public void testExplicitMethodReferences() throws IOException, RecognitionException {
+        String source = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                ".method public static main([Ljava/lang/String;)V\n" +
+                "    .registers 1\n" +
+                "    invoke-static {p0}, LHelloWorld;->toString()V\n" +
+                "    invoke-static {p0}, LHelloWorld;->V()V\n" +
+                "    invoke-static {p0}, LHelloWorld;->I()V\n" +
+                "    return-void\n" +
+                ".end method";
+
+        String expected = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                "# direct methods\n" +
+                ".method public static main([Ljava/lang/String;)V\n" +
+                "    .registers 1\n" +
+                "    invoke-static {p0}, LHelloWorld;->toString()V\n" +
+                "    invoke-static {p0}, LHelloWorld;->V()V\n" +
+                "    invoke-static {p0}, LHelloWorld;->I()V\n" +
+                "    return-void\n" +
+                ".end method\n";
+
+        BaksmaliOptions options = new BaksmaliOptions();
+        options.implicitReferences = false;
+
+        BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
+    }
+
+    @Test
+    public void testImplicitMethodLiterals() throws IOException, RecognitionException {
+        String source = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                ".field public static field1:Ljava/lang/reflect/Method; = LHelloWorld;->toString()V\n" +
+                ".field public static field2:Ljava/lang/reflect/Method; = LHelloWorld;->V()V\n" +
+                ".field public static field3:Ljava/lang/reflect/Method; = LHelloWorld;->I()V\n" +
+                ".field public static field4:Ljava/lang/Class; = I";
+
+        String expected = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                "# static fields\n" +
+                ".field public static field1:Ljava/lang/reflect/Method; = toString()V\n" +
+                ".field public static field2:Ljava/lang/reflect/Method; = V()V\n" +
+                ".field public static field3:Ljava/lang/reflect/Method; = I()V\n" +
+                ".field public static field4:Ljava/lang/Class; = I\n";
+
+        BaksmaliOptions options = new BaksmaliOptions();
+        options.implicitReferences = true;
+
+        BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
+    }
+
+    @Test
+    public void testExplicitMethodLiterals() throws IOException, RecognitionException {
+        String source = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                ".field public static field1:Ljava/lang/reflect/Method; = LHelloWorld;->toString()V\n" +
+                ".field public static field2:Ljava/lang/reflect/Method; = LHelloWorld;->V()V\n" +
+                ".field public static field3:Ljava/lang/reflect/Method; = LHelloWorld;->I()V\n" +
+                ".field public static field4:Ljava/lang/Class; = I";
+
+        String expected = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                "# static fields\n" +
+                ".field public static field1:Ljava/lang/reflect/Method; = LHelloWorld;->toString()V\n" +
+                ".field public static field2:Ljava/lang/reflect/Method; = LHelloWorld;->V()V\n" +
+                ".field public static field3:Ljava/lang/reflect/Method; = LHelloWorld;->I()V\n" +
+                ".field public static field4:Ljava/lang/Class; = I\n";
+
+        BaksmaliOptions options = new BaksmaliOptions();
+        options.implicitReferences = false;
+
+        BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
+    }
+
+    @Test
+    public void testImplicitFieldReferences() throws IOException, RecognitionException {
+        String source = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                ".method public static main([Ljava/lang/String;)V\n" +
+                "    .registers 1\n" +
+                "    sget v0, LHelloWorld;->someField:I\n" +
+                "    sget v0, LHelloWorld;->I:I\n" +
+                "    sget v0, LHelloWorld;->V:I\n" +
+                "    return-void\n" +
+                ".end method";
+
+        String expected = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                "# direct methods\n" +
+                ".method public static main([Ljava/lang/String;)V\n" +
+                "    .registers 1\n" +
+                "    sget p0, someField:I\n" +
+                "    sget p0, I:I\n" +
+                "    sget p0, V:I\n" +
+                "    return-void\n" +
+                ".end method\n";
+
+        BaksmaliOptions options = new BaksmaliOptions();
+        options.implicitReferences = true;
+
+        BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
+    }
+
+    @Test
+    public void testExplicitFieldReferences() throws IOException, RecognitionException {
+        String source = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                ".method public static main([Ljava/lang/String;)V\n" +
+                "    .registers 1\n" +
+                "    sget v0, LHelloWorld;->someField:I\n" +
+                "    sget v0, LHelloWorld;->I:I\n" +
+                "    sget v0, LHelloWorld;->V:I\n" +
+                "    return-void\n" +
+                ".end method";
+
+        String expected = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                "# direct methods\n" +
+                ".method public static main([Ljava/lang/String;)V\n" +
+                "    .registers 1\n" +
+                "    sget p0, LHelloWorld;->someField:I\n" +
+                "    sget p0, LHelloWorld;->I:I\n" +
+                "    sget p0, LHelloWorld;->V:I\n" +
+                "    return-void\n" +
+                ".end method\n";
+
+        BaksmaliOptions options = new BaksmaliOptions();
+        options.implicitReferences = false;
+
+        BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
+    }
+
+    @Test
+    public void testImplicitFieldLiterals() throws IOException, RecognitionException {
+        String source = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                ".field public static field1:Ljava/lang/reflect/Field; = LHelloWorld;->someField:I\n" +
+                ".field public static field2:Ljava/lang/reflect/Field; = LHelloWorld;->V:I\n" +
+                ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I";
+
+        String expected = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                "# static fields\n" +
+                ".field public static field1:Ljava/lang/reflect/Field; = someField:I\n" +
+                ".field public static field2:Ljava/lang/reflect/Field; = V:I\n" +
+                ".field public static field3:Ljava/lang/reflect/Field; = I:I\n";
+
+        BaksmaliOptions options = new BaksmaliOptions();
+        options.implicitReferences = true;
+
+        BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
+    }
+
+    @Test
+    public void testExplicitFieldLiterals() throws IOException, RecognitionException {
+        String source = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                ".field public static field1:Ljava/lang/reflect/Field; = LHelloWorld;->someField:I\n" +
+                ".field public static field2:Ljava/lang/reflect/Field; = LHelloWorld;->V:I\n" +
+                ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I";
+
+        String expected = "" +
+                ".class public LHelloWorld;\n" +
+                ".super Ljava/lang/Object;\n" +
+                "# static fields\n" +
+                ".field public static field1:Ljava/lang/reflect/Field; = LHelloWorld;->someField:I\n" +
+                ".field public static field2:Ljava/lang/reflect/Field; = LHelloWorld;->V:I\n" +
+                ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I\n";
+
+        BaksmaliOptions options = new BaksmaliOptions();
+        options.implicitReferences = false;
+
+        BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
+    }
+
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/InstructionMethodItemTest.java b/baksmali/src/test/java/org/jf/baksmali/InstructionMethodItemTest.java
new file mode 100644
index 0000000..20a3803
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/InstructionMethodItemTest.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2019, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.jf.baksmali.Adaptors.ClassDefinition;
+import org.jf.baksmali.Adaptors.Format.InstructionMethodItem;
+import org.jf.baksmali.Adaptors.MethodDefinition;
+import org.jf.baksmali.Adaptors.RegisterFormatter;
+import org.jf.dexlib2.Format;
+import org.jf.dexlib2.HiddenApiRestriction;
+import org.jf.dexlib2.Opcode;
+import org.jf.dexlib2.ReferenceType;
+import org.jf.dexlib2.base.reference.BaseMethodReference;
+import org.jf.dexlib2.base.reference.BaseStringReference;
+import org.jf.dexlib2.base.reference.BaseTypeReference;
+import org.jf.dexlib2.iface.*;
+import org.jf.dexlib2.iface.debug.DebugItem;
+import org.jf.dexlib2.iface.instruction.Instruction;
+import org.jf.dexlib2.iface.instruction.formats.Instruction21c;
+import org.jf.dexlib2.iface.reference.Reference;
+import org.jf.util.IndentingWriter;
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.Set;
+
+public class InstructionMethodItemTest {
+
+    @Test
+    public void testInvalidReference() throws IOException {
+
+        Instruction21c instruction = new Instruction21c() {
+            @Override
+            public int getRegisterA() {
+                return 0;
+            }
+
+            @Nonnull
+            @Override
+            public Reference getReference() {
+                return new BaseStringReference() {
+                    @Override
+                    public void validateReference() throws InvalidReferenceException {
+                        throw new InvalidReferenceException("blahblahblah");
+                    }
+
+                    @Nonnull
+                    @Override
+                    public String getString() {
+                        throw new RuntimeException("invalid reference");
+                    }
+                };
+            }
+
+            @Override
+            public int getReferenceType() {
+                return ReferenceType.STRING;
+            }
+
+            @Override
+            public Opcode getOpcode() {
+                return Opcode.CONST_STRING;
+            }
+
+            @Override
+            public int getCodeUnits() {
+                return Format.Format21c.size / 2;
+            }
+        };
+
+
+        MethodImplementation methodImplementation = new MethodImplementation() {
+            @Override
+            public int getRegisterCount() {
+                return 1;
+            }
+
+            @Nonnull
+            @Override
+            public Iterable<? extends Instruction> getInstructions() {
+                return ImmutableList.of(instruction);
+            }
+
+            @Nonnull
+            @Override
+            public List<? extends TryBlock<? extends ExceptionHandler>> getTryBlocks() {
+                return ImmutableList.of();
+            }
+
+            @Nonnull
+            @Override
+            public Iterable<? extends DebugItem> getDebugItems() {
+                return ImmutableList.of();
+            }
+        };
+
+        Method method = new TestMethod(methodImplementation);
+
+        ClassDefinition classDefinition = new ClassDefinition(
+                new BaksmaliOptions(), new TestClassDef());
+
+        MethodDefinition methodDefinition = new MethodDefinition(classDefinition, method, methodImplementation);
+        methodDefinition.registerFormatter = new RegisterFormatter(new BaksmaliOptions(), 1, 0);
+
+        InstructionMethodItem methodItem = new InstructionMethodItem<Instruction21c>(methodDefinition, 0, instruction);
+
+        StringWriter stringWriter = new StringWriter();
+        IndentingWriter indentingWriter = new IndentingWriter(stringWriter);
+        methodItem.writeTo(indentingWriter);
+
+        Assert.assertEquals("#Invalid reference\n#const-string v0, blahblahblah\nnop", stringWriter.toString());
+    }
+
+    private static class TestMethod extends BaseMethodReference implements Method {
+        private final MethodImplementation methodImplementation;
+
+        public TestMethod(MethodImplementation methodImplementation) {
+            this.methodImplementation = methodImplementation;
+        }
+
+        @Nonnull
+        @Override
+        public List<? extends MethodParameter> getParameters() {
+            return ImmutableList.of();
+        }
+
+        @Override
+        public int getAccessFlags() {
+            return 0;
+        }
+
+        @Nonnull
+        @Override
+        public Set<? extends Annotation> getAnnotations() {
+            return ImmutableSet.of();
+        }
+
+        @Nullable
+        @Override
+        public MethodImplementation getImplementation() {
+            return methodImplementation;
+        }
+
+        @Nonnull
+        @Override
+        public String getDefiningClass() {
+            return "Ltest;";
+        }
+
+        @Nonnull
+        @Override
+        public String getName() {
+            return "test";
+        }
+
+        @Nonnull
+        @Override
+        public List<? extends CharSequence> getParameterTypes() {
+            return ImmutableList.of();
+        }
+
+        @Nonnull
+        @Override
+        public String getReturnType() {
+            return "V";
+        }
+
+        @Nonnull @Override public Set<HiddenApiRestriction> getHiddenApiRestrictions() {
+            return ImmutableSet.of();
+        }
+    }
+
+    private static class TestClassDef extends BaseTypeReference implements ClassDef {
+        @Override
+        public int getAccessFlags() {
+            return 0;
+        }
+
+        @Nullable
+        @Override
+        public String getSuperclass() {
+            return "Ljava/lang/Object;";
+        }
+
+        @Nonnull
+        @Override
+        public List<String> getInterfaces() {
+            return ImmutableList.of();
+        }
+
+        @Nullable
+        @Override
+        public String getSourceFile() {
+            return null;
+        }
+
+        @Nonnull
+        @Override
+        public Set<? extends Annotation> getAnnotations() {
+            return ImmutableSet.of();
+        }
+
+        @Nonnull
+        @Override
+        public Iterable<? extends Field> getStaticFields() {
+            return ImmutableList.of();
+        }
+
+        @Nonnull
+        @Override
+        public Iterable<? extends Field> getInstanceFields() {
+            return ImmutableList.of();
+        }
+
+        @Nonnull
+        @Override
+        public Iterable<? extends Field> getFields() {
+            return ImmutableList.of();
+        }
+
+        @Nonnull
+        @Override
+        public Iterable<? extends Method> getDirectMethods() {
+            return ImmutableList.of();
+        }
+
+        @Nonnull
+        @Override
+        public Iterable<? extends Method> getVirtualMethods() {
+            return ImmutableList.of();
+        }
+
+        @Nonnull
+        @Override
+        public Iterable<? extends Method> getMethods() {
+            return ImmutableList.of();
+        }
+
+        @Nonnull
+        @Override
+        public String getType() {
+            return "Ltest;";
+        }
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/InstructionRoundtripTest.java b/baksmali/src/test/java/org/jf/baksmali/InstructionRoundtripTest.java
new file mode 100644
index 0000000..780631b
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/InstructionRoundtripTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.junit.Test;
+
+public class InstructionRoundtripTest extends IdenticalRoundtripTest {
+    @Test
+    public void testConstMethodHandle() {
+        BaksmaliOptions options = new BaksmaliOptions();
+        options.apiLevel = 28;
+        runTest("ConstMethodHandle", options);
+    }
+
+    @Test
+    public void testConstMethodType() {
+        BaksmaliOptions options = new BaksmaliOptions();
+        options.apiLevel = 28;
+        runTest("ConstMethodType", options);
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/InterfaceOrderTest.java b/baksmali/src/test/java/org/jf/baksmali/InterfaceOrderTest.java
new file mode 100644
index 0000000..f1ade1e
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/InterfaceOrderTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.junit.Test;
+
+public class InterfaceOrderTest extends IdenticalRoundtripTest {
+    @Test
+    public void testInterfaceOrder() {
+        runTest("InterfaceOrder", new BaksmaliOptions());
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/InvokeCustomTest.java b/baksmali/src/test/java/org/jf/baksmali/InvokeCustomTest.java
new file mode 100644
index 0000000..35f1482
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/InvokeCustomTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.junit.Test;
+
+public class InvokeCustomTest extends IdenticalRoundtripTest {
+    @Test
+    public void testInvokeCustom() {
+        BaksmaliOptions options = new BaksmaliOptions();
+        options.apiLevel = 26;
+        runTest("InvokeCustom", options);
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/LargeLocalTest.java b/baksmali/src/test/java/org/jf/baksmali/LargeLocalTest.java
new file mode 100644
index 0000000..28def63
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/LargeLocalTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.junit.Test;
+
+/**
+ * Test for a bug related to debug items that refer to a register that's outside the expected range for a method
+ */
+public class LargeLocalTest extends IdenticalRoundtripTest {
+    @Test
+    public void testLargeEndLocal() {
+        runTest("LargeEndLocal");
+    }
+
+    @Test
+    public void testLargeRestartLocal() {
+        runTest("LargeRestartLocal");
+    }
+
+    @Test
+    public void testLargeStartLocal() {
+        runTest("LargeStartLocal");
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/ManyRegistersTest.java b/baksmali/src/test/java/org/jf/baksmali/ManyRegistersTest.java
new file mode 100644
index 0000000..5a26716
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/ManyRegistersTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.junit.Test;
+
+public class ManyRegistersTest extends IdenticalRoundtripTest {
+
+    @Test
+    public void testManyRegisters() {
+        runTest("ManyRegisters");
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/MultiSwitchTest.java b/baksmali/src/test/java/org/jf/baksmali/MultiSwitchTest.java
new file mode 100644
index 0000000..cb29402
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/MultiSwitchTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.junit.Test;
+
+public class MultiSwitchTest extends DisassemblyTest {
+
+    @Test
+    public void testMultiSwitch() {
+        runTest("MultiSwitch");
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/ParamListMethodNameTest.java b/baksmali/src/test/java/org/jf/baksmali/ParamListMethodNameTest.java
new file mode 100644
index 0000000..42f7239
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/ParamListMethodNameTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.junit.Test;
+
+public class ParamListMethodNameTest extends IdenticalRoundtripTest {
+
+    @Test
+    public void testParamListMethodName() {
+        runTest("ParamListMethodName");
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java b/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java
new file mode 100644
index 0000000..81e98a3
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.antlr.runtime.RecognitionException;
+import org.junit.Assert;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A base test class for performing a roundtrip assembly/disassembly
+ *
+ * The test accepts a smali file as input, performs a smali -> dex -> smali roundtrip, and
+ * verifies that the result equals a known-good output smali file.
+ *
+ * By default, the input and output files should be resources at [testDir]/[testName]Input.smali
+ * and [testDir]/[testName]Output.smali respectively
+ */
+public abstract class RoundtripTest {
+    protected final String testDir;
+
+    protected RoundtripTest(@Nonnull String testDir) {
+        this.testDir = testDir;
+    }
+
+    protected RoundtripTest() {
+        this.testDir = this.getClass().getSimpleName();
+    }
+
+    @Nonnull
+    protected String getInputFilename(@Nonnull String testName) {
+        return String.format("%s%s%sInput.smali", testDir, File.separatorChar, testName);
+    }
+
+    @Nonnull
+    protected String getOutputFilename(@Nonnull String testName) {
+        return String.format("%s%s%sOutput.smali", testDir, File.separatorChar, testName);
+    }
+
+    protected void runTest(@Nonnull String testName) {
+        runTest(testName, new BaksmaliOptions());
+    }
+
+    protected void runTest(@Nonnull String testName, @Nonnull BaksmaliOptions options) {
+        try {
+            // Load file from resources as a stream
+            String inputFilename = getInputFilename(testName);
+            String input = BaksmaliTestUtils.readResourceFully(getInputFilename(testName));
+            String output;
+            if (getOutputFilename(testName).equals(inputFilename)) {
+                output = input;
+            } else {
+                output = BaksmaliTestUtils.readResourceFully(getOutputFilename(testName));
+            }
+
+            // Run smali, baksmali, and then compare strings are equal (minus comments/whitespace)
+            BaksmaliTestUtils.assertSmaliCompiledEquals(input, output, options, true);
+        } catch (IOException ex) {
+            Assert.fail();
+        } catch (RecognitionException ex) {
+            Assert.fail();
+        }
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/SwitchTest.java b/baksmali/src/test/java/org/jf/baksmali/SwitchTest.java
new file mode 100644
index 0000000..48b64b2
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/SwitchTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.junit.Test;
+
+public class SwitchTest extends RoundtripTest {
+    @Test
+    public void testUnorderedSparseSwitch() {
+        runTest("UnorderedSparseSwitch");
+    }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/ZeroArrayPayloadWidthTest.java b/baksmali/src/test/java/org/jf/baksmali/ZeroArrayPayloadWidthTest.java
new file mode 100644
index 0000000..b4e93f4
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/ZeroArrayPayloadWidthTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.baksmali;
+
+import org.junit.Test;
+
+public class ZeroArrayPayloadWidthTest extends DisassemblyTest {
+
+    @Test
+    public void testZeroArrayPayloadWidthTest() {
+        // This test uses a manually modified dex file with an array-payload instruction that has an element size of 0,
+        // and an element count that doesn't fit in an unsigned int.
+        runTest("ZeroArrayPayloadWidthTest");
+    }
+}
diff --git a/baksmali/src/test/resources/ConstructorTest/ConstructorTest.smali b/baksmali/src/test/resources/ConstructorTest/ConstructorTest.smali
new file mode 100644
index 0000000..88e2eb8
--- /dev/null
+++ b/baksmali/src/test/resources/ConstructorTest/ConstructorTest.smali
@@ -0,0 +1,16 @@
+.class public LConstructorTest;
+.super Ljava/lang/Object;
+
+
+# direct methods
+.method public constructor <init>()V
+    .registers 4
+
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(UninitThis,LConstructorTest;);
+    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(Reference,LConstructorTest;);
+
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(Reference,LConstructorTest;);
+    return-void
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(Reference,LConstructorTest;);
+.end method
diff --git a/baksmali/src/test/resources/ConstructorTest/ConstructorTest2.smali b/baksmali/src/test/resources/ConstructorTest/ConstructorTest2.smali
new file mode 100644
index 0000000..a376b25
--- /dev/null
+++ b/baksmali/src/test/resources/ConstructorTest/ConstructorTest2.smali
@@ -0,0 +1,25 @@
+.class public LConstructorTest2;
+.super Ljava/lang/Object;
+
+
+# direct methods
+.method public constructor <init>()V
+    .registers 4
+
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(UninitThis,LConstructorTest2;);
+    if-eqz p0, :cond_3
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(UninitThis,LConstructorTest2;);
+
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(UninitThis,LConstructorTest2;);
+    nop
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(UninitThis,LConstructorTest2;);
+
+    :cond_3
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(UninitThis,LConstructorTest2;);
+    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(Reference,LConstructorTest2;);
+
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(Reference,LConstructorTest2;);
+    return-void
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(Reference,LConstructorTest2;);
+.end method
diff --git a/baksmali/src/test/resources/ConstructorTest/classes.dex b/baksmali/src/test/resources/ConstructorTest/classes.dex
new file mode 100644
index 0000000..ef6e6d9
Binary files /dev/null and b/baksmali/src/test/resources/ConstructorTest/classes.dex differ
diff --git a/baksmali/src/test/resources/DuplicateTest/DuplicateDirectMethods.smali b/baksmali/src/test/resources/DuplicateTest/DuplicateDirectMethods.smali
new file mode 100644
index 0000000..fd43b02
--- /dev/null
+++ b/baksmali/src/test/resources/DuplicateTest/DuplicateDirectMethods.smali
@@ -0,0 +1,29 @@
+.class public LDuplicateDirectMethods;
+.super Ljava/lang/Object;
+
+
+# direct methods
+.method private alah()V
+    .registers 1
+
+    return-void
+.end method
+
+.method private blah()V
+    .registers 1
+
+    return-void
+.end method
+
+# duplicate method ignored
+# .method private blah()V
+#     .registers 1
+
+#     return-void
+# .end method
+
+.method private clah()V
+    .registers 1
+
+    return-void
+.end method
diff --git a/baksmali/src/test/resources/DuplicateTest/DuplicateDirectVirtualMethods.smali b/baksmali/src/test/resources/DuplicateTest/DuplicateDirectVirtualMethods.smali
new file mode 100644
index 0000000..5286573
--- /dev/null
+++ b/baksmali/src/test/resources/DuplicateTest/DuplicateDirectVirtualMethods.smali
@@ -0,0 +1,46 @@
+.class public LDuplicateDirectVirtualMethods;
+.super Ljava/lang/Object;
+
+
+# direct methods
+.method private blah()V
+    .registers 1
+
+    return-void
+.end method
+
+# duplicate method ignored
+# .method private blah()V
+#     .registers 1
+
+#     return-void
+# .end method
+
+
+# virtual methods
+.method public alah()V
+    .registers 1
+
+    return-void
+.end method
+
+# There is both a direct and virtual method with this signature.
+# You will need to rename one of these methods, including all references.
+.method public blah()V
+    .registers 1
+
+    return-void
+.end method
+
+# duplicate method ignored
+# .method public blah()V
+#     .registers 1
+
+#     return-void
+# .end method
+
+.method public clah()V
+    .registers 1
+
+    return-void
+.end method
diff --git a/baksmali/src/test/resources/DuplicateTest/DuplicateInstanceFields.smali b/baksmali/src/test/resources/DuplicateTest/DuplicateInstanceFields.smali
new file mode 100644
index 0000000..6efe9cf
--- /dev/null
+++ b/baksmali/src/test/resources/DuplicateTest/DuplicateInstanceFields.smali
@@ -0,0 +1,13 @@
+.class public LDuplicateInstanceFields;
+.super Ljava/lang/Object;
+
+
+# instance fields
+.field public alah:I
+
+.field public blah:I
+
+# duplicate field ignored
+# .field public blah:I
+
+.field public clah:I
diff --git a/baksmali/src/test/resources/DuplicateTest/DuplicateStaticFields.smali b/baksmali/src/test/resources/DuplicateTest/DuplicateStaticFields.smali
new file mode 100644
index 0000000..b71fbdf
--- /dev/null
+++ b/baksmali/src/test/resources/DuplicateTest/DuplicateStaticFields.smali
@@ -0,0 +1,13 @@
+.class public LDuplicateStaticFields;
+.super Ljava/lang/Object;
+
+
+# static fields
+.field public static alah:I
+
+.field public static blah:I
+
+# duplicate field ignored
+# .field public static blah:I
+
+.field public static clah:I
diff --git a/baksmali/src/test/resources/DuplicateTest/DuplicateStaticInstanceFields.smali b/baksmali/src/test/resources/DuplicateTest/DuplicateStaticInstanceFields.smali
new file mode 100644
index 0000000..9a066b8
--- /dev/null
+++ b/baksmali/src/test/resources/DuplicateTest/DuplicateStaticInstanceFields.smali
@@ -0,0 +1,22 @@
+.class public LDuplicateStaticInstanceFields;
+.super Ljava/lang/Object;
+
+
+# static fields
+.field public static blah:I
+
+# duplicate field ignored
+# .field public static blah:I
+
+
+# instance fields
+.field public alah:I
+
+# There is both a static and instance field with this signature.
+# You will need to rename one of these fields, including all references.
+.field public blah:I
+
+# duplicate field ignored
+# .field public blah:I
+
+.field public clah:I
diff --git a/baksmali/src/test/resources/DuplicateTest/DuplicateVirtualMethods.smali b/baksmali/src/test/resources/DuplicateTest/DuplicateVirtualMethods.smali
new file mode 100644
index 0000000..3c08002
--- /dev/null
+++ b/baksmali/src/test/resources/DuplicateTest/DuplicateVirtualMethods.smali
@@ -0,0 +1,29 @@
+.class public LDuplicateVirtualMethods;
+.super Ljava/lang/Object;
+
+
+# virtual methods
+.method public alah()V
+    .registers 1
+
+    return-void
+.end method
+
+.method public blah()V
+    .registers 1
+
+    return-void
+.end method
+
+# duplicate method ignored
+# .method public blah()V
+#     .registers 1
+
+#     return-void
+# .end method
+
+.method public clah()V
+    .registers 1
+
+    return-void
+.end method
diff --git a/baksmali/src/test/resources/DuplicateTest/classes.dex b/baksmali/src/test/resources/DuplicateTest/classes.dex
new file mode 100644
index 0000000..6876944
Binary files /dev/null and b/baksmali/src/test/resources/DuplicateTest/classes.dex differ
diff --git a/baksmali/src/test/resources/DuplicateTest/src/DuplicateDirectMethods.smali b/baksmali/src/test/resources/DuplicateTest/src/DuplicateDirectMethods.smali
new file mode 100644
index 0000000..efb7abb
--- /dev/null
+++ b/baksmali/src/test/resources/DuplicateTest/src/DuplicateDirectMethods.smali
@@ -0,0 +1,22 @@
+.class public LDuplicateDirectMethods;
+.super Ljava/lang/Object;
+
+.method private alah()V
+    .registers 1
+    return-void
+.end method
+
+.method private blah()V
+    .registers 1
+    return-void
+.end method
+
+.method private blah()V
+    .registers 1
+    return-void
+.end method
+
+.method private clah()V
+    .registers 1
+    return-void
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/resources/DuplicateTest/src/DuplicateDirectVirtualMethods.smali b/baksmali/src/test/resources/DuplicateTest/src/DuplicateDirectVirtualMethods.smali
new file mode 100644
index 0000000..09d97b5
--- /dev/null
+++ b/baksmali/src/test/resources/DuplicateTest/src/DuplicateDirectVirtualMethods.smali
@@ -0,0 +1,32 @@
+.class public LDuplicateDirectVirtualMethods;
+.super Ljava/lang/Object;
+
+.method public alah()V
+    .registers 1
+    return-void
+.end method
+
+.method private blah()V
+    .registers 1
+    return-void
+.end method
+
+.method private blah()V
+    .registers 1
+    return-void
+.end method
+
+.method public blah()V
+    .registers 1
+    return-void
+.end method
+
+.method public blah()V
+    .registers 1
+    return-void
+.end method
+
+.method public clah()V
+    .registers 1
+    return-void
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/resources/DuplicateTest/src/DuplicateInstanceFields.smali b/baksmali/src/test/resources/DuplicateTest/src/DuplicateInstanceFields.smali
new file mode 100644
index 0000000..1b92cd7
--- /dev/null
+++ b/baksmali/src/test/resources/DuplicateTest/src/DuplicateInstanceFields.smali
@@ -0,0 +1,9 @@
+.class public LDuplicateInstanceFields;
+.super Ljava/lang/Object;
+
+.field public alah:I
+
+.field public blah:I
+.field public blah:I
+
+.field public clah:I
\ No newline at end of file
diff --git a/baksmali/src/test/resources/DuplicateTest/src/DuplicateStaticFields.smali b/baksmali/src/test/resources/DuplicateTest/src/DuplicateStaticFields.smali
new file mode 100644
index 0000000..3c01ba9
--- /dev/null
+++ b/baksmali/src/test/resources/DuplicateTest/src/DuplicateStaticFields.smali
@@ -0,0 +1,9 @@
+.class public LDuplicateStaticFields;
+.super Ljava/lang/Object;
+
+.field public static alah:I
+
+.field public static blah:I
+.field public static blah:I
+
+.field public static clah:I
\ No newline at end of file
diff --git a/baksmali/src/test/resources/DuplicateTest/src/DuplicateStaticInstanceFields.smali b/baksmali/src/test/resources/DuplicateTest/src/DuplicateStaticInstanceFields.smali
new file mode 100644
index 0000000..30a1fe6
--- /dev/null
+++ b/baksmali/src/test/resources/DuplicateTest/src/DuplicateStaticInstanceFields.smali
@@ -0,0 +1,11 @@
+.class public LDuplicateStaticInstanceFields;
+.super Ljava/lang/Object;
+
+.field public alah:I
+
+.field public blah:I
+.field public blah:I
+.field static public blah:I
+.field static public blah:I
+
+.field public clah:I
\ No newline at end of file
diff --git a/baksmali/src/test/resources/DuplicateTest/src/DuplicateVirtualMethods.smali b/baksmali/src/test/resources/DuplicateTest/src/DuplicateVirtualMethods.smali
new file mode 100644
index 0000000..3a6368e
--- /dev/null
+++ b/baksmali/src/test/resources/DuplicateTest/src/DuplicateVirtualMethods.smali
@@ -0,0 +1,22 @@
+.class public LDuplicateVirtualMethods;
+.super Ljava/lang/Object;
+
+.method public alah()V
+    .registers 1
+    return-void
+.end method
+
+.method public blah()V
+    .registers 1
+    return-void
+.end method
+
+.method public blah()V
+    .registers 1
+    return-void
+.end method
+
+.method public clah()V
+    .registers 1
+    return-void
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/resources/DuplicateTest/src/README b/baksmali/src/test/resources/DuplicateTest/src/README
new file mode 100644
index 0000000..fae5c5a
--- /dev/null
+++ b/baksmali/src/test/resources/DuplicateTest/src/README
@@ -0,0 +1,3 @@
+The test dex file was produced from these smali files, using
+an old version of smali that doesn't check for field/method
+duplicates
\ No newline at end of file
diff --git a/baksmali/src/test/resources/FieldGapOrderTest/FieldGapOrderInput.dex b/baksmali/src/test/resources/FieldGapOrderTest/FieldGapOrderInput.dex
new file mode 100644
index 0000000..4e59351
Binary files /dev/null and b/baksmali/src/test/resources/FieldGapOrderTest/FieldGapOrderInput.dex differ
diff --git a/baksmali/src/test/resources/HiddenApiRestrictionsRoundtripTest/HiddenApiRestrictionsInput.smali b/baksmali/src/test/resources/HiddenApiRestrictionsRoundtripTest/HiddenApiRestrictionsInput.smali
new file mode 100644
index 0000000..d07e541
--- /dev/null
+++ b/baksmali/src/test/resources/HiddenApiRestrictionsRoundtripTest/HiddenApiRestrictionsInput.smali
@@ -0,0 +1,30 @@
+.class public LHiddenApiRestrictions;
+.super Ljava/lang/Object;
+
+
+.field public static whitelist staticField:I
+
+.field public core-platform-api domainSpecificFlagTest:I
+
+.field public blacklist instanceField:I
+
+.method public blacklist virtualMethod()V
+    .registers 1
+    return-void
+.end method
+
+.method private greylist-max-o directMethod()V
+    .registers 1
+    return-void
+.end method
+
+.method private core-platform-api corePlatformApiTest()V
+    .registers 1
+    return-void
+.end method
+
+.method greylist-max-q private core-platform-api corePlatformApiAndHiddenApiTest()V
+    .registers 1
+    return-void
+.end method
+
diff --git a/baksmali/src/test/resources/HiddenApiRestrictionsRoundtripTest/HiddenApiRestrictionsOutput.smali b/baksmali/src/test/resources/HiddenApiRestrictionsRoundtripTest/HiddenApiRestrictionsOutput.smali
new file mode 100644
index 0000000..d2bc1b3
--- /dev/null
+++ b/baksmali/src/test/resources/HiddenApiRestrictionsRoundtripTest/HiddenApiRestrictionsOutput.smali
@@ -0,0 +1,40 @@
+.class public LHiddenApiRestrictions;
+.super Ljava/lang/Object;
+
+
+# static fields
+.field public static whitelist staticField:I
+
+
+# instance fields
+.field public whitelist core-platform-api domainSpecificFlagTest:I
+
+.field public blacklist instanceField:I
+
+
+# direct methods
+.method private greylist-max-q core-platform-api corePlatformApiAndHiddenApiTest()V
+    .registers 1
+
+    return-void
+.end method
+
+.method private whitelist core-platform-api corePlatformApiTest()V
+    .registers 1
+
+    return-void
+.end method
+
+.method private greylist-max-o directMethod()V
+    .registers 1
+
+    return-void
+.end method
+
+
+# virtual methods
+.method public blacklist virtualMethod()V
+    .registers 1
+
+    return-void
+.end method
diff --git a/baksmali/src/test/resources/InstanceOfTest/InstanceOfTest.smali b/baksmali/src/test/resources/InstanceOfTest/InstanceOfTest.smali
new file mode 100644
index 0000000..8e3337a
--- /dev/null
+++ b/baksmali/src/test/resources/InstanceOfTest/InstanceOfTest.smali
@@ -0,0 +1,118 @@
+.class public LInstanceOfTest;
+.super Ljava/lang/Object;
+
+
+# virtual methods
+.method public testInstanceOfEqz(Ljava/lang/Object;)I
+    .registers 3
+
+    #v0=(Uninit);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+    instance-of v0, p1, Ljava/lang/String;
+    #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+
+    #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+    if-eqz v0, :cond_9
+    #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Unknown);
+
+    #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+    invoke-virtual {p1}, Ljava/lang/String;->length()I
+    #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+
+    #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+    move-result v0
+    #v0=(Integer);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+
+    #v0=(Integer);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+    return v0
+    #v0=(Integer);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+
+    :cond_9
+    #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+    const v0, -0x1
+    #v0=(Byte);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+
+    #v0=(Byte);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+    return v0
+    #v0=(Byte);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+.end method
+
+.method public testInstanceOfNez(Ljava/lang/Object;)I
+    .registers 3
+
+    #v0=(Uninit);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+    instance-of v0, p1, Ljava/lang/String;
+    #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+
+    #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+    if-nez v0, :cond_8
+    #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Unknown);
+
+    #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+    const v0, -0x1
+    #v0=(Byte);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+
+    #v0=(Byte);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+    return v0
+    #v0=(Byte);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+
+    :cond_8
+    #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+    invoke-virtual {p1}, Ljava/lang/String;->length()I
+    #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+
+    #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+    move-result v0
+    #v0=(Integer);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+
+    #v0=(Integer);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+    return v0
+    #v0=(Integer);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+.end method
+
+.method public testRegisterAlias(Ljava/lang/Object;)I
+    .registers 4
+
+    #v0=(Uninit);v1=(Uninit);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+    move-object p0, p1
+    #v0=(Uninit);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+
+    #v0=(Uninit);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+    instance-of v0, p0, Ljava/lang/String;
+    #v0=(Boolean);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+
+    #v0=(Boolean);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+    if-eqz v0, :cond_f
+    #v0=(Boolean);v1=(Uninit);p0=(Unknown);p1=(Unknown);
+
+    :cond_5
+    #v0=(Integer):merge{0x3:(Boolean),0xc:(Integer)}
+    #v1=(Conflicted):merge{0x3:(Uninit),0xc:(Null)}
+    #p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+    invoke-virtual {p1}, Ljava/lang/String;->length()I
+    #v0=(Integer);v1=(Conflicted);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+
+    #v0=(Integer);v1=(Conflicted);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+    move-result v0
+    #v0=(Integer);v1=(Conflicted);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+
+    #v0=(Integer);v1=(Conflicted);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+    const v1, 0x0
+    #v0=(Integer);v1=(Null);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+
+    #v0=(Integer);v1=(Null);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+    if-le v0, v1, :cond_5
+    #v0=(Integer);v1=(Null);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+
+    #v0=(Integer);v1=(Null);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+    return v0
+    #v0=(Integer);v1=(Null);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+
+    :cond_f
+    #v0=(Boolean);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+    const v0, -0x1
+    #v0=(Byte);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+
+    #v0=(Byte);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+    return v0
+    #v0=(Byte);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+.end method
diff --git a/baksmali/src/test/resources/InstanceOfTest/classes.dex b/baksmali/src/test/resources/InstanceOfTest/classes.dex
new file mode 100644
index 0000000..571bdb8
Binary files /dev/null and b/baksmali/src/test/resources/InstanceOfTest/classes.dex differ
diff --git a/baksmali/src/test/resources/InstructionRoundtripTest/ConstMethodHandle.smali b/baksmali/src/test/resources/InstructionRoundtripTest/ConstMethodHandle.smali
new file mode 100644
index 0000000..2d87184
--- /dev/null
+++ b/baksmali/src/test/resources/InstructionRoundtripTest/ConstMethodHandle.smali
@@ -0,0 +1,30 @@
+.class LConstMethodHandle;
+.super Ljava/lang/Object;
+
+
+# static fields
+.field public static staticField:Ljava/lang/Object;
+
+
+# instance fields
+.field public instanceField:Ljava/lang/Object;
+
+
+# direct methods
+.method public static constMethodHandle()V
+    .registers 15
+
+    const-method-handle v0, invoke-static@Ljava/lang/Integer;->toString(I)Ljava/lang/String;
+
+    const-method-handle v0, invoke-instance@Ljava/lang/Integer;->toString()Ljava/lang/String;
+
+    const-method-handle v0, static-put@LConstMethodHandle;->instanceField:Ljava/lang/Object;
+
+    const-method-handle v0, static-put@LConstMethodHandle;->instanceField:Ljava/lang/Object;
+
+    const-method-handle v0, static-put@LConstMethodHandle;->staticField:Ljava/lang/Object;
+
+    const-method-handle v0, static-put@LConstMethodHandle;->staticField:Ljava/lang/Object;
+
+    return-void
+.end method
diff --git a/baksmali/src/test/resources/InstructionRoundtripTest/ConstMethodType.smali b/baksmali/src/test/resources/InstructionRoundtripTest/ConstMethodType.smali
new file mode 100644
index 0000000..17f2889
--- /dev/null
+++ b/baksmali/src/test/resources/InstructionRoundtripTest/ConstMethodType.smali
@@ -0,0 +1,24 @@
+.class LConstMethodType;
+.super Ljava/lang/Object;
+
+
+# static fields
+.field public static staticField:Ljava/lang/Object;
+
+
+# instance fields
+.field public instanceField:Ljava/lang/Object;
+
+
+# direct methods
+.method public static constMethodHandle()V
+    .registers 15
+
+    const-method-type v0, ()V
+
+    const-method-type v0, (II)I
+
+    const-method-type v0, (Ljava/lang/String;)Ljava/lang/String;
+
+    return-void
+.end method
diff --git a/baksmali/src/test/resources/InterfaceOrderTest/InterfaceOrder.smali b/baksmali/src/test/resources/InterfaceOrderTest/InterfaceOrder.smali
new file mode 100644
index 0000000..b4745cb
--- /dev/null
+++ b/baksmali/src/test/resources/InterfaceOrderTest/InterfaceOrder.smali
@@ -0,0 +1,37 @@
+.class public LInterfaceOrder;
+.super Ljava/lang/Object;
+
+# Note how these two interfaces are not in alphabetical order
+.implements Ljava/io/Serializable;
+.implements Ljava/util/EventListener;
+.implements Ljava/lang/Runnable;
+.implements Ljava/io/Flushable;
+.implements Ljava/lang/Clonable;
+.implements Ljava/util/Observer;
+.implements Ljava/io/Closeable;
+
+# direct methods
+.method public constructor <init>()V
+    .registers 1
+    return-void
+.end method
+
+.method public close()V
+    .registers 1
+    return-void
+.end method
+
+.method public flush()V
+    .registers 1
+    return-void
+.end method
+
+.method public run()V
+    .registers 1
+    return-void
+.end method
+
+.method public update(Ljava/util/Observable;Ljava/lang/Object;)V
+    .registers 3
+    return-void
+.end method
diff --git a/baksmali/src/test/resources/InvokeCustomTest/InvokeCustom.smali b/baksmali/src/test/resources/InvokeCustomTest/InvokeCustom.smali
new file mode 100644
index 0000000..632acb0
--- /dev/null
+++ b/baksmali/src/test/resources/InvokeCustomTest/InvokeCustom.smali
@@ -0,0 +1,44 @@
+.class LInvokeCustom;
+.super Ljava/lang/Object;
+
+
+# direct methods
+.method public static invokeCustom([Ljava/lang/String;)V
+    .registers 15
+
+    new-instance v0, LCustom;
+
+    invoke-direct {v0}, LCustom;-><init>()V
+
+    const-string v1, "Arg to doSomething"
+
+    invoke-custom {v0, v1}, call_site_1("doSomething", (LCustom;Ljava/lang/String;)Ljava/lang/String;, "just testing")@LBootstrapLinker;->normalLink(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;
+
+    move-result-object v2
+
+    sget-object v3, Ljava/lang/System;->out:Ljava/io/PrintStream;
+
+    const-string v4, "got back - "
+
+    invoke-virtual {v3, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V
+
+    invoke-virtual {v3, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
+
+    invoke-custom {v0, v1}, call_site_0("doSomething", (LCustom;Ljava/lang/String;)Ljava/lang/String;, "just testing")@LBootstrapLinker;->backwardsLink(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;
+
+    move-result-object v2
+
+    sget-object v3, Ljava/lang/System;->out:Ljava/io/PrintStream;
+
+    const-string v4, "got back - "
+
+    invoke-virtual {v3, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V
+
+    invoke-virtual {v3, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
+.end method
+
+.method public static invokeCustomWithMethodHandleArgument([Ljava/lang/String;)V
+    .registers 15
+
+    invoke-custom {v0, v1}, call_site_2("doSomething", (LCustom;Ljava/lang/String;)Ljava/lang/String;, invoke-static@Lnonsense;->somemethod()V)@LBootstrapLinker;->normalLink(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite;
+.end method
diff --git a/baksmali/src/test/resources/LargeLocalTest/LargeEndLocal.smali b/baksmali/src/test/resources/LargeLocalTest/LargeEndLocal.smali
new file mode 100644
index 0000000..8c7e72c
--- /dev/null
+++ b/baksmali/src/test/resources/LargeLocalTest/LargeEndLocal.smali
@@ -0,0 +1,11 @@
+.class LLargeRestartLocal;
+.super Ljava/lang/Object;
+
+
+# virtual methods
+.method public static main([Ljava/lang/String;)V
+    .registers 2
+
+    .end local p99
+    return-void
+.end method
diff --git a/baksmali/src/test/resources/LargeLocalTest/LargeRestartLocal.smali b/baksmali/src/test/resources/LargeLocalTest/LargeRestartLocal.smali
new file mode 100644
index 0000000..41c60d0
--- /dev/null
+++ b/baksmali/src/test/resources/LargeLocalTest/LargeRestartLocal.smali
@@ -0,0 +1,11 @@
+.class LLargeEndLocal;
+.super Ljava/lang/Object;
+
+
+# virtual methods
+.method public static main([Ljava/lang/String;)V
+    .registers 2
+
+    .restart local p99
+    return-void
+.end method
diff --git a/baksmali/src/test/resources/LargeLocalTest/LargeStartLocal.smali b/baksmali/src/test/resources/LargeLocalTest/LargeStartLocal.smali
new file mode 100644
index 0000000..b811844
--- /dev/null
+++ b/baksmali/src/test/resources/LargeLocalTest/LargeStartLocal.smali
@@ -0,0 +1,11 @@
+.class LLargeStartLocal;
+.super Ljava/lang/Object;
+
+
+# virtual methods
+.method public static main([Ljava/lang/String;)V
+    .registers 2
+
+    .local p99, "blah":I
+    return-void
+.end method
diff --git a/baksmali/src/test/resources/LocalTest/LocalTest.smali b/baksmali/src/test/resources/LocalTest/LocalTest.smali
new file mode 100644
index 0000000..fe6d1ad
--- /dev/null
+++ b/baksmali/src/test/resources/LocalTest/LocalTest.smali
@@ -0,0 +1,31 @@
+.class public LLocalTest;
+.super Ljava/lang/Object;
+
+
+# direct methods
+.method public static method1()V
+    .registers 10
+
+    .local v0, "blah! This local name has some spaces, a colon, even a \nnewline!":I, "some sig info:\nblah."
+    .local v1, "blah! This local name has some spaces, a colon, even a \nnewline!":V, "some sig info:\nblah."
+    .local v2, "blah! This local name has some spaces, a colon, even a \nnewline!":I
+    .local v3, "blah! This local name has some spaces, a colon, even a \nnewline!":V
+    .local v4, null:I, "some sig info:\nblah."
+    .local v5, null:V, "some sig info:\nblah."
+    .local v6, null:I
+    .local v7
+    .local v8
+    .local v9
+    return-void
+.end method
+
+.method public static method2(IJLjava/lang/String;)V
+    .registers 10
+    .param p0, "blah! This local name has some spaces, a colon, even a \nnewline!"    # I
+    .param p1    # J
+        .annotation runtime LAnnotationWithValues;
+        .end annotation
+    .end param
+
+    return-void
+.end method
diff --git a/baksmali/src/test/resources/LocalTest/classes.dex b/baksmali/src/test/resources/LocalTest/classes.dex
new file mode 100644
index 0000000..5b6f026
Binary files /dev/null and b/baksmali/src/test/resources/LocalTest/classes.dex differ
diff --git a/baksmali/src/test/resources/ManyRegistersTest/ManyRegisters.smali b/baksmali/src/test/resources/ManyRegistersTest/ManyRegisters.smali
new file mode 100644
index 0000000..7f7c7bb
--- /dev/null
+++ b/baksmali/src/test/resources/ManyRegistersTest/ManyRegisters.smali
@@ -0,0 +1,7 @@
+.class LManyRegisters;
+.super Ljava/lang/Object;
+
+.method public manyRegisters()V
+    .registers 65535
+    return-void
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.dex b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.dex
new file mode 100644
index 0000000..6ef3d34
Binary files /dev/null and b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.dex differ
diff --git a/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.smali b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.smali
new file mode 100644
index 0000000..b4fd27d
--- /dev/null
+++ b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.smali
@@ -0,0 +1,72 @@
+.class public LMultiSwitch;
+.super Ljava/lang/Object;
+.source "Format31t.smali"
+
+.method public multi-packed-switch()V
+    .registers 1
+    const p0, 0xc
+    packed-switch p0, :pswitch_data_12
+    goto :goto_b
+    :pswitch_7
+    return-void
+    :pswitch_8
+    return-void
+    :pswitch_9
+    return-void
+    :pswitch_a
+    return-void
+    :goto_b
+    packed-switch p0, :pswitch_data_12
+        nop
+    return-void
+    :pswitch_f
+    return-void
+    :pswitch_10
+    return-void
+    :pswitch_11
+    return-void
+    :pswitch_12
+    :pswitch_data_12
+    .packed-switch 0xa
+    :pswitch_7
+    :pswitch_8
+    :pswitch_9
+    :pswitch_a
+    .end packed-switch
+
+.end method
+
+.method public multi-sparse-switch()V
+    .registers 1
+    const p0, 0xd
+    sparse-switch p0, :sswitch_data_12
+    goto :goto_b
+    :sswitch_7
+    return-void
+    :sswitch_8
+    return-void
+    :sswitch_9
+    return-void
+    :sswitch_a
+    return-void
+    :goto_b
+    sparse-switch p0, :sswitch_data_12
+    nop
+    return-void
+    :sswitch_f
+    return-void
+    :sswitch_10
+    return-void
+    :sswitch_11
+    return-void
+
+    :sswitch_12
+
+    :sswitch_data_12
+    .sparse-switch
+        0xa -> :sswitch_7
+        0xf -> :sswitch_9
+        0x14 -> :sswitch_8
+        0x63 -> :sswitch_a
+    .end sparse-switch
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchOutput.smali b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchOutput.smali
new file mode 100644
index 0000000..f3aeeed
--- /dev/null
+++ b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchOutput.smali
@@ -0,0 +1,119 @@
+.class public LMultiSwitch;
+.super Ljava/lang/Object;
+.source "Format31t.smali"
+
+
+# virtual methods
+.method public multi-packed-switch()V
+    .registers 1
+
+    const p0, 0xc
+
+    packed-switch p0, :pswitch_data_14
+
+    goto :goto_b
+
+    :pswitch_7
+    return-void
+
+    :pswitch_8
+    return-void
+
+    :pswitch_9
+    return-void
+
+    :pswitch_a
+    return-void
+
+    :goto_b
+    packed-switch p0, :pswitch_data_20
+
+    nop
+
+    :pswitch_f
+    return-void
+
+    :pswitch_10
+    return-void
+
+    :pswitch_11
+    return-void
+
+    :pswitch_12
+    return-void
+
+    nop
+
+    :pswitch_data_14
+    .packed-switch 0xa
+        :pswitch_7
+        :pswitch_8
+        :pswitch_9
+        :pswitch_a
+    .end packed-switch
+
+    :pswitch_data_20
+    .packed-switch 0xa
+        :pswitch_f
+        :pswitch_10
+        :pswitch_11
+        :pswitch_12
+    .end packed-switch
+.end method
+
+.method public multi-sparse-switch()V
+    .registers 1
+
+    const p0, 0xd
+
+    sparse-switch p0, :sswitch_data_14
+
+    goto :goto_b
+
+    :sswitch_7
+    return-void
+
+    :sswitch_8
+    return-void
+
+    :sswitch_9
+    return-void
+
+    :sswitch_a
+    return-void
+
+    :goto_b
+    sparse-switch p0, :sswitch_data_26
+
+    nop
+
+    :sswitch_f
+    return-void
+
+    :sswitch_10
+    return-void
+
+    :sswitch_11
+    return-void
+
+    :sswitch_12
+    return-void
+
+    nop
+
+    :sswitch_data_14
+    .sparse-switch
+        0xa -> :sswitch_7
+        0xf -> :sswitch_9
+        0x14 -> :sswitch_8
+        0x63 -> :sswitch_a
+    .end sparse-switch
+
+    :sswitch_data_26
+    .sparse-switch
+        0xa -> :sswitch_f
+        0xf -> :sswitch_11
+        0x14 -> :sswitch_10
+        0x63 -> :sswitch_12
+    .end sparse-switch
+.end method
diff --git a/baksmali/src/test/resources/MultipleStartInstructionsTest/MultipleStartInstructionsTest.smali b/baksmali/src/test/resources/MultipleStartInstructionsTest/MultipleStartInstructionsTest.smali
new file mode 100644
index 0000000..e329e6c
--- /dev/null
+++ b/baksmali/src/test/resources/MultipleStartInstructionsTest/MultipleStartInstructionsTest.smali
@@ -0,0 +1,46 @@
+.class public LMultipleStartInstructionsTest;
+.super Ljava/lang/Object;
+
+
+# direct methods
+.method public constructor <init>(Ljava/lang/String;)V
+    .registers 4
+
+    :try_start_0
+    #v0=(Uninit);v1=(Uninit);p0=(UninitThis,LMultipleStartInstructionsTest;);p1=(Reference,Ljava/lang/String;);
+    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+    #v0=(Uninit);v1=(Uninit);p0=(Reference,LMultipleStartInstructionsTest;);p1=(Reference,Ljava/lang/String;);
+
+    #v0=(Uninit);v1=(Uninit);p0=(Reference,LMultipleStartInstructionsTest;);p1=(Reference,Ljava/lang/String;);
+    const-string v0, "blah"
+    #v0=(Reference,Ljava/lang/String;);v1=(Uninit);p0=(Reference,LMultipleStartInstructionsTest;);p1=(Reference,Ljava/lang/String;);
+
+    #v0=(Reference,Ljava/lang/String;);v1=(Uninit);p0=(Reference,LMultipleStartInstructionsTest;);p1=(Reference,Ljava/lang/String;);
+    return-void
+    #v0=(Reference,Ljava/lang/String;);v1=(Uninit);p0=(Reference,LMultipleStartInstructionsTest;);p1=(Reference,Ljava/lang/String;);
+    :try_end_6
+    .catchall {:try_start_0 .. :try_end_6} :catchall_6
+
+    :catchall_6
+    :try_start_6
+    #v0=(Uninit);v1=(Uninit);
+    #p0=(Conflicted):merge{Start:(UninitThis,LMultipleStartInstructionsTest;),0x0:(Reference,LMultipleStartInstructionsTest;)}
+    #p1=(Reference,Ljava/lang/String;);
+    invoke-static {}, LMultipleStartInstructionsTest;->blah()V
+    #v0=(Uninit);v1=(Uninit);p0=(Conflicted);p1=(Reference,Ljava/lang/String;);
+    :try_end_9
+    .catchall {:try_start_6 .. :try_end_9} :catchall_9
+
+    :catchall_9
+    #v0=(Uninit);v1=(Uninit);
+    #p0=(Conflicted):merge{Start:(UninitThis,LMultipleStartInstructionsTest;),0x0:(Reference,LMultipleStartInstructionsTest;),0x6:(Conflicted)}
+    #p1=(Reference,Ljava/lang/String;);
+    return-void
+    #v0=(Uninit);v1=(Uninit);p0=(Conflicted);p1=(Reference,Ljava/lang/String;);
+.end method
+
+.method public static blah()V
+    .registers 0
+
+    return-void
+.end method
diff --git a/baksmali/src/test/resources/MultipleStartInstructionsTest/classes.dex b/baksmali/src/test/resources/MultipleStartInstructionsTest/classes.dex
new file mode 100644
index 0000000..f6876c2
Binary files /dev/null and b/baksmali/src/test/resources/MultipleStartInstructionsTest/classes.dex differ
diff --git a/baksmali/src/test/resources/ParamListMethodNameTest/ParamListMethodName.smali b/baksmali/src/test/resources/ParamListMethodNameTest/ParamListMethodName.smali
new file mode 100644
index 0000000..8571715
--- /dev/null
+++ b/baksmali/src/test/resources/ParamListMethodNameTest/ParamListMethodName.smali
@@ -0,0 +1,5 @@
+.class Lblah;
+.super Ljava/lang/Object;
+
+.method public abstract II()V
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/resources/RegisterEqualityOnMergeTest/RegisterEqualityOnMerge.smali b/baksmali/src/test/resources/RegisterEqualityOnMergeTest/RegisterEqualityOnMerge.smali
new file mode 100644
index 0000000..b2b9b52
--- /dev/null
+++ b/baksmali/src/test/resources/RegisterEqualityOnMergeTest/RegisterEqualityOnMerge.smali
@@ -0,0 +1,37 @@
+.class public LRegisterEqualityOnMerge;
+.super Ljava/lang/Object;
+
+
+# direct methods
+.method public constructor <init>()V
+    .registers 4
+
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(UninitThis,LRegisterEqualityOnMerge;);
+    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(Reference,LRegisterEqualityOnMerge;);
+
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(Reference,LRegisterEqualityOnMerge;);
+    invoke-virtual {p0}, Ljava/lang/Object;->toString()Ljava/lang/String;
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(Reference,LRegisterEqualityOnMerge;);
+
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(Reference,LRegisterEqualityOnMerge;);
+    move-result-object v0
+    #v0=(Reference,Ljava/lang/String;);v1=(Uninit);v2=(Uninit);p0=(Reference,LRegisterEqualityOnMerge;);
+
+    #v0=(Reference,Ljava/lang/String;);v1=(Uninit);v2=(Uninit);p0=(Reference,LRegisterEqualityOnMerge;);
+    if-eqz v0, :cond_d
+    #v0=(Reference,Ljava/lang/String;);v1=(Uninit);v2=(Uninit);p0=(Reference,LRegisterEqualityOnMerge;);
+
+    #v0=(Reference,Ljava/lang/String;);v1=(Uninit);v2=(Uninit);p0=(Reference,LRegisterEqualityOnMerge;);
+    invoke-virtual {p0}, Ljava/lang/Object;->toString()Ljava/lang/String;
+    #v0=(Reference,Ljava/lang/String;);v1=(Uninit);v2=(Uninit);p0=(Reference,LRegisterEqualityOnMerge;);
+
+    #v0=(Reference,Ljava/lang/String;);v1=(Uninit);v2=(Uninit);p0=(Reference,LRegisterEqualityOnMerge;);
+    move-result-object v0
+    #v0=(Reference,Ljava/lang/String;);v1=(Uninit);v2=(Uninit);p0=(Reference,LRegisterEqualityOnMerge;);
+
+    :cond_d
+    #v0=(Reference,Ljava/lang/String;);v1=(Uninit);v2=(Uninit);p0=(Reference,LRegisterEqualityOnMerge;);
+    return-void
+    #v0=(Reference,Ljava/lang/String;);v1=(Uninit);v2=(Uninit);p0=(Reference,LRegisterEqualityOnMerge;);
+.end method
diff --git a/baksmali/src/test/resources/RegisterEqualityOnMergeTest/classes.dex b/baksmali/src/test/resources/RegisterEqualityOnMergeTest/classes.dex
new file mode 100644
index 0000000..424fc20
Binary files /dev/null and b/baksmali/src/test/resources/RegisterEqualityOnMergeTest/classes.dex differ
diff --git a/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchInput.smali b/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchInput.smali
new file mode 100644
index 0000000..6e3d23d
--- /dev/null
+++ b/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchInput.smali
@@ -0,0 +1,35 @@
+.class public LUnorderedSparseSwitch;
+.super Ljava/lang/Object;
+
+.method public static test_sparse-switch()V
+    .registers 1
+
+    const v0, 13
+
+    sparse-switch v0, :SparseSwitch
+
+:Label10
+    return-void
+
+:Label20
+    return-void
+
+:Label15
+    return-void
+
+:Label13
+    return-void
+
+:Label99
+    return-void
+
+# Note: unordered keys
+:SparseSwitch
+    .sparse-switch
+        10 -> :Label10
+        20 -> :Label20
+        15 -> :Label15
+        99 -> :Label99
+        13 -> :Label13
+    .end sparse-switch
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchOutput.smali b/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchOutput.smali
new file mode 100644
index 0000000..c4c455b
--- /dev/null
+++ b/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchOutput.smali
@@ -0,0 +1,28 @@
+.class public LUnorderedSparseSwitch;
+.super Ljava/lang/Object;
+.method public static test_sparse-switch()V
+.registers 1
+const v0, 0xd
+sparse-switch v0, :sswitch_data_c
+:sswitch_6
+return-void
+:sswitch_7
+return-void
+:sswitch_8
+return-void
+:sswitch_9
+return-void
+:sswitch_a
+return-void
+nop
+
+# Note: ordered keys
+:sswitch_data_c
+.sparse-switch
+0xa -> :sswitch_6
+0xd -> :sswitch_9
+0xf -> :sswitch_8
+0x14 -> :sswitch_7
+0x63 -> :sswitch_a
+.end sparse-switch
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/resources/UninitRefIdentityTest/UninitRefIdentityTest.smali b/baksmali/src/test/resources/UninitRefIdentityTest/UninitRefIdentityTest.smali
new file mode 100644
index 0000000..1970d3b
--- /dev/null
+++ b/baksmali/src/test/resources/UninitRefIdentityTest/UninitRefIdentityTest.smali
@@ -0,0 +1,110 @@
+.class public LUninitRefIdentityTest;
+.super Ljava/lang/Object;
+
+
+# direct methods
+.method public constructor <init>()V
+    .registers 4
+
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(UninitThis,LUninitRefIdentityTest;);
+    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+
+    #v0=(Uninit);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+    new-instance v0, Ljava/lang/String;
+    #v0=(UninitRef,Ljava/lang/String;);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+
+    #v0=(UninitRef,Ljava/lang/String;);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+    if-eqz v0, :cond_9
+    #v0=(UninitRef,Ljava/lang/String;);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+
+    #v0=(UninitRef,Ljava/lang/String;);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+    new-instance v0, Ljava/lang/String;
+    #v0=(UninitRef,Ljava/lang/String;);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+
+    :cond_9
+    #v0=(Conflicted):merge{0x5:(UninitRef,Ljava/lang/String;),0x7:(UninitRef,Ljava/lang/String;)}
+    #v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+    invoke-direct {v0}, Ljava/lang/String;-><init>()V
+    #v0=(Conflicted);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+
+    #v0=(Conflicted);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+    return-void
+    #v0=(Conflicted);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+.end method
+
+.method public constructor <init>(I)V
+    .registers 2
+
+    #p0=(UninitThis,LUninitRefIdentityTest;);p1=(Integer);
+    move-object p1, p0
+    #p0=(UninitThis,LUninitRefIdentityTest;);p1=(UninitThis,LUninitRefIdentityTest;);
+
+    #p0=(UninitThis,LUninitRefIdentityTest;);p1=(UninitThis,LUninitRefIdentityTest;);
+    invoke-direct {p1}, Ljava/lang/Object;-><init>()V
+    #p0=(Reference,LUninitRefIdentityTest;);p1=(Reference,LUninitRefIdentityTest;);
+
+    :cond_4
+    #p0=(Reference,LUninitRefIdentityTest;);
+    #p1=(Reference,LUninitRefIdentityTest;):merge{0x1:(Reference,LUninitRefIdentityTest;),0x7:(Null)}
+    const p1, 0x0
+    #p0=(Reference,LUninitRefIdentityTest;);p1=(Null);
+
+    #p0=(Reference,LUninitRefIdentityTest;);p1=(Null);
+    if-nez p1, :cond_4
+    #p0=(Reference,LUninitRefIdentityTest;);p1=(Null);
+
+    #p0=(Reference,LUninitRefIdentityTest;);p1=(Null);
+    return-void
+    #p0=(Reference,LUninitRefIdentityTest;);p1=(Null);
+.end method
+
+.method public constructor <init>(Ljava/lang/String;)V
+    .registers 2
+
+    #p0=(UninitThis,LUninitRefIdentityTest;);p1=(Reference,Ljava/lang/String;);
+    move-object p1, p0
+    #p0=(UninitThis,LUninitRefIdentityTest;);p1=(UninitThis,LUninitRefIdentityTest;);
+
+    #p0=(UninitThis,LUninitRefIdentityTest;);p1=(UninitThis,LUninitRefIdentityTest;);
+    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+    #p0=(Reference,LUninitRefIdentityTest;);p1=(Reference,LUninitRefIdentityTest;);
+
+    #p0=(Reference,LUninitRefIdentityTest;);p1=(Reference,LUninitRefIdentityTest;);
+    return-void
+    #p0=(Reference,LUninitRefIdentityTest;);p1=(Reference,LUninitRefIdentityTest;);
+.end method
+
+
+# virtual methods
+.method public overlappingInits()V
+    .registers 3
+
+    #v0=(Uninit);v1=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+    new-instance v0, Ljava/lang/String;
+    #v0=(UninitRef,Ljava/lang/String;);v1=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+
+    #v0=(UninitRef,Ljava/lang/String;);v1=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+    new-instance v1, Ljava/lang/String;
+    #v0=(UninitRef,Ljava/lang/String;);v1=(UninitRef,Ljava/lang/String;);p0=(Reference,LUninitRefIdentityTest;);
+
+    #v0=(UninitRef,Ljava/lang/String;);v1=(UninitRef,Ljava/lang/String;);p0=(Reference,LUninitRefIdentityTest;);
+    new-instance p0, Ljava/lang/String;
+    #v0=(UninitRef,Ljava/lang/String;);v1=(UninitRef,Ljava/lang/String;);p0=(UninitRef,Ljava/lang/String;);
+
+    #v0=(UninitRef,Ljava/lang/String;);v1=(UninitRef,Ljava/lang/String;);p0=(UninitRef,Ljava/lang/String;);
+    invoke-direct {p0}, Ljava/lang/String;-><init>()V
+    #v0=(UninitRef,Ljava/lang/String;);v1=(UninitRef,Ljava/lang/String;);p0=(Reference,Ljava/lang/String;);
+
+    #v0=(UninitRef,Ljava/lang/String;);v1=(UninitRef,Ljava/lang/String;);p0=(Reference,Ljava/lang/String;);
+    invoke-direct {v1}, Ljava/lang/String;-><init>()V
+    #v0=(UninitRef,Ljava/lang/String;);v1=(Reference,Ljava/lang/String;);p0=(Reference,Ljava/lang/String;);
+
+    #v0=(UninitRef,Ljava/lang/String;);v1=(Reference,Ljava/lang/String;);p0=(Reference,Ljava/lang/String;);
+    invoke-direct {v0}, Ljava/lang/String;-><init>()V
+    #v0=(Reference,Ljava/lang/String;);v1=(Reference,Ljava/lang/String;);p0=(Reference,Ljava/lang/String;);
+
+    #v0=(Reference,Ljava/lang/String;);v1=(Reference,Ljava/lang/String;);p0=(Reference,Ljava/lang/String;);
+    return-void
+    #v0=(Reference,Ljava/lang/String;);v1=(Reference,Ljava/lang/String;);p0=(Reference,Ljava/lang/String;);
+.end method
diff --git a/baksmali/src/test/resources/UninitRefIdentityTest/classes.dex b/baksmali/src/test/resources/UninitRefIdentityTest/classes.dex
new file mode 100644
index 0000000..0f0caab
Binary files /dev/null and b/baksmali/src/test/resources/UninitRefIdentityTest/classes.dex differ
diff --git a/baksmali/src/test/resources/ZeroArrayPayloadWidthTest/ZeroArrayPayloadWidthTestInput.dex b/baksmali/src/test/resources/ZeroArrayPayloadWidthTest/ZeroArrayPayloadWidthTestInput.dex
new file mode 100644
index 0000000..37b2942
Binary files /dev/null and b/baksmali/src/test/resources/ZeroArrayPayloadWidthTest/ZeroArrayPayloadWidthTestInput.dex differ
diff --git a/baksmali/src/test/resources/ZeroArrayPayloadWidthTest/ZeroArrayPayloadWidthTestOutput.smali b/baksmali/src/test/resources/ZeroArrayPayloadWidthTest/ZeroArrayPayloadWidthTestOutput.smali
new file mode 100644
index 0000000..6172dec
--- /dev/null
+++ b/baksmali/src/test/resources/ZeroArrayPayloadWidthTest/ZeroArrayPayloadWidthTestOutput.smali
@@ -0,0 +1,15 @@
+.class public LZeroArrayPayloadWidthTest;
+.super Ljava/lang/Object;
+
+
+# virtual methods
+.method public zeroWidth()V
+    .registers 3
+
+    return-void
+
+    nop
+
+    .array-data 1
+    .end array-data
+.end method
diff --git a/baksmali/src/test/smali/baksmali_test_class.smali b/baksmali/src/test/smali/baksmali_test_class.smali
new file mode 100644
index 0000000..903baf4
--- /dev/null
+++ b/baksmali/src/test/smali/baksmali_test_class.smali
@@ -0,0 +1,218 @@
+.class public Lbaksmali/test/class;
+.super Ljava/lang/Object;
+
+.source "baksmali_test_class.smali"
+
+.implements Lsome/interface;
+.implements Lsome/other/interface;
+
+
+.annotation build Lsome/annotation;
+    value1 = "test"
+    value2 = .subannotation Lsome/annotation;
+        value1 = "test2"
+        value2 = Lsome/enum;
+    .end subannotation
+.end annotation
+
+.annotation system Lsome/annotation;
+.end annotation
+
+
+
+.field public static aStaticFieldWithoutAnInitializer:I
+
+.field public static longStaticField:J = 0x300000000L
+.field public static longNegStaticField:J = -0x300000000L
+
+.field public static intStaticField:I = 0x70000000
+.field public static intNegStaticField:I = -500
+
+.field public static shortStaticField:S = 500s
+.field public static shortNegStaticField:S = -500s
+
+.field public static byteStaticField:B = 123t
+.field public static byteNegStaticField:B = 0xAAt
+
+.field public static floatStaticField:F = 3.1415926f
+
+.field public static doubleStaticField:D = 3.141592653589793
+
+.field public static charStaticField:C = 'a'
+.field public static charEscapedStaticField:C = '\n'
+
+.field public static boolTrueStaticField:Z = true
+.field public static boolFalseStaticField:Z = false
+
+.field public static typeStaticField:Ljava/lang/Class; = Lbaksmali/test/class;
+
+.field public static stringStaticField:Ljava/lang/String; = "test"
+.field public static stringEscapedStaticField:Ljava/lang/String; = "test\ntest"
+
+
+.field public static fieldStaticField:Ljava/lang/reflect/Field; = Lbaksmali/test/class;->fieldStaticField:Ljava/lang/reflect/Field;
+
+.field public static methodStaticField:Ljava/lang/reflect/Method; = Lbaksmali/test/class;->teshMethod(ILjava/lang/String;)Ljava/lang/String;
+
+.field public static arrayStaticField:[I = {1, 2, 3, {1, 2, 3, 4}}
+
+.field public static enumStaticField:Lsome/enum; = .enum Lsome/enum;->someEnumValue:Lsome/enum;
+
+.field public static annotationStaticField:Lsome/annotation; = .subannotation Lsome/annotation;
+    value1 = "test"
+    value2 = .subannotation Lsome/annotation;
+        value1 = "test2"
+        value2 = Lsome/enum;
+    .end subannotation
+.end subannotation
+
+.field public static staticFieldWithAnnotation:I
+    .annotation runtime La/field/annotation;
+        this = "is"
+        a = "test"
+    .end annotation
+    .annotation runtime Lorg/junit/Test;
+    .end annotation
+.end field
+
+.field public instanceField:Ljava/lang/String;
+
+
+
+.method public constructor <init>()V
+    .registers 1
+    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+    return-void
+.end method
+
+.method public testMethod(ILjava/lang/String;)Ljava/lang/String;
+    .registers 3
+    .annotation runtime Lorg/junit/Test;
+    .end annotation
+    .annotation system Lyet/another/annotation;
+        somevalue = 1234
+        anothervalue = 3.14159
+    .end annotation
+
+    const-string v0, "testing\n123"
+
+    goto switch:
+
+    sget v0, Lbaksmali/test/class;->staticField:I
+
+    switch:
+    packed-switch v0, pswitch:
+
+    try_start:
+    const/4 v0, 7
+    const v0, 10
+    nop
+    try_end:
+    .catch Ljava/lang/Exception; {try_start: .. try_end:} handler:
+    .catchall {try_start: .. try_end:} handler2:
+
+    handler:
+
+    Label10:
+    Label11:
+    Label12:
+    Label13:
+    return-object v0
+
+
+
+    .array-data 4
+        1 2 3 4 5 6 200
+    .end array-data
+
+    pswitch:
+    .packed-switch 10
+        Label10:
+        Label11:
+        Label12:
+        Label13:
+    .end packed-switch
+
+    handler2:
+
+    return-void
+
+.end method
+
+.method public abstract testMethod2()V
+    .annotation runtime Lsome/annotation;
+        subannotation = .subannotation Lsome/other/annotation;
+            value = "value"
+        .end subannotation
+    .end annotation
+    .annotation runtime Lorg/junit/Test;
+    .end annotation
+.end method
+
+
+.method public tryTest()V
+    .registers 1
+
+    handler:
+    nop
+
+
+    try_start:
+    const/4 v0, 7
+    const v0, 10
+    nop
+    try_end:
+    .catch Ljava/lang/Exception; {try_start: .. try_end:} handler:
+.end method
+
+
+.method public debugTest(IIIII)V
+    .registers 10
+
+    .parameter "Blah"
+    .parameter
+    .parameter "BlahWithAnnotations"
+        .annotation runtime Lsome/annotation;
+            something = "some value"
+            somethingelse = 1234
+        .end annotation
+        .annotation runtime La/second/annotation;
+        .end annotation
+    .end parameter
+    .parameter
+        .annotation runtime Lsome/annotation;
+            something = "some value"
+            somethingelse = 1234
+        .end annotation
+    .end parameter
+    .parameter "LastParam"
+
+    .prologue
+
+    nop
+    nop
+
+    .source "somefile.java"
+    .line 101
+
+    nop
+
+
+    .line 50
+
+    .local v0, aNumber:I
+    const v0, 1234
+    .end local v0
+
+    .source "someotherfile.java"
+    .line 900
+
+    const-string v0, "1234"
+
+    .restart local v0
+    const v0, 6789
+    .end local v0
+
+    .epilogue
+
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/smali/deodex_test1/main.smali b/baksmali/src/test/smali/deodex_test1/main.smali
new file mode 100644
index 0000000..db88b46
--- /dev/null
+++ b/baksmali/src/test/smali/deodex_test1/main.smali
@@ -0,0 +1,70 @@
+.class public Lmain;
+
+.super Ljava/lang/Object;
+
+.method public static main([Ljava/lang/String;)V
+    .registers 3
+
+    :here4
+    const v0, 0
+
+    :here3
+
+    new-instance v2, Lsuperclass;
+    invoke-direct {v2}, Lsuperclass;-><init>()V
+
+    if-eqz v0, :here
+
+
+    #this is the unresolvable instruction. v0 is always null,
+    #and this will always throw an exception. It should be
+    #replaced with throw v0.
+    invoke-virtual {v0}, Lrandomclass;->getSuperclass()Lsuperclass;
+    move-result-object v1
+
+
+    if-eqz v0, :here
+
+    #this would normally be deodexable, except that it follows
+    #the above un-deodexeable instruction, which prevents the
+    #propagation of any register information. It can never be
+    #reached, and should be replaced with throw v2
+    invoke-virtual {v2}, Lsuperclass;->somemethod()V
+
+    #another odexed instruction that uses the result of the
+    #first unresolveable odex instruction. This should
+    #be replaced with throw v1
+    invoke-virtual {v1}, Lsuperclass;->somemethod()V
+
+    :here
+
+    #and we're back to the non-dead code
+    invoke-virtual {v2}, Lsuperclass;->somemethod()V
+
+    if-nez v0, :here3
+
+    return-void
+.end method
+
+.method public static FirstInstructionTest(Lrandomclass;)V
+    .registers 1
+
+    :try_start
+        invoke-virtual/range {p0}, Lrandomclass;->getSuperclass()Lsuperclass;
+        return-void
+    :try_end
+    .catch Ljava/lang/Exception; {:try_start .. :try_end} :handler
+    :handler
+        :inner_try_start
+            #this tests that the parameter register types are correctly propagated to the exception handlers, in the
+            #case that the first instruction of the method can throw an exception and is in a try black
+            invoke-virtual/range {p0}, Lrandomclass;->getSuperclass()Lsuperclass;
+            return-void
+        :inner_try_end
+        .catch Ljava/lang/Exception; {:inner_try_start .. :inner_try_end} :inner_handler
+        :inner_handler
+            #this additionally tests that the register types are propagated recursively, in the case that the first
+            #instruction in the exception handler can also throw an exception, and is covered by a try block
+            invoke-virtual/range {p0}, Lrandomclass;->getSuperclass()Lsuperclass;
+            return-void
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/smali/deodex_test1/randomclass.smali b/baksmali/src/test/smali/deodex_test1/randomclass.smali
new file mode 100644
index 0000000..3c02f13
--- /dev/null
+++ b/baksmali/src/test/smali/deodex_test1/randomclass.smali
@@ -0,0 +1,18 @@
+.class public Lrandomclass;
+
+.super Ljava/lang/Object;
+
+.method public constructor <init>()V
+    .registers 1
+    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+    return-void
+.end method
+
+.method public getSuperclass()Lsuperclass;
+   .registers 2
+
+    new-instance v0, Lsuperclass;
+    invoke-direct {v0}, Lsuperclass;-><init>()V
+
+    return-object v0
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/smali/deodex_test1/subclass.smali b/baksmali/src/test/smali/deodex_test1/subclass.smali
new file mode 100644
index 0000000..9ffa4de
--- /dev/null
+++ b/baksmali/src/test/smali/deodex_test1/subclass.smali
@@ -0,0 +1,21 @@
+.class public Lsubclass;
+
+.super Lsuperclass;
+
+.method public constructor <init>()V
+    .registers 1
+    invoke-direct {p0}, Lsuperclass;-><init>()V
+    return-void
+.end method
+
+.method public somemethod()V
+   .registers 2
+
+    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
+
+    const-string	v1, "subclass.somemethod"
+
+    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
+
+    return-void
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/smali/deodex_test1/superclass.smali b/baksmali/src/test/smali/deodex_test1/superclass.smali
new file mode 100644
index 0000000..2149103
--- /dev/null
+++ b/baksmali/src/test/smali/deodex_test1/superclass.smali
@@ -0,0 +1,21 @@
+.class public Lsuperclass;
+
+.super Ljava/lang/Object;
+
+.method public constructor <init>()V
+    .registers 1
+    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+    return-void
+.end method
+
+.method public somemethod()V
+   .registers 2
+
+    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
+
+    const-string	v1, "superclass.somemethod"
+
+    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
+
+    return-void
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/smali/deodex_test2/app_classes/main.smali b/baksmali/src/test/smali/deodex_test2/app_classes/main.smali
new file mode 100644
index 0000000..505312c
--- /dev/null
+++ b/baksmali/src/test/smali/deodex_test2/app_classes/main.smali
@@ -0,0 +1,41 @@
+.class public Lmain;
+
+.super Ljava/lang/Object;
+
+.method public static main([Ljava/lang/String;)V
+    .registers 6
+
+    const v2, 0
+
+
+    const v3, 1
+    const v4, 0
+    new-array v1, v3, [Lsubclass1;
+    new-instance v0, Lsubclass1;
+    invoke-direct {v0}, Lsubclass1;-><init>()V
+    aput-object v0, v1, v4
+
+    goto :here2
+
+    :here
+    const v2, 1
+
+    :here2
+
+    #this is tricky, because it has to merge two array types, [Lsubclass1; and [Lsubclass2
+    #which should result in [Lsuperclass;. However, this dex file won't have a reference
+    #to [Lsuperclass;
+    aget-object v5, v1, v4
+
+    invoke-virtual {v5}, Lsupersuperclass;->somemethod()V
+
+
+    new-array v1, v3, [Lsubclass2;
+    new-instance v0, Lsubclass2;
+    invoke-direct {v0}, Lsubclass2;-><init>()V
+    aput-object v0, v1, v4
+
+    if-eqz v2, :here
+
+    return-void
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/smali/deodex_test2/bootclass_classes/randomclass.smali b/baksmali/src/test/smali/deodex_test2/bootclass_classes/randomclass.smali
new file mode 100644
index 0000000..3c02f13
--- /dev/null
+++ b/baksmali/src/test/smali/deodex_test2/bootclass_classes/randomclass.smali
@@ -0,0 +1,18 @@
+.class public Lrandomclass;
+
+.super Ljava/lang/Object;
+
+.method public constructor <init>()V
+    .registers 1
+    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+    return-void
+.end method
+
+.method public getSuperclass()Lsuperclass;
+   .registers 2
+
+    new-instance v0, Lsuperclass;
+    invoke-direct {v0}, Lsuperclass;-><init>()V
+
+    return-object v0
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/smali/deodex_test2/bootclass_classes/subclass1.smali b/baksmali/src/test/smali/deodex_test2/bootclass_classes/subclass1.smali
new file mode 100644
index 0000000..d7b840f
--- /dev/null
+++ b/baksmali/src/test/smali/deodex_test2/bootclass_classes/subclass1.smali
@@ -0,0 +1,21 @@
+.class public Lsubclass1;
+
+.super Lsuperclass;
+
+.method public constructor <init>()V
+    .registers 1
+    invoke-direct {p0}, Lsuperclass;-><init>()V
+    return-void
+.end method
+
+.method public somemethod()V
+   .registers 2
+
+    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
+
+    const-string	v1, "subclass1.somemethod"
+
+    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
+
+    return-void
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/smali/deodex_test2/bootclass_classes/subclass2.smali b/baksmali/src/test/smali/deodex_test2/bootclass_classes/subclass2.smali
new file mode 100644
index 0000000..605cccb
--- /dev/null
+++ b/baksmali/src/test/smali/deodex_test2/bootclass_classes/subclass2.smali
@@ -0,0 +1,21 @@
+.class public Lsubclass2;
+
+.super Lsuperclass;
+
+.method public constructor <init>()V
+    .registers 1
+    invoke-direct {p0}, Lsuperclass;-><init>()V
+    return-void
+.end method
+
+.method public somemethod()V
+   .registers 2
+
+    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
+
+    const-string	v1, "subclass2.somemethod"
+
+    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
+
+    return-void
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/smali/deodex_test2/bootclass_classes/superclass.smali b/baksmali/src/test/smali/deodex_test2/bootclass_classes/superclass.smali
new file mode 100644
index 0000000..e44a17a
--- /dev/null
+++ b/baksmali/src/test/smali/deodex_test2/bootclass_classes/superclass.smali
@@ -0,0 +1,21 @@
+.class public Lsuperclass;
+
+.super Lsupersuperclass;
+
+.method public constructor <init>()V
+    .registers 1
+    invoke-direct {p0}, Lsupersuperclass;-><init>()V
+    return-void
+.end method
+
+.method public somemethod()V
+   .registers 2
+
+    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
+
+    const-string	v1, "superclass.somemethod"
+
+    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
+
+    return-void
+.end method
\ No newline at end of file
diff --git a/baksmali/src/test/smali/deodex_test2/bootclass_classes/supersuperclass.smali b/baksmali/src/test/smali/deodex_test2/bootclass_classes/supersuperclass.smali
new file mode 100644
index 0000000..9621e14
--- /dev/null
+++ b/baksmali/src/test/smali/deodex_test2/bootclass_classes/supersuperclass.smali
@@ -0,0 +1,21 @@
+.class public Lsupersuperclass;
+
+.super Ljava/lang/Object;
+
+.method public constructor <init>()V
+    .registers 1
+    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+    return-void
+.end method
+
+.method public somemethod()V
+   .registers 2
+
+    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
+
+    const-string	v1, "supersuperclass.somemethod"
+
+    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
+
+    return-void
+.end method
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..850c4e1
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.
+ */
+
+apply plugin: 'idea'
+
+version = '2.4.0'
+def jcommanderVersion = ''
+
+if (!('release' in gradle.startParameter.taskNames)) {
+    // we compile against 1.48 normally, to match what's in AOSP, but switch to a newer version
+    // for release, because it has some fixes required when running on Android
+    jcommanderVersion = 'com.beust:jcommander:1.48'
+
+    def versionSuffix
+    try {
+        def git = org.eclipse.jgit.api.Git.open(file('.'))
+        def head = git.getRepository().getRef('HEAD')
+        versionSuffix = head.getObjectId().abbreviate(8).name()
+
+        if (!git.status().call().clean) {
+            versionSuffix += '-dirty'
+        }
+    } catch (Exception) {
+        // In case we can't get the commit for some reason,
+        // just use -dev
+        versionSuffix = 'dev'
+    }
+
+    version += "-${versionSuffix}"
+} else {
+    jcommanderVersion = 'com.beust:jcommander:1.64'
+}
+
+// Note: please don't use this. This is strictly for the official releases
+// that are posted on, e.g. the bitbucket download page.
+task release() {
+}
+
+task(install).doLast {
+    println "Installing version: ${version}"
+}
+
+// The projects that get pushed to maven
+def maven_release_projects = ['smali', 'baksmali', 'dexlib2', 'util']
+
+subprojects {
+    apply plugin: 'java'
+    apply plugin: 'idea'
+
+    if (JavaVersion.current().isJava8Compatible()) {
+        allprojects {
+            tasks.withType(Javadoc) {
+                options.addStringOption('Xdoclint:none', '-quiet')
+            }
+        }
+    }
+
+    version = parent.version
+
+    ext {
+        depends = [
+                guava: 'com.google.guava:guava:27.1-android',
+                findbugs: 'com.google.code.findbugs:jsr305:1.3.9',
+                junit: 'junit:junit:4.12',
+                mockito: 'org.mockito:mockito-core:1.10.19',
+                antlr_runtime: 'org.antlr:antlr-runtime:3.5.2',
+                antlr: 'org.antlr:antlr:3.5.2',
+                stringtemplate: 'org.antlr:stringtemplate:3.2.1',
+                jflex_plugin: 'org.xbib.gradle.plugin:gradle-plugin-jflex:1.1.0',
+                proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1',
+                dx: 'com.google.android.tools:dx:1.7',
+                gson: 'com.google.code.gson:gson:2.3.1',
+                jcommander: jcommanderVersion
+        ]
+    }
+
+    repositories {
+        mavenCentral()
+    }
+
+    if (project.name in maven_release_projects) {
+        apply plugin: 'maven'
+        apply plugin: 'signing'
+
+        group = 'org.smali'
+
+        task javadocJar(type: Jar, dependsOn: javadoc) {
+            classifier = 'javadoc'
+            from 'build/docs/javadoc'
+        }
+
+        task sourcesJar(type: Jar) {
+            classifier = 'sources'
+            from sourceSets.main.allJava
+        }
+
+        artifacts {
+            archives javadocJar
+            archives sourcesJar
+        }
+
+        signing {
+            required { gradle.taskGraph.hasTask('uploadArchives') }
+            sign configurations.archives
+        }
+
+        uploadArchives {
+            repositories.mavenDeployer {
+                configuration = configurations.archives
+
+                beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+
+                if (rootProject.hasProperty('sonatypeUsername') && rootProject.hasProperty('sonatypePassword')) {
+                    repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/') {
+                        authentication(userName: sonatypeUsername, password: sonatypePassword)
+                    }
+                }
+
+                pom.artifactId = project.name
+
+                pom.project {
+                    name project.name
+                    url 'http://smali.org'
+                    packaging 'jar'
+                    licenses {
+                        license {
+                            name 'The BSD 3-Clause License'
+                            url 'http://opensource.org/licenses/BSD-3-Clause'
+                            distribution 'repo'
+                        }
+                    }
+                    scm {
+                        connection 'scm:git:git://github.com/JesusFreke/smali.git'
+                        developerConnection 'scm:git:git@github.com:JesusFreke/smali.git'
+                    }
+                    developers {
+                        developer {
+                            id 'jesusfreke'
+                            name 'Ben Gruver'
+                            email 'jesusfreke@jesusfreke.com'
+                        }
+                    }
+                }
+            }
+        }
+
+        tasks.getByPath(':release').dependsOn(uploadArchives)
+    }
+}
+
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'org.eclipse.jgit:org.eclipse.jgit:2.0.0.201206130900-r'
+    }
+}
+
+wrapper {
+    gradleVersion = '5.1'
+    distributionType = Wrapper.DistributionType.ALL
+}
diff --git a/deodexerant/Android.mk b/deodexerant/Android.mk
new file mode 100644
index 0000000..46a9dba
--- /dev/null
+++ b/deodexerant/Android.mk
@@ -0,0 +1,44 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#    * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#    * 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.
+#    * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "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 COPYRIGHT
+# OWNER OR CONTRIBUTORS 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := deodexerant
+
+LOCAL_SRC_FILES:= deodexerant.c
+
+LOCAL_SHARED_LIBRARIES := libdl
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_LDFLAGS := -Wl,--hash-style=sysv
+
+include $(BUILD_EXECUTABLE)
diff --git a/deodexerant/README b/deodexerant/README
new file mode 100644
index 0000000..0589e05
--- /dev/null
+++ b/deodexerant/README
@@ -0,0 +1,26 @@
+usage:
+adb push deodexerant /data/local
+adb shell chmod +x /data/local/deodexerant
+adb shell /data/local/deodexerant > inline.txt
+
+deodexerant is a binary that runs on a device and dumps out dalvik's inline
+method table.
+
+This can be used in cases where a particular built of dalvik has a non-standard
+inline method table for whatever reason. The output from this tool is intended
+to be used with the -T option for baksmali
+
+deodexerant is intended to be build within the AOSP build system. Assuming
+you have $MYDROID set to the root of the AOSP source tree, and $SMALI
+set to the root of the smali source tree,
+
+1. mkdir -p $MYDROID/external/deodexerant
+2. cp -r $SMALI/deodexerant $MYDROID/dalvik/deodexerant
+3. cd $MYDROID
+3. source build/envsetup.sh
+4. lunch generic-eng
+5. make deodexerant
+
+If all goes well, you should now have a deodexerant binary at:
+
+$MYDROID/out/target/product/generic/system/bin/deodexerant
diff --git a/deodexerant/deodexerant.c b/deodexerant/deodexerant.c
new file mode 100644
index 0000000..1cadee9
--- /dev/null
+++ b/deodexerant/deodexerant.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2011, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.
+ */
+
+#include <stdio.h>
+#include <dlfcn.h>
+
+typedef struct InlineOperation {
+    void *          func;
+    const char*     classDescriptor;
+    const char*     methodName;
+    const char*     methodSignature;
+} InlineOperation;
+
+typedef const InlineOperation* (*dvmGetInlineOpsTablePtr)();
+typedef int (*dvmGetInlineOpsTableLengthPtr)();
+
+void main(int argc, char **argv) {
+	int i;
+
+	void *libdvm = dlopen("libdvm.so", RTLD_LAZY);
+
+	if (libdvm == NULL) {
+		printf("Failed to load libdvm: %s\n", dlerror());
+		return;
+	}
+
+	dvmGetInlineOpsTablePtr dvmGetInlineOpsTable = dlsym(libdvm, "dvmGetInlineOpsTable");
+
+	if (dvmGetInlineOpsTable == NULL) {
+		// clear the error, and retry with the c++ mangled name
+		dlerror();
+		dvmGetInlineOpsTable = dlsym(libdvm, "_Z20dvmGetInlineOpsTablev");
+	}
+
+	if (dvmGetInlineOpsTable == NULL) {
+		printf("Failed to load dvmGetInlineOpsTable: %s\n", dlerror());
+		dlclose(libdvm);
+		return;
+	}
+
+	dvmGetInlineOpsTableLengthPtr dvmGetInlineOpsTableLength = dlsym(libdvm, "dvmGetInlineOpsTableLength");
+
+	if (dvmGetInlineOpsTableLength == NULL) {
+		// clear the error, and retry with the c++ mangled name
+		dlerror();
+		dvmGetInlineOpsTableLength = dlsym(libdvm, "_Z26dvmGetInlineOpsTableLengthv");
+	}
+
+	if (dvmGetInlineOpsTableLength == NULL) {
+		printf("Failed to load dvmGetInlineOpsTableLength: %s\n", dlerror());
+		dlclose(libdvm);
+		return;
+	}
+
+	const InlineOperation *inlineTable = dvmGetInlineOpsTable();
+	int length = dvmGetInlineOpsTableLength();
+
+	for (i=0; i<length; i++) {
+		InlineOperation *item = &inlineTable[i];
+
+		printf("%s->%s%s\n", item->classDescriptor, item->methodName, item->methodSignature);
+	}
+
+	dlclose(libdvm);
+	return;
+}
\ No newline at end of file
diff --git a/dexlib2/OatVersions-README.md b/dexlib2/OatVersions-README.md
new file mode 100644
index 0000000..87ad6a4
--- /dev/null
+++ b/dexlib2/OatVersions-README.md
@@ -0,0 +1,7 @@
+When assessing art changes for impact to baksmali/dexlib2's deodexing functionality, there are a
+few key structures to keep an eye on.
+
+- The oat version is stored in runtime/oat.h
+- The OatHeader structure in runtime/oat.h
+- The OatDexFile structure, as it is written in OatWriter::OatDexFile::Write in
+  compiler/oat_writer.cc (later moved to dex2oat/linker/oat_writer.cc)
\ No newline at end of file
diff --git a/dexlib2/OatVersions.txt b/dexlib2/OatVersions.txt
new file mode 100644
index 0000000..2a505d9
--- /dev/null
+++ b/dexlib2/OatVersions.txt
@@ -0,0 +1,171 @@
+7642cfc90fc9c3ebfd8e3b5041915705c93b5cf0 - 56
+ - first version with all stability fixes needed for deodexing
+14691c5e786e8c2c5734f687e4c96217340771be - 57
+1558b577907b613864e98f05862543557263e864 - 58
+f3251d12dfa387493dbde4c4148a633802f5f7e3 - 59
+706cae36209932f258b2fe2e396f31d2dd7d585e - 58 (revert of f3251d12)
+d7cbf8a6629942e7bd315ffae7e1c77b082f3e11 - 60
+ - return-void-barrier -> return-void-no-barrier
+1412dfa4adcd511902e510fa0c948b168ab5840c - 61 (re-commit of f3251d12)
+9d6bf69ad3012a9d843268fdd5325b6719b6d5f2 - 62
+- classpath list was added
+0de1133ba600f299b3d67938f650720d9f859eb2 - 63
+07785bb98dc8bbe192970e0f4c2cafd338a8dc68 - 64
+fa2c054b28d4b540c1b3651401a7a091282a015f - 65
+7070ccd8b6439477eafeea7ed3736645d78e003f - 64 (revert of fa2c054b)
+7bf2b4f1d08050f80782217febac55c8cfc5e4ef - 65 (re-commit of fa2c054b)
+0b71357fb52be9bb06d35396a3042b4381b01041 - 66
+fab6788358dfb64e5c370611ddbbbffab0ed0553 - 67
+- Change in FieldGap priority queue ordering
+1aee900d5a0b3a8d78725a7551356bda0d8554e1 - 68
+54b62480636ae846d705fc180c7bd6cd08ec1e42 - 69
+6e2d5747d00697a25251d25dd33b953e54709507 - 68 (revert of 54b62480)
+0747466fca310eedea5fc49e37d54f240a0b3c0f - 69 (re-commit of 54b62480)
+501fd635a557645ab05f893c56e1f358e21bab82 - 70
+99170c636dfae4908b102347cfe9f92bad1881cc - 71
+3cfa4d05afa76e19ca99ec964b535a15c73683f0 - 72
+- default methods
+d9786b0e5be23ea0258405165098b4216579209c - 73
+- fast class lookup table
+a4f1220c1518074db18ca1044e9201492975750b - 74
+625a64aad13905d8a2454bf3cc0e874487b110d5 - 75
+- bootclasspath list was added
+- class offsets moved out to a separate table
+919f5536182890d2e03f59b961acf8f7c836ff61 - 74 (revert of 625a64aa)
+9bdf108885a27ba05fae8501725649574d7c491b - 75 (re-commit of 625a64aa)
+a62d2f04a6ecf804f8a78e722a6ca8ccb2dfa931 - 76
+845e5064580bd37ad5014f7aa0d078be7265464d - 75 (revert of a62d2f04)
+29d38e77c553c6cf71fc4dafe2d22b4e3f814872 - 76 (re-commit of 845e5064)
+d1537b569b6cd18297c5e02d13cdd588c4366c51 - 77
+61b28a17d9b6e8e998103646e98e4a9772e11927 - 78
+9d07e3d128ccfa0ef7670feadd424a825e447d1d - 79
+952e1e3710158982941fc70326e9fddc3021235d - 80
+013e3f33495dcc31dba19c9de128d23ed441d7d3 - 81
+87f3fcbd0db352157fc59148e94647ef21b73bce - 82
+02b75806a80f8b75c3d6ba2ff97c995117630f36 - 83
+4359e61927866c254bc2d701e3ea4c48de10b79c - 84
+d549c28cfbddba945cb88857bcca3dce1414fb29 - 85
+952dbb19cd094b8bfb01dbb33e0878db429e499a - 86
+239d6eaff0cbb5c4c0139f7053a012758799f186 - 87 - introduction of vdex files
+77d9dd75d5d4a22ad1235f9a08d2cfbf2f0ae6fa - 89
+af1e2990cd1406a0fb7cba1d2e208208e950e413 - 90
+9fd8c60cdff7b28a89bb97fd90ae9d0f37cf8f8b - 91
+6beced4c017826f7c449f12fac7fa42403657f2b - 92
+58c3f6a0d15a4340c0a11ab7fbc8c4b990c64b77 - 93
+5923b5238091d9cd65f988fc059deb4fbb2e7f08 - 94
+2b615ba29c4dfcf54aaf44955f2eac60f5080b2e - 95
+f7aaacd97881c6924b8212c7f8fe4a4c8721ef53 - 94 (revert of 2b615ba)
+0d3998b5ff619364acf47bec0b541e7a49bd6fe7 - 95 (re-commit of 2b615ba)
+ac141397dc29189ad2b2df41f8d4312246beec60 - 96
+1998cd02603197f2acdc0734397a6d48b2f59b80 - 97
+e71b35446985835363a4508646cf7b1121bd95a3 - 98
+39cee66a8ddf0254626c9591662cf87e4a1cedc4 - 99
+cc99df230feb46ba717252f002d0cc2da6828421 - 100
+fee255039e30c1c3dfc70c426c3d176221c3cdf9 - 99 (revert of cc99df23)
+e761bccf9f0d884cc4d4ec104568cef968296492 - 100 (re-commit of cc99df23)
+8d91ac31ccb92557e434d89ffade3372466e1af5 - 101
+fd3161acfbe82c54ef49958f0ccc62511f224f91 - 102
+a2f526f889be06f96ea59624c9dfb1223b3839f3 - 103
+b048cb74b742b03eb6dd5f1d6dd49e559f730b36 - 104
+12f1b99775bbf7dd82d0a897587ab6ed0e75ee22 - 105
+ec7862283dd49f5a58d0ac45960ce27c2f7671b8 - 106
+45aa598cd1773f5eb1705dec13bea059238e054d - 107
+d16363a93053de0f32252c7897d839a46aff14ae - 108
+1a20b6801f2432a42b906f0de01e7e9586526aec - 109
+575d3e60c68b5cf481b615dde4a16283507b19ed - 110
+85c0f2ac03417f5125bc2ff1dab8109859c67d5c - 111
+5812e20ff7cbc8efa0b8d7486ada2f58840a6ad5 - 111
+b7ea3799c15b0090bb690e18ac1b5b0fddbdeee8 - 112
+    - version bump for missing bump in commits
+    - 3228908337fdfe851223f8ae374538de25cb5ad1
+    - 5812e20ff7cbc8efa0b8d7486ada2f58840a6ad5
+d776ff08e07494327716f0d2ea1a774b2ebfbca9 - 113
+bfb80d25eaeb7a604d5dd25a370e3869e96a33ab - 114
+1aea3510b8dd0c512cec61c91c5ef1f1e5d53d64 - 115
+6374c58f2ea403b3a05fb27376110fe4d0fc8e3f - 114 (revert of 1aea3510)
+0b66d6174bf1f6023f9d36dda8538490b79c2e9f - 113 (revert of bfb80d25)
+8d6768d47b66a688d35399d524ad5a5450e9d9d4 - 114 (i don't even)
+f44d36c8423f81cbb5e9f55d8813e26ffa1a7f3b - 115 (115 again. heck if I know what's going on)
+cbcedbf9382bc773713cd3552ed96f417bf1daeb - 116
+051071718085ce807a2e7c55278a8d723e238e86 - 116
+1595815c2a914a78df7dfb6f0082f47d4e82bb36 - 117
+f4f2daafb38c9c07ea74044a0fb89a2a19288b7a - 118
+6bc7774426cc0b6bbab5566fa62b3c509455e583 - 119
+88d329a698ba186aeb1f1ef8794355512ada84a9 - 120
+612ff540cd3329935351f05923358cf29b9c9b44 - 121
+c83dd7bfde2171c879efb92a31a363505385ffb9 - 122
+eee1c0ec2b08a6be642b329dc2fe885391127da3 - 123
+f977691961b5a49a074a535fcb29a5ad4a318974 - 124
+2665bc8159698429f20a08f814e63c434910d608 - 124
+88abba2b0cb0151d89e16da3e64025878dc2f142 - 125
+99cdddaf8e5bc6b31d0eb375755ec4071a9fb527 - 125
+c137cb03a90b9fd5a7d0ec7dd9b250db82ca88ef - 126
+0eb882bfc5d260e8014c26adfda11602065aa5d8 - 127
+    - The oat_dex_files_offset field is added to OatHeader
+    - The method_bss_mapping field is added to OatDexFile
+7b0648aa7cb4b7a58e73bf353e031dfe4553d9d7 - 128
+0cb172874481f736c6b7c491dd621166cc25561b - 129
+a308a327884920cbb1e3e62964c4b5a01c29af8c - 130 (changed kMultiDexSeparator from ':' to '!')
+4147fcc43c2ee019a06e55384985e3eaf82dcb8c - 131
+75c5ed6e75f70002db5fa7c609137c04dd2bdf40 - 132
+    - The dex_layout_sections field is added to OatDexFile
+0f3c7003e08a42a4ed8c9f8dfffb1bee1118de59 - 133
+6cfbdbc359ec5414d3e49f70d28f8c0e65b98d63 - 134
+f3c52b42a035902245d00a619fed0275afb063d2 - 135
+    - Add type_bss_mapping_ and string_bss_mapping_ fields to OatDexFile
+2c64a837e62c2839521c89060b5bb0dcb237ddda - 136
+dc682aa9d0eae1a851af059434adb6f6cf8f06f8 - 137
+4d17987da58d9411adbed1a18203d76d6119612d - 138
+    - introduced the concept of an uncompressed dex file: one that is not stored
+       in the oat/vdex file, but rather is left in the apk.
+b066d43b1d9184899aff32b1f243d092611ad9c6 - 139
+e47f60c482648172334aaca59e6c1ab7a3d42610 - 140
+dcd117e04b0831e4539544c38c524799114f3e66 - 141
+18259d7fb7164a5e029df4f883b3a79ccc2403e8 - 142
+dbaa5c7ba8935cf87ceb40a4054f9842929e9a51 - 143
+052f8ca1776ed7deb4f036498edd69eb6a1b942f - 144
+4b59d107f91601c4e0095d7a9db40970d4ed6956 - 145
+71ec1cc0665cdb9d39f4fd284d68962020417a53 - 146
+ea341d2830298b666d7c6faf369d2fc3fe94fef8 - 147
+    - Inline TypeLookupTable in OatDexFile
+4c8e12e66968929b36fac6a2237ca4b04160161e - 148
+50fac06c51864f293c61ff9d0983b82698cf6dac - 149
+f6ba5b316b51d0fb9f91cb51a42e51dfeee62ee4 - 150
+a38e6cf2aaf4fd3d92b05c0a7a146fb5525ea72d - 151
+afc97bca07c85d4c3b46801c557edd12d681fc96 - 152
+d109e30eab8ba25f8d89be2a83d9036e2d541af2 - 153
+2c76257e4bfcd6f522b0cd3487ba7d9900043243 - 154
+6ee06e97cef5ee92944deaeba0da4d10c4c33a2a - 155
+8808756b8fba036a9c73a45c800a56be09872364 - 156
+b73323c50d10d3850d2d8719a481f4f430fc51ce - 157
+8cd54547cec8a4537db5682c2da8be22843b1310 - 158
+3aaaa21055ab73562b8da7968ac4fa5fe9d44695 - 159
+a9f303c089aa2b2fc82d97201352945678ef54ae - 160
+a2d29a3a772f17014197e829aa8cb41026f88f05 - 161
+e0669326c0282b5b645aba75160425eef9d57617 - 162
+    - Removes image_patch_delta_ and image_file_location_oat_delta_begin_ fields in oat header
+776f3f7bfa33e0449e4e2c5535bae1babfdbaf83 - 163
+c10a0c60ca388ea5f45c11dd86ca0af11191015a - 164
+    - changed adler32_checksum_ to oat_checksum_ in oat header
+    - changed image_file_location_oat_checksum_ to boot_image_checksum_ in oat header
+    - no net size change
+91f1032505cfaec3aef51fc0a3085b213813f0ed - 165
+0ace5633680af8864b76b4f45d63b3407e4dcdf5 - 166
+    - removes the boot_image_checksum_ field in oat header
+e1412dacbf1d2a809bd1fca658cc8cb8f61f8ee6 - 167
+    - note: this commit was reverted and this version number was not re-used
+131f23a4c2c34b689c07e6efd05cea74190f0113 - 168
+    - note: this commit was reverted and this version number was not re-used
+e35ac04a1a9a22b1c4386b27f3a30cd840aa17b1 - 169
+    - note: this commit was reverted and this version number was not re-used
+98fb083a30e9b37685f943e2923e65e60e0a0971 - 170
+    - Removes the interpreter_to_interpreter_bridge_offset_ field in oat header
+    - Removes the interpreter_to_compiled_code_bridge_offset_ field in oat header
+e42a4b95eed312e6f7019645f4c66b2d77254433 - 171
+697c47a7ffd4489c4bc4edc229c8123309526286 - 172
+6c4ec5c1555aaeddd254750c15554a3c47bfc722 - 173
+02820f424714e711bbd4cb4b04a109416eb0c8b8 - 174
+f5c5eb30fc71e0c305d678bd3c1c995a5c36d508 - 175
+2191069047034ad891ea15f60a217246edc38d53 - 176
+e2a3aa988630b3c2952ac44943f03dde60454195 - 177
+a59af8aeaad8fe7d68d8f8de63eab9cf85b6ab31 - 178
diff --git a/dexlib2/VdexVersions.txt b/dexlib2/VdexVersions.txt
new file mode 100644
index 0000000..727ccfc
--- /dev/null
+++ b/dexlib2/VdexVersions.txt
@@ -0,0 +1,42 @@
+7b49e6cade09bc65b3b5f22d45fc9d0a7184e4f2 - 0
+    - introduction of vdex files
+5d5a36bddbc008cd52a3207aa2b31177c47f9a49 - 0
+    - verifier deps
+4acefd33064d37b41ca55c3c9355345a20e5f9c2 - 0
+    - quickening info
+f54e5df37cb42d9a83fc54b375da5ef335d604a9 - 1
+    - dex file count + dex location checksum
+7498105ec7497bae2ba9f1a697da9efa0c979654 - 2
+    - verify profile
+3eba863e41d531340392d9ec64e17963ac898d81 - 3
+97fa9928c07d3e0ee631235e9619fb0f8949ed7a - 4
+6e54f78c7c1e01c1a91a458c6e51cca1c7d13ad4 - 5
+71fa64f4a1cf113b0d2ec00d05a168de07302032 - 6
+    - TODO: separate update from previous commit - need to check previous commits
+de4b08ff24c330d5b36b5c4dc8664ed4848eeca6 - 7
+b02ba93fb8089ae70229c4e3e90478cb4a9e4668 - 8
+ba118827465d12177f3996e50133960087b1c916 - 9
+bf755fefbfcfbb2677a519c12efe7890f3879854 - 10
+b4c6acbf281c1cf960444e35bcac254a1f77c3ed - 11
+    - changed layout of post-dex data in the vdex file.
+    - shouldn't have any impact on deodexing
+210531f8775c89feb90d430cd5b6026b4cf8ef89 - 13
+    - adds offset to QuickeningInfo structure before each dex file
+    - version 12 was a previous commit of this, which was reverted and recommited
+8892c6bd9235e7ae697039c901aaeea1597a7473 - 14
+8740c66cbfbeb3b7f306279c843650c3b18f2dca - 15
+    - Adds CompactDex code item
+c3a22aa19bbe35ff8447460b29e07d42937a39de - 16
+    - Add shared separate data section for compact dex
+8e9a5e82fe4f1beb6daddefb7b55226573f406be - 17
+    - unclear why version bumped? Due to previous commit that mistakenly didn't bump?
+20b2ce459f681f8c41bf0f5df31a24d4eb781f50 - 18
+    - unclear why version bumped? Due to previous commit that mistakenly didn't bump?
+3a29355967f29999ba81b5772ec66d3ddee9ce3e - 19, 1
+    - added separate version for dex section
+3a29355967f29999ba81b5772ec66d3ddee9ce3e - 19, 2
+    - Add owned section for CompactDex
+feb228244619237d110d8817865d7647f37b9b4f - 20, 2
+    - Improve `verified`, add `redefined` class status in VerifierDeps
+35a3f6a09931ee631a0377ee3ca98a7fb1f4f61d - 21, 2
+    - Add bootclasspath checksums
\ No newline at end of file
diff --git a/dexlib2/accessorTestGenerator/build.gradle b/dexlib2/accessorTestGenerator/build.gradle
new file mode 100644
index 0000000..e50bdc0
--- /dev/null
+++ b/dexlib2/accessorTestGenerator/build.gradle
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.
+ */
+
+dependencies {
+    compile project(':util')
+    compile 'com.google.code.findbugs:jsr305:1.3.9'
+    compile 'com.google.guava:guava:13.0.1'
+    compile 'org.antlr:ST4:4.0.7'
+
+    testCompile 'junit:junit:4.6'
+}
\ No newline at end of file
diff --git a/dexlib2/accessorTestGenerator/src/main/java/org/jf/dexlib2/AccessorTestGenerator.java b/dexlib2/accessorTestGenerator/src/main/java/org/jf/dexlib2/AccessorTestGenerator.java
new file mode 100644
index 0000000..6540b3d
--- /dev/null
+++ b/dexlib2/accessorTestGenerator/src/main/java/org/jf/dexlib2/AccessorTestGenerator.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+import org.stringtemplate.v4.*;
+
+import java.io.*;
+import java.net.URL;
+
+public class AccessorTestGenerator {
+    private static class UnaryOperation {
+        public final String name;
+        public UnaryOperation(String name) {
+            this.name = name;
+        }
+    }
+
+    private static class BinaryOperation {
+        public final String name;
+        public final String[] inputTypes;
+        public BinaryOperation(String name, String[] inputTypes) {
+            this.name = name;
+            this.inputTypes = inputTypes;
+        }
+    }
+
+    private static class TypeDef {
+        public final String name;
+        public final UnaryOperation[] unaryOperations;
+        public final BinaryOperation[] binaryOperations;
+        public TypeDef(String name, UnaryOperation[] unaryOperations, BinaryOperation[] binaryOperations) {
+            this.name = name;
+            this.unaryOperations = unaryOperations;
+            this.binaryOperations = binaryOperations;
+        }
+    }
+
+    private static final UnaryOperation[] unaryOperations = new UnaryOperation[] {
+            new UnaryOperation("preinc"),
+            new UnaryOperation("postinc"),
+            new UnaryOperation("predec"),
+            new UnaryOperation("postdec")
+    };
+
+    private static final String[] booleanInputs = new String[] {"boolean"};
+    private static final String[] integralInputs = new String[] {"int", "long"};
+    private static final String[] allInputs = new String[] {"int", "float", "long", "double"};
+
+    private static final BinaryOperation[] booleanOperations = new BinaryOperation[] {
+            new BinaryOperation("and", booleanInputs),
+            new BinaryOperation("or", booleanInputs),
+            new BinaryOperation("xor", booleanInputs),
+    };
+
+    private static final BinaryOperation[] floatOperations = new BinaryOperation[] {
+            new BinaryOperation("add", allInputs),
+            new BinaryOperation("sub", allInputs),
+            new BinaryOperation("mul", allInputs),
+            new BinaryOperation("div", allInputs),
+            new BinaryOperation("rem", allInputs),
+    };
+
+    private static final BinaryOperation[] integralOperations = new BinaryOperation[] {
+            new BinaryOperation("add", allInputs),
+            new BinaryOperation("sub", allInputs),
+            new BinaryOperation("mul", allInputs),
+            new BinaryOperation("div", allInputs),
+            new BinaryOperation("rem", allInputs),
+            new BinaryOperation("and", integralInputs),
+            new BinaryOperation("or", integralInputs),
+            new BinaryOperation("xor", integralInputs),
+            new BinaryOperation("shl", integralInputs),
+            new BinaryOperation("shr", integralInputs),
+            new BinaryOperation("ushr", integralInputs),
+    };
+
+    private static final TypeDef[] types = new TypeDef[] {
+            new TypeDef("boolean", new UnaryOperation[0], booleanOperations),
+            new TypeDef("byte", unaryOperations, integralOperations),
+            new TypeDef("char", unaryOperations, integralOperations),
+            new TypeDef("short", unaryOperations, integralOperations),
+            new TypeDef("int", unaryOperations, integralOperations),
+            new TypeDef("long", unaryOperations, integralOperations),
+            new TypeDef("float", unaryOperations, floatOperations),
+            new TypeDef("double", unaryOperations, floatOperations),
+    };
+
+
+    public static void main(String[] args) throws IOException {
+        if (args.length != 1) {
+            System.err.println("Usage: java org.jf.dexlib2.AccessorTestGenerator <output_file>");
+        }
+
+        URL stgUrl = AccessorTestGenerator.class.getClassLoader().getResource("AccessorTest.stg");
+        STGroupFile stg = new STGroupFile(stgUrl, "utf-8", '<', '>');
+        ST fileSt = stg.getInstanceOf("file");
+        fileSt.add("types", types);
+
+        PrintWriter w = null;
+        try {
+            w = new PrintWriter(new BufferedWriter(new FileWriter(args[0])));
+            w.print(fileSt.render());
+        } finally {
+            if (w != null) {
+                w.close();
+            }
+        }
+    }
+}
+
+
+
diff --git a/dexlib2/accessorTestGenerator/src/main/resources/AccessorTest.stg b/dexlib2/accessorTestGenerator/src/main/resources/AccessorTest.stg
new file mode 100644
index 0000000..5c76eec
--- /dev/null
+++ b/dexlib2/accessorTestGenerator/src/main/resources/AccessorTest.stg
@@ -0,0 +1,86 @@
+decl(type, name, value) ::= "<type> <name><init(value)>;"
+init(v) ::= "<if(v)> = <v><endif>"
+
+field_decl(type) ::= "private <type.name> <type.name>_val;"
+
+preinc_template(type) ::=  "++<type.name>_val;"
+postinc_template(type) ::= "<type.name>_val++;"
+predec_template(type) ::= "--<type.name>_val;"
+postdec_template(type) ::= "<type.name>_val--;"
+add_template(type) ::= "<type.name>_val += val;"
+sub_template(type) ::= "<type.name>_val -= val;"
+mul_template(type) ::= "<type.name>_val *= val;"
+div_template(type) ::= "<type.name>_val /= val;"
+rem_template(type) ::= "<type.name>_val %= val;"
+and_template(type) ::= "<type.name>_val &= val;"
+or_template(type) ::= "<type.name>_val |= val;"
+xor_template(type) ::= "<type.name>_val ^= val;"
+shl_template(type) ::= "<type.name>_val \<\<= val;"
+shr_template(type) ::= "<type.name>_val >>= val;"
+ushr_template(type) ::= "<type.name>_val >>>= val;"
+
+operation_template_name(operation) ::= "<operation.name>_template"
+
+binary_method(input, type, binary_operation) ::= <<
+public void <type.name>_<binary_operation.name>(<input> val) {
+    <(operation_template_name(binary_operation))(type)>
+}
+>>
+
+binary_methods(binary_operation, type) ::= <<
+<binary_operation.inputTypes:binary_method(type, binary_operation);separator="\n\n">
+>>
+
+unary_method(unary_operation, type) ::= <<
+public void <type.name>_<unary_operation.name>() {
+    <(operation_template_name(unary_operation))(type)>
+}
+>>
+
+type_methods(type) ::= <<
+<[type.unaryOperations:unary_method(type), type.binaryOperations:binary_methods(type)];separator="\n\n">
+>>
+
+
+file(types) ::= <<
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+public class AccessorTypes {
+    <types:field_decl();separator="\n">
+
+    private class Accessors {
+        <types:type_methods();separator="\n\n">
+    }
+}
+>>
diff --git a/dexlib2/build.gradle b/dexlib2/build.gradle
new file mode 100644
index 0000000..b6a7490
--- /dev/null
+++ b/dexlib2/build.gradle
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.
+ */
+
+ext.testAccessorOutputDir = file("${buildDir}/generated-src/accessorTest/java")
+ext.testAccessorOutputFile = file("${testAccessorOutputDir}/org/jf/dexlib2/AccessorTypes.java")
+
+sourceSets {
+    accessorTest {
+        java {
+            srcDir testAccessorOutputDir
+        }
+    }
+}
+
+configurations {
+    accessorTestGenerator
+    dx
+}
+
+dependencies {
+    compile depends.findbugs
+    compile depends.guava
+
+    testCompile depends.junit
+    testCompile depends.mockito
+
+    accessorTestGenerator project('accessorTestGenerator')
+
+    dx depends.dx
+}
+
+// You must manually execute this task to regenerate SyntheticAccessorFSM.java, after modifying the ragel file
+// e.g. ./gradlew ragel
+task ragel(type:Exec) {
+    workingDir = 'src/main/ragel'
+
+    commandLine 'ragel', '-J', '-o', file('src/main/java/org/jf/dexlib2/util/SyntheticAccessorFSM.java'),
+            'SyntheticAccessorFSM.rl'
+}
+
+task generateAccessorTestSource(type: JavaExec) {
+    file(testAccessorOutputFile.parent).mkdirs()
+    outputs.dir file(testAccessorOutputDir)
+
+    classpath = configurations.accessorTestGenerator
+    main = 'org.jf.dexlib2.AccessorTestGenerator'
+    args testAccessorOutputFile
+}
+compileAccessorTestJava.dependsOn(generateAccessorTestSource)
+
+// You must manually execute this task to regenerate src/test/resources/accessorTest.dex
+task generateAccessorTestDex(type: JavaExec, dependsOn: compileAccessorTestJava) {
+    def outputDex = file('src/test/resources/accessorTest.dex')
+    file(outputDex.parent).mkdirs()
+
+    inputs.dir(project.sourceSets.accessorTest.output.classesDirs)
+    outputs.file outputDex
+
+    main 'com.android.dx.command.Main'
+    classpath = configurations.dx
+
+    args '--dex'
+    args '--no-strict'
+    args "--output=${outputDex}"
+    args sourceSets.accessorTest.output.classesDirs
+}
+
+uploadArchives {
+    repositories.mavenDeployer {
+        pom.project {
+            description 'dexlib2 is a library for reading/modifying/writing Android dex files'
+            scm {
+                url 'https://github.com/JesusFreke/smali/tree/master/dexlib2'
+            }
+        }
+    }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/AccessFlags.java b/dexlib2/src/main/java/org/jf/dexlib2/AccessFlags.java
new file mode 100644
index 0000000..d3cf74c
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/AccessFlags.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+import java.util.HashMap;
+
+public enum AccessFlags
+{
+    PUBLIC(0x1, "public", true, true, true),
+    PRIVATE(0x2, "private", true, true, true),
+    PROTECTED(0x4, "protected", true, true, true),
+    STATIC(0x8, "static", true, true, true),
+    FINAL(0x10, "final", true, true, true),
+    SYNCHRONIZED(0x20, "synchronized", false, true, false),
+    VOLATILE(0x40, "volatile", false, false, true),
+    BRIDGE(0x40, "bridge", false, true, false),
+    TRANSIENT(0x80, "transient", false, false, true),
+    VARARGS(0x80, "varargs", false, true, false),
+    NATIVE(0x100, "native", false, true, false),
+    INTERFACE(0x200, "interface", true, false, false),
+    ABSTRACT(0x400, "abstract", true, true, false),
+    STRICTFP(0x800, "strictfp", false, true, false),
+    SYNTHETIC(0x1000, "synthetic", true, true, true),
+    ANNOTATION(0x2000, "annotation", true, false, false),
+    ENUM(0x4000, "enum", true, false, true),
+    CONSTRUCTOR(0x10000, "constructor", false, true, false),
+    DECLARED_SYNCHRONIZED(0x20000, "declared-synchronized", false, true, false);
+
+    private int value;
+    private String accessFlagName;
+    private boolean validForClass;
+    private boolean validForMethod;
+    private boolean validForField;
+
+    //cache the array of all AccessFlags, because .values() allocates a new array for every call
+    private final static AccessFlags[] allFlags;
+
+    private static HashMap<String, AccessFlags> accessFlagsByName;
+
+    static {
+        allFlags = AccessFlags.values();
+
+        accessFlagsByName = new HashMap<String, AccessFlags>();
+        for (AccessFlags accessFlag: allFlags) {
+            accessFlagsByName.put(accessFlag.accessFlagName, accessFlag);
+        }
+    }
+
+    private AccessFlags(int value, String accessFlagName, boolean validForClass, boolean validForMethod,
+                        boolean validForField) {
+        this.value = value;
+        this.accessFlagName = accessFlagName;
+        this.validForClass = validForClass;
+        this.validForMethod = validForMethod;
+        this.validForField = validForField;
+    }
+
+    public boolean isSet(int accessFlags) {
+        return (this.value & accessFlags) != 0;
+    }
+
+    public static AccessFlags[] getAccessFlagsForClass(int accessFlagValue) {
+        int size = 0;
+        for (AccessFlags accessFlag: allFlags) {
+            if (accessFlag.validForClass && (accessFlagValue & accessFlag.value) != 0) {
+                size++;
+            }
+        }
+
+        AccessFlags[] accessFlags = new AccessFlags[size];
+        int accessFlagsPosition = 0;
+        for (AccessFlags accessFlag: allFlags) {
+            if (accessFlag.validForClass && (accessFlagValue & accessFlag.value) != 0) {
+                accessFlags[accessFlagsPosition++] = accessFlag;
+            }
+        }
+        return accessFlags;
+    }
+
+    private static String formatAccessFlags(AccessFlags[] accessFlags) {
+        int size = 0;
+        for (AccessFlags accessFlag: accessFlags) {
+            size += accessFlag.toString().length() + 1;
+        }
+
+        StringBuilder sb = new StringBuilder(size);
+        for (AccessFlags accessFlag: accessFlags) {
+            sb.append(accessFlag.toString());
+            sb.append(" ");
+        }
+        if (accessFlags.length > 0) {
+            sb.delete(sb.length() - 1, sb.length());
+        }
+        return sb.toString();
+    }
+
+    public static String formatAccessFlagsForClass(int accessFlagValue) {
+        return formatAccessFlags(getAccessFlagsForClass(accessFlagValue));
+    }
+
+    public static AccessFlags[] getAccessFlagsForMethod(int accessFlagValue) {
+        int size = 0;
+        for (AccessFlags accessFlag: allFlags) {
+            if (accessFlag.validForMethod && (accessFlagValue & accessFlag.value) != 0) {
+                size++;
+            }
+        }
+
+        AccessFlags[] accessFlags = new AccessFlags[size];
+        int accessFlagsPosition = 0;
+        for (AccessFlags accessFlag: allFlags) {
+            if (accessFlag.validForMethod && (accessFlagValue & accessFlag.value) != 0) {
+                accessFlags[accessFlagsPosition++] = accessFlag;
+            }
+        }
+        return accessFlags;
+    }
+
+    public static String formatAccessFlagsForMethod(int accessFlagValue) {
+        return formatAccessFlags(getAccessFlagsForMethod(accessFlagValue));
+    }
+
+    public static AccessFlags[] getAccessFlagsForField(int accessFlagValue) {
+        int size = 0;
+        for (AccessFlags accessFlag: allFlags) {
+            if (accessFlag.validForField && (accessFlagValue & accessFlag.value) != 0) {
+                size++;
+            }
+        }
+
+        AccessFlags[] accessFlags = new AccessFlags[size];
+        int accessFlagsPosition = 0;
+        for (AccessFlags accessFlag: allFlags) {
+            if (accessFlag.validForField && (accessFlagValue & accessFlag.value) != 0) {
+                accessFlags[accessFlagsPosition++] = accessFlag;
+            }
+        }
+        return accessFlags;
+    }
+
+    public static String formatAccessFlagsForField(int accessFlagValue) {
+        return formatAccessFlags(getAccessFlagsForField(accessFlagValue));
+    }
+
+    public static AccessFlags getAccessFlag(String accessFlag) {
+        return accessFlagsByName.get(accessFlag);
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public String toString() {
+        return accessFlagName;
+    }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/AnnotationVisibility.java b/dexlib2/src/main/java/org/jf/dexlib2/AnnotationVisibility.java
new file mode 100644
index 0000000..06c4677
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/AnnotationVisibility.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+import org.jf.util.ExceptionWithContext;
+
+public final class AnnotationVisibility {
+    public static final int BUILD = 0;
+    public static final int RUNTIME = 1;
+    public static final int SYSTEM = 2;
+
+    private static String[] NAMES = new String[] {"build", "runtime", "system"};
+
+    public static String getVisibility(int visibility) {
+        if (visibility < 0 || visibility >= NAMES.length) {
+            throw new ExceptionWithContext("Invalid annotation visibility %d", visibility);
+        }
+        return NAMES[visibility];
+    }
+
+    public static int getVisibility(String visibility) {
+        visibility = visibility.toLowerCase();
+        if (visibility.equals("build")) {
+            return BUILD;
+        }
+        if (visibility.equals("runtime")) {
+            return RUNTIME;
+        }
+        if (visibility.equals("system")) {
+            return SYSTEM;
+        }
+        throw new ExceptionWithContext("Invalid annotation visibility: %s", visibility);
+    }
+
+    private AnnotationVisibility() {}
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/DebugItemType.java b/dexlib2/src/main/java/org/jf/dexlib2/DebugItemType.java
new file mode 100644
index 0000000..7f4d887
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/DebugItemType.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+public final class DebugItemType {
+    // The debug items that directly correspond with one of the dexlib2.iface.debug interfaces
+    public static final int START_LOCAL = 0x03;
+    public static final int END_LOCAL = 0x05;
+    public static final int RESTART_LOCAL = 0x06;
+    public static final int PROLOGUE_END = 0x07;
+    public static final int EPILOGUE_BEGIN = 0x08;
+    public static final int SET_SOURCE_FILE = 0x09;
+    public static final int LINE_NUMBER = 0x0a;
+
+    // Other items, which are typically handled internally
+    public static final int END_SEQUENCE = 0x00;
+    public static final int ADVANCE_PC = 0x01;
+    public static final int ADVANCE_LINE = 0x02;
+    public static final int START_LOCAL_EXTENDED = 0x04;
+
+    private DebugItemType() {}
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java b/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java
new file mode 100644
index 0000000..4841fde
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
+import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
+import org.jf.dexlib2.dexbacked.OatFile;
+import org.jf.dexlib2.dexbacked.OatFile.NotAnOatFileException;
+import org.jf.dexlib2.dexbacked.OatFile.VdexProvider;
+import org.jf.dexlib2.dexbacked.ZipDexContainer;
+import org.jf.dexlib2.dexbacked.ZipDexContainer.NotAZipFileException;
+import org.jf.dexlib2.iface.DexFile;
+import org.jf.dexlib2.iface.MultiDexContainer;
+import org.jf.dexlib2.writer.pool.DexPool;
+import org.jf.util.ExceptionWithContext;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.*;
+import java.util.List;
+
+public final class DexFileFactory {
+
+    @Nonnull
+    public static DexBackedDexFile loadDexFile(@Nonnull String path, @Nullable Opcodes opcodes) throws IOException {
+        return loadDexFile(new File(path), opcodes);
+    }
+
+    /**
+     * Loads a dex/apk/odex/oat file.
+     *
+     * For oat files with multiple dex files, the first will be opened. For zip/apk files, the "classes.dex" entry
+     * will be opened.
+     *
+     * @param file The file to open
+     * @param opcodes The set of opcodes to use
+     * @return A DexBackedDexFile for the given file
+     *
+     * @throws UnsupportedOatVersionException If file refers to an unsupported oat file
+     * @throws DexFileNotFoundException If file does not exist, if file is a zip file but does not have a "classes.dex"
+     * entry, or if file is an oat file that has no dex entries.
+     * @throws UnsupportedFileTypeException If file is not a valid dex/zip/odex/oat file, or if the "classes.dex" entry
+     * in a zip file is not a valid dex file
+     */
+    @Nonnull
+    public static DexBackedDexFile loadDexFile(@Nonnull File file, @Nullable Opcodes opcodes) throws IOException {
+        if (!file.exists()) {
+            throw new DexFileNotFoundException("%s does not exist", file.getName());
+        }
+
+        try {
+            ZipDexContainer container = new ZipDexContainer(file, opcodes);
+            return new DexEntryFinder(file.getPath(), container).findEntry("classes.dex", true).getDexFile();
+        } catch (NotAZipFileException ex) {
+            // eat it and continue
+        }
+
+        InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
+        try {
+            try {
+                return DexBackedDexFile.fromInputStream(opcodes, inputStream);
+            } catch (DexBackedDexFile.NotADexFile ex) {
+                // just eat it
+            }
+
+            try {
+                return DexBackedOdexFile.fromInputStream(opcodes, inputStream);
+            } catch (DexBackedOdexFile.NotAnOdexFile ex) {
+                // just eat it
+            }
+
+            // Note: DexBackedDexFile.fromInputStream and DexBackedOdexFile.fromInputStream will reset inputStream
+            // back to the same position, if they fails
+
+            OatFile oatFile = null;
+            try {
+                oatFile = OatFile.fromInputStream(inputStream, new FilenameVdexProvider(file));
+            } catch (NotAnOatFileException ex) {
+                // just eat it
+            }
+
+            if (oatFile != null) {
+                if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) {
+                    throw new UnsupportedOatVersionException(oatFile);
+                }
+
+                List<DexBackedDexFile> oatDexFiles = oatFile.getDexFiles();
+
+                if (oatDexFiles.size() == 0) {
+                    throw new DexFileNotFoundException("Oat file %s contains no dex files", file.getName());
+                }
+
+                return oatDexFiles.get(0);
+            }
+        } finally {
+            inputStream.close();
+        }
+
+        throw new UnsupportedFileTypeException("%s is not an apk, dex, odex or oat file.", file.getPath());
+    }
+
+    /**
+     * Loads a dex entry from a container format (zip/oat)
+     *
+     * This has two modes of operation, depending on the exactMatch parameter. When exactMatch is true, it will only
+     * load an entry whose name exactly matches that provided by the dexEntry parameter.
+     *
+     * When exactMatch is false, then it will search for any entry that dexEntry is a path suffix of. "path suffix"
+     * meaning all the path components in dexEntry must fully match the corresponding path components in the entry name,
+     * but some path components at the beginning of entry name can be missing.
+     *
+     * For example, if an oat file contains a "/system/framework/framework.jar:classes2.dex" entry, then the following
+     * will match (not an exhaustive list):
+     *
+     * "/system/framework/framework.jar:classes2.dex"
+     * "system/framework/framework.jar:classes2.dex"
+     * "framework/framework.jar:classes2.dex"
+     * "framework.jar:classes2.dex"
+     * "classes2.dex"
+     *
+     * Note that partial path components specifically don't match. So something like "work/framework.jar:classes2.dex"
+     * would not match.
+     *
+     * If dexEntry contains an initial slash, it will be ignored for purposes of this suffix match -- but not when
+     * performing an exact match.
+     *
+     * If multiple entries match the given dexEntry, a MultipleMatchingDexEntriesException will be thrown
+     *
+     * @param file The container file. This must be either a zip (apk) file or an oat file.
+     * @param dexEntry The name of the entry to load. This can either be the exact entry name, if exactMatch is true,
+     *                 or it can be a path suffix.
+     * @param exactMatch If true, dexE
+     * @param opcodes The set of opcodes to use
+     * @return A DexBackedDexFile for the given entry
+     *
+     * @throws UnsupportedOatVersionException If file refers to an unsupported oat file
+     * @throws DexFileNotFoundException If the file does not exist, or if no matching entry could be found
+     * @throws UnsupportedFileTypeException If file is not a valid zip/oat file, or if the matching entry is not a
+     * valid dex file
+     * @throws MultipleMatchingDexEntriesException If multiple entries match the given dexEntry
+     */
+    public static MultiDexContainer.DexEntry<? extends DexBackedDexFile> loadDexEntry(
+            @Nonnull File file,
+            @Nonnull String dexEntry,
+            boolean exactMatch,
+            @Nullable Opcodes opcodes) throws IOException {
+        if (!file.exists()) {
+            throw new DexFileNotFoundException("Container file %s does not exist", file.getName());
+        }
+
+        try {
+            ZipDexContainer container = new ZipDexContainer(file, opcodes);
+            return new DexEntryFinder(file.getPath(), container).findEntry(dexEntry, exactMatch);
+        } catch (NotAZipFileException ex) {
+            // eat it and continue
+        }
+
+        InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
+        try {
+            OatFile oatFile = null;
+            try {
+                oatFile = OatFile.fromInputStream(inputStream, new FilenameVdexProvider(file));
+            } catch (NotAnOatFileException ex) {
+                // just eat it
+            }
+
+            if (oatFile != null) {
+                if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) {
+                    throw new UnsupportedOatVersionException(oatFile);
+                }
+
+                List<? extends DexFile> oatDexFiles = oatFile.getDexFiles();
+
+                if (oatDexFiles.size() == 0) {
+                    throw new DexFileNotFoundException("Oat file %s contains no dex files", file.getName());
+                }
+
+                return new DexEntryFinder(file.getPath(), oatFile).findEntry(dexEntry, exactMatch);
+            }
+        } finally {
+            inputStream.close();
+        }
+
+        throw new UnsupportedFileTypeException("%s is not an apk or oat file.", file.getPath());
+    }
+
+    /**
+     * Loads a file containing 1 or more dex files
+     *
+     * If the given file is a dex or odex file, it will return a MultiDexContainer containing that single entry.
+     * Otherwise, for an oat or zip file, it will return an OatFile or ZipDexContainer respectively.
+     *
+     * @param file The file to open
+     * @param opcodes The set of opcodes to use
+     * @return A MultiDexContainer
+     * @throws DexFileNotFoundException If the given file does not exist
+     * @throws UnsupportedFileTypeException If the given file is not a valid dex/zip/odex/oat file
+     */
+    public static MultiDexContainer<? extends DexBackedDexFile> loadDexContainer(
+            @Nonnull File file, @Nullable final Opcodes opcodes) throws IOException {
+        if (!file.exists()) {
+            throw new DexFileNotFoundException("%s does not exist", file.getName());
+        }
+
+        ZipDexContainer zipDexContainer = new ZipDexContainer(file, opcodes);
+        if (zipDexContainer.isZipFile()) {
+            return zipDexContainer;
+        }
+
+        InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
+        try {
+            try {
+                DexBackedDexFile dexFile = DexBackedDexFile.fromInputStream(opcodes, inputStream);
+                return new SingletonMultiDexContainer(file.getPath(), dexFile);
+            } catch (DexBackedDexFile.NotADexFile ex) {
+                // just eat it
+            }
+
+            try {
+                DexBackedOdexFile odexFile = DexBackedOdexFile.fromInputStream(opcodes, inputStream);
+                return new SingletonMultiDexContainer(file.getPath(), odexFile);
+            } catch (DexBackedOdexFile.NotAnOdexFile ex) {
+                // just eat it
+            }
+
+            // Note: DexBackedDexFile.fromInputStream and DexBackedOdexFile.fromInputStream will reset inputStream
+            // back to the same position, if they fails
+
+            OatFile oatFile = null;
+            try {
+                oatFile = OatFile.fromInputStream(inputStream, new FilenameVdexProvider(file));
+            } catch (NotAnOatFileException ex) {
+                // just eat it
+            }
+
+            if (oatFile != null) {
+                // TODO: we should support loading earlier oat files, just not deodexing them
+                if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) {
+                    throw new UnsupportedOatVersionException(oatFile);
+                }
+                return oatFile;
+            }
+        } finally {
+            inputStream.close();
+        }
+
+        throw new UnsupportedFileTypeException("%s is not an apk, dex, odex or oat file.", file.getPath());
+    }
+
+    /**
+     * Writes a DexFile out to disk
+     *
+     * @param path The path to write the dex file to
+     * @param dexFile a DexFile to write
+     */
+    public static void writeDexFile(@Nonnull String path, @Nonnull DexFile dexFile) throws IOException {
+        DexPool.writeTo(path, dexFile);
+    }
+
+    private DexFileFactory() {}
+
+    public static class DexFileNotFoundException extends ExceptionWithContext {
+        public DexFileNotFoundException(@Nullable String message, Object... formatArgs) {
+            super(message, formatArgs);
+        }
+
+        public DexFileNotFoundException(Throwable cause, @Nullable String message, Object... formatArgs) {
+            super(cause, message, formatArgs);
+        }
+    }
+
+    public static class UnsupportedOatVersionException extends ExceptionWithContext {
+        @Nonnull public final OatFile oatFile;
+
+        public UnsupportedOatVersionException(@Nonnull OatFile oatFile) {
+            super("Unsupported oat version: %d", oatFile.getOatVersion());
+            this.oatFile = oatFile;
+        }
+    }
+
+    public static class MultipleMatchingDexEntriesException extends ExceptionWithContext {
+        public MultipleMatchingDexEntriesException(@Nonnull String message, Object... formatArgs) {
+            super(String.format(message, formatArgs));
+        }
+    }
+
+    public static class UnsupportedFileTypeException extends ExceptionWithContext {
+        public UnsupportedFileTypeException(@Nonnull String message, Object... formatArgs) {
+            super(String.format(message, formatArgs));
+        }
+    }
+
+    /**
+     * Matches two entries fully, ignoring any initial slash, if any
+     */
+    private static boolean fullEntryMatch(@Nonnull String entry, @Nonnull String targetEntry) {
+        if (entry.equals(targetEntry)) {
+            return true;
+        }
+
+        if (entry.charAt(0) == '/') {
+            entry = entry.substring(1);
+        }
+
+        if (targetEntry.charAt(0) == '/') {
+            targetEntry = targetEntry.substring(1);
+        }
+
+        return entry.equals(targetEntry);
+    }
+
+    /**
+     * Performs a partial match against entry and targetEntry.
+     *
+     * This is considered a partial match if targetEntry is a suffix of entry, and if the suffix starts
+     * on a path "part" (ignoring the initial separator, if any). '/' and ':' and '!' are considered separators for
+     * this.
+     *
+     * So entry="/blah/blah/something.dex" and targetEntry="lah/something.dex" shouldn't match, but
+     * both targetEntry="blah/something.dex" and "/blah/something.dex" should match.
+     */
+    private static boolean partialEntryMatch(String entry, String targetEntry) {
+        if (entry.equals(targetEntry)) {
+            return true;
+        }
+
+        if (!entry.endsWith(targetEntry)) {
+            return false;
+        }
+
+        // Make sure the first matching part is a full entry. We don't want to match "/blah/blah/something.dex" with
+        // "lah/something.dex", but both "/blah/something.dex" and "blah/something.dex" should match
+        char precedingChar = entry.charAt(entry.length() - targetEntry.length() - 1);
+        char firstTargetChar = targetEntry.charAt(0);
+        // This is a device path, so we should always use the linux separator '/', rather than the current platform's
+        // separator
+        return firstTargetChar == ':' || firstTargetChar == '/' || firstTargetChar == '!' ||
+                precedingChar == ':' || precedingChar == '/' || precedingChar == '!';
+    }
+
+    protected static class DexEntryFinder {
+        private final String filename;
+        private final MultiDexContainer<? extends DexBackedDexFile> dexContainer;
+
+        public DexEntryFinder(@Nonnull String filename,
+                              @Nonnull MultiDexContainer<? extends DexBackedDexFile> dexContainer) {
+            this.filename = filename;
+            this.dexContainer = dexContainer;
+        }
+
+        @Nonnull
+        public MultiDexContainer.DexEntry<? extends DexBackedDexFile> findEntry(
+                @Nonnull String targetEntry, boolean exactMatch) throws IOException {
+            if (exactMatch) {
+                try {
+                    MultiDexContainer.DexEntry<? extends DexBackedDexFile> entry = dexContainer.getEntry(targetEntry);
+                    if (entry == null) {
+                        throw new DexFileNotFoundException("Could not find entry %s in %s.", targetEntry, filename);
+                    }
+                    return entry;
+                } catch (NotADexFile ex) {
+                    throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file", targetEntry, filename);
+                }
+            }
+
+            // find all full and partial matches
+            List<String> fullMatches = Lists.newArrayList();
+            List<MultiDexContainer.DexEntry<? extends DexBackedDexFile>> fullEntries = Lists.newArrayList();
+            List<String> partialMatches = Lists.newArrayList();
+            List<MultiDexContainer.DexEntry<? extends DexBackedDexFile>> partialEntries = Lists.newArrayList();
+            for (String entry: dexContainer.getDexEntryNames()) {
+                if (fullEntryMatch(entry, targetEntry)) {
+                    // We want to grab all full matches, regardless of whether they're actually a dex file.
+                    fullMatches.add(entry);
+                    MultiDexContainer.DexEntry<? extends DexBackedDexFile> dexEntry = dexContainer.getEntry(entry);
+                    assert dexEntry != null;
+                    fullEntries.add(dexEntry);
+                } else if (partialEntryMatch(entry, targetEntry)) {
+                    partialMatches.add(entry);
+                    MultiDexContainer.DexEntry<? extends DexBackedDexFile> dexEntry = dexContainer.getEntry(entry);
+                    assert dexEntry != null;
+                    partialEntries.add(dexEntry);
+                }
+            }
+
+            // full matches always take priority
+            if (fullEntries.size() == 1) {
+                try {
+                    MultiDexContainer.DexEntry<? extends DexBackedDexFile> dexEntry = fullEntries.get(0);
+                    assert dexEntry != null;
+                    return dexEntry;
+                } catch (NotADexFile ex) {
+                    throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file",
+                            fullMatches.get(0), filename);
+                }
+            }
+            if (fullEntries.size() > 1) {
+                // This should be quite rare. This would only happen if an oat file has two entries that differ
+                // only by an initial path separator. e.g. "/blah/blah.dex" and "blah/blah.dex"
+                throw new MultipleMatchingDexEntriesException(String.format(
+                        "Multiple entries in %s match %s: %s", filename, targetEntry,
+                        Joiner.on(", ").join(fullMatches)));
+            }
+
+            if (partialEntries.size() == 0) {
+                throw new DexFileNotFoundException("Could not find a dex entry in %s matching %s",
+                        filename, targetEntry);
+            }
+            if (partialEntries.size() > 1) {
+                throw new MultipleMatchingDexEntriesException(String.format(
+                        "Multiple dex entries in %s match %s: %s", filename, targetEntry,
+                        Joiner.on(", ").join(partialMatches)));
+            }
+            return partialEntries.get(0);
+        }
+    }
+
+    private static class SingletonMultiDexContainer implements MultiDexContainer<DexBackedDexFile> {
+        private final String entryName;
+        private final DexBackedDexFile dexFile;
+
+        public SingletonMultiDexContainer(@Nonnull String entryName, @Nonnull DexBackedDexFile dexFile) {
+            this.entryName = entryName;
+            this.dexFile = dexFile;
+        }
+
+        @Nonnull @Override public List<String> getDexEntryNames() {
+            return ImmutableList.of(entryName);
+        }
+
+        @Nullable @Override public DexEntry<DexBackedDexFile> getEntry(@Nonnull String entryName) {
+            if (entryName.equals(this.entryName)) {
+                return new DexEntry<DexBackedDexFile>() {
+                    @Nonnull
+                    @Override
+                    public String getEntryName() {
+                        return entryName;
+                    }
+
+                    @Nonnull
+                    @Override
+                    public DexBackedDexFile getDexFile() {
+                        return dexFile;
+                    }
+
+                    @Nonnull
+                    @Override
+                    public MultiDexContainer<? extends DexBackedDexFile> getContainer() {
+                        return SingletonMultiDexContainer.this;
+                    }
+                };
+            }
+            return null;
+        }
+    }
+
+    public static class FilenameVdexProvider implements VdexProvider {
+        private final File vdexFile;
+
+        @Nullable
+        private byte[] buf = null;
+        private boolean loadedVdex = false;
+
+        public FilenameVdexProvider(File oatFile) {
+            File oatParent = oatFile.getAbsoluteFile().getParentFile();
+            String baseName = Files.getNameWithoutExtension(oatFile.getAbsolutePath());
+            vdexFile = new File(oatParent, baseName + ".vdex");
+        }
+
+        @Nullable @Override public byte[] getVdex() {
+            if (!loadedVdex) {
+                File candidateFile = vdexFile;
+
+                if (!candidateFile.exists()) {
+                    // On api 28, for framework files, the vdex file in the architecture-specific directory is just a
+                    // symlink to a common vdex file in the framework directory. When loop-mounting a system image, that
+                    // symlink won't resolve because it uses an absolute path. As a workaround, we'll just search upward
+                    // one directory to see if it's there.
+                    File parentDirectory = candidateFile.getParentFile().getParentFile();
+                    if (parentDirectory != null) {
+                        candidateFile = new File(parentDirectory, vdexFile.getName());
+                    }
+                }
+
+                if (candidateFile.exists()) {
+                    try {
+                        buf = ByteStreams.toByteArray(new FileInputStream(candidateFile));
+                    } catch (FileNotFoundException e) {
+                        buf = null;
+                    } catch (IOException ex) {
+                        throw new RuntimeException(ex);
+                    }
+                }
+                loadedVdex = true;
+            }
+
+            return buf;
+        }
+    }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/Format.java b/dexlib2/src/main/java/org/jf/dexlib2/Format.java
new file mode 100644
index 0000000..fd90a6d
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/Format.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+public enum Format {
+    Format10t(2),
+    Format10x(2),
+    Format11n(2),
+    Format11x(2),
+    Format12x(2),
+    Format20bc(4),
+    Format20t(4),
+    Format21c(4),
+    Format21ih(4),
+    Format21lh(4),
+    Format21s(4),
+    Format21t(4),
+    Format22b(4),
+    Format22c(4),
+    Format22cs(4),
+    Format22s(4),
+    Format22t(4),
+    Format22x(4),
+    Format23x(4),
+    Format30t(6),
+    Format31c(6),
+    Format31i(6),
+    Format31t(6),
+    Format32x(6),
+    Format35c(6),
+    Format35mi(6),
+    Format35ms(6),
+    Format3rc(6),
+    Format3rmi(6),
+    Format3rms(6),
+    Format45cc(8),
+    Format4rcc(8),
+    Format51l(10),
+    ArrayPayload(-1, true),
+    PackedSwitchPayload(-1, true),
+    SparseSwitchPayload(-1, true),
+    UnresolvedOdexInstruction(-1);
+
+    public final int size;
+    public final boolean isPayloadFormat;
+
+    private Format(int size) {
+        this(size, false);
+    }
+
+    private Format(int size, boolean isPayloadFormat) {
+        this.size = size;
+        this.isPayloadFormat = isPayloadFormat;
+    }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/HiddenApiRestriction.java b/dexlib2/src/main/java/org/jf/dexlib2/HiddenApiRestriction.java
new file mode 100644
index 0000000..46880e1
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/HiddenApiRestriction.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2020, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+
+public enum HiddenApiRestriction {
+    WHITELIST(0, "whitelist", false),
+    GREYLIST(1, "greylist", false),
+    BLACKLIST(2, "blacklist", false),
+    GREYLIST_MAX_O(3, "greylist-max-o", false),
+    GREYLIST_MAX_P(4, "greylist-max-p", false),
+    GREYLIST_MAX_Q(5, "greylist-max-q", false),
+    CORE_PLATFORM_API(8, "core-platform-api", true);
+
+    private static final HiddenApiRestriction[] hiddenApiFlags = new HiddenApiRestriction[] {
+            WHITELIST,
+            GREYLIST,
+            BLACKLIST,
+            GREYLIST_MAX_O,
+            GREYLIST_MAX_P,
+            GREYLIST_MAX_Q
+    };
+
+    private static final HiddenApiRestriction[] domainSpecificApiFlags = new HiddenApiRestriction[] {
+            CORE_PLATFORM_API
+    };
+
+    private static final Map<String, HiddenApiRestriction> hiddenApiRestrictionsByName;
+
+    static {
+        hiddenApiRestrictionsByName = new HashMap<>();
+        for (HiddenApiRestriction hiddenApiRestriction : HiddenApiRestriction.values()) {
+            hiddenApiRestrictionsByName.put(hiddenApiRestriction.toString(), hiddenApiRestriction);
+        }
+    }
+
+    private static final int HIDDENAPI_FLAG_MASK = 0x7;
+
+    private final int value;
+    private final String name;
+    private final boolean isDomainSpecificApiFlag;
+
+    HiddenApiRestriction(int value, String name, boolean isDomainSpecificApiFlag) {
+        this.value = value;
+        this.name = name;
+        this.isDomainSpecificApiFlag = isDomainSpecificApiFlag;
+    }
+
+    public String toString() {
+        return name;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public boolean isSet(int value) {
+        if (isDomainSpecificApiFlag) {
+            return (value & ~HIDDENAPI_FLAG_MASK) == this.value;
+        } else {
+            return (value & HIDDENAPI_FLAG_MASK) == this.value;
+        }
+    }
+
+    public boolean isDomainSpecificApiFlag() {
+        return isDomainSpecificApiFlag;
+    }
+
+    public static Set<HiddenApiRestriction> getAllFlags(int value) {
+        HiddenApiRestriction normalRestriction = hiddenApiFlags[value & HIDDENAPI_FLAG_MASK];
+
+        int domainSpecificPart = (value & ~HIDDENAPI_FLAG_MASK);
+        if (domainSpecificPart == 0) {
+            return ImmutableSet.of(normalRestriction);
+        }
+        return ImmutableSet.of(normalRestriction, domainSpecificApiFlags[(domainSpecificPart >> 3) - 1]);
+    }
+
+    public static String formatHiddenRestrictions(int value) {
+        StringJoiner joiner = new StringJoiner("|");
+        for (HiddenApiRestriction hiddenApiRestriction : getAllFlags(value)) {
+            joiner.add(hiddenApiRestriction.toString());
+        }
+        return joiner.toString();
+    }
+
+    public static int combineFlags(Iterable<HiddenApiRestriction> flags) {
+        boolean gotHiddenApiFlag = false;
+        boolean gotDomainSpecificApiFlag = false;
+
+        int value = 0;
+
+        for (HiddenApiRestriction flag : flags) {
+            if (flag.isDomainSpecificApiFlag) {
+                if (gotDomainSpecificApiFlag) {
+                    throw new IllegalArgumentException(
+                            "Cannot combine multiple flags for domain-specific api restrictions");
+                }
+                gotDomainSpecificApiFlag = true;
+                value += flag.value;
+            } else {
+                if (gotHiddenApiFlag) {
+                    throw new IllegalArgumentException(
+                            "Cannot combine multiple flags for hidden api restrictions");
+                }
+                gotHiddenApiFlag = true;
+                value += flag.value;
+            }
+        }
+
+        return value;
+    }
+
+    public static HiddenApiRestriction forName(String name) {
+        return hiddenApiRestrictionsByName.get(name);
+    }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/MethodHandleType.java b/dexlib2/src/main/java/org/jf/dexlib2/MethodHandleType.java
new file mode 100644
index 0000000..11ee010
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/MethodHandleType.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2018, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
+import org.jf.util.ExceptionWithContext;
+
+import javax.annotation.Nonnull;
+
+public class MethodHandleType {
+    public static final int STATIC_PUT = 0;
+    public static final int STATIC_GET = 1;
+    public static final int INSTANCE_PUT = 2;
+    public static final int INSTANCE_GET = 3;
+    public static final int INVOKE_STATIC = 4;
+    public static final int INVOKE_INSTANCE = 5;
+    public static final int INVOKE_CONSTRUCTOR = 6;
+    public static final int INVOKE_DIRECT = 7;
+    public static final int INVOKE_INTERFACE = 8;
+
+    private static final BiMap<Integer, String> methodHandleTypeNames = new ImmutableBiMap.Builder<Integer, String>()
+            .put(STATIC_PUT, "static-put")
+            .put(STATIC_GET, "static-get")
+            .put(INSTANCE_PUT, "instance-put")
+            .put(INSTANCE_GET, "instance-get")
+            .put(INVOKE_STATIC, "invoke-static")
+            .put(INVOKE_INSTANCE, "invoke-instance")
+            .put(INVOKE_CONSTRUCTOR, "invoke-constructor")
+            .put(INVOKE_DIRECT, "invoke-direct")
+            .put(INVOKE_INTERFACE, "invoke-interface")
+            .build();
+
+    @Nonnull public static String toString(int methodHandleType) {
+        String val = methodHandleTypeNames.get(methodHandleType);
+        if (val == null) {
+            throw new InvalidMethodHandleTypeException(methodHandleType);
+        }
+        return val;
+    }
+
+    public static int getMethodHandleType(String methodHandleType) {
+        Integer ret = methodHandleTypeNames.inverse().get(methodHandleType);
+        if (ret == null) {
+            throw new ExceptionWithContext("Invalid method handle type: %s", methodHandleType);
+        }
+        return ret;
+    }
+
+    public static class InvalidMethodHandleTypeException extends ExceptionWithContext {
+        private final int methodHandleType;
+
+        public InvalidMethodHandleTypeException(int methodHandleType) {
+            super("Invalid method handle type: %d", methodHandleType);
+            this.methodHandleType = methodHandleType;
+        }
+
+        public InvalidMethodHandleTypeException(int methodHandleType, String message, Object... formatArgs) {
+            super(message, formatArgs);
+            this.methodHandleType = methodHandleType;
+        }
+
+        public int getMethodHandleType() {
+            return methodHandleType;
+        }
+    }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java b/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java
new file mode 100644
index 0000000..5d0eeae
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java
@@ -0,0 +1,494 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+import com.google.common.collect.ImmutableRangeMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeMap;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+public enum Opcode
+{
+    NOP(0x00, "nop", ReferenceType.NONE, Format.Format10x, Opcode.CAN_CONTINUE),
+    MOVE(0x01, "move", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MOVE_FROM16(0x02, "move/from16", ReferenceType.NONE, Format.Format22x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MOVE_16(0x03, "move/16", ReferenceType.NONE, Format.Format32x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MOVE_WIDE(0x04, "move-wide", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    MOVE_WIDE_FROM16(0x05, "move-wide/from16", ReferenceType.NONE, Format.Format22x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    MOVE_WIDE_16(0x06, "move-wide/16", ReferenceType.NONE, Format.Format32x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    MOVE_OBJECT(0x07, "move-object", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MOVE_OBJECT_FROM16(0x08, "move-object/from16", ReferenceType.NONE, Format.Format22x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MOVE_OBJECT_16(0x09, "move-object/16", ReferenceType.NONE, Format.Format32x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MOVE_RESULT(0x0a, "move-result", ReferenceType.NONE, Format.Format11x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MOVE_RESULT_WIDE(0x0b, "move-result-wide", ReferenceType.NONE, Format.Format11x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    MOVE_RESULT_OBJECT(0x0c, "move-result-object", ReferenceType.NONE, Format.Format11x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MOVE_EXCEPTION(0x0d, "move-exception", ReferenceType.NONE, Format.Format11x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    RETURN_VOID(0x0e, "return-void", ReferenceType.NONE, Format.Format10x),
+    RETURN(0x0f, "return", ReferenceType.NONE, Format.Format11x),
+    RETURN_WIDE(0x10, "return-wide", ReferenceType.NONE, Format.Format11x),
+    RETURN_OBJECT(0x11, "return-object", ReferenceType.NONE, Format.Format11x),
+    CONST_4(0x12, "const/4", ReferenceType.NONE, Format.Format11n, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    CONST_16(0x13, "const/16", ReferenceType.NONE, Format.Format21s, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    CONST(0x14, "const", ReferenceType.NONE, Format.Format31i, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    CONST_HIGH16(0x15, "const/high16", ReferenceType.NONE, Format.Format21ih, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    CONST_WIDE_16(0x16, "const-wide/16", ReferenceType.NONE, Format.Format21s, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    CONST_WIDE_32(0x17, "const-wide/32", ReferenceType.NONE, Format.Format31i, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    CONST_WIDE(0x18, "const-wide", ReferenceType.NONE, Format.Format51l, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    CONST_WIDE_HIGH16(0x19, "const-wide/high16", ReferenceType.NONE, Format.Format21lh, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    CONST_STRING(0x1a, "const-string", ReferenceType.STRING, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    CONST_STRING_JUMBO(0x1b, "const-string/jumbo", ReferenceType.STRING, Format.Format31c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    CONST_CLASS(0x1c, "const-class", ReferenceType.TYPE, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MONITOR_ENTER(0x1d, "monitor-enter", ReferenceType.NONE, Format.Format11x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    MONITOR_EXIT(0x1e, "monitor-exit", ReferenceType.NONE, Format.Format11x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    CHECK_CAST(0x1f, "check-cast", ReferenceType.TYPE, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    INSTANCE_OF(0x20, "instance-of", ReferenceType.TYPE, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    ARRAY_LENGTH(0x21, "array-length", ReferenceType.NONE, Format.Format12x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    NEW_INSTANCE(0x22, "new-instance", ReferenceType.TYPE, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    NEW_ARRAY(0x23, "new-array", ReferenceType.TYPE, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    FILLED_NEW_ARRAY(0x24, "filled-new-array", ReferenceType.TYPE, Format.Format35c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    FILLED_NEW_ARRAY_RANGE(0x25, "filled-new-array/range", ReferenceType.TYPE, Format.Format3rc, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    FILL_ARRAY_DATA(0x26, "fill-array-data", ReferenceType.NONE, Format.Format31t, Opcode.CAN_CONTINUE),
+    THROW(0x27, "throw", ReferenceType.NONE, Format.Format11x, Opcode.CAN_THROW),
+    GOTO(0x28, "goto", ReferenceType.NONE, Format.Format10t),
+    GOTO_16(0x29, "goto/16", ReferenceType.NONE, Format.Format20t),
+    GOTO_32(0x2a, "goto/32", ReferenceType.NONE, Format.Format30t),
+    PACKED_SWITCH(0x2b, "packed-switch", ReferenceType.NONE, Format.Format31t, Opcode.CAN_CONTINUE),
+    SPARSE_SWITCH(0x2c, "sparse-switch", ReferenceType.NONE, Format.Format31t, Opcode.CAN_CONTINUE),
+    CMPL_FLOAT(0x2d, "cmpl-float", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    CMPG_FLOAT(0x2e, "cmpg-float", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    CMPL_DOUBLE(0x2f, "cmpl-double", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    CMPG_DOUBLE(0x30, "cmpg-double", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    CMP_LONG(0x31, "cmp-long", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    IF_EQ(0x32, "if-eq", ReferenceType.NONE, Format.Format22t, Opcode.CAN_CONTINUE),
+    IF_NE(0x33, "if-ne", ReferenceType.NONE, Format.Format22t, Opcode.CAN_CONTINUE),
+    IF_LT(0x34, "if-lt", ReferenceType.NONE, Format.Format22t, Opcode.CAN_CONTINUE),
+    IF_GE(0x35, "if-ge", ReferenceType.NONE, Format.Format22t, Opcode.CAN_CONTINUE),
+    IF_GT(0x36, "if-gt", ReferenceType.NONE, Format.Format22t, Opcode.CAN_CONTINUE),
+    IF_LE(0x37, "if-le", ReferenceType.NONE, Format.Format22t, Opcode.CAN_CONTINUE),
+    IF_EQZ(0x38, "if-eqz", ReferenceType.NONE, Format.Format21t, Opcode.CAN_CONTINUE),
+    IF_NEZ(0x39, "if-nez", ReferenceType.NONE, Format.Format21t, Opcode.CAN_CONTINUE),
+    IF_LTZ(0x3a, "if-ltz", ReferenceType.NONE, Format.Format21t, Opcode.CAN_CONTINUE),
+    IF_GEZ(0x3b, "if-gez", ReferenceType.NONE, Format.Format21t, Opcode.CAN_CONTINUE),
+    IF_GTZ(0x3c, "if-gtz", ReferenceType.NONE, Format.Format21t, Opcode.CAN_CONTINUE),
+    IF_LEZ(0x3d, "if-lez", ReferenceType.NONE, Format.Format21t, Opcode.CAN_CONTINUE),
+    AGET(0x44, "aget", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    AGET_WIDE(0x45, "aget-wide", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    AGET_OBJECT(0x46, "aget-object", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    AGET_BOOLEAN(0x47, "aget-boolean", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    AGET_BYTE(0x48, "aget-byte", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    AGET_CHAR(0x49, "aget-char", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    AGET_SHORT(0x4a, "aget-short", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    APUT(0x4b, "aput", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    APUT_WIDE(0x4c, "aput-wide", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    APUT_OBJECT(0x4d, "aput-object", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    APUT_BOOLEAN(0x4e, "aput-boolean", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    APUT_BYTE(0x4f, "aput-byte", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    APUT_CHAR(0x50, "aput-char", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    APUT_SHORT(0x51, "aput-short", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    IGET(0x52, "iget", ReferenceType.FIELD, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    IGET_WIDE(0x53, "iget-wide", ReferenceType.FIELD, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    IGET_OBJECT(0x54, "iget-object", ReferenceType.FIELD, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    IGET_BOOLEAN(0x55, "iget-boolean", ReferenceType.FIELD, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    IGET_BYTE(0x56, "iget-byte", ReferenceType.FIELD, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    IGET_CHAR(0x57, "iget-char", ReferenceType.FIELD, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    IGET_SHORT(0x58, "iget-short", ReferenceType.FIELD, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    IPUT(0x59, "iput", ReferenceType.FIELD, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    IPUT_WIDE(0x5a, "iput-wide", ReferenceType.FIELD, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    IPUT_OBJECT(0x5b, "iput-object", ReferenceType.FIELD, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    IPUT_BOOLEAN(0x5c, "iput-boolean", ReferenceType.FIELD, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    IPUT_BYTE(0x5d, "iput-byte", ReferenceType.FIELD, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    IPUT_CHAR(0x5e, "iput-char", ReferenceType.FIELD, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    IPUT_SHORT(0x5f, "iput-short", ReferenceType.FIELD, Format.Format22c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    SGET(0x60, "sget", ReferenceType.FIELD, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.STATIC_FIELD_ACCESSOR),
+    SGET_WIDE(0x61, "sget-wide", ReferenceType.FIELD, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER | Opcode.STATIC_FIELD_ACCESSOR),
+    SGET_OBJECT(0x62, "sget-object", ReferenceType.FIELD, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.STATIC_FIELD_ACCESSOR),
+    SGET_BOOLEAN(0x63, "sget-boolean", ReferenceType.FIELD, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.STATIC_FIELD_ACCESSOR),
+    SGET_BYTE(0x64, "sget-byte", ReferenceType.FIELD, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.STATIC_FIELD_ACCESSOR),
+    SGET_CHAR(0x65, "sget-char", ReferenceType.FIELD, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.STATIC_FIELD_ACCESSOR),
+    SGET_SHORT(0x66, "sget-short", ReferenceType.FIELD, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.STATIC_FIELD_ACCESSOR),
+    SPUT(0x67, "sput", ReferenceType.FIELD, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.STATIC_FIELD_ACCESSOR),
+    SPUT_WIDE(0x68, "sput-wide", ReferenceType.FIELD, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.STATIC_FIELD_ACCESSOR),
+    SPUT_OBJECT(0x69, "sput-object", ReferenceType.FIELD, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.STATIC_FIELD_ACCESSOR),
+    SPUT_BOOLEAN(0x6a, "sput-boolean", ReferenceType.FIELD, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.STATIC_FIELD_ACCESSOR),
+    SPUT_BYTE(0x6b, "sput-byte", ReferenceType.FIELD, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.STATIC_FIELD_ACCESSOR),
+    SPUT_CHAR(0x6c, "sput-char", ReferenceType.FIELD, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.STATIC_FIELD_ACCESSOR),
+    SPUT_SHORT(0x6d, "sput-short", ReferenceType.FIELD, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.STATIC_FIELD_ACCESSOR),
+    INVOKE_VIRTUAL(0x6e, "invoke-virtual", ReferenceType.METHOD, Format.Format35c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    INVOKE_SUPER(0x6f, "invoke-super", ReferenceType.METHOD, Format.Format35c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    INVOKE_DIRECT(0x70, "invoke-direct", ReferenceType.METHOD, Format.Format35c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT | Opcode.CAN_INITIALIZE_REFERENCE),
+    INVOKE_STATIC(0x71, "invoke-static", ReferenceType.METHOD, Format.Format35c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    INVOKE_INTERFACE(0x72, "invoke-interface", ReferenceType.METHOD, Format.Format35c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    INVOKE_VIRTUAL_RANGE(0x74, "invoke-virtual/range", ReferenceType.METHOD, Format.Format3rc, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    INVOKE_SUPER_RANGE(0x75, "invoke-super/range", ReferenceType.METHOD, Format.Format3rc, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    INVOKE_DIRECT_RANGE(0x76, "invoke-direct/range", ReferenceType.METHOD, Format.Format3rc, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT | Opcode.CAN_INITIALIZE_REFERENCE),
+    INVOKE_STATIC_RANGE(0x77, "invoke-static/range", ReferenceType.METHOD, Format.Format3rc, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    INVOKE_INTERFACE_RANGE(0x78, "invoke-interface/range", ReferenceType.METHOD, Format.Format3rc, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    NEG_INT(0x7b, "neg-int", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    NOT_INT(0x7c, "not-int", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    NEG_LONG(0x7d, "neg-long", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    NOT_LONG(0x7e, "not-long", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    NEG_FLOAT(0x7f, "neg-float", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    NEG_DOUBLE(0x80, "neg-double", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    INT_TO_LONG(0x81, "int-to-long", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    INT_TO_FLOAT(0x82, "int-to-float", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    INT_TO_DOUBLE(0x83, "int-to-double", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    LONG_TO_INT(0x84, "long-to-int", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    LONG_TO_FLOAT(0x85, "long-to-float", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    LONG_TO_DOUBLE(0x86, "long-to-double", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    FLOAT_TO_INT(0x87, "float-to-int", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    FLOAT_TO_LONG(0x88, "float-to-long", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    FLOAT_TO_DOUBLE(0x89, "float-to-double", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    DOUBLE_TO_INT(0x8a, "double-to-int", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    DOUBLE_TO_LONG(0x8b, "double-to-long", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    DOUBLE_TO_FLOAT(0x8c, "double-to-float", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    INT_TO_BYTE(0x8d, "int-to-byte", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    INT_TO_CHAR(0x8e, "int-to-char", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    INT_TO_SHORT(0x8f, "int-to-short", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    ADD_INT(0x90, "add-int", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    SUB_INT(0x91, "sub-int", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MUL_INT(0x92, "mul-int", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    DIV_INT(0x93, "div-int", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    REM_INT(0x94, "rem-int", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    AND_INT(0x95, "and-int", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    OR_INT(0x96, "or-int", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    XOR_INT(0x97, "xor-int", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    SHL_INT(0x98, "shl-int", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    SHR_INT(0x99, "shr-int", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    USHR_INT(0x9a, "ushr-int", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    ADD_LONG(0x9b, "add-long", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    SUB_LONG(0x9c, "sub-long", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    MUL_LONG(0x9d, "mul-long", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    DIV_LONG(0x9e, "div-long", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    REM_LONG(0x9f, "rem-long", ReferenceType.NONE, Format.Format23x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    AND_LONG(0xa0, "and-long", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    OR_LONG(0xa1, "or-long", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    XOR_LONG(0xa2, "xor-long", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    SHL_LONG(0xa3, "shl-long", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    SHR_LONG(0xa4, "shr-long", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    USHR_LONG(0xa5, "ushr-long", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    ADD_FLOAT(0xa6, "add-float", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    SUB_FLOAT(0xa7, "sub-float", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MUL_FLOAT(0xa8, "mul-float", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    DIV_FLOAT(0xa9, "div-float", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    REM_FLOAT(0xaa, "rem-float", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    ADD_DOUBLE(0xab, "add-double", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    SUB_DOUBLE(0xac, "sub-double", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    MUL_DOUBLE(0xad, "mul-double", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    DIV_DOUBLE(0xae, "div-double", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    REM_DOUBLE(0xaf, "rem-double", ReferenceType.NONE, Format.Format23x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    ADD_INT_2ADDR(0xb0, "add-int/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    SUB_INT_2ADDR(0xb1, "sub-int/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MUL_INT_2ADDR(0xb2, "mul-int/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    DIV_INT_2ADDR(0xb3, "div-int/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    REM_INT_2ADDR(0xb4, "rem-int/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    AND_INT_2ADDR(0xb5, "and-int/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    OR_INT_2ADDR(0xb6, "or-int/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    XOR_INT_2ADDR(0xb7, "xor-int/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    SHL_INT_2ADDR(0xb8, "shl-int/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    SHR_INT_2ADDR(0xb9, "shr-int/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    USHR_INT_2ADDR(0xba, "ushr-int/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    ADD_LONG_2ADDR(0xbb, "add-long/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    SUB_LONG_2ADDR(0xbc, "sub-long/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    MUL_LONG_2ADDR(0xbd, "mul-long/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    DIV_LONG_2ADDR(0xbe, "div-long/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    REM_LONG_2ADDR(0xbf, "rem-long/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    AND_LONG_2ADDR(0xc0, "and-long/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    OR_LONG_2ADDR(0xc1, "or-long/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    XOR_LONG_2ADDR(0xc2, "xor-long/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    SHL_LONG_2ADDR(0xc3, "shl-long/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    SHR_LONG_2ADDR(0xc4, "shr-long/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    USHR_LONG_2ADDR(0xc5, "ushr-long/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    ADD_FLOAT_2ADDR(0xc6, "add-float/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    SUB_FLOAT_2ADDR(0xc7, "sub-float/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MUL_FLOAT_2ADDR(0xc8, "mul-float/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    DIV_FLOAT_2ADDR(0xc9, "div-float/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    REM_FLOAT_2ADDR(0xca, "rem-float/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    ADD_DOUBLE_2ADDR(0xcb, "add-double/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    SUB_DOUBLE_2ADDR(0xcc, "sub-double/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    MUL_DOUBLE_2ADDR(0xcd, "mul-double/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    DIV_DOUBLE_2ADDR(0xce, "div-double/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    REM_DOUBLE_2ADDR(0xcf, "rem-double/2addr", ReferenceType.NONE, Format.Format12x, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    ADD_INT_LIT16(0xd0, "add-int/lit16", ReferenceType.NONE, Format.Format22s, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    RSUB_INT(0xd1, "rsub-int", ReferenceType.NONE, Format.Format22s, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MUL_INT_LIT16(0xd2, "mul-int/lit16", ReferenceType.NONE, Format.Format22s, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    DIV_INT_LIT16(0xd3, "div-int/lit16", ReferenceType.NONE, Format.Format22s, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    REM_INT_LIT16(0xd4, "rem-int/lit16", ReferenceType.NONE, Format.Format22s, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    AND_INT_LIT16(0xd5, "and-int/lit16", ReferenceType.NONE, Format.Format22s, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    OR_INT_LIT16(0xd6, "or-int/lit16", ReferenceType.NONE, Format.Format22s, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    XOR_INT_LIT16(0xd7, "xor-int/lit16", ReferenceType.NONE, Format.Format22s, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    ADD_INT_LIT8(0xd8, "add-int/lit8", ReferenceType.NONE, Format.Format22b, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    RSUB_INT_LIT8(0xd9, "rsub-int/lit8", ReferenceType.NONE, Format.Format22b, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    MUL_INT_LIT8(0xda, "mul-int/lit8", ReferenceType.NONE, Format.Format22b, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    DIV_INT_LIT8(0xdb, "div-int/lit8", ReferenceType.NONE, Format.Format22b, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    REM_INT_LIT8(0xdc, "rem-int/lit8", ReferenceType.NONE, Format.Format22b, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    AND_INT_LIT8(0xdd, "and-int/lit8", ReferenceType.NONE, Format.Format22b, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    OR_INT_LIT8(0xde, "or-int/lit8", ReferenceType.NONE, Format.Format22b, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    XOR_INT_LIT8(0xdf, "xor-int/lit8", ReferenceType.NONE, Format.Format22b, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    SHL_INT_LIT8(0xe0, "shl-int/lit8", ReferenceType.NONE, Format.Format22b, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    SHR_INT_LIT8(0xe1, "shr-int/lit8", ReferenceType.NONE, Format.Format22b, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    USHR_INT_LIT8(0xe2, "ushr-int/lit8", ReferenceType.NONE, Format.Format22b, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+
+    IGET_VOLATILE(firstApi(0xe3, 9), "iget-volatile", ReferenceType.FIELD, Format.Format22c, Opcode.ODEX_ONLY | Opcode.VOLATILE_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    IPUT_VOLATILE(firstApi(0xe4, 9), "iput-volatile", ReferenceType.FIELD, Format.Format22c, Opcode.ODEX_ONLY | Opcode.VOLATILE_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    SGET_VOLATILE(firstApi(0xe5, 9), "sget-volatile", ReferenceType.FIELD, Format.Format21c, Opcode.ODEX_ONLY | Opcode.VOLATILE_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.STATIC_FIELD_ACCESSOR),
+    SPUT_VOLATILE(firstApi(0xe6, 9), "sput-volatile", ReferenceType.FIELD, Format.Format21c, Opcode.ODEX_ONLY | Opcode.VOLATILE_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.STATIC_FIELD_ACCESSOR),
+    IGET_OBJECT_VOLATILE(firstApi(0xe7, 9), "iget-object-volatile", ReferenceType.FIELD, Format.Format22c, Opcode.ODEX_ONLY | Opcode.VOLATILE_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    IGET_WIDE_VOLATILE(firstApi(0xe8, 9), "iget-wide-volatile", ReferenceType.FIELD, Format.Format22c, Opcode.ODEX_ONLY | Opcode.VOLATILE_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    IPUT_WIDE_VOLATILE(firstApi(0xe9, 9), "iput-wide-volatile", ReferenceType.FIELD, Format.Format22c, Opcode.ODEX_ONLY | Opcode.VOLATILE_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    SGET_WIDE_VOLATILE(firstApi(0xea, 9), "sget-wide-volatile", ReferenceType.FIELD, Format.Format21c, Opcode.ODEX_ONLY | Opcode.VOLATILE_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER | Opcode.STATIC_FIELD_ACCESSOR),
+    SPUT_WIDE_VOLATILE(firstApi(0xeb, 9), "sput-wide-volatile", ReferenceType.FIELD, Format.Format21c, Opcode.ODEX_ONLY | Opcode.VOLATILE_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.STATIC_FIELD_ACCESSOR),
+
+    THROW_VERIFICATION_ERROR(firstApi(0xed, 5), "throw-verification-error", ReferenceType.NONE, Format.Format20bc, Opcode.ODEX_ONLY | Opcode.CAN_THROW),
+    EXECUTE_INLINE(allApis(0xee), "execute-inline", ReferenceType.NONE,  Format.Format35mi, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    EXECUTE_INLINE_RANGE(firstApi(0xef, 8), "execute-inline/range", ReferenceType.NONE,  Format.Format3rmi,  Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    INVOKE_DIRECT_EMPTY(lastApi(0xf0, 13), "invoke-direct-empty", ReferenceType.METHOD,  Format.Format35c, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT | Opcode.CAN_INITIALIZE_REFERENCE),
+    INVOKE_OBJECT_INIT_RANGE(firstApi(0xf0, 14), "invoke-object-init/range", ReferenceType.METHOD,  Format.Format3rc, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT | Opcode.CAN_INITIALIZE_REFERENCE),
+    RETURN_VOID_BARRIER(combine(firstApi(0xf1, 11), lastArtVersion(0x73, 59)), "return-void-barrier", ReferenceType.NONE, Format.Format10x, Opcode.ODEX_ONLY),
+    RETURN_VOID_NO_BARRIER(firstArtVersion(0x73, 60), "return-void-no-barrier", ReferenceType.NONE, Format.Format10x, Opcode.ODEX_ONLY),
+    IGET_QUICK(combine(allApis(0xf2), allArtVersions(0xe3)), "iget-quick", ReferenceType.NONE,  Format.Format22cs, Opcode.ODEX_ONLY | Opcode.QUICK_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    IGET_WIDE_QUICK(combine(allApis(0xf3), allArtVersions(0xe4)), "iget-wide-quick", ReferenceType.NONE,  Format.Format22cs, Opcode.ODEX_ONLY | Opcode.QUICK_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+    IGET_OBJECT_QUICK(combine(allApis(0xf4), allArtVersions(0xe5)), "iget-object-quick", ReferenceType.NONE,  Format.Format22cs, Opcode.ODEX_ONLY | Opcode.QUICK_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    IPUT_QUICK(combine(allApis(0xf5), allArtVersions(0xe6)), "iput-quick", ReferenceType.NONE,  Format.Format22cs, Opcode.ODEX_ONLY | Opcode.QUICK_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    IPUT_WIDE_QUICK(combine(allApis(0xf6), allArtVersions(0xe7)), "iput-wide-quick", ReferenceType.NONE,  Format.Format22cs, Opcode.ODEX_ONLY | Opcode.QUICK_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    IPUT_OBJECT_QUICK(combine(allApis(0xf7), allArtVersions(0xe8)), "iput-object-quick", ReferenceType.NONE,  Format.Format22cs, Opcode.ODEX_ONLY | Opcode.QUICK_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    IPUT_BOOLEAN_QUICK(allArtVersions(0xeb), "iput-boolean-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.QUICK_FIELD_ACCESSOR),
+    IPUT_BYTE_QUICK(allArtVersions(0xec), "iput-byte-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.QUICK_FIELD_ACCESSOR),
+    IPUT_CHAR_QUICK(allArtVersions(0xed), "iput-char-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.QUICK_FIELD_ACCESSOR),
+    IPUT_SHORT_QUICK(allArtVersions(0xee), "iput-short-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.QUICK_FIELD_ACCESSOR),
+    IGET_BOOLEAN_QUICK(allArtVersions(0xef), "iget-boolean-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.QUICK_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    IGET_BYTE_QUICK(allArtVersions(0xf0), "iget-byte-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.QUICK_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    IGET_CHAR_QUICK(allArtVersions(0xf1), "iget-char-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.QUICK_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    IGET_SHORT_QUICK(allArtVersions(0xf2), "iget-short-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.QUICK_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    
+    INVOKE_VIRTUAL_QUICK(combine(allApis(0xf8), allArtVersions(0xe9)), "invoke-virtual-quick", ReferenceType.NONE,  Format.Format35ms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    INVOKE_VIRTUAL_QUICK_RANGE(combine(allApis(0xf9), allArtVersions(0xea)), "invoke-virtual-quick/range", ReferenceType.NONE,  Format.Format3rms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    INVOKE_SUPER_QUICK(lastApi(0xfa, 25), "invoke-super-quick", ReferenceType.NONE,  Format.Format35ms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    INVOKE_SUPER_QUICK_RANGE(lastApi(0xfb, 25), "invoke-super-quick/range", ReferenceType.NONE,  Format.Format3rms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+
+    IPUT_OBJECT_VOLATILE(firstApi(0xfc, 9), "iput-object-volatile", ReferenceType.FIELD, Format.Format22c, Opcode.ODEX_ONLY | Opcode.VOLATILE_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+    SGET_OBJECT_VOLATILE(firstApi(0xfd, 9), "sget-object-volatile", ReferenceType.FIELD, Format.Format21c, Opcode.ODEX_ONLY | Opcode.VOLATILE_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.STATIC_FIELD_ACCESSOR),
+    SPUT_OBJECT_VOLATILE(betweenApi(0xfe, 9, 19), "sput-object-volatile", ReferenceType.FIELD, Format.Format21c, Opcode.ODEX_ONLY | Opcode.VOLATILE_FIELD_ACCESSOR | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.STATIC_FIELD_ACCESSOR),
+
+    PACKED_SWITCH_PAYLOAD(0x100, "packed-switch-payload", ReferenceType.NONE, Format.PackedSwitchPayload, 0),
+    SPARSE_SWITCH_PAYLOAD(0x200, "sparse-switch-payload", ReferenceType.NONE, Format.SparseSwitchPayload, 0),
+    ARRAY_PAYLOAD(0x300, "array-payload", ReferenceType.NONE, Format.ArrayPayload, 0),
+
+    INVOKE_POLYMORPHIC(firstArtVersion(0xfa, 87), "invoke-polymorphic", ReferenceType.METHOD, ReferenceType.METHOD_PROTO, Format.Format45cc, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    INVOKE_POLYMORPHIC_RANGE(firstArtVersion(0xfb, 87), "invoke-polymorphic/range", ReferenceType.METHOD, ReferenceType.METHOD_PROTO, Format.Format4rcc, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+
+    INVOKE_CUSTOM(firstArtVersion(0xfc, 111), "invoke-custom", ReferenceType.CALL_SITE, Format.Format35c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+    INVOKE_CUSTOM_RANGE(firstArtVersion(0xfd, 111), "invoke-custom/range", ReferenceType.CALL_SITE, Format.Format3rc, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+
+    CONST_METHOD_HANDLE(firstArtVersion(0xfe, 134), "const-method-handle", ReferenceType.METHOD_HANDLE, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+    CONST_METHOD_TYPE(firstArtVersion(0xff, 134), "const-method-type", ReferenceType.METHOD_PROTO, Format.Format21c, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER);
+
+    //if the instruction can throw an exception
+    public static final int CAN_THROW = 0x1;
+    //if the instruction is an odex only instruction
+    public static final int ODEX_ONLY = 0x2;
+    //if execution can continue to the next instruction
+    public static final int CAN_CONTINUE = 0x4;
+    //if the instruction sets the "hidden" result register
+    public static final int SETS_RESULT = 0x8;
+    //if the instruction sets the value of it's first register
+    public static final int SETS_REGISTER = 0x10;
+    //if the instruction sets the value of it's first register to a wide type
+    public static final int SETS_WIDE_REGISTER = 0x20;
+    //if the instruction is an iget-quick/iput-quick instruction
+    public static final int QUICK_FIELD_ACCESSOR = 0x40;
+    //if the instruction is a *get-volatile/*put-volatile instruction
+    public static final int VOLATILE_FIELD_ACCESSOR = 0x80;
+    //if the instruction is a static sget-*/sput-*instruction
+    public static final int STATIC_FIELD_ACCESSOR = 0x100;
+    //if the instruction is a jumbo instruction
+    public static final int JUMBO_OPCODE = 0x200;
+    //if the instruction can initialize an uninitialized object reference
+    public static final int CAN_INITIALIZE_REFERENCE = 0x400;
+
+    private static final int ALL_APIS = 0xFFFF0000;
+
+    private static int minApi(int api) {
+        return 0xFFFF0000 | (api & 0xFFFF);
+    }
+
+    private static int maxApi(int api) {
+        return api << 16;
+    }
+
+    // values and minApis provide a mapping of api -> bytecode value.
+    // the apis in minApis are guaranteed to be
+    public final RangeMap<Integer, Short> apiToValueMap;
+    public final RangeMap<Integer, Short> artVersionToValueMap;
+
+    public final String name;
+    public final int referenceType;
+    public final Format format;
+    public final int flags;
+    public final int referenceType2;
+
+    Opcode(int opcodeValue, String opcodeName, int referenceType, Format format) {
+        this(opcodeValue, opcodeName, referenceType, format, 0);
+    }
+
+    Opcode(int opcodeValue, String opcodeName, int referenceType, Format format, int flags) {
+        this(allVersions(opcodeValue), opcodeName, referenceType, format, flags);
+    }
+
+    Opcode(List<VersionConstraint> versionConstraints, String opcodeName, int referenceType, Format format, int flags) {
+        this(versionConstraints, opcodeName, referenceType, -1, format, flags);
+    }
+
+    Opcode(List<VersionConstraint> versionConstraints, String opcodeName, int referenceType, int referenceType2,
+           Format format, int flags) {
+        ImmutableRangeMap.Builder<Integer, Short> apiToValueBuilder = ImmutableRangeMap.builder();
+        ImmutableRangeMap.Builder<Integer, Short> artVersionToValueBuilder = ImmutableRangeMap.builder();
+
+        for (VersionConstraint versionConstraint : versionConstraints) {
+            if (!versionConstraint.apiRange.isEmpty()) {
+                apiToValueBuilder.put(versionConstraint.apiRange, (short)versionConstraint.opcodeValue);
+            }
+            if (!versionConstraint.artVersionRange.isEmpty()) {
+                artVersionToValueBuilder.put(versionConstraint.artVersionRange, (short)versionConstraint.opcodeValue);
+            }
+        }
+
+        this.apiToValueMap = apiToValueBuilder.build();
+        this.artVersionToValueMap = artVersionToValueBuilder.build();
+        this.name = opcodeName;
+        this.referenceType = referenceType;
+        this.referenceType2 = referenceType2;
+        this.format = format;
+        this.flags = flags;
+    }
+
+    private static List<VersionConstraint> firstApi(int opcodeValue, int api) {
+        return Lists.newArrayList(new VersionConstraint(Range.atLeast(api), Range.openClosed(0, 0), opcodeValue));
+    }
+
+    private static List<VersionConstraint> lastApi(int opcodeValue, int api) {
+        return Lists.newArrayList(new VersionConstraint(Range.atMost(api), Range.openClosed(0, 0), opcodeValue));
+    }
+
+    private static List<VersionConstraint> betweenApi(int opcodeValue, int minApi, int maxApi) {
+        return Lists.newArrayList(new VersionConstraint(Range.closed(minApi, maxApi), Range.openClosed(0, 0),
+                opcodeValue));
+    }
+
+    private static List<VersionConstraint> firstArtVersion(int opcodeValue, int artVersion) {
+        return Lists.newArrayList(new VersionConstraint(Range.openClosed(0, 0), Range.atLeast(artVersion), opcodeValue));
+    }
+
+    private static List<VersionConstraint> lastArtVersion(int opcodeValue, int artVersion) {
+        return Lists.newArrayList(new VersionConstraint(Range.openClosed(0, 0), Range.atMost(artVersion), opcodeValue));
+    }
+
+    private static List<VersionConstraint> allVersions(int opcodeValue) {
+        return Lists.newArrayList(new VersionConstraint(Range.<Integer>all(), Range.<Integer>all(), opcodeValue));
+    }
+
+    private static List<VersionConstraint> allApis(int opcodeValue) {
+        return Lists.newArrayList(new VersionConstraint(Range.<Integer>all(), Range.openClosed(0, 0), opcodeValue));
+    }
+
+    private static List<VersionConstraint> allArtVersions(int opcodeValue) {
+        return Lists.newArrayList(new VersionConstraint(Range.openClosed(0, 0), Range.<Integer>all(), opcodeValue));
+    }
+
+    @SuppressWarnings("unchecked")
+    private static List<VersionConstraint> combine(List<VersionConstraint>... versionConstraints) {
+        List<VersionConstraint> combinedList = Lists.newArrayList();
+        for (List<VersionConstraint> versionConstraintList: versionConstraints) {
+            combinedList.addAll(versionConstraintList);
+        }
+        return combinedList;
+    }
+
+    public final boolean canThrow() {
+        return (flags & CAN_THROW) != 0;
+    }
+
+    public final boolean odexOnly() {
+        return (flags & ODEX_ONLY) != 0;
+    }
+
+    public final boolean canContinue() {
+        return (flags & CAN_CONTINUE) != 0;
+    }
+
+    public final boolean setsResult() {
+        return (flags & SETS_RESULT) != 0;
+    }
+
+    public final boolean setsRegister() {
+        return (flags & SETS_REGISTER) != 0;
+    }
+
+    public final boolean setsWideRegister() {
+        return (flags & SETS_WIDE_REGISTER) != 0;
+    }
+
+    public final boolean isQuickFieldaccessor() {
+        return (flags & QUICK_FIELD_ACCESSOR) != 0;
+    }
+
+    public final boolean isVolatileFieldAccessor() {
+        return (flags & VOLATILE_FIELD_ACCESSOR) != 0;
+    }
+
+    public final boolean isStaticFieldAccessor() {
+        return (flags & STATIC_FIELD_ACCESSOR) != 0;
+    }
+
+    public final boolean isJumboOpcode() {
+        return (flags & JUMBO_OPCODE) != 0;
+    }
+
+    public final boolean canInitializeReference() {
+        return (flags & CAN_INITIALIZE_REFERENCE) != 0;
+    }
+
+    private static class VersionConstraint {
+        @Nonnull public final Range<Integer> apiRange;
+        @Nonnull public final Range<Integer> artVersionRange;
+        public final int opcodeValue;
+
+        public VersionConstraint(@Nonnull Range<Integer> apiRange, @Nonnull Range<Integer> artVersionRange,
+                                 int opcodeValue) {
+            this.apiRange = apiRange;
+            this.artVersionRange = artVersionRange;
+            this.opcodeValue = opcodeValue;
+        }
+    }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java b/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java
new file mode 100644
index 0000000..8f24e02
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.RangeMap;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.EnumMap;
+import java.util.HashMap;
+
+import static org.jf.dexlib2.VersionMap.NO_VERSION;
+import static org.jf.dexlib2.VersionMap.mapApiToArtVersion;
+import static org.jf.dexlib2.VersionMap.mapArtVersionToApi;
+
+public class Opcodes {
+
+    /**
+     * Either the api level for dalvik opcodes, or the art version for art opcodes
+     */
+    public final int api;
+    public final int artVersion;
+    @Nonnull private final Opcode[] opcodesByValue = new Opcode[256];
+    @Nonnull private final EnumMap<Opcode, Short> opcodeValues;
+    @Nonnull private final HashMap<String, Opcode> opcodesByName;
+
+    @Nonnull
+    public static Opcodes forApi(int api) {
+        return new Opcodes(api, NO_VERSION);
+    }
+
+    @Nonnull
+    public static Opcodes forArtVersion(int artVersion) {
+        return new Opcodes(NO_VERSION, artVersion);
+    }
+
+    @Nonnull
+    public static Opcodes forDexVersion(int dexVersion) {
+        int api = VersionMap.mapDexVersionToApi(dexVersion);
+        if (api == NO_VERSION) {
+            throw new RuntimeException("Unsupported dex version " + dexVersion);
+        }
+        return new Opcodes(api, NO_VERSION);
+    }
+
+    /**
+     * @return a default Opcodes instance for when the exact Opcodes to use doesn't matter or isn't known
+     */
+    @Nonnull
+    public static Opcodes getDefault() {
+        // The last pre-art api
+        return forApi(20);
+    }
+
+    private Opcodes(int api, int artVersion) {
+        if (api >= 21) {
+            this.api = api;
+            this.artVersion = mapApiToArtVersion(api);
+        } else if (artVersion >= 0 && artVersion < 39) {
+            this.api = mapArtVersionToApi(artVersion);
+            this.artVersion = artVersion;
+        } else {
+            this.api = api;
+            this.artVersion = artVersion;
+        }
+
+        opcodeValues = new EnumMap<Opcode, Short>(Opcode.class);
+        opcodesByName = Maps.newHashMap();
+
+        int version;
+        if (isArt()) {
+            version = this.artVersion;
+        } else {
+            version = this.api;
+        }
+
+        for (Opcode opcode: Opcode.values()) {
+            RangeMap<Integer, Short> versionToValueMap;
+
+            if (isArt()) {
+                versionToValueMap = opcode.artVersionToValueMap;
+            } else {
+                versionToValueMap = opcode.apiToValueMap;
+            }
+
+            Short opcodeValue = versionToValueMap.get(version);
+            if (opcodeValue != null) {
+                if (!opcode.format.isPayloadFormat) {
+                    opcodesByValue[opcodeValue] = opcode;
+                }
+                opcodeValues.put(opcode, opcodeValue);
+                opcodesByName.put(opcode.name.toLowerCase(), opcode);
+            }
+        }
+    }
+
+    @Nullable
+    public Opcode getOpcodeByName(@Nonnull String opcodeName) {
+        return opcodesByName.get(opcodeName.toLowerCase());
+    }
+
+    @Nullable
+    public Opcode getOpcodeByValue(int opcodeValue) {
+        switch (opcodeValue) {
+            case 0x100:
+                return Opcode.PACKED_SWITCH_PAYLOAD;
+            case 0x200:
+                return Opcode.SPARSE_SWITCH_PAYLOAD;
+            case 0x300:
+                return Opcode.ARRAY_PAYLOAD;
+            default:
+                if (opcodeValue >= 0 && opcodeValue < opcodesByValue.length) {
+                    return opcodesByValue[opcodeValue];
+                }
+                return null;
+        }
+    }
+
+    @Nullable
+    public Short getOpcodeValue(@Nonnull Opcode opcode) {
+        return opcodeValues.get(opcode);
+    }
+
+    public boolean isArt() {
+        return artVersion != NO_VERSION;
+    }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/ReferenceType.java b/dexlib2/src/main/java/org/jf/dexlib2/ReferenceType.java
new file mode 100644
index 0000000..8b77416
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/ReferenceType.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+import org.jf.dexlib2.iface.reference.*;
+import org.jf.util.ExceptionWithContext;
+
+public final class ReferenceType {
+    public static final int STRING = 0;
+    public static final int TYPE = 1;
+    public static final int FIELD = 2;
+    public static final int METHOD = 3;
+    public static final int METHOD_PROTO = 4;
+    public static final int CALL_SITE = 5;
+    public static final int METHOD_HANDLE = 6;
+    public static final int NONE = 7;
+
+    public static int getReferenceType(Reference reference) {
+        if (reference instanceof StringReference) {
+            return STRING;
+        } else if (reference instanceof TypeReference) {
+            return TYPE;
+        } else if (reference instanceof FieldReference) {
+            return FIELD;
+        } else if (reference instanceof MethodReference) {
+            return METHOD;
+        } else if (reference instanceof MethodProtoReference) {
+            return METHOD_PROTO;
+        } else if (reference instanceof CallSiteReference) {
+            return CALL_SITE;
+        } else if (reference instanceof MethodHandleReference) {
+            return METHOD_HANDLE;
+        } else {
+            throw new IllegalStateException("Invalid reference");
+        }
+    }
+
+    /**
+     * Validate a specific reference type. Note that the NONE placeholder is specifically not considered valid here.
+     *
+     * @throws InvalidReferenceTypeException
+     */
+    public static void validateReferenceType(int referenceType) {
+        if (referenceType < 0 || referenceType > 4) {
+            throw new InvalidReferenceTypeException(referenceType);
+        }
+    }
+
+    public static class InvalidReferenceTypeException extends ExceptionWithContext {
+        private final int referenceType;
+
+        public InvalidReferenceTypeException(int referenceType) {
+            super("Invalid reference type: %d", referenceType);
+            this.referenceType = referenceType;
+        }
+
+        public InvalidReferenceTypeException(int referenceType, String message, Object... formatArgs) {
+            super(message, formatArgs);
+            this.referenceType = referenceType;
+        }
+
+        public int getReferenceType() {
+            return referenceType;
+        }
+    }
+
+    private ReferenceType() {}
+}
\ No newline at end of file
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/ValueType.java b/dexlib2/src/main/java/org/jf/dexlib2/ValueType.java
new file mode 100644
index 0000000..ecb967b
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/ValueType.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+public final class ValueType {
+    public static final int BYTE = 0x00;
+    public static final int SHORT = 0x02;
+    public static final int CHAR = 0x03;
+    public static final int INT = 0x04;
+    public static final int LONG = 0x06;
+    public static final int FLOAT = 0x10;
+    public static final int DOUBLE = 0x11;
+    public static final int METHOD_TYPE = 0x15;
+    public static final int METHOD_HANDLE = 0x16;
+    public static final int STRING = 0x17;
+    public static final int TYPE = 0x18;
+    public static final int FIELD = 0x19;
+    public static final int METHOD = 0x1a;
+    public static final int ENUM = 0x1b;
+    public static final int ARRAY = 0x1c;
+    public static final int ANNOTATION = 0x1d;
+    public static final int NULL = 0x1e;
+    public static final int BOOLEAN = 0x1f;
+
+    private ValueType() {}
+
+    public static String getValueTypeName(int valueType) {
+        switch (valueType) {
+            case BYTE:
+                return "byte";
+            case SHORT:
+                return "short";
+            case CHAR:
+                return "char";
+            case INT:
+                return "int";
+            case LONG:
+                return "long";
+            case FLOAT:
+                return "float";
+            case DOUBLE:
+                return "double";
+            case METHOD_TYPE:
+                return "method_type";
+            case METHOD_HANDLE:
+                return "method_handle";
+            case STRING:
+                return "string";
+            case TYPE:
+                return "type";
+            case FIELD:
+                return "field";
+            case METHOD:
+                return "method";
+            case ENUM:
+                return "enum";
+            case ARRAY:
+                return "array";
+            case ANNOTATION:
+                return "annotation";
+            case NULL:
+                return "null";
+            case BOOLEAN:
+                return "boolean";
+            default:
+                throw new IllegalArgumentException("Unknown encoded value type: " + valueType);
+        }
+    }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/VerificationError.java b/dexlib2/src/main/java/org/jf/dexlib2/VerificationError.java
new file mode 100644
index 0000000..d0aa029
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/VerificationError.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+import com.google.common.collect.Maps;
+import org.jf.util.ExceptionWithContext;
+
+import javax.annotation.Nullable;
+import java.util.HashMap;
+
+public class VerificationError {
+    public static final int GENERIC = 1;
+    public static final int NO_SUCH_CLASS = 2;
+    public static final int NO_SUCH_FIELD = 3;
+    public static final int NO_SUCH_METHOD = 4;
+    public static final int ILLEGAL_CLASS_ACCESS = 5;
+    public static final int ILLEGAL_FIELD_ACCESS = 6;
+    public static final int ILLEGAL_METHOD_ACCESS = 7;
+    public static final int CLASS_CHANGE_ERROR = 8;
+    public static final int INSTANTIATION_ERROR = 9;
+
+    private static final HashMap<String, Integer> verificationErrorNames = Maps.newHashMap();
+
+    static {
+        verificationErrorNames.put("generic-error", GENERIC);
+        verificationErrorNames.put("no-such-class", NO_SUCH_CLASS);
+        verificationErrorNames.put("no-such-field", NO_SUCH_FIELD);
+        verificationErrorNames.put("no-such-method", NO_SUCH_METHOD);
+        verificationErrorNames.put("illegal-class-access", ILLEGAL_CLASS_ACCESS);
+        verificationErrorNames.put("illegal-field-access", ILLEGAL_FIELD_ACCESS);
+        verificationErrorNames.put("illegal-method-access", ILLEGAL_METHOD_ACCESS);
+        verificationErrorNames.put("class-change-error", CLASS_CHANGE_ERROR);
+        verificationErrorNames.put("instantiation-error", INSTANTIATION_ERROR);
+    }
+
+    @Nullable
+    public static String getVerificationErrorName(int verificationError) {
+        switch (verificationError) {
+            case GENERIC:
+                return "generic-error";
+            case NO_SUCH_CLASS:
+                return "no-such-class";
+            case NO_SUCH_FIELD:
+                return "no-such-field";
+            case NO_SUCH_METHOD:
+                return "no-such-method";
+            case ILLEGAL_CLASS_ACCESS:
+                return "illegal-class-access";
+            case ILLEGAL_FIELD_ACCESS:
+                return "illegal-field-access";
+            case ILLEGAL_METHOD_ACCESS:
+                return "illegal-method-access";
+            case CLASS_CHANGE_ERROR:
+                return "class-change-error";
+            case INSTANTIATION_ERROR:
+                return "instantiation-error";
+            default:
+                return null;
+        }
+    }
+
+    public static int getVerificationError(String verificationError) {
+        Integer ret = verificationErrorNames.get(verificationError);
+        if (ret == null) {
+            throw new ExceptionWithContext("Invalid verification error: %s", verificationError);
+        }
+        return ret;
+    }
+
+    public static boolean isValidVerificationError(int verificationError) {
+        return verificationError > 0 && verificationError < 10;
+    }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/VersionMap.java b/dexlib2/src/main/java/org/jf/dexlib2/VersionMap.java
new file mode 100644
index 0000000..38773d3
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/VersionMap.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2;
+
+public class VersionMap {
+    public static final int NO_VERSION = -1;
+
+    public static int mapDexVersionToApi(int dexVersion) {
+        switch (dexVersion) {
+            case 35:
+                return 23;
+            case 37:
+                return 25;
+            case 38:
+                return 27;
+            case 39:
+                return 28;
+            default:
+                return NO_VERSION;
+        }
+    }
+
+    public static int mapApiToDexVersion(int api) {
+        if (api <= 23) {
+            return 35;
+        }
+        if (api <= 25) {
+            return 37;
+        }
+        if (api <= 27) {
+            return 38;
+        }
+        return 39;
+    }
+
+    public static int mapArtVersionToApi(int artVersion) {
+        if (artVersion >= 170) {
+            return 29;
+        }
+        if (artVersion >= 138) {
+            return 28;
+        }
+        if (artVersion >= 131) {
+            return 27;
+        }
+        if (artVersion >= 124) {
+            return 26;
+        }
+        if (artVersion >= 79) {
+            return 24;
+        }
+        if (artVersion >= 64) {
+            return 23;
+        }
+        if (artVersion >= 45) {
+            return 22;
+        }
+        if (artVersion >= 39) {
+            return 21;
+        }
+        return 19;
+    }
+
+    public static int mapApiToArtVersion(int api) {
+        if (api < 19) {
+            return NO_VERSION;
+        }
+
+        switch (api) {
+            case 19:
+            case 20:
+                return 7;
+            case 21:
+                return 39;
+            case 22:
+                return 45;
+            case 23:
+                return 64;
+            case 24:
+            case 25:
+                return 79;
+            case 26:
+                return 124;
+            case 27:
+                return 131;
+            case 28:
+                return 138;
+            case 29:
+                return 170;
+            default:
+                // 178 is the current version in the master branch of AOSP as of 2020-02-02
+                return 178;
+        }
+    }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalysisException.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalysisException.java
new file mode 100644
index 0000000..4ca7b13
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalysisException.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2.analysis;
+
+import org.jf.util.ExceptionWithContext;
+
+public class AnalysisException extends ExceptionWithContext {
+    public int codeAddress;
+
+    public AnalysisException(Throwable cause) {
+        super(cause);
+    }
+
+    public AnalysisException(Throwable cause, String message, Object... formatArgs) {
+        super(cause, message, formatArgs);
+    }
+
+    public AnalysisException(String message, Object... formatArgs) {
+        super(message, formatArgs);
+    }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalyzedInstruction.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalyzedInstruction.java
new file mode 100644
index 0000000..1a9b9ad
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalyzedInstruction.java
@@ -0,0 +1,669 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.dexlib2.analysis;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.jf.dexlib2.Opcode;
+import org.jf.dexlib2.iface.instruction.*;
+import org.jf.dexlib2.iface.instruction.formats.Instruction22c;
+import org.jf.dexlib2.iface.reference.MethodReference;
+import org.jf.dexlib2.iface.reference.Reference;
+import org.jf.dexlib2.iface.reference.TypeReference;
+import org.jf.util.ExceptionWithContext;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.*;
+
+public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
+    /**
+     * The MethodAnalyzer containing this instruction
+     */
+    @Nonnull
+    protected final MethodAnalyzer methodAnalyzer;
+
+    /**
+     * The actual instruction
+     */
+    @Nonnull
+    protected Instruction instruction;
+
+    /**
+     * The index of the instruction, where the first instruction in the method is at index 0, and so on
+     */
+    protected final int instructionIndex;
+
+    /**
+     * Instructions that can pass on execution to this one during normal execution
+     */
+    @Nonnull
+    protected final TreeSet<AnalyzedInstruction> predecessors = new TreeSet<AnalyzedInstruction>();
+
+    /**
+     * Instructions that can execution could pass on to next during normal execution
+     */
+    @Nonnull
+    protected final LinkedList<AnalyzedInstruction> successors = new LinkedList<AnalyzedInstruction>();
+
+    /**
+     * This contains the register types *before* the instruction has executed
+     */
+    @Nonnull
+    protected final RegisterType[] preRegisterMap;
+
+    /**
+     * This contains the register types *after* the instruction has executed
+     */
+    @Nonnull
+    protected final RegisterType[] postRegisterMap;
+
+    /**
+     * This contains optional register type overrides for register types from predecessors
+     */
+    @Nullable
+    protected Map<PredecessorOverrideKey, RegisterType> predecessorRegisterOverrides = null;
+
+    /**
+     * When deodexing, we might need to deodex this instruction multiple times, when we merge in new register
+     * information. When this happens, we need to restore the original (odexed) instruction, so we can deodex it again
+     */
+    protected final Instruction originalInstruction;
+
+    public AnalyzedInstruction(@Nonnull MethodAnalyzer methodAnalyzer, @Nonnull Instruction instruction,
+                               int instructionIndex, int registerCount) {
+        this.methodAnalyzer = methodAnalyzer;
+        this.instruction = instruction;
+        this.originalInstruction = instruction;
+        this.instructionIndex = instructionIndex;
+        this.postRegisterMap = new RegisterType[registerCount];
+        this.preRegisterMap = new RegisterType[registerCount];
+        RegisterType unknown = RegisterType.getRegisterType(RegisterType.UNKNOWN, null);
+        for (int i=0; i<registerCount; i++) {
+            preRegisterMap[i] = unknown;
+            postRegisterMap[i] = unknown;
+        }
+    }
+
+    public int getInstructionIndex() {
+        return instructionIndex;
+    }
+
+    public int getPredecessorCount() {
+        return predecessors.size();
+    }
+
+    public SortedSet<AnalyzedInstruction> getPredecessors() {
+        return Collections.unmodifiableSortedSet(predecessors);
+    }
+
+    public RegisterType getPredecessorRegisterType(@Nonnull AnalyzedInstruction predecessor, int registerNumber) {
+        if (predecessorRegisterOverrides != null) {
+            RegisterType override = predecessorRegisterOverrides.get(
+                    new PredecessorOverrideKey(predecessor, registerNumber));
+            if (override != null) {
+                return override;
+            }
+        }
+        return predecessor.postRegisterMap[registerNumber];
+    }
+
+    protected boolean addPredecessor(AnalyzedInstruction predecessor) {
+        return predecessors.add(predecessor);
+    }
+
+    protected void addSuccessor(AnalyzedInstruction successor) {
+        successors.add(successor);
+    }
+
+    protected void setDeodexedInstruction(Instruction instruction) {
+        assert originalInstruction.getOpcode().odexOnly();
+        this.instruction = instruction;
+    }
+
+    protected void restoreOdexedInstruction() {
+        assert originalInstruction.getOpcode().odexOnly();
+        instruction = originalInstruction;
+    }
+
+    @Nonnull
+    public List<AnalyzedInstruction> getSuccessors() {
+        return Collections.unmodifiableList(successors);
+    }
+
+    @Nonnull
+    public Instruction getInstruction() {
+        return instruction;
+    }
+
+    @Nonnull
+    public Instruction getOriginalInstruction() {
+        return originalInstruction;
+    }
+
+    /**
+     * Is this instruction a "beginning instruction". A beginning instruction is defined to be an instruction
+     * that can be the first successfully executed instruction in the method. The first instruction is always a
+     * beginning instruction. If the first instruction can throw an exception, and is covered by a try block, then
+     * the first instruction of any exception handler for that try block is also a beginning instruction. And likewise,
+     * if any of those instructions can throw an exception and are covered by try blocks, the first instruction of the
+     * corresponding exception handler is a beginning instruction, etc.
+     *
+     * To determine this, we simply check if the first predecessor is the fake "StartOfMethod" instruction, which has
+     * an instruction index of -1.
+     * @return a boolean value indicating whether this instruction is a beginning instruction
+     */
+    public boolean isBeginningInstruction() {
+        //if this instruction has no predecessors, it is either the fake "StartOfMethod" instruction or it is an
+        //unreachable instruction.
+        if (predecessors.size() == 0) {
+            return false;
+        }
+        return predecessors.first().instructionIndex == -1;
+    }
+
+    /*
+     * Merges the given register type into the specified pre-instruction register, and also sets the post-instruction
+     * register type accordingly if it isn't a destination register for this instruction
+     * @param registerNumber Which register to set
+     * @param registerType The register type
+     * @returns true If the post-instruction register type was changed. This might be false if either the specified
+     * register is a destination register for this instruction, or if the pre-instruction register type didn't change
+     * after merging in the given register type
+     */
+    protected boolean mergeRegister(int registerNumber, RegisterType registerType, BitSet verifiedInstructions,
+                                    boolean override) {
+        assert registerNumber >= 0 && registerNumber < postRegisterMap.length;
+        assert registerType != null;
+
+        RegisterType oldRegisterType = preRegisterMap[registerNumber];
+
+        RegisterType mergedRegisterType;
+        if (override) {
+            mergedRegisterType = getMergedPreRegisterTypeFromPredecessors(registerNumber);
+        } else {
+            mergedRegisterType = oldRegisterType.merge(registerType);
+        }
+
+        if (mergedRegisterType.equals(oldRegisterType)) {
+            return false;
+        }
+
+        preRegisterMap[registerNumber] = mergedRegisterType;
+        verifiedInstructions.clear(instructionIndex);
+
+        if (!setsRegister(registerNumber)) {
+            postRegisterMap[registerNumber] = mergedRegisterType;
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Iterates over the predecessors of this instruction, and merges all the post-instruction register types for the
+     * given register. Any dead, unreachable, or odexed predecessor is ignored. This takes into account any overridden
+     * predecessor register types
+     *
+     * @param registerNumber the register number
+     * @return The register type resulting from merging the post-instruction register types from all predecessors
+     */
+    @Nonnull
+    protected RegisterType getMergedPreRegisterTypeFromPredecessors(int registerNumber) {
+        RegisterType mergedRegisterType = null;
+        for (AnalyzedInstruction predecessor: predecessors) {
+            RegisterType predecessorRegisterType = getPredecessorRegisterType(predecessor, registerNumber);
+            if (predecessorRegisterType != null) {
+                if (mergedRegisterType == null) {
+                    mergedRegisterType = predecessorRegisterType;
+                } else {
+                    mergedRegisterType = predecessorRegisterType.merge(mergedRegisterType);
+                }
+            }
+        }
+        if (mergedRegisterType == null) {
+            // This is a start-of-method or unreachable instruction.
+            throw new IllegalStateException();
+        }
+        return mergedRegisterType;
+    }
+    /**
+     * Sets the "post-instruction" register type as indicated.
+     * @param registerNumber Which register to set
+     * @param registerType The "post-instruction" register type
+     * @return true if the given register type is different than the existing post-instruction register type
+     */
+    protected boolean setPostRegisterType(int registerNumber, RegisterType registerType) {
+        assert registerNumber >= 0 && registerNumber < postRegisterMap.length;
+        assert registerType != null;
+
+        RegisterType oldRegisterType = postRegisterMap[registerNumber];
+        if (oldRegisterType.equals(registerType)) {
+            return false;
+        }
+
+        postRegisterMap[registerNumber] = registerType;
+        return true;
+    }
+
+    /**
+     * Adds an override for a register type from a predecessor.
+     *
+     * This is used to set the register type for only one branch from a conditional jump.
+     *
+     * @param predecessor Which predecessor is being overridden
+     * @param registerNumber The register number of the register being overridden
+     * @param registerType The overridden register type
+     * @param verifiedInstructions A bit vector of instructions that have been verified
+     *
+     * @return true if the post-instruction register type for this instruction changed as a result of this override
+     */
+    protected boolean overridePredecessorRegisterType(@Nonnull AnalyzedInstruction predecessor, int registerNumber,
+                                                      @Nonnull RegisterType registerType, BitSet verifiedInstructions) {
+        if (predecessorRegisterOverrides == null) {
+            predecessorRegisterOverrides = Maps.newHashMap();
+        }
+        predecessorRegisterOverrides.put(new PredecessorOverrideKey(predecessor, registerNumber), registerType);
+
+        RegisterType mergedType = getMergedPreRegisterTypeFromPredecessors(registerNumber);
+
+        if (preRegisterMap[registerNumber].equals(mergedType)) {
+            return false;
+        }
+
+        preRegisterMap[registerNumber] = mergedType;
+        verifiedInstructions.clear(instructionIndex);
+
+        if (!setsRegister(registerNumber)) {
+            if (!postRegisterMap[registerNumber].equals(mergedType)) {
+                postRegisterMap[registerNumber] = mergedType;
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public boolean isInvokeInit() {
+        if (!instruction.getOpcode().canInitializeReference()) {
+            return false;
+        }
+
+        ReferenceInstruction instruction = (ReferenceInstruction)this.instruction;
+
+        Reference reference = instruction.getReference();
+        if (reference instanceof MethodReference) {
+            return ((MethodReference)reference).getName().equals("<init>");
+        }
+
+        return false;
+    }
+
+    /**
+     * Determines if this instruction sets the given register, or alters its type
+     *
+     * @param registerNumber The register to check
+     * @return true if this instruction sets the given register or alters its type
+     */
+    public boolean setsRegister(int registerNumber) {
+        // This method could be implemented by calling getSetRegisters and checking if registerNumber is in the result
+        // However, this is a frequently called method, and this is a more efficient implementation, because it doesn't
+        // allocate a new list, and it can potentially exit earlier
+
+        if (isInvokeInit()) {
+            // When constructing a new object, the register type will be an uninitialized reference after the
+            // new-instance instruction, but becomes an initialized reference once the <init> method is called. So even
+            // though invoke instructions don't normally change any registers, calling an <init> method will change the
+            // type of its object register. If the uninitialized reference has been copied to other registers, they will
+            // be initialized as well,