/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * The "dexdump" tool is intended to mimic "objdump".  When possible, use
 * similar command-line arguments.
 *
 * TODO: rework the "plain" output format to be more regexp-friendly
 *
 * Differences between XML output and the "current.xml" file:
 * - classes in same package are not all grouped together; generally speaking
 *   nothing is sorted
 * - no "deprecated" on fields and methods
 * - no "value" on fields
 * - no parameter names
 * - no generic signatures on parameters, e.g. type="java.lang.Class&lt;?&gt;"
 * - class shows declared fields and methods; does not show inherited fields
 */

#include "libdex/DexFile.h"

#include "libdex/CmdUtils.h"
#include "libdex/DexCatch.h"
#include "libdex/DexClass.h"
#include "libdex/DexDebugInfo.h"
#include "libdex/DexOpcodes.h"
#include "libdex/DexProto.h"
#include "libdex/InstrUtils.h"
#include "libdex/SysUtil.h"

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <assert.h>
#include <inttypes.h>

static const char* gProgName = "dexdump";

enum OutputFormat {
    OUTPUT_PLAIN = 0,               /* default */
    OUTPUT_XML,                     /* fancy */
};

/* command-line options */
struct Options {
    bool checksumOnly;
    bool disassemble;
    bool showFileHeaders;
    bool showSectionHeaders;
    bool ignoreBadChecksum;
    bool dumpRegisterMaps;
    OutputFormat outputFormat;
    const char* tempFileName;
    bool exportsOnly;
    bool verbose;
};

struct Options gOptions;

/* basic info about a field or method */
struct FieldMethodInfo {
    const char* classDescriptor;
    const char* name;
    const char* signature;
};


/* basic info about a prototype */
struct ProtoInfo {
    char* parameterTypes;  // dynamically allocated with malloc
    const char* returnType;
};

/*
 * Get 2 little-endian bytes.
 */
static inline u2 get2LE(unsigned char const* pSrc)
{
    return pSrc[0] | (pSrc[1] << 8);
}

/*
 * Get 4 little-endian bytes.
 */
static inline u4 get4LE(unsigned char const* pSrc)
{
    return pSrc[0] | (pSrc[1] << 8) | (pSrc[2] << 16) | (pSrc[3] << 24);
}

/*
 * Converts a single-character primitive type into its human-readable
 * equivalent.
 */
static const char* primitiveTypeLabel(char typeChar)
{
    switch (typeChar) {
    case 'B':   return "byte";
    case 'C':   return "char";
    case 'D':   return "double";
    case 'F':   return "float";
    case 'I':   return "int";
    case 'J':   return "long";
    case 'S':   return "short";
    case 'V':   return "void";
    case 'Z':   return "boolean";
    default:
                return "UNKNOWN";
    }
}

/*
 * Converts a type descriptor to human-readable "dotted" form.  For
 * example, "Ljava/lang/String;" becomes "java.lang.String", and
 * "[I" becomes "int[]".
 */
static char* descriptorToDot(const char* str)
{
    int targetLen = strlen(str);
    int offset = 0;
    int arrayDepth = 0;
    char* newStr;

    /* strip leading [s; will be added to end */
    while (targetLen > 1 && str[offset] == '[') {
        offset++;
        targetLen--;
    }
    arrayDepth = offset;

    if (targetLen == 1) {
        /* primitive type */
        str = primitiveTypeLabel(str[offset]);
        offset = 0;
        targetLen = strlen(str);
    } else {
        /* account for leading 'L' and trailing ';' */
        if (targetLen >= 2 && str[offset] == 'L' &&
            str[offset+targetLen-1] == ';')
        {
            targetLen -= 2;
            offset++;
        }
    }

    newStr = (char*)malloc(targetLen + arrayDepth * 2 +1);

    /* copy class name over */
    int i;
    for (i = 0; i < targetLen; i++) {
        char ch = str[offset + i];
        newStr[i] = (ch == '/') ? '.' : ch;
    }

    /* add the appropriate number of brackets for arrays */
    while (arrayDepth-- > 0) {
        newStr[i++] = '[';
        newStr[i++] = ']';
    }
    newStr[i] = '\0';
    assert(i == targetLen + arrayDepth * 2);

    return newStr;
}

/*
 * Retrieves the class name portion of a type descriptor.
 *
 * Returns a newly-allocated string.
 */
static char* descriptorClassToName(const char* str)
{
    const char* lastSlash;
    char* newStr;

    /* reduce to just the class name, trimming trailing ';' */
    lastSlash = strrchr(str, '/');
    if (lastSlash == NULL)
        lastSlash = str + 1;        /* start past 'L' */
    else
        lastSlash++;                /* start past '/' */

    newStr = strdup(lastSlash);
    newStr[strlen(lastSlash)-1] = '\0';

    return newStr;
}

/*
 * Returns a quoted string representing the boolean value.
 */
static const char* quotedBool(bool val)
{
    if (val)
        return "\"true\"";
    else
        return "\"false\"";
}

static const char* quotedVisibility(u4 accessFlags)
{
    if ((accessFlags & ACC_PUBLIC) != 0)
        return "\"public\"";
    else if ((accessFlags & ACC_PROTECTED) != 0)
        return "\"protected\"";
    else if ((accessFlags & ACC_PRIVATE) != 0)
        return "\"private\"";
    else
        return "\"package\"";
}

/*
 * Count the number of '1' bits in a word.
 */
static int countOnes(u4 val)
{
    int count = 0;

    val = val - ((val >> 1) & 0x55555555);
    val = (val & 0x33333333) + ((val >> 2) & 0x33333333);
    count = (((val + (val >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;

    return count;
}

/*
 * Flag for use with createAccessFlagStr().
 */
enum AccessFor {
    kAccessForClass = 0, kAccessForMethod = 1, kAccessForField = 2,
    kAccessForMAX
};

/*
 * Create a new string with human-readable access flags.
 *
 * In the base language the access_flags fields are type u2; in Dalvik
 * they're u4.
 */
static char* createAccessFlagStr(u4 flags, AccessFor forWhat)
{
#define NUM_FLAGS   18
    static const char* kAccessStrings[kAccessForMAX][NUM_FLAGS] = {
        {
            /* class, inner class */
            "PUBLIC",           /* 0x0001 */
            "PRIVATE",          /* 0x0002 */
            "PROTECTED",        /* 0x0004 */
            "STATIC",           /* 0x0008 */
            "FINAL",            /* 0x0010 */
            "?",                /* 0x0020 */
            "?",                /* 0x0040 */
            "?",                /* 0x0080 */
            "?",                /* 0x0100 */
            "INTERFACE",        /* 0x0200 */
            "ABSTRACT",         /* 0x0400 */
            "?",                /* 0x0800 */
            "SYNTHETIC",        /* 0x1000 */
            "ANNOTATION",       /* 0x2000 */
            "ENUM",             /* 0x4000 */
            "?",                /* 0x8000 */
            "VERIFIED",         /* 0x10000 */
            "OPTIMIZED",        /* 0x20000 */
        },
        {
            /* method */
            "PUBLIC",           /* 0x0001 */
            "PRIVATE",          /* 0x0002 */
            "PROTECTED",        /* 0x0004 */
            "STATIC",           /* 0x0008 */
            "FINAL",            /* 0x0010 */
            "SYNCHRONIZED",     /* 0x0020 */
            "BRIDGE",           /* 0x0040 */
            "VARARGS",          /* 0x0080 */
            "NATIVE",           /* 0x0100 */
            "?",                /* 0x0200 */
            "ABSTRACT",         /* 0x0400 */
            "STRICT",           /* 0x0800 */
            "SYNTHETIC",        /* 0x1000 */
            "?",                /* 0x2000 */
            "?",                /* 0x4000 */
            "MIRANDA",          /* 0x8000 */
            "CONSTRUCTOR",      /* 0x10000 */
            "DECLARED_SYNCHRONIZED", /* 0x20000 */
        },
        {
            /* field */
            "PUBLIC",           /* 0x0001 */
            "PRIVATE",          /* 0x0002 */
            "PROTECTED",        /* 0x0004 */
            "STATIC",           /* 0x0008 */
            "FINAL",            /* 0x0010 */
            "?",                /* 0x0020 */
            "VOLATILE",         /* 0x0040 */
            "TRANSIENT",        /* 0x0080 */
            "?",                /* 0x0100 */
            "?",                /* 0x0200 */
            "?",                /* 0x0400 */
            "?",                /* 0x0800 */
            "SYNTHETIC",        /* 0x1000 */
            "?",                /* 0x2000 */
            "ENUM",             /* 0x4000 */
            "?",                /* 0x8000 */
            "?",                /* 0x10000 */
            "?",                /* 0x20000 */
        },
    };
    const int kLongest = 21;        /* strlen of longest string above */
    int i, count;
    char* str;
    char* cp;

    /*
     * Allocate enough storage to hold the expected number of strings,
     * plus a space between each.  We over-allocate, using the longest
     * string above as the base metric.
     */
    count = countOnes(flags);
    cp = str = (char*) malloc(count * (kLongest+1) +1);

    for (i = 0; i < NUM_FLAGS; i++) {
        if (flags & 0x01) {
            const char* accessStr = kAccessStrings[forWhat][i];
            int len = strlen(accessStr);
            if (cp != str)
                *cp++ = ' ';

            memcpy(cp, accessStr, len);
            cp += len;
        }
        flags >>= 1;
    }
    *cp = '\0';

    return str;
}


/*
 * Copy character data from "data" to "out", converting non-ASCII values
 * to printf format chars or an ASCII filler ('.' or '?').
 *
 * The output buffer must be able to hold (2*len)+1 bytes.  The result is
 * NUL-terminated.
 */
static void asciify(char* out, const unsigned char* data, size_t len)
{
    while (len--) {
        if (*data < 0x20) {
            /* could do more here, but we don't need them yet */
            switch (*data) {
            case '\0':
                *out++ = '\\';
                *out++ = '0';
                break;
            case '\n':
                *out++ = '\\';
                *out++ = 'n';
                break;
            default:
                *out++ = '.';
                break;
            }
        } else if (*data >= 0x80) {
            *out++ = '?';
        } else {
            *out++ = *data;
        }
        data++;
    }
    *out = '\0';
}

/*
 * Dump the file header.
 */
void dumpFileHeader(const DexFile* pDexFile)
{
    const DexOptHeader* pOptHeader = pDexFile->pOptHeader;
    const DexHeader* pHeader = pDexFile->pHeader;
    char sanitized[sizeof(pHeader->magic)*2 +1];

    assert(sizeof(pHeader->magic) == sizeof(pOptHeader->magic));

    if (pOptHeader != NULL) {
        printf("Optimized DEX file header:\n");

        asciify(sanitized, pOptHeader->magic, sizeof(pOptHeader->magic));
        printf("magic               : '%s'\n", sanitized);
        printf("dex_offset          : %d (0x%06x)\n",
            pOptHeader->dexOffset, pOptHeader->dexOffset);
        printf("dex_length          : %d\n", pOptHeader->dexLength);
        printf("deps_offset         : %d (0x%06x)\n",
            pOptHeader->depsOffset, pOptHeader->depsOffset);
        printf("deps_length         : %d\n", pOptHeader->depsLength);
        printf("opt_offset          : %d (0x%06x)\n",
            pOptHeader->optOffset, pOptHeader->optOffset);
        printf("opt_length          : %d\n", pOptHeader->optLength);
        printf("flags               : %08x\n", pOptHeader->flags);
        printf("checksum            : %08x\n", pOptHeader->checksum);
        printf("\n");
    }

    printf("DEX file header:\n");
    asciify(sanitized, pHeader->magic, sizeof(pHeader->magic));
    printf("magic               : '%s'\n", sanitized);
    printf("checksum            : %08x\n", pHeader->checksum);
    printf("signature           : %02x%02x...%02x%02x\n",
        pHeader->signature[0], pHeader->signature[1],
        pHeader->signature[kSHA1DigestLen-2],
        pHeader->signature[kSHA1DigestLen-1]);
    printf("file_size           : %d\n", pHeader->fileSize);
    printf("header_size         : %d\n", pHeader->headerSize);
    printf("link_size           : %d\n", pHeader->linkSize);
    printf("link_off            : %d (0x%06x)\n",
        pHeader->linkOff, pHeader->linkOff);
    printf("string_ids_size     : %d\n", pHeader->stringIdsSize);
    printf("string_ids_off      : %d (0x%06x)\n",
        pHeader->stringIdsOff, pHeader->stringIdsOff);
    printf("type_ids_size       : %d\n", pHeader->typeIdsSize);
    printf("type_ids_off        : %d (0x%06x)\n",
        pHeader->typeIdsOff, pHeader->typeIdsOff);
    printf("proto_ids_size       : %d\n", pHeader->protoIdsSize);
    printf("proto_ids_off        : %d (0x%06x)\n",
        pHeader->protoIdsOff, pHeader->protoIdsOff);
    printf("field_ids_size      : %d\n", pHeader->fieldIdsSize);
    printf("field_ids_off       : %d (0x%06x)\n",
        pHeader->fieldIdsOff, pHeader->fieldIdsOff);
    printf("method_ids_size     : %d\n", pHeader->methodIdsSize);
    printf("method_ids_off      : %d (0x%06x)\n",
        pHeader->methodIdsOff, pHeader->methodIdsOff);
    printf("class_defs_size     : %d\n", pHeader->classDefsSize);
    printf("class_defs_off      : %d (0x%06x)\n",
        pHeader->classDefsOff, pHeader->classDefsOff);
    printf("data_size           : %d\n", pHeader->dataSize);
    printf("data_off            : %d (0x%06x)\n",
        pHeader->dataOff, pHeader->dataOff);
    printf("\n");
}

/*
 * Dump the "table of contents" for the opt area.
 */
void dumpOptDirectory(const DexFile* pDexFile)
{
    const DexOptHeader* pOptHeader = pDexFile->pOptHeader;
    if (pOptHeader == NULL)
        return;

    printf("OPT section contents:\n");

    const u4* pOpt = (const u4*) ((u1*) pOptHeader + pOptHeader->optOffset);

    if (*pOpt == 0) {
        printf("(1.0 format, only class lookup table is present)\n\n");
        return;
    }

    /*
     * The "opt" section is in "chunk" format: a 32-bit identifier, a 32-bit
     * length, then the data.  Chunks start on 64-bit boundaries.
     */
    while (*pOpt != kDexChunkEnd) {
        const char* verboseStr;

        u4 size = *(pOpt+1);

        switch (*pOpt) {
        case kDexChunkClassLookup:
            verboseStr = "class lookup hash table";
            break;
        case kDexChunkRegisterMaps:
            verboseStr = "register maps";
            break;
        default:
            verboseStr = "(unknown chunk type)";
            break;
        }

        printf("Chunk %08x (%c%c%c%c) - %s (%d bytes)\n", *pOpt,
            *pOpt >> 24, (char)(*pOpt >> 16), (char)(*pOpt >> 8), (char)*pOpt,
            verboseStr, size);

        size = (size + 8 + 7) & ~7;
        pOpt += size / sizeof(u4);
    }
    printf("\n");
}

/*
 * Dump a class_def_item.
 */
void dumpClassDef(DexFile* pDexFile, int idx)
{
    const DexClassDef* pClassDef;
    const u1* pEncodedData;
    DexClassData* pClassData;

    pClassDef = dexGetClassDef(pDexFile, idx);
    pEncodedData = dexGetClassData(pDexFile, pClassDef);
    pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL);

    if (pClassData == NULL) {
        fprintf(stderr, "Trouble reading class data\n");
        return;
    }

    printf("Class #%d header:\n", idx);
    printf("class_idx           : %d\n", pClassDef->classIdx);
    printf("access_flags        : %d (0x%04x)\n",
        pClassDef->accessFlags, pClassDef->accessFlags);
    printf("superclass_idx      : %d\n", pClassDef->superclassIdx);
    printf("interfaces_off      : %d (0x%06x)\n",
        pClassDef->interfacesOff, pClassDef->interfacesOff);
    printf("source_file_idx     : %d\n", pClassDef->sourceFileIdx);
    printf("annotations_off     : %d (0x%06x)\n",
        pClassDef->annotationsOff, pClassDef->annotationsOff);
    printf("class_data_off      : %d (0x%06x)\n",
        pClassDef->classDataOff, pClassDef->classDataOff);
    printf("static_fields_size  : %d\n", pClassData->header.staticFieldsSize);
    printf("instance_fields_size: %d\n",
            pClassData->header.instanceFieldsSize);
    printf("direct_methods_size : %d\n", pClassData->header.directMethodsSize);
    printf("virtual_methods_size: %d\n",
            pClassData->header.virtualMethodsSize);
    printf("\n");

    free(pClassData);
}

/*
 * Dump an interface that a class declares to implement.
 */
void dumpInterface(const DexFile* pDexFile, const DexTypeItem* pTypeItem,
    int i)
{
    const char* interfaceName =
        dexStringByTypeIdx(pDexFile, pTypeItem->typeIdx);

    if (gOptions.outputFormat == OUTPUT_PLAIN) {
        printf("    #%d              : '%s'\n", i, interfaceName);
    } else {
        char* dotted = descriptorToDot(interfaceName);
        printf("<implements name=\"%s\">\n</implements>\n", dotted);
        free(dotted);
    }
}

/*
 * Dump the catches table associated with the code.
 */
void dumpCatches(DexFile* pDexFile, const DexCode* pCode)
{
    u4 triesSize = pCode->triesSize;

    if (triesSize == 0) {
        printf("      catches       : (none)\n");
        return;
    }

    printf("      catches       : %d\n", triesSize);

    const DexTry* pTries = dexGetTries(pCode);
    u4 i;

    for (i = 0; i < triesSize; i++) {
        const DexTry* pTry = &pTries[i];
        u4 start = pTry->startAddr;
        u4 end = start + pTry->insnCount;
        DexCatchIterator iterator;

        printf("        0x%04x - 0x%04x\n", start, end);

        dexCatchIteratorInit(&iterator, pCode, pTry->handlerOff);

        for (;;) {
            DexCatchHandler* handler = dexCatchIteratorNext(&iterator);
            const char* descriptor;

            if (handler == NULL) {
                break;
            }

            descriptor = (handler->typeIdx == kDexNoIndex) ? "<any>" :
                dexStringByTypeIdx(pDexFile, handler->typeIdx);

            printf("          %s -> 0x%04x\n", descriptor,
                    handler->address);
        }
    }
}

static int dumpPositionsCb(void * /* cnxt */, u4 address, u4 lineNum)
{
    printf("        0x%04x line=%d\n", address, lineNum);
    return 0;
}

/*
 * Dump the positions list.
 */
void dumpPositions(DexFile* pDexFile, const DexCode* pCode,
        const DexMethod *pDexMethod)
{
    printf("      positions     : \n");
    const DexMethodId *pMethodId
            = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
    const char *classDescriptor
            = dexStringByTypeIdx(pDexFile, pMethodId->classIdx);

    dexDecodeDebugInfo(pDexFile, pCode, classDescriptor, pMethodId->protoIdx,
            pDexMethod->accessFlags, dumpPositionsCb, NULL, NULL);
}

static void dumpLocalsCb(void * /* cnxt */, u2 reg, u4 startAddress,
        u4 endAddress, const char *name, const char *descriptor,
        const char *signature)
{
    printf("        0x%04x - 0x%04x reg=%d %s %s %s\n",
            startAddress, endAddress, reg, name, descriptor,
            signature);
}

/*
 * Dump the locals list.
 */
void dumpLocals(DexFile* pDexFile, const DexCode* pCode,
        const DexMethod *pDexMethod)
{
    printf("      locals        : \n");

    const DexMethodId *pMethodId
            = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
    const char *classDescriptor
            = dexStringByTypeIdx(pDexFile, pMethodId->classIdx);

    dexDecodeDebugInfo(pDexFile, pCode, classDescriptor, pMethodId->protoIdx,
            pDexMethod->accessFlags, NULL, dumpLocalsCb, NULL);
}

/*
 * Get information about a method.
 */
bool getMethodInfo(DexFile* pDexFile, u4 methodIdx, FieldMethodInfo* pMethInfo)
{
    const DexMethodId* pMethodId;

    if (methodIdx >= pDexFile->pHeader->methodIdsSize)
        return false;

    pMethodId = dexGetMethodId(pDexFile, methodIdx);
    pMethInfo->name = dexStringById(pDexFile, pMethodId->nameIdx);
    pMethInfo->signature = dexCopyDescriptorFromMethodId(pDexFile, pMethodId);

    pMethInfo->classDescriptor =
            dexStringByTypeIdx(pDexFile, pMethodId->classIdx);
    return true;
}

/*
 * Get information about a field.
 */
bool getFieldInfo(DexFile* pDexFile, u4 fieldIdx, FieldMethodInfo* pFieldInfo)
{
    const DexFieldId* pFieldId;

    if (fieldIdx >= pDexFile->pHeader->fieldIdsSize)
        return false;

    pFieldId = dexGetFieldId(pDexFile, fieldIdx);
    pFieldInfo->name = dexStringById(pDexFile, pFieldId->nameIdx);
    pFieldInfo->signature = dexStringByTypeIdx(pDexFile, pFieldId->typeIdx);
    pFieldInfo->classDescriptor =
        dexStringByTypeIdx(pDexFile, pFieldId->classIdx);
    return true;
}

/*
 * Get information about a ProtoId.
 */
bool getProtoInfo(DexFile* pDexFile, u4 protoIdx, ProtoInfo* pProtoInfo)
{
    if (protoIdx >= pDexFile->pHeader->protoIdsSize) {
        return false;
    }

    const DexProtoId* protoId = dexGetProtoId(pDexFile, protoIdx);

    // Get string for return type.
    if (protoId->returnTypeIdx >= pDexFile->pHeader->typeIdsSize) {
        return false;
    }
    pProtoInfo->returnType = dexStringByTypeIdx(pDexFile, protoId->returnTypeIdx);

    // Build string for parameter types.
    size_t bufSize = 1;
    char* buf = (char*)malloc(bufSize);
    if (buf == NULL) {
        return false;
    }

    buf[0] = '\0';
    size_t bufUsed = 1;

    const DexTypeList* paramTypes = dexGetProtoParameters(pDexFile, protoId);
    if (paramTypes == NULL) {
        // No parameters.
        pProtoInfo->parameterTypes = buf;
        return true;
    }

    for (u4 i = 0; i < paramTypes->size; ++i) {
        if (paramTypes->list[i].typeIdx >= pDexFile->pHeader->typeIdsSize) {
            free(buf);
            return false;
        }
        const char* param = dexStringByTypeIdx(pDexFile, paramTypes->list[i].typeIdx);
        size_t paramLen = strlen(param);
        size_t newUsed = bufUsed + paramLen;
        if (newUsed > bufSize) {
            char* newBuf = (char*)realloc(buf, newUsed);
            if (newBuf == NULL) {
                free(buf);
                return false;
            }
            buf = newBuf;
            bufSize = newUsed;
        }
        memcpy(buf + bufUsed - 1, param, paramLen + 1);
        bufUsed = newUsed;
    }

    pProtoInfo->parameterTypes = buf;
    return true;
}

/*
 * Look up a class' descriptor.
 */
const char* getClassDescriptor(DexFile* pDexFile, u4 classIdx)
{
    return dexStringByTypeIdx(pDexFile, classIdx);
}

/*
 * Helper for dumpInstruction(), which builds the string
 * representation for the index in the given instruction. This will
 * first try to use the given buffer, but if the result won't fit,
 * then this will allocate a new buffer to hold the result. A pointer
 * to the buffer which holds the full result is always returned, and
 * this can be compared with the one passed in, to see if the result
 * needs to be free()d.
 */
static char* indexString(DexFile* pDexFile, const DecodedInstruction* pDecInsn, size_t bufSize)
{
    char* buf = (char*)malloc(bufSize);
    if (buf == NULL) {
      return NULL;
    }

    int outSize;
    u4 index;
    u4 secondaryIndex = 0;
    u4 width;

    /* TODO: Make the index *always* be in field B, to simplify this code. */
    switch (dexGetFormatFromOpcode(pDecInsn->opcode)) {
    case kFmt20bc:
    case kFmt21c:
    case kFmt35c:
    case kFmt35ms:
    case kFmt3rc:
    case kFmt3rms:
    case kFmt35mi:
    case kFmt3rmi:
        index = pDecInsn->vB;
        width = 4;
        break;
    case kFmt31c:
        index = pDecInsn->vB;
        width = 8;
        break;
    case kFmt22c:
    case kFmt22cs:
        index = pDecInsn->vC;
        width = 4;
        break;
    case kFmt45cc:
    case kFmt4rcc:
        index = pDecInsn->vB;  // method index
        secondaryIndex = pDecInsn->arg[4];  // proto index
        width = 4;
        break;
    default:
        index = 0;
        width = 4;
        break;
    }

    switch (pDecInsn->indexType) {
    case kIndexUnknown:
        /*
         * This function shouldn't ever get called for this type, but do
         * something sensible here, just to help with debugging.
         */
        outSize = snprintf(buf, bufSize, "<unknown-index>");
        break;
    case kIndexNone:
        /*
         * This function shouldn't ever get called for this type, but do
         * something sensible here, just to help with debugging.
         */
        outSize = snprintf(buf, bufSize, "<no-index>");
        break;
    case kIndexVaries:
        /*
         * This one should never show up in a dexdump, so no need to try
         * to get fancy here.
         */
        outSize = snprintf(buf, bufSize, "<index-varies> // thing@%0*x",
                width, index);
        break;
    case kIndexTypeRef:
        if (index < pDexFile->pHeader->typeIdsSize) {
            outSize = snprintf(buf, bufSize, "%s // type@%0*x",
                               getClassDescriptor(pDexFile, index), width, index);
        } else {
            outSize = snprintf(buf, bufSize, "<type?> // type@%0*x", width, index);
        }
        break;
    case kIndexStringRef:
        if (index < pDexFile->pHeader->stringIdsSize) {
            outSize = snprintf(buf, bufSize, "\"%s\" // string@%0*x",
                               dexStringById(pDexFile, index), width, index);
        } else {
            outSize = snprintf(buf, bufSize, "<string?> // string@%0*x",
                               width, index);
        }
        break;
    case kIndexMethodRef:
        {
            FieldMethodInfo methInfo;
            if (getMethodInfo(pDexFile, index, &methInfo)) {
                outSize = snprintf(buf, bufSize, "%s.%s:%s // method@%0*x",
                        methInfo.classDescriptor, methInfo.name,
                        methInfo.signature, width, index);
                free((void *) methInfo.signature);
            } else {
                outSize = snprintf(buf, bufSize, "<method?> // method@%0*x",
                        width, index);
            }
        }
        break;
    case kIndexFieldRef:
        {
            FieldMethodInfo fieldInfo;
            if (getFieldInfo(pDexFile, index, &fieldInfo)) {
                outSize = snprintf(buf, bufSize, "%s.%s:%s // field@%0*x",
                        fieldInfo.classDescriptor, fieldInfo.name,
                        fieldInfo.signature, width, index);
            } else {
                outSize = snprintf(buf, bufSize, "<field?> // field@%0*x",
                        width, index);
            }
        }
        break;
    case kIndexInlineMethod:
        outSize = snprintf(buf, bufSize, "[%0*x] // inline #%0*x",
                width, index, width, index);
        break;
    case kIndexVtableOffset:
        outSize = snprintf(buf, bufSize, "[%0*x] // vtable #%0*x",
                width, index, width, index);
        break;
    case kIndexFieldOffset:
        outSize = snprintf(buf, bufSize, "[obj+%0*x]", width, index);
        break;
    case kIndexMethodAndProtoRef:
        {
            FieldMethodInfo methInfo;
            ProtoInfo protoInfo;
            protoInfo.parameterTypes = NULL;
            if (getMethodInfo(pDexFile, index, &methInfo) &&
                getProtoInfo(pDexFile, secondaryIndex, &protoInfo)) {
                outSize = snprintf(buf, bufSize, "%s.%s:%s, (%s)%s // method@%0*x, proto@%0*x",
                                   methInfo.classDescriptor, methInfo.name, methInfo.signature,
                                   protoInfo.parameterTypes, protoInfo.returnType,
                                   width, index, width, secondaryIndex);
            } else {
                outSize = snprintf(buf, bufSize, "<method?>, <proto?> // method@%0*x, proto@%0*x",
                                   width, index, width, secondaryIndex);
            }
            free(protoInfo.parameterTypes);
        }
        break;
    case kIndexCallSiteRef:
        outSize = snprintf(buf, bufSize, "call_site@%0*x", width, index);
        break;
    case kIndexMethodHandleRef:
        outSize = snprintf(buf, bufSize, "methodhandle@%0*x", width, index);
        break;
    case kIndexProtoRef:
        {
            ProtoInfo protoInfo;
            if (getProtoInfo(pDexFile, index, &protoInfo)) {
                outSize = snprintf(buf, bufSize, "(%s)%s // proto@%0*x",
                                   protoInfo.parameterTypes, protoInfo.returnType,
                                   width, index);

            } else {
                outSize = snprintf(buf, bufSize, "<proto?> // proto@%0*x",
                                   width, secondaryIndex);
            }
            free(protoInfo.parameterTypes);
        }
        break;
    default:
        outSize = snprintf(buf, bufSize, "<?>");
        break;
    }

    if (outSize >= (int) bufSize) {
        /*
         * The buffer wasn't big enough; allocate and retry. Note:
         * snprintf() doesn't count the '\0' as part of its returned
         * size, so we add explicit space for it here.
         */
        free(buf);
        return indexString(pDexFile, pDecInsn, outSize + 1);
    } else {
        return buf;
    }
}

/*
 * Dump a single instruction.
 */
void dumpInstruction(DexFile* pDexFile, const DexCode* pCode, int insnIdx,
    int insnWidth, const DecodedInstruction* pDecInsn)
{
    const u2* insns = pCode->insns;
    int i;

    // Address of instruction (expressed as byte offset).
    printf("%06zx:", ((u1*)insns - pDexFile->baseAddr) + insnIdx*2);

    for (i = 0; i < 8; i++) {
        if (i < insnWidth) {
            if (i == 7) {
                printf(" ... ");
            } else {
                /* print 16-bit value in little-endian order */
                const u1* bytePtr = (const u1*) &insns[insnIdx+i];
                printf(" %02x%02x", bytePtr[0], bytePtr[1]);
            }
        } else {
            fputs("     ", stdout);
        }
    }

    if (pDecInsn->opcode == OP_NOP) {
        u2 instr = get2LE((const u1*) &insns[insnIdx]);
        if (instr == kPackedSwitchSignature) {
            printf("|%04x: packed-switch-data (%d units)",
                insnIdx, insnWidth);
        } else if (instr == kSparseSwitchSignature) {
            printf("|%04x: sparse-switch-data (%d units)",
                insnIdx, insnWidth);
        } else if (instr == kArrayDataSignature) {
            printf("|%04x: array-data (%d units)",
                insnIdx, insnWidth);
        } else {
            printf("|%04x: nop // spacer", insnIdx);
        }
    } else {
        printf("|%04x: %s", insnIdx, dexGetOpcodeName(pDecInsn->opcode));
    }

    // Provide an initial buffer that usually suffices, although indexString()
    // may reallocate the buffer if more space is needed.
    char* indexBuf = NULL;
    if (pDecInsn->indexType != kIndexNone) {
        indexBuf = indexString(pDexFile, pDecInsn, 200);
    }

    switch (dexGetFormatFromOpcode(pDecInsn->opcode)) {
    case kFmt10x:        // op
        break;
    case kFmt12x:        // op vA, vB
        printf(" v%d, v%d", pDecInsn->vA, pDecInsn->vB);
        break;
    case kFmt11n:        // op vA, #+B
        printf(" v%d, #int %d // #%x",
            pDecInsn->vA, (s4)pDecInsn->vB, (u1)pDecInsn->vB);
        break;
    case kFmt11x:        // op vAA
        printf(" v%d", pDecInsn->vA);
        break;
    case kFmt10t:        // op +AA
    case kFmt20t:        // op +AAAA
        {
            s4 targ = (s4) pDecInsn->vA;
            printf(" %04x // %c%04x",
                insnIdx + targ,
                (targ < 0) ? '-' : '+',
                (targ < 0) ? -targ : targ);
        }
        break;
    case kFmt22x:        // op vAA, vBBBB
        printf(" v%d, v%d", pDecInsn->vA, pDecInsn->vB);
        break;
    case kFmt21t:        // op vAA, +BBBB
        {
            s4 targ = (s4) pDecInsn->vB;
            printf(" v%d, %04x // %c%04x", pDecInsn->vA,
                insnIdx + targ,
                (targ < 0) ? '-' : '+',
                (targ < 0) ? -targ : targ);
        }
        break;
    case kFmt21s:        // op vAA, #+BBBB
        printf(" v%d, #int %d // #%x",
            pDecInsn->vA, (s4)pDecInsn->vB, (u2)pDecInsn->vB);
        break;
    case kFmt21h:        // op vAA, #+BBBB0000[00000000]
        // The printed format varies a bit based on the actual opcode.
        if (pDecInsn->opcode == OP_CONST_HIGH16) {
            s4 value = pDecInsn->vB << 16;
            printf(" v%d, #int %d // #%x",
                pDecInsn->vA, value, (u2)pDecInsn->vB);
        } else {
            s8 value = ((s8) pDecInsn->vB) << 48;
            printf(" v%d, #long %" PRId64 " // #%x",
                pDecInsn->vA, value, (u2)pDecInsn->vB);
        }
        break;
    case kFmt21c:        // op vAA, thing@BBBB
    case kFmt31c:        // op vAA, thing@BBBBBBBB
        printf(" v%d, %s", pDecInsn->vA, indexBuf);
        break;
    case kFmt23x:        // op vAA, vBB, vCC
        printf(" v%d, v%d, v%d", pDecInsn->vA, pDecInsn->vB, pDecInsn->vC);
        break;
    case kFmt22b:        // op vAA, vBB, #+CC
        printf(" v%d, v%d, #int %d // #%02x",
            pDecInsn->vA, pDecInsn->vB, (s4)pDecInsn->vC, (u1)pDecInsn->vC);
        break;
    case kFmt22t:        // op vA, vB, +CCCC
        {
            s4 targ = (s4) pDecInsn->vC;
            printf(" v%d, v%d, %04x // %c%04x", pDecInsn->vA, pDecInsn->vB,
                insnIdx + targ,
                (targ < 0) ? '-' : '+',
                (targ < 0) ? -targ : targ);
        }
        break;
    case kFmt22s:        // op vA, vB, #+CCCC
        printf(" v%d, v%d, #int %d // #%04x",
            pDecInsn->vA, pDecInsn->vB, (s4)pDecInsn->vC, (u2)pDecInsn->vC);
        break;
    case kFmt22c:        // op vA, vB, thing@CCCC
    case kFmt22cs:       // [opt] op vA, vB, field offset CCCC
        printf(" v%d, v%d, %s", pDecInsn->vA, pDecInsn->vB, indexBuf);
        break;
    case kFmt30t:
        printf(" #%08x", pDecInsn->vA);
        break;
    case kFmt31i:        // op vAA, #+BBBBBBBB
        {
            /* this is often, but not always, a float */
            union {
                float f;
                u4 i;
            } conv;
            conv.i = pDecInsn->vB;
            printf(" v%d, #float %f // #%08x",
                pDecInsn->vA, conv.f, pDecInsn->vB);
        }
        break;
    case kFmt31t:       // op vAA, offset +BBBBBBBB
        printf(" v%d, %08x // +%08x",
            pDecInsn->vA, insnIdx + pDecInsn->vB, pDecInsn->vB);
        break;
    case kFmt32x:        // op vAAAA, vBBBB
        printf(" v%d, v%d", pDecInsn->vA, pDecInsn->vB);
        break;
    case kFmt35c:        // op {vC, vD, vE, vF, vG}, thing@BBBB
    case kFmt35ms:       // [opt] invoke-virtual+super
    case kFmt35mi:       // [opt] inline invoke
        {
            fputs(" {", stdout);
            for (i = 0; i < (int) pDecInsn->vA; i++) {
                if (i == 0)
                    printf("v%d", pDecInsn->arg[i]);
                else
                    printf(", v%d", pDecInsn->arg[i]);
            }
            printf("}, %s", indexBuf);
        }
        break;
    case kFmt3rc:        // op {vCCCC .. v(CCCC+AA-1)}, thing@BBBB
    case kFmt3rms:       // [opt] invoke-virtual+super/range
    case kFmt3rmi:       // [opt] execute-inline/range
        {
            /*
             * This doesn't match the "dx" output when some of the args are
             * 64-bit values -- dx only shows the first register.
             */
            fputs(" {", stdout);
            for (i = 0; i < (int) pDecInsn->vA; i++) {
                if (i == 0)
                    printf("v%d", pDecInsn->vC + i);
                else
                    printf(", v%d", pDecInsn->vC + i);
            }
            printf("}, %s", indexBuf);
        }
        break;
    case kFmt51l:        // op vAA, #+BBBBBBBBBBBBBBBB
        {
            /* this is often, but not always, a double */
            union {
                double d;
                u8 j;
            } conv;
            conv.j = pDecInsn->vB_wide;
            printf(" v%d, #double %f // #%016" PRIx64,
                pDecInsn->vA, conv.d, pDecInsn->vB_wide);
        }
        break;
    case kFmt00x:        // unknown op or breakpoint
        break;
    case kFmt45cc:
        {
            fputs("  {", stdout);
            printf("v%d", pDecInsn->vC);
            for (int i = 0; i < (int) pDecInsn->vA - 1; ++i) {
                printf(", v%d", pDecInsn->arg[i]);
            }
            printf("}, %s", indexBuf);
        }
        break;
    case kFmt4rcc:
        {
            fputs("  {", stdout);
            printf("v%d", pDecInsn->vC);
            for (int i = 1; i < (int) pDecInsn->vA; ++i) {
                printf(", v%d", pDecInsn->vC + i);
            }
            printf("}, %s", indexBuf);
        }
        break;
    default:
        printf(" ???");
        break;
    }

    putchar('\n');

    free(indexBuf);
}

/*
 * Dump a bytecode disassembly.
 */
void dumpBytecodes(DexFile* pDexFile, const DexMethod* pDexMethod)
{
    const DexCode* pCode = dexGetCode(pDexFile, pDexMethod);
    const u2* insns;
    int insnIdx;
    FieldMethodInfo methInfo;
    int startAddr;
    char* className = NULL;

    assert(pCode->insnsSize > 0);
    insns = pCode->insns;

    methInfo.classDescriptor =
    methInfo.name =
    methInfo.signature = NULL;

    getMethodInfo(pDexFile, pDexMethod->methodIdx, &methInfo);
    startAddr = ((u1*)pCode - pDexFile->baseAddr);
    className = descriptorToDot(methInfo.classDescriptor);

    printf("%06x:                                        |[%06x] %s.%s:%s\n",
        startAddr, startAddr,
        className, methInfo.name, methInfo.signature);
    free((void *) methInfo.signature);

    insnIdx = 0;
    while (insnIdx < (int) pCode->insnsSize) {
        int insnWidth;
        DecodedInstruction decInsn;
        u2 instr;

        /*
         * Note: This code parallels the function
         * dexGetWidthFromInstruction() in InstrUtils.c, but this version
         * can deal with data in either endianness.
         *
         * TODO: Figure out if this really matters, and possibly change
         * this to just use dexGetWidthFromInstruction().
         */
        instr = get2LE((const u1*)insns);
        if (instr == kPackedSwitchSignature) {
            insnWidth = 4 + get2LE((const u1*)(insns+1)) * 2;
        } else if (instr == kSparseSwitchSignature) {
            insnWidth = 2 + get2LE((const u1*)(insns+1)) * 4;
        } else if (instr == kArrayDataSignature) {
            int width = get2LE((const u1*)(insns+1));
            int size = get2LE((const u1*)(insns+2)) |
                       (get2LE((const u1*)(insns+3))<<16);
            // The plus 1 is to round up for odd size and width.
            insnWidth = 4 + ((size * width) + 1) / 2;
        } else {
            Opcode opcode = dexOpcodeFromCodeUnit(instr);
            insnWidth = dexGetWidthFromOpcode(opcode);
            if (insnWidth == 0) {
                fprintf(stderr,
                    "GLITCH: zero-width instruction at idx=0x%04x\n", insnIdx);
                break;
            }
        }

        dexDecodeInstruction(insns, &decInsn);
        dumpInstruction(pDexFile, pCode, insnIdx, insnWidth, &decInsn);

        insns += insnWidth;
        insnIdx += insnWidth;
    }

    free(className);
}

/*
 * Dump a "code" struct.
 */
void dumpCode(DexFile* pDexFile, const DexMethod* pDexMethod)
{
    const DexCode* pCode = dexGetCode(pDexFile, pDexMethod);

    printf("      registers     : %d\n", pCode->registersSize);
    printf("      ins           : %d\n", pCode->insSize);
    printf("      outs          : %d\n", pCode->outsSize);
    printf("      insns size    : %d 16-bit code units\n", pCode->insnsSize);

    if (gOptions.disassemble)
        dumpBytecodes(pDexFile, pDexMethod);

    dumpCatches(pDexFile, pCode);
    /* both of these are encoded in debug info */
    dumpPositions(pDexFile, pCode, pDexMethod);
    dumpLocals(pDexFile, pCode, pDexMethod);
}

/*
 * Dump a method.
 */
void dumpMethod(DexFile* pDexFile, const DexMethod* pDexMethod, int i)
{
    const DexMethodId* pMethodId;
    const char* backDescriptor;
    const char* name;
    char* typeDescriptor = NULL;
    char* accessStr = NULL;

    if (gOptions.exportsOnly &&
        (pDexMethod->accessFlags & (ACC_PUBLIC | ACC_PROTECTED)) == 0)
    {
        return;
    }

    pMethodId = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
    name = dexStringById(pDexFile, pMethodId->nameIdx);
    typeDescriptor = dexCopyDescriptorFromMethodId(pDexFile, pMethodId);

    backDescriptor = dexStringByTypeIdx(pDexFile, pMethodId->classIdx);

    accessStr = createAccessFlagStr(pDexMethod->accessFlags,
                    kAccessForMethod);

    if (gOptions.outputFormat == OUTPUT_PLAIN) {
        printf("    #%d              : (in %s)\n", i, backDescriptor);
        printf("      name          : '%s'\n", name);
        printf("      type          : '%s'\n", typeDescriptor);
        printf("      access        : 0x%04x (%s)\n",
            pDexMethod->accessFlags, accessStr);

        if (pDexMethod->codeOff == 0) {
            printf("      code          : (none)\n");
        } else {
            printf("      code          -\n");
            dumpCode(pDexFile, pDexMethod);
        }

        if (gOptions.disassemble)
            putchar('\n');
    } else if (gOptions.outputFormat == OUTPUT_XML) {
        bool constructor = (name[0] == '<');

        if (constructor) {
            char* tmp;

            tmp = descriptorClassToName(backDescriptor);
            printf("<constructor name=\"%s\"\n", tmp);
            free(tmp);

            tmp = descriptorToDot(backDescriptor);
            printf(" type=\"%s\"\n", tmp);
            free(tmp);
        } else {
            printf("<method name=\"%s\"\n", name);

            const char* returnType = strrchr(typeDescriptor, ')');
            if (returnType == NULL) {
                fprintf(stderr, "bad method type descriptor '%s'\n",
                    typeDescriptor);
                goto bail;
            }

            char* tmp = descriptorToDot(returnType+1);
            printf(" return=\"%s\"\n", tmp);
            free(tmp);

            printf(" abstract=%s\n",
                quotedBool((pDexMethod->accessFlags & ACC_ABSTRACT) != 0));
            printf(" native=%s\n",
                quotedBool((pDexMethod->accessFlags & ACC_NATIVE) != 0));

            bool isSync =
                (pDexMethod->accessFlags & ACC_SYNCHRONIZED) != 0 ||
                (pDexMethod->accessFlags & ACC_DECLARED_SYNCHRONIZED) != 0;
            printf(" synchronized=%s\n", quotedBool(isSync));
        }

        printf(" static=%s\n",
            quotedBool((pDexMethod->accessFlags & ACC_STATIC) != 0));
        printf(" final=%s\n",
            quotedBool((pDexMethod->accessFlags & ACC_FINAL) != 0));
        // "deprecated=" not knowable w/o parsing annotations
        printf(" visibility=%s\n",
            quotedVisibility(pDexMethod->accessFlags));

        printf(">\n");

        /*
         * Parameters.
         */
        if (typeDescriptor[0] != '(') {
            fprintf(stderr, "ERROR: bad descriptor '%s'\n", typeDescriptor);
            goto bail;
        }

        char tmpBuf[strlen(typeDescriptor)+1];      /* more than big enough */
        int argNum = 0;

        const char* base = typeDescriptor+1;

        while (*base != ')') {
            char* cp = tmpBuf;

            while (*base == '[')
                *cp++ = *base++;

            if (*base == 'L') {
                /* copy through ';' */
                do {
                    *cp = *base++;
                } while (*cp++ != ';');
            } else {
                /* primitive char, copy it */
                if (strchr("ZBCSIFJD", *base) == NULL) {
                    fprintf(stderr, "ERROR: bad method signature '%s'\n", base);
                    goto bail;
                }
                *cp++ = *base++;
            }

            /* null terminate and display */
            *cp++ = '\0';

            char* tmp = descriptorToDot(tmpBuf);
            printf("<parameter name=\"arg%d\" type=\"%s\">\n</parameter>\n",
                argNum++, tmp);
            free(tmp);
        }

        if (constructor)
            printf("</constructor>\n");
        else
            printf("</method>\n");
    }

bail:
    free(typeDescriptor);
    free(accessStr);
}

/*
 * Dump a static (class) field.
 */
void dumpSField(const DexFile* pDexFile, const DexField* pSField, int i)
{
    const DexFieldId* pFieldId;
    const char* backDescriptor;
    const char* name;
    const char* typeDescriptor;
    char* accessStr;

    if (gOptions.exportsOnly &&
        (pSField->accessFlags & (ACC_PUBLIC | ACC_PROTECTED)) == 0)
    {
        return;
    }

    pFieldId = dexGetFieldId(pDexFile, pSField->fieldIdx);
    name = dexStringById(pDexFile, pFieldId->nameIdx);
    typeDescriptor = dexStringByTypeIdx(pDexFile, pFieldId->typeIdx);
    backDescriptor = dexStringByTypeIdx(pDexFile, pFieldId->classIdx);

    accessStr = createAccessFlagStr(pSField->accessFlags, kAccessForField);

    if (gOptions.outputFormat == OUTPUT_PLAIN) {
        printf("    #%d              : (in %s)\n", i, backDescriptor);
        printf("      name          : '%s'\n", name);
        printf("      type          : '%s'\n", typeDescriptor);
        printf("      access        : 0x%04x (%s)\n",
            pSField->accessFlags, accessStr);
    } else if (gOptions.outputFormat == OUTPUT_XML) {
        char* tmp;

        printf("<field name=\"%s\"\n", name);

        tmp = descriptorToDot(typeDescriptor);
        printf(" type=\"%s\"\n", tmp);
        free(tmp);

        printf(" transient=%s\n",
            quotedBool((pSField->accessFlags & ACC_TRANSIENT) != 0));
        printf(" volatile=%s\n",
            quotedBool((pSField->accessFlags & ACC_VOLATILE) != 0));
        // "value=" not knowable w/o parsing annotations
        printf(" static=%s\n",
            quotedBool((pSField->accessFlags & ACC_STATIC) != 0));
        printf(" final=%s\n",
            quotedBool((pSField->accessFlags & ACC_FINAL) != 0));
        // "deprecated=" not knowable w/o parsing annotations
        printf(" visibility=%s\n",
            quotedVisibility(pSField->accessFlags));
        printf(">\n</field>\n");
    }

    free(accessStr);
}

/*
 * Dump an instance field.
 */
void dumpIField(const DexFile* pDexFile, const DexField* pIField, int i)
{
    dumpSField(pDexFile, pIField, i);
}

/*
 * Dump the class.
 *
 * Note "idx" is a DexClassDef index, not a DexTypeId index.
 *
 * If "*pLastPackage" is NULL or does not match the current class' package,
 * the value will be replaced with a newly-allocated string.
 */
void dumpClass(DexFile* pDexFile, int idx, char** pLastPackage)
{
    const DexTypeList* pInterfaces;
    const DexClassDef* pClassDef;
    DexClassData* pClassData = NULL;
    const u1* pEncodedData;
    const char* fileName;
    const char* classDescriptor;
    const char* superclassDescriptor;
    char* accessStr = NULL;
    int i;

    pClassDef = dexGetClassDef(pDexFile, idx);

    if (gOptions.exportsOnly && (pClassDef->accessFlags & ACC_PUBLIC) == 0) {
        //printf("<!-- omitting non-public class %s -->\n",
        //    classDescriptor);
        goto bail;
    }

    pEncodedData = dexGetClassData(pDexFile, pClassDef);
    pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL);

    if (pClassData == NULL) {
        printf("Trouble reading class data (#%d)\n", idx);
        goto bail;
    }

    classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx);

    /*
     * For the XML output, show the package name.  Ideally we'd gather
     * up the classes, sort them, and dump them alphabetically so the
     * package name wouldn't jump around, but that's not a great plan
     * for something that needs to run on the device.
     */
    if (!(classDescriptor[0] == 'L' &&
          classDescriptor[strlen(classDescriptor)-1] == ';'))
    {
        /* arrays and primitives should not be defined explicitly */
        fprintf(stderr, "Malformed class name '%s'\n", classDescriptor);
        /* keep going? */
    } else if (gOptions.outputFormat == OUTPUT_XML) {
        char* mangle;
        char* lastSlash;
        char* cp;

        mangle = strdup(classDescriptor + 1);
        mangle[strlen(mangle)-1] = '\0';

        /* reduce to just the package name */
        lastSlash = strrchr(mangle, '/');
        if (lastSlash != NULL) {
            *lastSlash = '\0';
        } else {
            *mangle = '\0';
        }

        for (cp = mangle; *cp != '\0'; cp++) {
            if (*cp == '/')
                *cp = '.';
        }

        if (*pLastPackage == NULL || strcmp(mangle, *pLastPackage) != 0) {
            /* start of a new package */
            if (*pLastPackage != NULL)
                printf("</package>\n");
            printf("<package name=\"%s\"\n>\n", mangle);
            free(*pLastPackage);
            *pLastPackage = mangle;
        } else {
            free(mangle);
        }
    }

    accessStr = createAccessFlagStr(pClassDef->accessFlags, kAccessForClass);

    if (pClassDef->superclassIdx == kDexNoIndex) {
        superclassDescriptor = NULL;
    } else {
        superclassDescriptor =
            dexStringByTypeIdx(pDexFile, pClassDef->superclassIdx);
    }

    if (gOptions.outputFormat == OUTPUT_PLAIN) {
        printf("Class #%d            -\n", idx);
        printf("  Class descriptor  : '%s'\n", classDescriptor);
        printf("  Access flags      : 0x%04x (%s)\n",
            pClassDef->accessFlags, accessStr);

        if (superclassDescriptor != NULL)
            printf("  Superclass        : '%s'\n", superclassDescriptor);

        printf("  Interfaces        -\n");
    } else {
        char* tmp;

        tmp = descriptorClassToName(classDescriptor);
        printf("<class name=\"%s\"\n", tmp);
        free(tmp);

        if (superclassDescriptor != NULL) {
            tmp = descriptorToDot(superclassDescriptor);
            printf(" extends=\"%s\"\n", tmp);
            free(tmp);
        }
        printf(" abstract=%s\n",
            quotedBool((pClassDef->accessFlags & ACC_ABSTRACT) != 0));
        printf(" static=%s\n",
            quotedBool((pClassDef->accessFlags & ACC_STATIC) != 0));
        printf(" final=%s\n",
            quotedBool((pClassDef->accessFlags & ACC_FINAL) != 0));
        // "deprecated=" not knowable w/o parsing annotations
        printf(" visibility=%s\n",
            quotedVisibility(pClassDef->accessFlags));
        printf(">\n");
    }
    pInterfaces = dexGetInterfacesList(pDexFile, pClassDef);
    if (pInterfaces != NULL) {
        for (i = 0; i < (int) pInterfaces->size; i++)
            dumpInterface(pDexFile, dexGetTypeItem(pInterfaces, i), i);
    }

    if (gOptions.outputFormat == OUTPUT_PLAIN)
        printf("  Static fields     -\n");
    for (i = 0; i < (int) pClassData->header.staticFieldsSize; i++) {
        dumpSField(pDexFile, &pClassData->staticFields[i], i);
    }

    if (gOptions.outputFormat == OUTPUT_PLAIN)
        printf("  Instance fields   -\n");
    for (i = 0; i < (int) pClassData->header.instanceFieldsSize; i++) {
        dumpIField(pDexFile, &pClassData->instanceFields[i], i);
    }

    if (gOptions.outputFormat == OUTPUT_PLAIN)
        printf("  Direct methods    -\n");
    for (i = 0; i < (int) pClassData->header.directMethodsSize; i++) {
        dumpMethod(pDexFile, &pClassData->directMethods[i], i);
    }

    if (gOptions.outputFormat == OUTPUT_PLAIN)
        printf("  Virtual methods   -\n");
    for (i = 0; i < (int) pClassData->header.virtualMethodsSize; i++) {
        dumpMethod(pDexFile, &pClassData->virtualMethods[i], i);
    }

    // TODO: Annotations.

    if (pClassDef->sourceFileIdx != kDexNoIndex)
        fileName = dexStringById(pDexFile, pClassDef->sourceFileIdx);
    else
        fileName = "unknown";

    if (gOptions.outputFormat == OUTPUT_PLAIN) {
        printf("  source_file_idx   : %d (%s)\n",
            pClassDef->sourceFileIdx, fileName);
        printf("\n");
    }

    if (gOptions.outputFormat == OUTPUT_XML) {
        printf("</class>\n");
    }

bail:
    free(pClassData);
    free(accessStr);
}


/*
 * Dump a map in the "differential" format.
 *
 * TODO: show a hex dump of the compressed data.  (We can show the
 * uncompressed data if we move the compression code to libdex; otherwise
 * it's too complex to merit a fast & fragile implementation here.)
 */
void dumpDifferentialCompressedMap(const u1** pData)
{
    const u1* data = *pData;
    const u1* dataStart = data -1;      // format byte already removed
    u1 regWidth;
    u2 numEntries;

    /* standard header */
    regWidth = *data++;
    numEntries = *data++;
    numEntries |= (*data++) << 8;

    /* compressed data begins with the compressed data length */
    int compressedLen = readUnsignedLeb128(&data);
    int addrWidth = 1;
    if ((*data & 0x80) != 0)
        addrWidth++;

    int origLen = 4 + (addrWidth + regWidth) * numEntries;
    int compLen = (data - dataStart) + compressedLen;

    printf("        (differential compression %d -> %d [%d -> %d])\n",
        origLen, compLen,
        (addrWidth + regWidth) * numEntries, compressedLen);

    /* skip past end of entry */
    data += compressedLen;

    *pData = data;
}

/*
 * Dump register map contents of the current method.
 *
 * "*pData" should point to the start of the register map data.  Advances
 * "*pData" to the start of the next map.
 */
void dumpMethodMap(DexFile* pDexFile, const DexMethod* pDexMethod, int idx,
    const u1** pData)
{
    const u1* data = *pData;
    const DexMethodId* pMethodId;
    const char* name;
    int offset = data - (u1*) pDexFile->pOptHeader;

    pMethodId = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
    name = dexStringById(pDexFile, pMethodId->nameIdx);
    printf("      #%d: 0x%08x %s\n", idx, offset, name);

    u1 format;
    int addrWidth;

    format = *data++;
    if (format == 1) {              /* kRegMapFormatNone */
        /* no map */
        printf("        (no map)\n");
        addrWidth = 0;
    } else if (format == 2) {       /* kRegMapFormatCompact8 */
        addrWidth = 1;
    } else if (format == 3) {       /* kRegMapFormatCompact16 */
        addrWidth = 2;
    } else if (format == 4) {       /* kRegMapFormatDifferential */
        dumpDifferentialCompressedMap(&data);
        goto bail;
    } else {
        printf("        (unknown format %d!)\n", format);
        /* don't know how to skip data; failure will cascade to end of class */
        goto bail;
    }

    if (addrWidth > 0) {
        u1 regWidth;
        u2 numEntries;
        int idx, addr, byte;

        regWidth = *data++;
        numEntries = *data++;
        numEntries |= (*data++) << 8;

        for (idx = 0; idx < numEntries; idx++) {
            addr = *data++;
            if (addrWidth > 1)
                addr |= (*data++) << 8;

            printf("        %4x:", addr);
            for (byte = 0; byte < regWidth; byte++) {
                printf(" %02x", *data++);
            }
            printf("\n");
        }
    }

bail:
    //if (addrWidth >= 0)
    //    *pData = align32(data);
    *pData = data;
}

/*
 * Dump the contents of the register map area.
 *
 * These are only present in optimized DEX files, and the structure is
 * not really exposed to other parts of the VM itself.  We're going to
 * dig through them here, but this is pretty fragile.  DO NOT rely on
 * this or derive other code from it.
 */
void dumpRegisterMaps(DexFile* pDexFile)
{
    const u1* pClassPool = (const u1*)pDexFile->pRegisterMapPool;
    const u4* classOffsets;
    const u1* ptr;
    u4 numClasses;
    int baseFileOffset = (u1*) pClassPool - (u1*) pDexFile->pOptHeader;
    int idx;

    if (pClassPool == NULL) {
        printf("No register maps found\n");
        return;
    }

    ptr = pClassPool;
    numClasses = get4LE(ptr);
    ptr += sizeof(u4);
    classOffsets = (const u4*) ptr;

    printf("RMAP begins at offset 0x%07x\n", baseFileOffset);
    printf("Maps for %d classes\n", numClasses);
    for (idx = 0; idx < (int) numClasses; idx++) {
        const DexClassDef* pClassDef;
        const char* classDescriptor;

        pClassDef = dexGetClassDef(pDexFile, idx);
        classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx);

        printf("%4d: +%d (0x%08x) %s\n", idx, classOffsets[idx],
            baseFileOffset + classOffsets[idx], classDescriptor);

        if (classOffsets[idx] == 0)
            continue;

        /*
         * What follows is a series of RegisterMap entries, one for every
         * direct method, then one for every virtual method.
         */
        DexClassData* pClassData;
        const u1* pEncodedData;
        const u1* data = (u1*) pClassPool + classOffsets[idx];
        u2 methodCount;
        int i;

        pEncodedData = dexGetClassData(pDexFile, pClassDef);
        pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL);
        if (pClassData == NULL) {
            fprintf(stderr, "Trouble reading class data\n");
            continue;
        }

        methodCount = *data++;
        methodCount |= (*data++) << 8;
        data += 2;      /* two pad bytes follow methodCount */
        if (methodCount != pClassData->header.directMethodsSize
                            + pClassData->header.virtualMethodsSize)
        {
            printf("NOTE: method count discrepancy (%d != %d + %d)\n",
                methodCount, pClassData->header.directMethodsSize,
                pClassData->header.virtualMethodsSize);
            /* this is bad, but keep going anyway */
        }

        printf("    direct methods: %d\n",
            pClassData->header.directMethodsSize);
        for (i = 0; i < (int) pClassData->header.directMethodsSize; i++) {
            dumpMethodMap(pDexFile, &pClassData->directMethods[i], i, &data);
        }

        printf("    virtual methods: %d\n",
            pClassData->header.virtualMethodsSize);
        for (i = 0; i < (int) pClassData->header.virtualMethodsSize; i++) {
            dumpMethodMap(pDexFile, &pClassData->virtualMethods[i], i, &data);
        }

        free(pClassData);
    }
}

static const DexMapItem* findMapItem(const DexFile* pDexFile, u4 type)
{
    const u4 offset = pDexFile->pHeader->mapOff;
    const DexMapList* list = (const DexMapList*)(pDexFile->baseAddr + offset);
    for (u4 i = 0; i < list->size; ++i) {
        if (list->list[i].type == type) {
            return &list->list[i];
        }
    }
    return nullptr;
}

static void dumpMethodHandles(DexFile* pDexFile)
{
    const DexMapItem* item = findMapItem(pDexFile, kDexTypeMethodHandleItem);
    if (item == nullptr) return;
    const DexMethodHandleItem* method_handles =
            (const DexMethodHandleItem*)(pDexFile->baseAddr + item->offset);
    for (u4 i = 0; i < item->size; ++i) {
        const DexMethodHandleItem& mh = method_handles[i];
        const char* type;
        bool is_invoke;
        bool is_static;
        switch ((MethodHandleType) mh.methodHandleType) {
            case MethodHandleType::STATIC_PUT:
                type = "put-static";
                is_invoke = false;
                is_static = true;
                break;
            case MethodHandleType::STATIC_GET:
                type = "get-static";
                is_invoke = false;
                is_static = true;
                break;
            case MethodHandleType::INSTANCE_PUT:
                type = "put-instance";
                is_invoke = false;
                is_static = false;
                break;
            case MethodHandleType::INSTANCE_GET:
                type = "get-instance";
                is_invoke = false;
                is_static = false;
                break;
            case MethodHandleType::INVOKE_STATIC:
                type = "invoke-static";
                is_invoke = true;
                is_static = true;
                break;
            case MethodHandleType::INVOKE_INSTANCE:
                type = "invoke-instance";
                is_invoke = true;
                is_static = false;
                break;
            case MethodHandleType::INVOKE_CONSTRUCTOR:
                type = "invoke-constructor";
                is_invoke = true;
                is_static = false;
                break;
            case MethodHandleType::INVOKE_DIRECT:
                type = "invoke-direct";
                is_invoke = true;
                is_static = false;
                break;
            case  MethodHandleType::INVOKE_INTERFACE:
                type = "invoke-interface";
                is_invoke = true;
                is_static = false;
                break;
            default:
                printf("Unknown method handle type 0x%02x, skipped.", mh.methodHandleType);
                continue;
        }

        FieldMethodInfo info;
        if (is_invoke) {
            if (!getMethodInfo(pDexFile, mh.fieldOrMethodIdx, &info)) {
                printf("Unknown method handle target method@%04x, skipped.", mh.fieldOrMethodIdx);
                continue;
            }
        } else {
            if (!getFieldInfo(pDexFile, mh.fieldOrMethodIdx, &info)) {
                printf("Unknown method handle target field@%04x, skipped.", mh.fieldOrMethodIdx);
                continue;
            }
        }

        const char* instance = is_static ? "" : info.classDescriptor;

        if (gOptions.outputFormat == OUTPUT_XML) {
            printf("<method_handle index index=\"%u\"\n", i);
            printf(" type=\"%s\"\n", type);
            printf(" target_class=\"%s\"\n", info.classDescriptor);
            printf(" target_member=\"%s\"\n", info.name);
            printf(" target_member_type=\"%c%s%s\"\n",
                   info.signature[0], instance, info.signature + 1);
            printf("</method_handle>\n");
        } else {
            printf("Method Handle #%u:\n", i);
            printf("  type        : %s\n", type);
            printf("  target      : %s %s\n", info.classDescriptor, info.name);
            printf("  target_type : %c%s%s\n", info.signature[0], instance, info.signature + 1);
        }
    }
}

/* Helper for dumpCallSites(), which reads a 1- to 8- byte signed
 * little endian value. */
static u8 readSignedLittleEndian(const u1** pData, u4 size) {
    const u1* data = *pData;
    u8 result = 0;
    u4 i;

    for (i = 0; i < size; i++) {
        result = (result >> 8) | (((int64_t)*data++) << 56);
    }

    result >>= (8 - size) * 8;
    *pData = data;
    return result;
}

/* Helper for dumpCallSites(), which reads a 1- to 8- byte unsigned
 * little endian value. */
static u8 readUnsignedLittleEndian(const u1** pData, u4 size, bool fillOnRight = false) {
    const u1* data = *pData;
    u8 result = 0;
    u4 i;

    for (i = 0; i < size; i++) {
        result = (result >> 8) | (((u8)*data++) << 56);
    }

    if (!fillOnRight) {
        result >>= (8u - size) * 8;
    }

    *pData = data;
    return result;
}

static void dumpCallSites(DexFile* pDexFile)
{
    const DexMapItem* item = findMapItem(pDexFile, kDexTypeCallSiteIdItem);
    if (item == nullptr) return;
    const DexCallSiteId* ids = (const DexCallSiteId*)(pDexFile->baseAddr + item->offset);
    for (u4 index = 0; index < item->size; ++index) {
        bool doXml = (gOptions.outputFormat == OUTPUT_XML);
        printf(doXml ? "<call_site index=\"%u\" offset=\"%u\">\n" : "Call Site #%u // offset %u\n",
               index, ids[index].callSiteOff);
        const u1* data = pDexFile->baseAddr + ids[index].callSiteOff;
        u4 count = readUnsignedLeb128(&data);
        for (u4 i = 0; i < count; ++i) {
            printf(doXml ? "<link_argument index=\"%u\" " : "  link_argument[%u] : ", i);
            u1 headerByte = *data++;
            u4 valueType = headerByte & kDexAnnotationValueTypeMask;
            u4 valueArg = headerByte >> kDexAnnotationValueArgShift;
            switch (valueType) {
                case kDexAnnotationByte: {
                    printf(doXml ? "type=\"byte\" value=\"%d\"/>" : "%d (byte)", (int)*data++);
                    break;
                }
                case kDexAnnotationShort: {
                    printf(doXml ? "type=\"short\" value=\"%d\"/>" : "%d (short)",
                           (int) readSignedLittleEndian(&data, valueArg + 1));
                    break;
                }
                case kDexAnnotationChar: {
                    printf(doXml ? "type=\"short\" value=\"%u\"/>" : "%u (char)",
                           (u2) readUnsignedLittleEndian(&data, valueArg + 1));
                    break;
                }
                case kDexAnnotationInt: {
                    printf(doXml ? "type=\"int\" value=\"%d\"/>" : "%d (int)",
                           (int) readSignedLittleEndian(&data, valueArg + 1));
                    break;
                }
                case kDexAnnotationLong: {
                    printf(doXml ? "type=\"long\" value=\"%" PRId64 "\"/>" : "%" PRId64 " (long)",
                           (int64_t) readSignedLittleEndian(&data, valueArg + 1));
                    break;
                }
                case kDexAnnotationFloat: {
                    u4 rawValue = (u4) (readUnsignedLittleEndian(&data, valueArg + 1, true) >> 32);
                    printf(doXml ? "type=\"float\" value=\"%g\"/>" : "%g (float)",
                           *((float*) &rawValue));
                    break;
                }
                case kDexAnnotationDouble: {
                    u8 rawValue = readUnsignedLittleEndian(&data, valueArg + 1, true);
                    printf(doXml ? "type=\"double\" value=\"%g\"/>" : "%g (double)",
                           *((double*) &rawValue));
                    break;
                }
                case kDexAnnotationMethodType: {
                    u4 idx = (u4) readUnsignedLittleEndian(&data, valueArg + 1);
                    ProtoInfo protoInfo;
                    memset(&protoInfo, 0, sizeof(protoInfo));
                    getProtoInfo(pDexFile, idx, &protoInfo);
                    printf(doXml ? "type=\"MethodType\" value=\"(%s)%s\"/>" : "(%s)%s (MethodType)",
                           protoInfo.parameterTypes, protoInfo.returnType);
                    free(protoInfo.parameterTypes);
                    break;
                }
                case kDexAnnotationMethodHandle: {
                    u4 idx = (u4) readUnsignedLittleEndian(&data, valueArg + 1);
                    printf(doXml ? "type=\"MethodHandle\" value=\"%u\"/>" : "%u (MethodHandle)",
                           idx);
                    break;
                }
                case kDexAnnotationString: {
                    u4 idx = (u4) readUnsignedLittleEndian(&data, valueArg + 1);
                    printf(doXml ? "type=\"String\" value=\"%s\"/>" : "%s (String)",
                           dexStringById(pDexFile, idx));
                    break;
                }
                case kDexAnnotationType: {
                    u4 idx = (u4) readUnsignedLittleEndian(&data, valueArg + 1);
                    printf(doXml ? "type=\"Class\" value=\"%s\"/>" : "%s (Class)",
                           dexStringByTypeIdx(pDexFile, idx));
                    break;
                }
                case kDexAnnotationNull: {
                    printf(doXml ? "type=\"null\" value=\"null\"/>" : "null (null)");
                    break;
                }
                case kDexAnnotationBoolean: {
                    printf(doXml ? "type=\"boolean\" value=\"%s\"/>" : "%s (boolean)",
                           (valueArg & 1) == 0 ? "false" : "true");
                    break;
                }
                default:
                    // Other types are not anticipated being reached here.
                    printf("Unexpected type found, bailing on call site info.\n");
                    i = count;
                    break;
            }
            printf("\n");
        }

        if (doXml) {
            printf("</callsite>\n");
        }
    }
}

/*
 * Dump the requested sections of the file.
 */
void processDexFile(const char* fileName, DexFile* pDexFile)
{
    char* package = NULL;
    int i;

    if (gOptions.verbose) {
        printf("Opened '%s', DEX version '%.3s'\n", fileName,
            pDexFile->pHeader->magic +4);
    }

    if (gOptions.dumpRegisterMaps) {
        dumpRegisterMaps(pDexFile);
        return;
    }

    if (gOptions.showFileHeaders) {
        dumpFileHeader(pDexFile);
        dumpOptDirectory(pDexFile);
    }

    if (gOptions.outputFormat == OUTPUT_XML)
        printf("<api>\n");

    for (i = 0; i < (int) pDexFile->pHeader->classDefsSize; i++) {
        if (gOptions.showSectionHeaders)
            dumpClassDef(pDexFile, i);

        dumpClass(pDexFile, i, &package);
    }

    dumpMethodHandles(pDexFile);
    dumpCallSites(pDexFile);

    /* free the last one allocated */
    if (package != NULL) {
        printf("</package>\n");
        free(package);
    }

    if (gOptions.outputFormat == OUTPUT_XML)
        printf("</api>\n");
}


/*
 * Process one file.
 */
int process(const char* fileName)
{
    DexFile* pDexFile = NULL;
    MemMapping map;
    bool mapped = false;
    int result = -1;

    if (gOptions.verbose)
        printf("Processing '%s'...\n", fileName);

    if (dexOpenAndMap(fileName, gOptions.tempFileName, &map, false) != 0) {
        return result;
    }
    mapped = true;

    int flags = kDexParseVerifyChecksum;
    if (gOptions.ignoreBadChecksum)
        flags |= kDexParseContinueOnError;

    pDexFile = dexFileParse((u1*)map.addr, map.length, flags);
    if (pDexFile == NULL) {
        fprintf(stderr, "ERROR: DEX parse failed\n");
        goto bail;
    }

    if (gOptions.checksumOnly) {
        printf("Checksum verified\n");
    } else {
        processDexFile(fileName, pDexFile);
    }

    result = 0;

bail:
    if (mapped)
        sysReleaseShmem(&map);
    if (pDexFile != NULL)
        dexFileFree(pDexFile);
    return result;
}


/*
 * Show usage.
 */
void usage(void)
{
    fprintf(stderr, "Copyright (C) 2007 The Android Open Source Project\n\n");
    fprintf(stderr,
        "%s: [-c] [-d] [-f] [-h] [-i] [-l layout] [-m] [-t tempfile] dexfile...\n",
        gProgName);
    fprintf(stderr, "\n");
    fprintf(stderr, " -c : verify checksum and exit\n");
    fprintf(stderr, " -d : disassemble code sections\n");
    fprintf(stderr, " -f : display summary information from file header\n");
    fprintf(stderr, " -h : display file header details\n");
    fprintf(stderr, " -i : ignore checksum failures\n");
    fprintf(stderr, " -l : output layout, either 'plain' or 'xml'\n");
    fprintf(stderr, " -m : dump register maps (and nothing else)\n");
    fprintf(stderr, " -t : temp file name (defaults to /sdcard/dex-temp-*)\n");
}

/*
 * Parse args.
 *
 * I'm not using getopt_long() because we may not have it in libc.
 */
int main(int argc, char* const argv[])
{
    bool wantUsage = false;
    int ic;

    memset(&gOptions, 0, sizeof(gOptions));
    gOptions.verbose = true;

    while (1) {
        ic = getopt(argc, argv, "cdfhil:mt:");
        if (ic < 0)
            break;

        switch (ic) {
        case 'c':       // verify the checksum then exit
            gOptions.checksumOnly = true;
            break;
        case 'd':       // disassemble Dalvik instructions
            gOptions.disassemble = true;
            break;
        case 'f':       // dump outer file header
            gOptions.showFileHeaders = true;
            break;
        case 'h':       // dump section headers, i.e. all meta-data
            gOptions.showSectionHeaders = true;
            break;
        case 'i':       // continue even if checksum is bad
            gOptions.ignoreBadChecksum = true;
            break;
        case 'l':       // layout
            if (strcmp(optarg, "plain") == 0) {
                gOptions.outputFormat = OUTPUT_PLAIN;
            } else if (strcmp(optarg, "xml") == 0) {
                gOptions.outputFormat = OUTPUT_XML;
                gOptions.verbose = false;
                gOptions.exportsOnly = true;
            } else {
                wantUsage = true;
            }
            break;
        case 'm':       // dump register maps only
            gOptions.dumpRegisterMaps = true;
            break;
        case 't':       // temp file, used when opening compressed Jar
            gOptions.tempFileName = optarg;
            break;
        default:
            wantUsage = true;
            break;
        }
    }

    if (optind == argc) {
        fprintf(stderr, "%s: no file specified\n", gProgName);
        wantUsage = true;
    }

    if (gOptions.checksumOnly && gOptions.ignoreBadChecksum) {
        fprintf(stderr, "Can't specify both -c and -i\n");
        wantUsage = true;
    }

    if (wantUsage) {
        usage();
        return 2;
    }

    int result = 0;
    while (optind < argc) {
        result |= process(argv[optind++]);
    }

    return (result != 0);
}
