Beginning of annotated dump implementation
diff --git a/baksmali/src/main/java/org/jf/baksmali/dump.java b/baksmali/src/main/java/org/jf/baksmali/dump.java
index f428642..afe4824 100644
--- a/baksmali/src/main/java/org/jf/baksmali/dump.java
+++ b/baksmali/src/main/java/org/jf/baksmali/dump.java
@@ -28,79 +28,39 @@
 
 package org.jf.baksmali;
 
-import org.jf.dexlib.DexFile;
-import org.jf.dexlib.Util.ByteArrayAnnotatedOutput;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.util.ConsoleUtil;
 
-import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
 
 public class dump {
-    public static void dump(DexFile dexFile, String dumpFileName, String outputDexFileName, boolean sort)
+    public static void dump(DexBackedDexFile dexFile, String dumpFileName)
             throws IOException {
 
-        if (sort) {
-            //sort all items, to guarantee a unique ordering
-            dexFile.setSortAllItems(true);
-        } else {
-            //don't change the order
-            dexFile.setInplace(true);
-        }
-
-        ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
-
         if (dumpFileName != null) {
-            out.enableAnnotations(120, true);
-        }
-
-        dexFile.place();
-        dexFile.writeTo(out);
-
-        //write the dump
-        if (dumpFileName != null) {
-            out.finishAnnotating();
             FileWriter writer = null;
 
 
             try {
                 writer = new FileWriter(dumpFileName);
-                out.writeAnnotationsTo(writer);
+
+                int consoleWidth = ConsoleUtil.getConsoleWidth();
+                if (consoleWidth <= 0) {
+                    consoleWidth = 120;
+                }
+
+                dexFile.dumpTo(writer, consoleWidth);
             } catch (IOException ex) {
-                System.err.println("\n\nThere was an error while dumping the dex file to " + dumpFileName);
-                ex.printStackTrace();
+                System.err.println("There was an error while dumping the dex file to " + dumpFileName);
+                ex.printStackTrace(System.err);
             } finally {
                 if (writer != null) {
                     try {
                         writer.close();
                     } catch (IOException ex) {
-                        System.err.println("\n\nThere was an error while closing the dump file " + dumpFileName);
-                        ex.printStackTrace();
-                    }
-                }
-            }
-        }
-
-        //rewrite the dex file
-        if (outputDexFileName != null) {
-            byte[] bytes = out.toByteArray();
-
-            DexFile.calcSignature(bytes);
-            DexFile.calcChecksum(bytes);
-
-            FileOutputStream fileOutputStream = null;
-            try {
-                fileOutputStream = new FileOutputStream(outputDexFileName);
-                fileOutputStream.write(bytes);
-            } catch (IOException ex) {
-                System.err.println("\n\nThere was an error while writing the dex file " + outputDexFileName);
-                ex.printStackTrace();
-            } finally {
-                if (fileOutputStream != null) {
-                    try {
-                        fileOutputStream.close();
-                    } catch (IOException ex) {
-                        System.err.println("\n\nThere was an error while closing the dex file " + outputDexFileName);
-                        ex.printStackTrace();
+                        System.err.println("There was an error while closing the dump file " + dumpFileName);
+                        ex.printStackTrace(System.err);
                     }
                 }
             }
diff --git a/baksmali/src/main/java/org/jf/baksmali/main.java b/baksmali/src/main/java/org/jf/baksmali/main.java
index f5b9f87..11fb69a 100644
--- a/baksmali/src/main/java/org/jf/baksmali/main.java
+++ b/baksmali/src/main/java/org/jf/baksmali/main.java
@@ -307,16 +307,18 @@
                         noAccessorComments, registerInfo, verify, ignoreErrors, inlineTable, checkPackagePrivateAccess);
             }
 
-            //TODO: uncomment
-            /*if ((doDump || write) && !dexFile.isOdex()) {
+            // TODO: implement rewrite + optional sort functionality
+
+            // TODO: need to check if odex file
+            if (doDump) {
                 try
                 {
-                    dump.dump(dexFile, dumpFileName, outputDexFileName, sort);
+                    dump.dump(dexFile, dumpFileName);
                 }catch (IOException ex) {
                     System.err.println("Error occured while writing dump file");
                     ex.printStackTrace();
                 }
-            }*/
+            }
         } catch (RuntimeException ex) {
             System.err.println("\n\nUNEXPECTED TOP-LEVEL EXCEPTION:");
             ex.printStackTrace();
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java
index 6faf0b0..707c444 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java
@@ -31,13 +31,19 @@
 
 package org.jf.dexlib2.dexbacked;
 
+import org.jf.dexlib2.dexbacked.raw.HeaderItem;
+import org.jf.dexlib2.dexbacked.raw.StringIdItem;
+import org.jf.dexlib2.dexbacked.raw.TypeIdItem;
 import org.jf.dexlib2.dexbacked.util.FixedSizeSet;
 import org.jf.dexlib2.iface.DexFile;
+import org.jf.dexlib2.util.AnnotatedBytes;
 import org.jf.util.ExceptionWithContext;
 import org.jf.util.Utf8Utils;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import java.io.IOException;
+import java.io.Writer;
 import java.util.Set;
 
 public abstract class DexBackedDexFile extends BaseDexBuffer implements DexFile {
@@ -57,6 +63,8 @@
 
     @Override @Nonnull public abstract DexReader readerAt(int offset);
 
+    public abstract void dumpTo(Writer out, int width) throws IOException;
+
     public static class Impl extends DexBackedDexFile {
         private final int stringCount;
         private final int stringStartOffset;
@@ -71,33 +79,6 @@
         private final int classCount;
         private final int classStartOffset;
 
-        private static final byte[][] MAGIC_VALUES= new byte[][] {
-                new byte[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x35, 0x00},
-                new byte[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x36, 0x00}};
-
-        private static final int LITTLE_ENDIAN_TAG = 0x12345678;
-        private static final int BIG_ENDIAN_TAG = 0x78563412;
-
-        private static final int CHECKSUM_OFFSET = 8;
-        private static final int SIGNATURE_OFFSET = 12;
-        private static final int ENDIAN_TAG_OFFSET = 40;
-        private static final int MAP_OFFSET = 52;
-        private static final int STRING_COUNT_OFFSET = 56;
-        private static final int STRING_START_OFFSET = 60;
-        private static final int TYPE_COUNT_OFFSET = 64;
-        private static final int TYPE_START_OFFSET = 68;
-        private static final int PROTO_COUNT_OFFSET = 72;
-        private static final int PROTO_START_OFFSET = 76;
-        private static final int FIELD_COUNT_OFFSET = 80;
-        private static final int FIELD_START_OFFSET = 84;
-        private static final int METHOD_COUNT_OFFSET = 88;
-        private static final int METHOD_START_OFFSET = 92;
-        private static final int CLASS_COUNT_OFFSET = 96;
-        private static final int CLASS_START_OFFSET = 100;
-
-        private static final int SIGNATURE_SIZE = 20;
-
-        private static final int STRING_ID_ITEM_SIZE = 4;
         private static final int TYPE_ID_ITEM_SIZE = 4;
         private static final int PROTO_ID_ITEM_SIZE = 12;
         private static final int FIELD_ID_ITEM_SIZE = 8;
@@ -124,18 +105,18 @@
 
             verifyMagic();
             verifyEndian();
-            stringCount = readSmallUint(STRING_COUNT_OFFSET);
-            stringStartOffset = readSmallUint(STRING_START_OFFSET);
-            typeCount = readSmallUint(TYPE_COUNT_OFFSET);
-            typeStartOffset = readSmallUint(TYPE_START_OFFSET);
-            protoCount = readSmallUint(PROTO_COUNT_OFFSET);
-            protoStartOffset = readSmallUint(PROTO_START_OFFSET);
-            fieldCount = readSmallUint(FIELD_COUNT_OFFSET);
-            fieldStartOffset = readSmallUint(FIELD_START_OFFSET);
-            methodCount = readSmallUint(METHOD_COUNT_OFFSET);
-            methodStartOffset = readSmallUint(METHOD_START_OFFSET);
-            classCount = readSmallUint(CLASS_COUNT_OFFSET);
-            classStartOffset = readSmallUint(CLASS_START_OFFSET);
+            stringCount = readSmallUint(HeaderItem.STRING_COUNT_OFFSET);
+            stringStartOffset = readSmallUint(HeaderItem.STRING_START_OFFSET);
+            typeCount = readSmallUint(HeaderItem.TYPE_COUNT_OFFSET);
+            typeStartOffset = readSmallUint(HeaderItem.TYPE_START_OFFSET);
+            protoCount = readSmallUint(HeaderItem.PROTO_COUNT_OFFSET);
+            protoStartOffset = readSmallUint(HeaderItem.PROTO_START_OFFSET);
+            fieldCount = readSmallUint(HeaderItem.FIELD_COUNT_OFFSET);
+            fieldStartOffset = readSmallUint(HeaderItem.FIELD_START_OFFSET);
+            methodCount = readSmallUint(HeaderItem.METHOD_COUNT_OFFSET);
+            methodStartOffset = readSmallUint(HeaderItem.METHOD_START_OFFSET);
+            classCount = readSmallUint(HeaderItem.CLASS_COUNT_OFFSET);
+            classStartOffset = readSmallUint(HeaderItem.CLASS_START_OFFSET);
         }
 
         @Nonnull
@@ -156,7 +137,7 @@
         }
 
         private void verifyMagic() {
-            outer: for (byte[] magic: MAGIC_VALUES) {
+            outer: for (byte[] magic: HeaderItem.MAGIC_VALUES) {
                 for (int i=0; i<magic.length; i++) {
                     if (buf[i] != magic[i]) {
                         continue outer;
@@ -172,13 +153,13 @@
         }
 
         private void verifyEndian() {
-            int endian = readInt(ENDIAN_TAG_OFFSET);
-            if (endian == BIG_ENDIAN_TAG) {
+            int endian = readInt(HeaderItem.ENDIAN_TAG_OFFSET);
+            if (endian == HeaderItem.BIG_ENDIAN_TAG) {
                 throw new ExceptionWithContext("dexlib does not currently support big endian dex files.");
-            } else if (endian != LITTLE_ENDIAN_TAG) {
+            } else if (endian != HeaderItem.LITTLE_ENDIAN_TAG) {
                 StringBuilder sb = new StringBuilder("Invalid endian tag:");
                 for (int i=0; i<4; i++) {
-                    sb.append(String.format(" %02x", buf[ENDIAN_TAG_OFFSET+i]));
+                    sb.append(String.format(" %02x", buf[HeaderItem.ENDIAN_TAG_OFFSET+i]));
                 }
                 throw new ExceptionWithContext(sb.toString());
             }
@@ -188,7 +169,7 @@
             if (stringIndex < 0 || stringIndex >= stringCount) {
                 throw new ExceptionWithContext("String index out of bounds: %d", stringIndex);
             }
-            return stringStartOffset + stringIndex*STRING_ID_ITEM_SIZE;
+            return stringStartOffset + stringIndex*StringIdItem.ITEM_SIZE;
         }
 
         public int getTypeIdItemOffset(int typeIndex) {
@@ -274,5 +255,13 @@
         public DexReader readerAt(int offset) {
             return new DexReader(this, offset);
         }
+
+        public void dumpTo(Writer out, int width) throws IOException {
+            AnnotatedBytes annotatedBytes = new AnnotatedBytes(width);
+            HeaderItem.getSection().annotateSection(annotatedBytes, this, 1);
+            annotatedBytes.annotate(0, " ");
+            StringIdItem.getSection().annotateSection(annotatedBytes, this, stringCount);
+            annotatedBytes.writeAnnotations(out, buf);
+        }
     }
 }
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java
new file mode 100644
index 0000000..a176fb3
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java
@@ -0,0 +1,147 @@
+/*
+ * 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.dexbacked.raw;
+
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.util.AnnotatedBytes;
+import org.jf.util.StringUtils;
+
+import javax.annotation.Nonnull;
+
+public class HeaderItem {
+    public static final int ITEM_SIZE = 0x70;
+
+    public static final byte[][] MAGIC_VALUES= new byte[][] {
+            new byte[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x35, 0x00},
+            new byte[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x36, 0x00}};
+
+    public static final int LITTLE_ENDIAN_TAG = 0x12345678;
+    public static final int BIG_ENDIAN_TAG = 0x78563412;
+
+    public static final int CHECKSUM_OFFSET = 8;
+
+    public static final int SIGNATURE_OFFSET = 12;
+    public static final int ENDIAN_TAG_OFFSET = 40;
+
+    public static final int MAP_OFFSET = 52;
+
+    public static final int STRING_COUNT_OFFSET = 56;
+    public static final int STRING_START_OFFSET = 60;
+
+    public static final int TYPE_COUNT_OFFSET = 64;
+    public static final int TYPE_START_OFFSET = 68;
+
+    public static final int PROTO_COUNT_OFFSET = 72;
+    public static final int PROTO_START_OFFSET = 76;
+
+    public static final int FIELD_COUNT_OFFSET = 80;
+    public static final int FIELD_START_OFFSET = 84;
+
+    public static final int METHOD_COUNT_OFFSET = 88;
+    public static final int METHOD_START_OFFSET = 92;
+
+    public static final int CLASS_COUNT_OFFSET = 96;
+    public static final int CLASS_START_OFFSET = 100;
+
+    public static final int SIGNATURE_SIZE = 20;
+
+    public static Section getSection() {
+        return new Section() {
+            @Override
+            public void annotateSection(@Nonnull AnnotatedBytes out, @Nonnull DexBackedDexFile dexFile, int length) {
+                int startOffset = out.getCursor();
+                int headerSize;
+
+                out.annotate(0, "-----------------------------");
+                out.annotate(0, "header item");
+                out.annotate(0, "-----------------------------");
+                out.annotate(0, "");
+
+                StringBuilder magicBuilder = new StringBuilder();
+                for (int i=0; i<8; i++) {
+                    magicBuilder.append((char)dexFile.readUbyte(startOffset + i));
+                }
+
+                out.annotate(8, "magic: %s", StringUtils.escapeString(magicBuilder.toString()));
+                out.annotate(4, "checksum");
+                out.annotate(20, "signature");
+                out.annotate(4, "file_size: %d", dexFile.readInt(out.getCursor()));
+
+                headerSize = dexFile.readInt(out.getCursor());
+                out.annotate(4, "header_size: %d", headerSize);
+
+                int endianTag = dexFile.readInt(out.getCursor());
+                out.annotate(4, "endian_tag: 0x%x (%s)", endianTag, getEndianText(endianTag));
+
+                out.annotate(4, "link_size: %d", dexFile.readInt(out.getCursor()));
+                out.annotate(4, "link_offset: 0x%x", dexFile.readInt(out.getCursor()));
+
+                out.annotate(4, "map_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+                out.annotate(4, "string_ids_size: %d", dexFile.readInt(out.getCursor()));
+                out.annotate(4, "string_ids_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+                out.annotate(4, "type_ids_size: %d", dexFile.readInt(out.getCursor()));
+                out.annotate(4, "type_ids_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+                out.annotate(4, "proto_ids_size: %d", dexFile.readInt(out.getCursor()));
+                out.annotate(4, "proto_ids_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+                out.annotate(4, "field_ids_size: %d", dexFile.readInt(out.getCursor()));
+                out.annotate(4, "field_ids_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+                out.annotate(4, "method_ids_size: %d", dexFile.readInt(out.getCursor()));
+                out.annotate(4, "method_ids_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+                out.annotate(4, "class_defs_size: %d", dexFile.readInt(out.getCursor()));
+                out.annotate(4, "class_defs_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+                out.annotate(4, "data_size: %d", dexFile.readInt(out.getCursor()));
+                out.annotate(4, "data_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+                if (headerSize > ITEM_SIZE) {
+                    out.annotate(headerSize - ITEM_SIZE, "header padding");
+                }
+            }
+        };
+    }
+
+    private static String getEndianText(int endianTag) {
+        if (endianTag == LITTLE_ENDIAN_TAG) {
+            return "Little Endian";
+        }
+        if (endianTag == BIG_ENDIAN_TAG) {
+            return "Big Endian";
+        }
+        return "Invalid";
+    }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/Section.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/Section.java
new file mode 100644
index 0000000..0904738
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/Section.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dexbacked.raw;
+
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.util.AnnotatedBytes;
+
+import javax.annotation.Nonnull;
+
+public interface Section {
+
+    /**
+     * Write out annotations for this section
+     *
+     * @param out The AnnotatedBytes object to annotate to
+     * @param dexFile The DexBackedDexFile representing the dex file being annotated
+     * @param length The number of items in the section (from the header/map)
+     */
+    public void annotateSection(@Nonnull AnnotatedBytes out, @Nonnull DexBackedDexFile dexFile, int length);
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/StringIdItem.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/StringIdItem.java
new file mode 100644
index 0000000..90eaf24
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/StringIdItem.java
@@ -0,0 +1,80 @@
+/*
+ * 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.dexbacked.raw;
+
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.util.AnnotatedBytes;
+import org.jf.util.StringUtils;
+
+import javax.annotation.Nonnull;
+
+public class StringIdItem {
+    public static final int ITEM_SIZE = 4;
+
+    public static Section getSection() {
+        return new Section() {
+            @Override
+            public void annotateSection(@Nonnull AnnotatedBytes out, @Nonnull DexBackedDexFile dexFile, int length) {
+                int startOffset = out.getCursor();
+                if (length > 0) {
+                    out.annotate(0, "-----------------------------");
+                    out.annotate(0, "string_id_item section");
+                    out.annotate(0, "-----------------------------");
+                    out.annotate(0, "");
+
+                    for (int i=0; i<length; i++) {
+                        out.annotate(0, "[%d] string_id_item", i);
+                        out.indent();
+                        annotateString(out, dexFile, i, startOffset + i * ITEM_SIZE);
+                        out.deindent();
+                    }
+                }
+            }
+        };
+    }
+
+    private static void annotateString(@Nonnull AnnotatedBytes out, @Nonnull DexBackedDexFile dexFile, int index,
+                                       int offset) {
+        int stringDataOffset = dexFile.readSmallUint(offset);
+        try {
+            String stringValue = dexFile.getString(index);
+            out.annotate(4, "string_data_item[0x%x]: \"%s\"", stringDataOffset, StringUtils.escapeString(stringValue));
+            return;
+        } catch (Exception ex) {
+            System.err.print("Error while resolving string value at index: ");
+            System.err.print(index);
+            ex.printStackTrace(System.err);
+        }
+
+        out.annotate(4, "string_id_item[0x%x]", stringDataOffset);
+    }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/util/AnnotatedBytes.java b/dexlib2/src/main/java/org/jf/dexlib2/util/AnnotatedBytes.java
new file mode 100644
index 0000000..e942c3f
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/util/AnnotatedBytes.java
@@ -0,0 +1,170 @@
+/*
+ * 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.util;
+
+import com.google.common.collect.Lists;
+import org.jf.util.Hex;
+import org.jf.util.TwoColumnOutput;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+
+/**
+ * Collects/presents a set of textual annotations, each associated with a range of bytes
+ */
+public class AnnotatedBytes {
+    @Nonnull private List<AnnotationItem> annotations = Lists.newArrayList();
+    private int cursor;
+    private int indentLevel;
+
+    /** &gt;= 40 (if used); the desired maximum output width */
+    private int outputWidth;
+
+    /**
+     * &gt;= 8 (if used); the number of bytes of hex output to use
+     * in annotations
+     */
+    private int hexCols = 8;
+
+    public AnnotatedBytes(int width) {
+        this.outputWidth = width;
+    }
+
+    /**
+     * Add an annotation of the given length at the current location.
+     *
+     * @param length the length of data being annotated
+     * @param msg the annotation message
+     * @param formatArgs format arguments to pass to String.format
+     */
+    public void annotate(int length, @Nonnull String msg, Object... formatArgs) {
+        annotations.add(new AnnotationItem(cursor, indentLevel, String.format(msg, formatArgs)));
+        cursor += length;
+    }
+
+    public void indent() {
+        indentLevel++;
+    }
+
+    public void deindent() {
+        indentLevel--;
+        if (indentLevel < 0) {
+            indentLevel = 0;
+        }
+    }
+
+    public int getCursor() {
+        return cursor;
+    }
+
+    private static class AnnotationItem {
+        public final int offset;
+        public final int indentLevel;
+        public final String annotation;
+
+        public AnnotationItem(int offset, int  indentLevel, String annotation) {
+            this.offset = offset;
+            this.indentLevel = indentLevel;
+            this.annotation = annotation;
+        }
+    }
+
+    /**
+     * Gets the width of the right side containing the annotations
+     * @return
+     */
+    public int getAnnotationWidth() {
+        int leftWidth = 8 + (hexCols * 2) + (hexCols / 2);
+
+        return outputWidth - leftWidth;
+    }
+
+    /**
+     * Writes the annotated content of this instance to the given writer.
+     *
+     * @param out non-null; where to write to
+     */
+    public void writeAnnotations(Writer out, byte[] data) throws IOException {
+        int rightWidth = getAnnotationWidth();
+        int leftWidth = outputWidth - rightWidth - 1;
+
+        StringBuilder padding = new StringBuilder();
+        for (int i=0; i<1000; i++) {
+            padding.append(' ');
+        }
+
+        TwoColumnOutput twoc = new TwoColumnOutput(out, leftWidth, rightWidth, "|");
+        Writer left = twoc.getLeft();
+        Writer right = twoc.getRight();
+        int leftAt = 0; // left-hand byte output cursor
+        int rightAt = 0; // right-hand annotation index
+        int rightSz = annotations.size();
+
+        while ((leftAt < cursor) && (rightAt < rightSz)) {
+            AnnotationItem a = annotations.get(rightAt);
+            int start = a.offset;
+            int end;
+
+            if (rightAt + 1 < annotations.size()) {
+                end = annotations.get(rightAt+1).offset;
+            } else {
+                end = cursor;
+            }
+            String text;
+
+             // This is an area with an annotation.
+            text = padding.substring(0, a.indentLevel * 2) + a.annotation;
+            rightAt++;
+
+            left.write(Hex.dump(data, start, end - start, start, hexCols, 6));
+            right.write(text);
+            twoc.flush();
+            leftAt = end;
+        }
+
+        if (leftAt < cursor) {
+            // There is unannotated output at the end.
+            left.write(Hex.dump(data, leftAt, cursor - leftAt, leftAt,
+                    hexCols, 6));
+        }
+
+        while (rightAt < rightSz) {
+            // There are zero-byte annotations at the end.
+            right.write(annotations.get(rightAt).annotation);
+            rightAt++;
+        }
+
+        twoc.flush();
+    }
+}
\ No newline at end of file
diff --git a/util/src/main/java/org/jf/util/TwoColumnOutput.java b/util/src/main/java/org/jf/util/TwoColumnOutput.java
new file mode 100644
index 0000000..6f923d4
--- /dev/null
+++ b/util/src/main/java/org/jf/util/TwoColumnOutput.java
@@ -0,0 +1,265 @@
+/*
+ * 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.util;
+
+import java.io.*;
+
+/**
+ * Class that takes a combined output destination and provides two
+ * output writers, one of which ends up writing to the left column and
+ * one which goes on the right.
+ */
+public final class TwoColumnOutput {
+    /** non-null; underlying writer for final output */
+    private final Writer out;
+
+    /** &gt; 0; the left column width */
+    private final int leftWidth;
+
+    /** non-null; pending left column output */
+    private final StringBuffer leftBuf;
+
+    /** non-null; pending right column output */
+    private final StringBuffer rightBuf;
+
+    /** non-null; left column writer */
+    private final WrappedIndentingWriter leftColumn;
+
+    /** non-null; right column writer */
+    private final WrappedIndentingWriter rightColumn;
+
+    /**
+     * Turns the given two strings (with widths) and spacer into a formatted
+     * two-column string.
+     *
+     * @param s1 non-null; first string
+     * @param width1 &gt; 0; width of the first column
+     * @param spacer non-null; spacer string
+     * @param s2 non-null; second string
+     * @param width2 &gt; 0; width of the second column
+     * @return non-null; an appropriately-formatted string
+     */
+    public static String toString(String s1, int width1, String spacer,
+                                  String s2, int width2) {
+        int len1 = s1.length();
+        int len2 = s2.length();
+
+        StringWriter sw = new StringWriter((len1 + len2) * 3);
+        TwoColumnOutput twoOut =
+            new TwoColumnOutput(sw, width1, width2, spacer);
+
+        try {
+            twoOut.getLeft().write(s1);
+            twoOut.getRight().write(s2);
+        } catch (IOException ex) {
+            throw new RuntimeException("shouldn't happen", ex);
+        }
+
+        twoOut.flush();
+        return sw.toString();
+    }
+
+    /**
+     * Constructs an instance.
+     *
+     * @param out non-null; writer to send final output to
+     * @param leftWidth &gt; 0; width of the left column, in characters
+     * @param rightWidth &gt; 0; width of the right column, in characters
+     * @param spacer non-null; spacer string to sit between the two columns
+     */
+    public TwoColumnOutput(Writer out, int leftWidth, int rightWidth,
+                           String spacer) {
+        if (out == null) {
+            throw new NullPointerException("out == null");
+        }
+
+        if (leftWidth < 1) {
+            throw new IllegalArgumentException("leftWidth < 1");
+        }
+
+        if (rightWidth < 1) {
+            throw new IllegalArgumentException("rightWidth < 1");
+        }
+
+        if (spacer == null) {
+            throw new NullPointerException("spacer == null");
+        }
+
+        StringWriter leftWriter = new StringWriter(1000);
+        StringWriter rightWriter = new StringWriter(1000);
+
+        this.out = out;
+        this.leftWidth = leftWidth;
+        this.leftBuf = leftWriter.getBuffer();
+        this.rightBuf = rightWriter.getBuffer();
+        this.leftColumn = new WrappedIndentingWriter(leftWriter, leftWidth);
+        this.rightColumn =
+            new WrappedIndentingWriter(rightWriter, rightWidth, spacer);
+    }
+
+    /**
+     * Constructs an instance.
+     *
+     * @param out non-null; stream to send final output to
+     * @param leftWidth &gt;= 1; width of the left column, in characters
+     * @param rightWidth &gt;= 1; width of the right column, in characters
+     * @param spacer non-null; spacer string to sit between the two columns
+     */
+    public TwoColumnOutput(OutputStream out, int leftWidth, int rightWidth,
+                           String spacer) {
+        this(new OutputStreamWriter(out), leftWidth, rightWidth, spacer);
+    }
+
+    /**
+     * Gets the writer to use to write to the left column.
+     *
+     * @return non-null; the left column writer
+     */
+    public Writer getLeft() {
+        return leftColumn;
+    }
+
+    /**
+     * Gets the writer to use to write to the right column.
+     *
+     * @return non-null; the right column writer
+     */
+    public Writer getRight() {
+        return rightColumn;
+    }
+
+    /**
+     * Flushes the output. If there are more lines of pending output in one
+     * column, then the other column will get filled with blank lines.
+     */
+    public void flush() {
+        try {
+            appendNewlineIfNecessary(leftBuf, leftColumn);
+            appendNewlineIfNecessary(rightBuf, rightColumn);
+            outputFullLines();
+            flushLeft();
+            flushRight();
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    /**
+     * Outputs to the final destination as many full line pairs as
+     * there are in the pending output, removing those lines from
+     * their respective buffers. This method terminates when at
+     * least one of the two column buffers is empty.
+     */
+    private void outputFullLines() throws IOException {
+        for (;;) {
+            int leftLen = leftBuf.indexOf("\n");
+            if (leftLen < 0) {
+                return;
+            }
+
+            int rightLen = rightBuf.indexOf("\n");
+            if (rightLen < 0) {
+                return;
+            }
+
+            if (leftLen != 0) {
+                out.write(leftBuf.substring(0, leftLen));
+            }
+
+            if (rightLen != 0) {
+                writeSpaces(out, leftWidth - leftLen);
+                out.write(rightBuf.substring(0, rightLen));
+            }
+
+            out.write('\n');
+
+            leftBuf.delete(0, leftLen + 1);
+            rightBuf.delete(0, rightLen + 1);
+        }
+    }
+
+    /**
+     * Flushes the left column buffer, printing it and clearing the buffer.
+     * If the buffer is already empty, this does nothing.
+     */
+    private void flushLeft() throws IOException {
+        appendNewlineIfNecessary(leftBuf, leftColumn);
+
+        while (leftBuf.length() != 0) {
+            rightColumn.write('\n');
+            outputFullLines();
+        }
+    }
+
+    /**
+     * Flushes the right column buffer, printing it and clearing the buffer.
+     * If the buffer is already empty, this does nothing.
+     */
+    private void flushRight() throws IOException {
+        appendNewlineIfNecessary(rightBuf, rightColumn);
+
+        while (rightBuf.length() != 0) {
+            leftColumn.write('\n');
+            outputFullLines();
+        }
+    }
+
+    /**
+     * Appends a newline to the given buffer via the given writer, but
+     * only if it isn't empty and doesn't already end with one.
+     *
+     * @param buf non-null; the buffer in question
+     * @param out non-null; the writer to use
+     */
+    private static void appendNewlineIfNecessary(StringBuffer buf,
+                                                 Writer out)
+            throws IOException {
+        int len = buf.length();
+
+        if ((len != 0) && (buf.charAt(len - 1) != '\n')) {
+            out.write('\n');
+        }
+    }
+
+    /**
+     * Writes the given number of spaces to the given writer.
+     *
+     * @param out non-null; where to write
+     * @param amt &gt;= 0; the number of spaces to write
+     */
+    private static void writeSpaces(Writer out, int amt) throws IOException {
+        while (amt > 0) {
+            out.write(' ');
+            amt--;
+        }
+    }
+}
diff --git a/util/src/main/java/org/jf/util/WrappedIndentingWriter.java b/util/src/main/java/org/jf/util/WrappedIndentingWriter.java
new file mode 100644
index 0000000..eb1acda
--- /dev/null
+++ b/util/src/main/java/org/jf/util/WrappedIndentingWriter.java
@@ -0,0 +1,184 @@
+/*
+ * 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.util;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Writer that wraps another writer and passes width-limited and
+ * optionally-prefixed output to its subordinate. When lines are
+ * wrapped they are automatically indented based on the start of the
+ * line.
+ */
+public final class WrappedIndentingWriter extends FilterWriter {
+    /** null-ok; optional prefix for every line */
+    private final String prefix;
+
+    /** &gt; 0; the maximum output width */
+    private final int width;
+
+    /** &gt; 0; the maximum indent */
+    private final int maxIndent;
+
+    /** &gt;= 0; current output column (zero-based) */
+    private int column;
+
+    /** whether indent spaces are currently being collected */
+    private boolean collectingIndent;
+
+    /** &gt;= 0; current indent amount */
+    private int indent;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param out non-null; writer to send final output to
+     * @param width &gt;= 0; the maximum output width (not including
+     * <code>prefix</code>), or <code>0</code> for no maximum
+     * @param prefix non-null; the prefix for each line
+     */
+    public WrappedIndentingWriter(Writer out, int width, String prefix) {
+        super(out);
+
+        if (out == null) {
+            throw new NullPointerException("out == null");
+        }
+
+        if (width < 0) {
+            throw new IllegalArgumentException("width < 0");
+        }
+
+        if (prefix == null) {
+            throw new NullPointerException("prefix == null");
+        }
+
+        this.width = (width != 0) ? width : Integer.MAX_VALUE;
+        this.maxIndent = width >> 1;
+        this.prefix = (prefix.length() == 0) ? null : prefix;
+
+        bol();
+    }
+
+    /**
+     * Constructs a no-prefix instance.
+     *
+     * @param out non-null; writer to send final output to
+     * @param width &gt;= 0; the maximum output width (not including
+     * <code>prefix</code>), or <code>0</code> for no maximum
+     */
+    public WrappedIndentingWriter(Writer out, int width) {
+        this(out, width, "");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void write(int c) throws IOException {
+        synchronized (lock) {
+            if (collectingIndent) {
+                if (c == ' ') {
+                    indent++;
+                    if (indent >= maxIndent) {
+                        indent = maxIndent;
+                        collectingIndent = false;
+                    }
+                } else {
+                    collectingIndent = false;
+                }
+            }
+
+            if ((column == width) && (c != '\n')) {
+                out.write('\n');
+                column = 0;
+                /*
+                 * Note: No else, so this should fall through to the next
+                 * if statement.
+                 */
+            }
+
+            if (column == 0) {
+                if (prefix != null) {
+                    out.write(prefix);
+                }
+
+                if (!collectingIndent) {
+                    for (int i = 0; i < indent; i++) {
+                        out.write(' ');
+                    }
+                    column = indent;
+                }
+            }
+
+            out.write(c);
+
+            if (c == '\n') {
+                bol();
+            } else {
+                column++;
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void write(char[] cbuf, int off, int len) throws IOException {
+        synchronized (lock) {
+            while (len > 0) {
+                write(cbuf[off]);
+                off++;
+                len--;
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void write(String str, int off, int len) throws IOException {
+        synchronized (lock) {
+            while (len > 0) {
+                write(str.charAt(off));
+                off++;
+                len--;
+            }
+        }
+    }
+
+    /**
+     * Indicates that output is at the beginning of a line.
+     */
+    private void bol() {
+        column = 0;
+        collectingIndent = (maxIndent != 0);
+        indent = 0;
+    }
+}