/*
 * $Id: /mirror/trunk/languages/dotnet/pmc/dotnetassembly.pmc 30749 2008-09-03T07:40:37.912150Z chromatic  $
 * Copyright (C) 2006-2008, The Perl Foundation.
 */

/* .NET CLI Assembly PMC */

#include "parrot/extend.h"
#include "tableinfo.h"
#include "structures.h"


/* We'll need to know the type numbers of related classes. */
static INTVAL dynclass_DotNetClassMetadata;
static INTVAL dynclass_DotNetMethodMetadata;
static INTVAL dynclass_DotNetFieldMetadata;
static INTVAL dynclass_DotNetParamMetadata;
static INTVAL dynclass_DotNetBytecode;
static INTVAL dynclass_DotNetEH;
static INTVAL dynclass_DotNetTypeRefMetadata;
static INTVAL dynclass_DotNetMemberRefMetadata;
static INTVAL dynclass_DotNetAssemblyRefMetadata;


/* This frees memory associated with a dotnet_assembly structure. */
static void destroy_dotnet_assembly(dotnet_assembly *ass)
{
    if (ass != NULL)
    {
        if (ass->strings)
            free(ass->strings);
        if (ass->user_strings)
            free(ass->user_strings);
        if (ass->blobs)
            free(ass->blobs);
        if (ass->tables)
            free(ass->tables);
        free(ass);
    }
}


/* This creates a new dotnet_assembly structure and sets the filename. */
static dotnet_assembly* new_dotnet_assembly(STRING *s)
{
    dotnet_assembly *ass = mem_allocate_zeroed_typed(dotnet_assembly);
    ass->filename = s;
    return ass;
}


/* These functions are to read a words stored in little endian format
   to the endian format of the current machine from both files and
   in-memory streams. */
static Parrot_UInt4 read_word_u(FILE *in)
{
    Parrot_UInt4 value;
    char *valptr = (char*) &value;
    fread(valptr, 4, 1, in);
    if (PARROT_BIGENDIAN)
    {
        char x = valptr[0];
        valptr[0] = valptr[3];
        valptr[3] = x;
        x = valptr[1];
        valptr[1] = valptr[2];
        valptr[2] = x;
    }
    return value;
}
static Parrot_UInt4 stream_word_u(char *stream, int offset)
{
    Parrot_UInt4 value;
    char *valptr = (char*) &value;
    int i;
    if (PARROT_BIGENDIAN)
        for (i = 0; i < 4; i++)
            valptr[3 - i] = stream[offset + i];
    else
        for (i = 0; i < 4; i++)
            valptr[i] = stream[offset + i];
    return value;
}
static Parrot_Int4 read_word(FILE *in)
{
    Parrot_Int4 value;
    char *valptr = (char*) &value;
    fread(valptr, 4, 1, in);
    if (PARROT_BIGENDIAN)
    {
        char x = valptr[0];
        valptr[0] = valptr[3];
        valptr[3] = x;
        x = valptr[1];
        valptr[1] = valptr[2];
        valptr[2] = x;
    }
    return value;
}
static Parrot_Int4 stream_word(char *stream, int offset)
{
    Parrot_Int4 value;
    char *valptr = (char*) &value;
    int i;
    if (PARROT_BIGENDIAN)
        for (i = 0; i < 4; i++)
            valptr[3 - i] = stream[offset + i];
    else
        for (i = 0; i < 4; i++)
            valptr[i] = stream[offset + i];
    return value;
}
static Parrot_UInt2 read_short_u(FILE *in)
{
    Parrot_UInt2 value;
    char *valptr = (char*) &value;
    fread(valptr, 2, 1, in);
    if (PARROT_BIGENDIAN)
    {
        char x = valptr[0];
        valptr[0] = valptr[1];
        valptr[1] = x;
    }
    return value;
}
static Parrot_UInt2 stream_short_u(char *stream, int offset)
{
    Parrot_Int2 value;
    char *valptr = (char*) &value;
    int i;
    if (PARROT_BIGENDIAN)
        for (i = 0; i < 2; i++)
            valptr[1 - i] = stream[offset + i];
    else
        for (i = 0; i < 2; i++)
            valptr[i] = stream[offset + i];
    return value;
}
static Parrot_Int2 read_short(FILE *in)
{
    Parrot_Int2 value;
    char *valptr = (char*) &value;
    fread(valptr, 2, 1, in);
    if (PARROT_BIGENDIAN)
    {
        char x = valptr[0];
        valptr[0] = valptr[1];
        valptr[1] = x;
    }
    return value;
}
static Parrot_Int2 stream_short(char *stream, int offset)
{
    Parrot_Int2 value;
    char *valptr = (char*) &value;
    int i;
    if (PARROT_BIGENDIAN)
        for (i = 0; i < 2; i++)
            valptr[1 - i] = stream[offset + i];
    else
        for (i = 0; i < 2; i++)
            valptr[i] = stream[offset + i];
    return value;
}


/* This function reads a string ending with a NULL padded to a 4 byte
   boundary. */
static char* read_string_padded(FILE *in)
{
    int   string_len = 16;
    int   string_pos = 0;
    char *string     = (char *)mem_sys_allocate(string_len);
    int   done       = 0;

    while (done == 0)
    {
        fread(string + string_pos, 1, 4, in);
        string_pos += 4;
        if (string_pos == string_len)
        {
            string_len *= 2;
            string      = (char *)mem_sys_realloc(string, string_len);
        }
        if (string[string_pos - 1] == 0)
            done = 1;
    }
    return string;
}


/* This converts a relative virtual address in a PE file into a position in
   the file. Returns 0 if the RVA cannot be converted. */
static int rva_to_filepos(FILE *in, dotnet_assembly *ass, Parrot_UInt4 rva)
{
    Parrot_UInt2 i;
    int saved_pos = ftell(in);
    int file_pos = 0;

    /* Seek to start of sections. */
    fseek(in, ass->pe_sections_pos, SEEK_SET);

    /* Search the sections to find what we need to calculate the file position
       corresponding to this RVA. */
    for (i = 0; i < ass->pe_sections && file_pos == 0; i++)
    {
        /* Get section's virtual address and offset. */
        Parrot_UInt4 section_rva, section_length, section_offset;
        fseek(in, 12, SEEK_CUR);
        section_rva = read_word_u(in);
        section_length = read_word_u(in);
        section_offset = read_word_u(in);
        fseek(in, 16, SEEK_CUR);

        /* If we're in the right segment for the RVA, calculate position. */
        if (section_rva < rva && section_rva + section_length > rva)
            file_pos = section_offset + (rva - section_rva);
    }

    /* Reset file position and return the position we found. */
    fseek(in, saved_pos, SEEK_SET);
    return file_pos;
}


/* This loads a section of data from a file into a chunk of memory. */
static char* load_to_memory(FILE *in, int file_pos, int size)
{
    char *loaded  = (char *)mem_sys_allocate(size);
    int saved_pos = ftell(in);

    /* Seek to position. */
    fseek(in, file_pos, SEEK_SET);
    fread(loaded, 1, size, in);

    /* Reset file position and return the memory holding the loaded data. */
    fseek(in, saved_pos, SEEK_SET);
    return loaded;
}


/* This loads the PE parts of the file. If there are any problems then
   throws a Parrot_ex_throw_from_c_args and returns zero. */
static int load_pe_file(Interp *INTERP, FILE *in, dotnet_assembly *ass)
{
    char temp[64];
    Parrot_UInt4 sig_location;
    Parrot_UInt2 machine;
    Parrot_UInt2 characteristics;
    Parrot_UInt2 opt_header_size;
    Parrot_UInt4 cli_header_rva;
    Parrot_UInt4 cli_header_filepos;
    unsigned char expected_header[60] = {
        0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00,
        0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00
    };

    /* Check start of MS DOS stub is as expected, just for sanity. */
    fread(temp, 1, 60, in);
    if (memcmp(temp, expected_header, 60) != 0)
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
                "Invalid MS DOS stub in PE header");

    /* Get offset to PE signature and seek to it. */
    sig_location = read_word_u(in);
    fseek(in, sig_location, SEEK_SET);

    /* Check PE signature is OK. */
    fread(temp, 1, 4, in);
    if (temp[0] != 'P' || temp[1] != 'E' || temp[2] != 0 || temp[3] != 0)
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
                "Invalid PE signature");

    /* Now read in the PE header. Care about number of sections for later on,
       plus we find out whether this is a DLL or not. */
    machine = read_short_u(in);
    if (machine != 0x14c)
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
                "Invalid machine type in PE header");

    ass->pe_sections = read_short_u(in);
    fseek(in, 12, SEEK_CUR);
    opt_header_size = read_short_u(in);
    characteristics = read_short_u(in);
    if (characteristics & 0x2000)
        ass->is_dll = 1;
    else
        ass->is_dll = 0;

    /* Next comes the PE optional header. From the standard bit, get relative
       virtual addresses of code and data. */
    fseek(in, 20, SEEK_CUR);
    ass->rva_code = read_word_u(in);
    ass->rva_data = read_word_u(in);

    /* Next is the NT specific bit of the header. It's boring. */
    fseek(in, 68, SEEK_CUR);

    /* The only thing of interest in the data directory for now is the RVA
       of the CLI header. */
    fseek(in, 112, SEEK_CUR);
    cli_header_rva = read_word_u(in);
    fseek(in, 12, SEEK_CUR);

    /* Sections start here. Stash pointer to start of sections (we'll need it
       for RVA lookups). */
    ass->pe_sections_pos = ftell(in);

    /* Attempt to locate position of CLI header. */
    cli_header_filepos = rva_to_filepos(in, ass, cli_header_rva);
    if (cli_header_filepos > 0)
    {
        /* Found header. Seek to it and return success. */
        fseek(in, cli_header_filepos, SEEK_SET);
        return 1;
    }
    else {
        /* Couldn't locate the CLI header. */
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
            "Cannot locate CLI header in PE file");
    }
}


/* This loads the CLI header, assuming that the file position is at the start
   of it. If any problem occurs, the function calls Parrot_ex_throw_from_c_args */

static int load_cli_header(Interp *INTERP, FILE *in, dotnet_assembly *ass)
{
    Parrot_UInt4 metadata_rva;
    int metadata_filepos;
    Parrot_UInt2 major_version, minor_version;

    /* Skip over header size. */
    fseek(in, 4, SEEK_CUR);

    /* Check version below 2.0. */
    major_version = read_short_u(in);
    minor_version = read_short_u(in);
    if (!(major_version < 2 || (major_version == 2 && minor_version == 0)))
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
            "Can only support CLI files up to version 2.0");

    /* Get metadata RVA and entry point token pointer. */
    metadata_rva = read_word_u(in);
    fseek(in, 4, SEEK_CUR);
    ass->entry_point = read_word_u(in);

    /* Try to resolve and seek to RVA. */
    metadata_filepos = rva_to_filepos(in, ass, metadata_rva);
    if (metadata_filepos > 0)
    {
        /* Found metadata root. Seek to it and return success. */
        fseek(in, metadata_filepos, SEEK_SET);
        return 1;
    }
    else {
        /* Couldn't locate the metadata root. */
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
            "Cannot locate CLI metadata root");
    }
}


/* This loads the .NET metadata streams from the file. */
static int load_cli_streams(Interp *INTERP, FILE *in, dotnet_assembly *ass)
{
    Parrot_UInt4 magic_word;
    Parrot_UInt4 vstring_length;
    Parrot_UInt2 num_streams, i;
    int metadata_pos = ftell(in);

    /* Check magic signature. */
    magic_word = read_word_u(in);
    if (magic_word != 0x424A5342)
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
            "Invalid magic signature in CLI metadata root");

    /* Skip over version data, version string and (unused) flags. */
    fseek(in, 8, SEEK_CUR);
    vstring_length = read_word_u(in);
    fseek(in, vstring_length + 2, SEEK_CUR);

    /* Now we start to look at the streams. Loop over them. */
    num_streams = read_short_u(in);
    for (i = 0; i < num_streams; i++)
    {
        /* Get stream info. */
        Parrot_UInt4 stream_pos = read_word_u(in);
        Parrot_UInt4 stream_size = read_word_u(in);
        char *name = read_string_padded(in);

        /* Now we'll do stuff to load the various types of stream. */
        if (strcmp(name, "#Strings") == 0)
        {
            /* Strings heap. We'll load this from the file. */
            ass->strings = load_to_memory(in, metadata_pos + stream_pos,
                stream_size);
            ass->strings_size = stream_size;
        }
        else if (strcmp(name, "#US") == 0)
        {
            /* User strings heap. We'll load this from the file. */
            ass->user_strings = load_to_memory(in, metadata_pos + stream_pos,
                stream_size);
            ass->user_strings_size = stream_size;
        }
        else if (strcmp(name, "#Blob") == 0)
        {
            /* Blobs heap. We'll load this from the file. */
            ass->blobs = load_to_memory(in, metadata_pos + stream_pos,
                stream_size);
            ass->blobs_size = stream_size;
        }
        else if (strcmp(name, "#GUID") == 0)
        {
            /* GUIDs. We ignore these for now. */
        }
        else if (strcmp(name, "#~") == 0) {
            /* Metadata tables stream. We'll load this from the file. */
            ass->tables = load_to_memory(in, metadata_pos + stream_pos,
                stream_size);
            ass->tables_size = stream_size;
        }
        else {
            /* Unknown stream. */
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
                "Unknown stream in CLI metadata root");
        }
        free(name);
    }
    return 1;
}


/* Function for checking if table is present in valid bit vector.
   (Not a macro; stupid C compilers moan about invalid shift sizes. */
static int TablePresent(dotnet_assembly *ass, int t)
{
        return t >= 32 ?
        (ass->valid_hi & (1 << (t - 32))) :
        (ass->valid_lo & (1 << t));
}


/* This calculates the positions and finds the lengths of all the tables. */
static int locate_tables(Interp *INTERP, dotnet_assembly *ass)
{
    char *stream = ass->tables;
    int cur_pos;
    int i;

    /* The heap size flags detail how big references into the heaps
       are for each type of heap. */
    ass->strings_ptr_size = (stream[6] & 0x01) ? 4 : 2;
    ass->guid_ptr_size    = (stream[6] & 0x02) ? 4 : 2;
    ass->blobs_ptr_size   = (stream[6] & 0x04) ? 4 : 2;

    /* Get valid tables vector. */
    ass->valid_lo = stream_word_u(stream, 8);
    ass->valid_hi = stream_word_u(stream, 12);

    /* Now go through and get table lengths. */
    cur_pos = 24;
    for (i = 0; i <= MAX_TABLE; i++)
    {
        /* Do we have this table in the file? */
        if (TablePresent(ass, i))
        {
            /* Yes. Read a rows count. */
            ass->rows[i] = stream_word_u(stream, cur_pos);
            cur_pos += 4;
        }
        else
            ass->rows[i] = 0;
    }

    /* The next chunk of code goes through each table type and, if it's
       in the file, saves its start position. */
    if (TablePresent(ass, Table_Module))
    {
        ass->positions[Table_Module] = cur_pos;
        cur_pos += ass->rows[Table_Module] * Table_Module_RL(ass);
    }
    if (TablePresent(ass, Table_TypeRef))
    {
        ass->positions[Table_TypeRef] = cur_pos;
        cur_pos += ass->rows[Table_TypeRef] * Table_TypeRef_RL(ass);
    }
    if (TablePresent(ass, Table_TypeDef))
    {
        ass->positions[Table_TypeDef] = cur_pos;
        cur_pos += ass->rows[Table_TypeDef] * Table_TypeDef_RL(ass);
    }
    if (TablePresent(ass, Table_Field))
    {
        ass->positions[Table_Field] = cur_pos;
        cur_pos += ass->rows[Table_Field] * Table_Field_RL(ass);
    }
    if (TablePresent(ass, Table_MethodDef))
    {
        ass->positions[Table_MethodDef] = cur_pos;
        cur_pos += ass->rows[Table_MethodDef] * Table_MethodDef_RL(ass);
    }
    if (TablePresent(ass, Table_Param))
    {
        ass->positions[Table_Param] = cur_pos;
        cur_pos += ass->rows[Table_Param] * Table_Param_RL(ass);
    }
    if (TablePresent(ass, Table_InterfaceImpl))
    {
        ass->positions[Table_InterfaceImpl] = cur_pos;
        cur_pos += ass->rows[Table_InterfaceImpl] * Table_InterfaceImpl_RL(ass);
    }
    if (TablePresent(ass, Table_MemberRef))
    {
        ass->positions[Table_MemberRef] = cur_pos;
        cur_pos += ass->rows[Table_MemberRef] * Table_MemberRef_RL(ass);
    }
    if (TablePresent(ass, Table_Constant))
    {
        ass->positions[Table_Constant] = cur_pos;
        cur_pos += ass->rows[Table_Constant] * Table_Constant_RL(ass);
    }
    if (TablePresent(ass, Table_CustomAttribute))
    {
        ass->positions[Table_CustomAttribute] = cur_pos;
        cur_pos += ass->rows[Table_CustomAttribute] * Table_CustomAttribute_RL(ass);
    }
    if (TablePresent(ass, Table_FieldMarshall))
    {
        ass->positions[Table_FieldMarshall] = cur_pos;
        cur_pos += ass->rows[Table_FieldMarshall] * Table_FieldMarshall_RL(ass);
    }
    if (TablePresent(ass, Table_DeclSecurity))
    {
        ass->positions[Table_DeclSecurity] = cur_pos;
        cur_pos += ass->rows[Table_DeclSecurity] * Table_DeclSecurity_RL(ass);
    }
    if (TablePresent(ass, Table_ClassLayout))
    {
        ass->positions[Table_ClassLayout] = cur_pos;
        cur_pos += ass->rows[Table_ClassLayout] * Table_ClassLayout_RL(ass);
    }
    if (TablePresent(ass, Table_FieldLayout))
    {
        ass->positions[Table_FieldLayout] = cur_pos;
        cur_pos += ass->rows[Table_FieldLayout] * Table_FieldLayout_RL(ass);
    }
    if (TablePresent(ass, Table_StandAloneSig))
    {
        ass->positions[Table_StandAloneSig] = cur_pos;
        cur_pos += ass->rows[Table_StandAloneSig] * Table_StandAloneSig_RL(ass);
    }
    if (TablePresent(ass, Table_EventMap))
    {
        ass->positions[Table_EventMap] = cur_pos;
        cur_pos += ass->rows[Table_EventMap] * Table_EventMap_RL(ass);
    }
    if (TablePresent(ass, Table_Event))
    {
        ass->positions[Table_Event] = cur_pos;
        cur_pos += ass->rows[Table_Event] * Table_Event_RL(ass);
    }
    if (TablePresent(ass, Table_PropertyMap))
    {
        ass->positions[Table_PropertyMap] = cur_pos;
        cur_pos += ass->rows[Table_PropertyMap] * Table_PropertyMap_RL(ass);
    }
    if (TablePresent(ass, Table_Property))
    {
        ass->positions[Table_Property] = cur_pos;
        cur_pos += ass->rows[Table_Property] * Table_Property_RL(ass);
    }
    if (TablePresent(ass, Table_MethodSemantics))
    {
        ass->positions[Table_MethodSemantics] = cur_pos;
        cur_pos += ass->rows[Table_MethodSemantics] * Table_MethodSemantics_RL(ass);
    }
    if (TablePresent(ass, Table_MethodImpl))
    {
        ass->positions[Table_MethodImpl] = cur_pos;
        cur_pos += ass->rows[Table_MethodImpl] * Table_MethodImpl_RL(ass);
    }
    if (TablePresent(ass, Table_ModuleRef))
    {
        ass->positions[Table_ModuleRef] = cur_pos;
        cur_pos += ass->rows[Table_ModuleRef] * Table_ModuleRef_RL(ass);
    }
    if (TablePresent(ass, Table_TypeSpec))
    {
        ass->positions[Table_TypeSpec] = cur_pos;
        cur_pos += ass->rows[Table_TypeSpec] * Table_TypeSpec_RL(ass);
    }
    if (TablePresent(ass, Table_ImplMap))
    {
        ass->positions[Table_ImplMap] = cur_pos;
        cur_pos += ass->rows[Table_ImplMap] * Table_ImplMap_RL(ass);
    }
    if (TablePresent(ass, Table_FieldRVA))
    {
        ass->positions[Table_FieldRVA] = cur_pos;
        cur_pos += ass->rows[Table_FieldRVA] * Table_FieldRVA_RL(ass);
    }
    if (TablePresent(ass, Table_Assembly))
    {
        ass->positions[Table_Assembly] = cur_pos;
        cur_pos += ass->rows[Table_Assembly] * Table_Assembly_RL(ass);
    }
    if (TablePresent(ass, Table_AssemblyProcessor))
    {
        ass->positions[Table_AssemblyProcessor] = cur_pos;
        cur_pos += ass->rows[Table_AssemblyProcessor] * Table_AssemblyProcessor_RL(ass);
    }
    if (TablePresent(ass, Table_AssemblyOS))
    {
        ass->positions[Table_AssemblyOS] = cur_pos;
        cur_pos += ass->rows[Table_AssemblyOS] * Table_AssemblyOS_RL(ass);
    }
    if (TablePresent(ass, Table_AssemblyRef))
    {
        ass->positions[Table_AssemblyRef] = cur_pos;
        cur_pos += ass->rows[Table_AssemblyRef] * Table_AssemblyRef_RL(ass);
    }
    if (TablePresent(ass, Table_AssemblyRefProcessor))
    {
        ass->positions[Table_AssemblyRefProcessor] = cur_pos;
        cur_pos += ass->rows[Table_AssemblyRefProcessor] * Table_AssemblyRefProcessor_RL(ass);
    }
    if (TablePresent(ass, Table_AssemblyRefOS))
    {
        ass->positions[Table_AssemblyRefOS] = cur_pos;
        cur_pos += ass->rows[Table_AssemblyRefOS] * Table_AssemblyRefOS_RL(ass);
    }
    if (TablePresent(ass, Table_File))
    {
        ass->positions[Table_File] = cur_pos;
        cur_pos += ass->rows[Table_File] * Table_File_RL(ass);
    }
    if (TablePresent(ass, Table_ExportedType))
    {
        ass->positions[Table_ExportedType] = cur_pos;
        cur_pos += ass->rows[Table_ExportedType] * Table_ExportedType_RL(ass);
    }
    if (TablePresent(ass, Table_ManifestResource))
    {
        ass->positions[Table_ManifestResource] = cur_pos;
        cur_pos += ass->rows[Table_ManifestResource] * Table_ManifestResource_RL(ass);
    }
    if (TablePresent(ass, Table_NestedClass))
    {
        ass->positions[Table_NestedClass] = cur_pos;
        cur_pos += ass->rows[Table_NestedClass] * Table_NestedClass_RL(ass);
    }
    return 1;
}


/* This takes an RVA and turns it into a bytecode walking PMC and array of
   exception handler PMCs. */
static PMC* make_bytecode_pmc(Interp *INTERP, PMC *self, dotnet_assembly *ass,
                           FILE *in, Parrot_UInt4 rva)
{
    int bc_filepos;
    unsigned char b1, b2;
    int flags;
    int header_size;
    Parrot_UInt4 sig_token;

    /* Create new PMC, underlying struct and tie 'em together. */
    PMC *bc = pmc_new(INTERP, dynclass_DotNetBytecode);
    dotnet_bytecode *bytecode = mem_allocate_zeroed_typed(dotnet_bytecode);
    PMC_struct_val(bc) = bytecode;

    /* Resolve RVA to file position. */
    bc_filepos = rva_to_filepos(in, ass, rva);
    if (bc_filepos == 0)
        /* Couldn't locate the bytecode. */
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
            "Cannot locate bytecode for method with RVA %d", rva);

    /* Set parent. */
    bytecode->parent = self;

    /* Seek to the file psotion. */
    fseek(in, bc_filepos, SEEK_SET);

    /* Get the first byte. */
    fread(&b1, 1, 1, in);

    /* Is this a thin or fat header? */
    if ((b1 & 0x3) == 2)
    {
        /* Thin header. Code size encoded in bits of this byte. */
        bytecode->body_size = b1 >> 2;
        bytecode->body      = (char *)mem_sys_allocate(bytecode->body_size);
        fread(bytecode->body, bytecode->body_size, 1, in);
    }
    else
    {
        /* Fat header. Need to get flags and header size. */
        fread(&b2, 1, 1, in);
        header_size = b2 >> 4;
        flags = ((b2 & 0xF) << 8) | b1;

        /* Skip over max stack. */
        fseek(in, 2, SEEK_CUR);

        /* Get code size and local signature location. */
        bytecode->body_size = read_word_u(in);
        sig_token = read_word_u(in);
        if (ass->rows[Table_StandAloneSig] < 2 << 15)
            sig_token &= 0xFFFF; /* RT#48130 Why do we have to do this? */

        /* Load code. */
        bytecode->body = (char *)mem_sys_allocate(bytecode->body_size);
        fseek(in, bc_filepos + (header_size * 4), SEEK_SET);
        fread(bytecode->body, bytecode->body_size, 1, in);

        /* Resolve signature token to offset. */
        if (sig_token > 0 && sig_token <= ass->rows[Table_StandAloneSig])
        {
            if (ass->blobs_ptr_size == 2)
            {
                bytecode->locals_signature = stream_short_u(ass->tables,
                    ass->positions[Table_StandAloneSig] +
                    ((sig_token - 1) * Table_StandAloneSig_RL(ass)));
            }
            else
            {
                bytecode->locals_signature = stream_word_u(ass->tables,
                    ass->positions[Table_StandAloneSig] +
                    ((sig_token - 1) * Table_StandAloneSig_RL(ass)));
            }
        }

        /* Set init locals flag as needed. */
        bytecode->init_locals_flag = flags & 0x10;

        /* Do we have extra sections? */
        if (b1 & 0x8)
        {
            int another_sect = 1;

            /* Initialize exceptions PMC array. */
            bytecode->eh = pmc_new(INTERP, enum_class_ResizablePMCArray);

            /* Align to 4-byte boundary. */
            if (bytecode->body_size & 0x3)
                fseek(in, 4 - (bytecode->body_size & 0x3), SEEK_CUR);

            /* Loop reading sections while there are some to read. */
            while (another_sect)
            {
                unsigned char kind;

                /* Read header flags and size. */
                fread(&kind, 1, 1, in);
                if ((kind & 0x40) && (kind & 0x01))
                {
                    /* Fat format header and fat exception handler blocks.
                       Loop over handlers.*/
                    Parrot_UInt4 class_token;
                    unsigned char s1, s2, s3;
                    int size;
                    int i;
                    fread(&s1, 1, 1, in);
                    fread(&s2, 1, 1, in);
                    fread(&s3, 1, 1, in);
                    size = s1 | (s2 << 8) | (s3 << 16);
                    for (i = 0; i < (size - 4) / 24; i++)
                    {
                        /* Create PMC, underlying struct and tie 'em together. */
                        PMC *eh = pmc_new(INTERP, dynclass_DotNetEH);
                        dotnet_eh *handler =
                            mem_allocate_zeroed_typed(dotnet_eh);
                        PMC_struct_val(eh) = handler;

                        /* Read data and stash it in the struct. */
                        handler->flags = read_word_u(in);
                        handler->try_offset = read_word_u(in);
                        handler->try_length = read_word_u(in);
                        handler->handler_offset = read_word_u(in);
                        handler->handler_length = read_word_u(in);
                        if (handler->flags == 1)
                        {
                            handler->filter_offset = read_word_u(in);
                        }
                        else
                        {
                            class_token = read_word_u(in);
                            handler->class_type = class_token >> 24;
                            handler->class_id = class_token & 0xFFFFFF;
                        }

                        /* Stash PMC in handlers array. */
                        VTABLE_push_pmc(INTERP, bytecode->eh, eh);
                    }
                }
                else if (kind & 0x01)
                {
                    /* Thin format header and thin exception handler blocks.
                       Loop over handlers.*/
                    Parrot_UInt4 class_token;
                    unsigned char size, tmp;
                    int i;
                    fread(&size, 1, 1, in);
                    fseek(in, 2, SEEK_CUR);
                    for (i = 0; i < (size - 4) / 12; i++)
                    {
                        /* Create PMC, underlying struct and tie 'em together. */
                        PMC *eh = pmc_new(INTERP, dynclass_DotNetEH);
                        dotnet_eh *handler =
                            mem_allocate_zeroed_typed(dotnet_eh);
                        PMC_struct_val(eh) = handler;

                        /* Read data and stash it in the struct. */
                        handler->flags = read_short_u(in);
                        handler->try_offset = read_short_u(in);
                        fread(&tmp, 1, 1, in);
                        handler->try_length = tmp;
                        handler->handler_offset = read_short_u(in);
                        fread(&tmp, 1, 1, in);
                        handler->handler_length = tmp;
                        if (handler->flags == 1)
                        {
                            handler->filter_offset = read_word_u(in);
                        }
                        else
                        {
                            class_token = read_word_u(in);
                            handler->class_type = class_token & 0x7;
                            handler->class_id = class_token >> 3;
                        }

                        /* Stash PMC in handlers array. */
                        VTABLE_push_pmc(INTERP, bytecode->eh, eh);
                    }
                }
                else {
                    /* Not exception data, not spec'd, not good. */
                    Parrot_ex_throw_from_c_args(INTERP, NULL,
                        EXCEPTION_INVALID_OPERATION,
                        "Unknown method data section (kind: %d)", (int) kind);
                }

                /* Loop no more if no more sections. */
                if (!(kind & 0x80))
                    another_sect = 0;
            }
        }
    }

    /* Return the PMC. */
    return bc;
}


/* This looks up a row in the params metadata table and makes a parameter PMC
   from it. */
static PMC* make_param_pmc(Interp *INTERP, PMC *self, dotnet_assembly *ass,
                           FILE *in, Parrot_UInt4 row)
{
    Parrot_UInt4 row_addr;
    Parrot_UInt4 pos_name;

    /* Create new PMC, underlying struct and tie 'em together. */
    PMC *p = pmc_new(INTERP, dynclass_DotNetParamMetadata);
    dotnet_param *param = mem_allocate_zeroed_typed(dotnet_param);
    PMC_struct_val(p) = param;

    /* Sanity check. */
    if (row > ass->rows[Table_Param])
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
            "Param row out of range.");

    /* Set parent. */
    param->parent = self;

    /* Calculate table row address. */
    row_addr = ass->positions[Table_Param] +
        ((row - 1) * Table_Param_RL(ass));

    /* Get flags and sequence. */
    param->flags = stream_short_u(ass->tables, row_addr);
    param->sequence = stream_short_u(ass->tables, row_addr + 2);

    /* Get name. */
    if (ass->strings_ptr_size == 2)
        pos_name = stream_short_u(ass->tables, row_addr + 4);
    else
        pos_name = stream_word_u(ass->tables, row_addr + 4);
    if (pos_name)
        param->str_name = string_from_cstring(INTERP, ass->strings + pos_name, 0);
    else
        param->str_name = NULL;

    /* Return the PMC. */
    return p;
}


/* This looks up a row in the fields metadata table and makes a field PMC
   from it. */
static PMC* make_field_pmc(Interp *INTERP, PMC *self, dotnet_assembly *ass,
                           FILE *in, Parrot_UInt4 row)
{
    Parrot_UInt4 row_addr;
    Parrot_UInt4 pos_name;
    Parrot_UInt4 cur_pos;

    /* Create new PMC, underlying struct and tie 'em together. */
    PMC *f = pmc_new(INTERP, dynclass_DotNetFieldMetadata);
    dotnet_field *field = mem_allocate_zeroed_typed(dotnet_field);
    PMC_struct_val(f) = field;

    /* Sanity check. */
    if (row > ass->rows[Table_Field])
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
            "Field row out of range.");

    /* Set parent. */
    field->parent = self;

    /* Calculate table row address. */
    row_addr = ass->positions[Table_Field] +
        ((row - 1) * Table_Field_RL(ass));

    /* Get flags. */
    field->flags = stream_short_u(ass->tables, row_addr);

    /* Get name. */
    if (ass->strings_ptr_size == 2)
    {
        pos_name = stream_short_u(ass->tables, row_addr + 2);
        cur_pos = 4;
    }
    else
    {
        pos_name = stream_word_u(ass->tables, row_addr + 2);
        cur_pos = 6;
    }
    if (pos_name)
        field->str_name = string_from_cstring(INTERP, ass->strings + pos_name, 0);
    else
        field->str_name = NULL;

    /* Get signature blob position. */
    if (ass->blobs_ptr_size == 2)
        field->signature = stream_short_u(ass->tables, row_addr + cur_pos);
    else
        field->signature = stream_word_u(ass->tables, row_addr + cur_pos);

    /* Return the PMC. */
    return f;
}


/* This looks up a row in the methods metadata table and makes a method PMC
   from it. */
static PMC* make_method_pmc(Interp *INTERP, PMC *self, dotnet_assembly *ass,
                            FILE *in, Parrot_UInt4 row)
{
    Parrot_UInt4 row_addr;
    Parrot_UInt4 rva;
    Parrot_UInt4 pos_name;
    Parrot_UInt4 cur_pos;
    Parrot_UInt4 first_param;
    Parrot_UInt4 last_param;
    Parrot_UInt4 i;

    /* Create new PMC, underlying struct and tie 'em together. */
    PMC *m = pmc_new(INTERP, dynclass_DotNetMethodMetadata);
    dotnet_method *method = mem_allocate_zeroed_typed(dotnet_method);
    PMC_struct_val(m) = method;

    /* Sanity check. */
    if (row > ass->rows[Table_MethodDef])
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
            "MethodDef row out of range.");

    /* Set parent. */
    method->parent = self;

    /* Calculate table row address. */
    row_addr = ass->positions[Table_MethodDef] +
        ((row - 1) * Table_MethodDef_RL(ass));

    /* Get RVA of method body. */
    rva = stream_word_u(ass->tables, row_addr);

    /* Get flags. */
    method->implFlags = stream_short_u(ass->tables, row_addr + 4);
    method->flags = stream_short_u(ass->tables, row_addr + 6);

    /* Get name. */
    if (ass->strings_ptr_size == 2)
    {
        pos_name = stream_short_u(ass->tables, row_addr + 8);
        cur_pos = 10;
    }
    else
    {
        pos_name = stream_word_u(ass->tables, row_addr + 8);
        cur_pos = 12;
    }
    if (pos_name)
        method->str_name = string_from_cstring(INTERP, ass->strings + pos_name, 0);
    else
        method->str_name = NULL;

    /* Get signature position. */
    if (ass->blobs_ptr_size == 2)
    {
        method->signature = stream_short_u(ass->tables, row_addr + cur_pos);
        cur_pos += 2;
    }
    else
    {
        method->signature = stream_word_u(ass->tables, row_addr + cur_pos);
        cur_pos += 4;
    }

    /* Get start and end of parameters list. */
    if (ass->rows[Table_Param] >= 2 << 15)
        first_param = stream_word_u(ass->tables, row_addr + cur_pos);
    else
        first_param = stream_short_u(ass->tables, row_addr + cur_pos);
    if (row < ass->rows[Table_MethodDef])
    {
        /* There's another method with some parameters. */
        if (ass->rows[Table_Param] >= 2 << 15)
            last_param = stream_word_u(ass->tables,
                row_addr + Table_MethodDef_RL(ass) + cur_pos);
        else
            last_param = stream_short_u(ass->tables,
                row_addr + Table_MethodDef_RL(ass) + cur_pos);
    }
    else
    {
        /* Last method, so last param is last row. */
        last_param = ass->rows[Table_Param] + 1;
    }

    /* Build parameters PMC array. */
    method->params = pmc_new(INTERP, enum_class_FixedPMCArray);
    VTABLE_set_integer_native(INTERP, method->params,
        last_param - first_param);
    for (i = first_param; i < last_param; i++)
    {
        PMC *p = make_param_pmc(INTERP, m, ass, in, i);
        if (p == NULL)
            return NULL;
        VTABLE_set_pmc_keyed_int(INTERP, method->params,
            i - first_param, p);
    }

    /* Create bytecode PMC, if we have an RVA. */
    if (rva != 0)
    {
        method->bytecode = make_bytecode_pmc(INTERP, m, ass, in, rva);
        if (method->bytecode == NULL)
            return NULL;
    }
    else
    {
        method->bytecode = NULL;
    }

    /* Return the PMC. */
    return m;
}


/* This takes a class and looks for all of its PMCs. */
static void add_class_interfaces(Interp *INTERP, PMC *self,
                                 dotnet_assembly *ass,
                                 Parrot_UInt4 row)
{
    Parrot_UInt4 cur_addr;
    Parrot_UInt4 i;
    dotnet_class *type = (dotnet_class *)PMC_struct_val(self);

    /* Set up two resizable PMC arrays for the interfaces info. */
    type->interface_types = pmc_new(INTERP, enum_class_ResizableIntegerArray);
    type->interface_ids = pmc_new(INTERP, enum_class_ResizableIntegerArray);

    /* Loop over the interfaces implementations table. */
    cur_addr = ass->positions[Table_InterfaceImpl];
    for (i = 0; i < ass->rows[Table_InterfaceImpl]; i++)
    {
        Parrot_UInt4 class_id, int_type, int_id;
        int class_info;

        /* Read class ID. */
        if (ass->rows[Table_TypeDef] < (2 << 15))
        {
            class_id = stream_short_u(ass->tables, cur_addr);
            cur_addr += 2;
        }
        else
        {
            class_id = stream_word_u(ass->tables, cur_addr);
            cur_addr += 4;
        }

        /* Read interface type and ID. */
        if (ass->rows[Table_TypeDef] < (2 << 13) &&
            ass->rows[Table_TypeRef] < (2 << 13) &&
            ass->rows[Table_TypeSpec] < (2 << 13))
        {
            class_info = stream_short_u(ass->tables, cur_addr);
            int_type = class_info & 0x3;
            int_id = class_info >> 2;
            cur_addr += 2;
        }
        else
        {
            class_info = stream_word_u(ass->tables, cur_addr);
            int_type = class_info & 0x3;
            int_id = class_info >> 2;
            cur_addr += 4;
        }

        /* If this is the class we're looking for, add to list. */
        if (class_id == row)
        {
            VTABLE_push_integer(INTERP, type->interface_types, int_type);
            VTABLE_push_integer(INTERP, type->interface_ids, int_id);
        }
    }
}


/* This looks up a row in the classes metadata table and makes a class PMC from
   it. */
static PMC* make_class_pmc(Interp *INTERP, PMC *self, dotnet_assembly *ass,
                           FILE *in, Parrot_UInt4 row)
{
    Parrot_UInt4 row_addr;
    Parrot_UInt4 pos_name;
    Parrot_UInt4 pos_namespace;
    Parrot_UInt4 cur_pos;
    Parrot_UInt4 first_method;
    Parrot_UInt4 last_method;
    Parrot_UInt4 first_field;
    Parrot_UInt4 last_field;
    Parrot_UInt4 i;

    /*  Create new PMC, underlying struct and tie 'em together. */
    PMC *c = pmc_new(INTERP, dynclass_DotNetClassMetadata);
    dotnet_class *type = mem_allocate_zeroed_typed(dotnet_class);
    PMC_struct_val(c) = type;

    /* Sanity check. */
    if (row > ass->rows[Table_TypeDef])
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
            "TypeDef row out of range.");

    /* Set parent. */
    type->parent = self;

    /* Calculate table row address. */
    row_addr = ass->positions[Table_TypeDef] +
        ((row - 1) * Table_TypeDef_RL(ass));

    /* Get flags. */
     type->flags = ass->tables[row_addr];

    /* Get name and namespace. */
    if (ass->strings_ptr_size == 2)
    {
        pos_name = stream_short_u(ass->tables, row_addr + 4);
        pos_namespace = stream_short_u(ass->tables, row_addr + 6);
        cur_pos = 8;
    }
    else
    {
        pos_name = stream_word_u(ass->tables, row_addr + 4);
        pos_namespace = stream_word_u(ass->tables, row_addr + 8);
        cur_pos = 12;
    }
    if (pos_name)
        type->str_name = string_from_cstring(INTERP, ass->strings + pos_name, 0);
    else
        type->str_name = NULL;
    if (pos_namespace)
    {
        type->str_namespace = string_from_cstring(INTERP,
            ass->strings + pos_namespace, 0);
        type->str_fullname = string_concat(INTERP, type->str_namespace,
            string_from_literal(INTERP, "."), 0);
        type->str_fullname = string_concat(INTERP, type->str_fullname,
            type->str_name, 0);
    }
    else
    {
        type->str_namespace = NULL;
        type->str_fullname = type->str_name;
    }

    /* Read extends entry. */
    if (ass->rows[Table_Module] < (2 << 13) &&
        ass->rows[Table_ModuleRef] < (2 << 13) &&
        ass->rows[Table_AssemblyRef] < (2 << 13) &&
        ass->rows[Table_TypeRef] < (2 << 13))
    {
        Parrot_UInt2 extends = stream_short_u(ass->tables, row_addr + cur_pos);
        type->parent_id = extends  >> 2;
        type->parent_type = extends & 0x3;
        cur_pos += 2;
    }
    else
    {
        Parrot_UInt4 extends = stream_word_u(ass->tables, row_addr + cur_pos);
        type->parent_id = extends >> 2;
        type->parent_type = extends & 0x3;
        cur_pos += 4;
    }

    /* Find interfaces. */
    add_class_interfaces(INTERP, c, ass, row);

    /* Get start and end of fields list. */
    if (ass->rows[Table_Field] >= 2 << 15)
        first_field = stream_word_u(ass->tables, row_addr + cur_pos);
    else
        first_field = stream_short_u(ass->tables, row_addr + cur_pos);
    if (row < ass->rows[Table_TypeDef])
    {
        /* There's another tyepdef with some fields. */
        if (ass->rows[Table_Field] >= 2 << 15)
            last_field = stream_word_u(ass->tables,
                row_addr + Table_TypeDef_RL(ass) + cur_pos);
        else
            last_field = stream_short_u(ass->tables,
                row_addr + Table_TypeDef_RL(ass) + cur_pos);
    }
    else
    {
        /* Last typedef, so last field is last row. */
        last_field = ass->rows[Table_Field] + 1;
    }
    cur_pos += ass->rows[Table_Field] >= 2 << 15 ? 4 : 2;

    /* Build fields PMC array. */
    type->fields = pmc_new(INTERP, enum_class_FixedPMCArray);
    VTABLE_set_integer_native(INTERP, type->fields,
        last_field - first_field);
    for (i = first_field; i < last_field; i++)
    {
        PMC *f = make_field_pmc(INTERP, c, ass, in, i);
        if (f == NULL)
            return NULL;
        VTABLE_set_pmc_keyed_int(INTERP, type->fields,
            i - first_field, f);
        VTABLE_set_pmc_keyed_int(INTERP, ass->fields,
            i - 1, f);
    }

    /* Get start and end of methods list. */
    if (ass->rows[Table_MethodDef] >= 2 << 15)
        first_method = stream_word_u(ass->tables, row_addr + cur_pos);
    else
        first_method = stream_short_u(ass->tables, row_addr + cur_pos);
    if (row < ass->rows[Table_TypeDef])
    {
        /* There's another tyepdef with some methods, maybe. */
        if (ass->rows[Table_MethodDef] >= 2 << 15)
            last_method = stream_word_u(ass->tables,
                row_addr + Table_TypeDef_RL(ass) + cur_pos);
        else
            last_method = stream_short_u(ass->tables,
                row_addr + Table_TypeDef_RL(ass) + cur_pos);
    }
    else
    {
        /* Last typedef, so last method is last row. */
        last_method = ass->rows[Table_MethodDef] + 1;
    }

    /* Build methods PMC array. */
    type->methods = pmc_new(INTERP, enum_class_FixedPMCArray);
    VTABLE_set_integer_native(INTERP, type->methods,
        last_method - first_method);
    for (i = first_method; i < last_method; i++)
    {
        PMC *m = make_method_pmc(INTERP, c, ass, in, i);
        if (m == NULL)
            return NULL;
        VTABLE_set_pmc_keyed_int(INTERP, type->methods,
            i - first_method, m);
        VTABLE_set_pmc_keyed_int(INTERP, ass->methods,
            i - 1, m);
    }

    /* Return the PMC. */
    return c;
}


/* This looks up a row in the typeref metadata table and makes a typeref PMC from
   it. */
static PMC* make_typeref_pmc(Interp *INTERP, PMC *self, dotnet_assembly *ass,
                           FILE *in, Parrot_UInt4 row)
{
    Parrot_UInt4 row_addr;
    Parrot_UInt4 pos_name;
    Parrot_UInt4 pos_namespace;
    Parrot_UInt4 cur_pos;

    /*  Create new PMC, underlying struct and tie 'em together. */
    PMC *tr = pmc_new(INTERP, dynclass_DotNetTypeRefMetadata);
    dotnet_typeref *typeref = mem_allocate_zeroed_typed(dotnet_typeref);
    PMC_struct_val(tr) = typeref;

    /* Sanity check. */
    if (row > ass->rows[Table_TypeRef])
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
            "TypeRef row out of range.");

    /* Calculate table row address. */
    row_addr = ass->positions[Table_TypeRef] +
        ((row - 1) * Table_TypeRef_RL(ass));

    /* Get resolution scope. */
    cur_pos = (ass->rows[Table_Module] < (2 << 13) &&
               ass->rows[Table_ModuleRef] < (2 << 13) &&
               ass->rows[Table_AssemblyRef] < (2 << 13) &&
               ass->rows[Table_TypeRef] < (2 << 13)) ? 2 : 4;
    if (cur_pos == 2)
    {
        Parrot_UInt2 rs = ass->tables[row_addr];
        typeref->resolution_scope = rs;
    }
    else
    {
        Parrot_UInt4 rs = ass->tables[row_addr];
        typeref->resolution_scope = rs;
    }

    /* Get name and namespace. */
    if (ass->strings_ptr_size == 2)
    {
        pos_name = stream_short_u(ass->tables, row_addr + cur_pos);
        pos_namespace = stream_short_u(ass->tables, row_addr + cur_pos + 2);
    }
    else
    {
        pos_name = stream_word_u(ass->tables, row_addr + cur_pos);
        pos_namespace = stream_word_u(ass->tables, row_addr + cur_pos + 4);
    }
    if (pos_name)
        typeref->str_name = string_from_cstring(INTERP, ass->strings + pos_name, 0);
    else
        typeref->str_name = NULL;
    if (pos_namespace)
        typeref->str_namespace = string_from_cstring(INTERP,
            ass->strings + pos_namespace, 0);
    else
        typeref->str_namespace = NULL;

    /* Return the PMC. */
    return tr;
}


/* This looks up a row in the MemberRef metadata table and makes a memberref
   PMC from it. */
static PMC* make_memberref_pmc(Interp *INTERP, PMC *self, dotnet_assembly *ass,
                               FILE *in, Parrot_UInt4 row)
{
    Parrot_UInt4 row_addr;
    Parrot_UInt4 pos_name;
    Parrot_UInt4 cur_pos;

    /*  Create new PMC, underlying struct and tie 'em together. */
    PMC *mr = pmc_new(INTERP, dynclass_DotNetMemberRefMetadata);
    dotnet_memberref *memberref = mem_allocate_zeroed_typed(dotnet_memberref);
    PMC_struct_val(mr) = memberref;

    /* Sanity check. */
    if (row > ass->rows[Table_MemberRef])
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
            "MemberRef row out of range.");

    /* Calculate table row address. */
    row_addr = ass->positions[Table_MemberRef] +
        ((row - 1) * Table_MemberRef_RL(ass));

    /* Get "class" and split into type and id. */
    if (ass->rows[Table_TypeRef] < (2 << 12) &&
        ass->rows[Table_ModuleRef] < (2 << 12) &&
        ass->rows[Table_MethodDef] < (2 << 12) &&
        ass->rows[Table_TypeSpec] < (2 << 12) &&
        ass->rows[Table_TypeDef] < (2 << 12))
    {
        int class_info = stream_short_u(ass->tables, row_addr);
        memberref->class_type = class_info & 0x7;
        memberref->class_id = class_info >> 3;
        cur_pos = 2;
    }
    else
    {
        int class_info = stream_word_u(ass->tables, row_addr);
        memberref->class_type = class_info & 0x7;
        memberref->class_id = class_info >> 3;
        cur_pos = 4;
    }

    /* Get name. */
    if (ass->strings_ptr_size == 2)
    {
        pos_name = stream_short_u(ass->tables, row_addr + cur_pos);
        cur_pos += 2;
    }
    else
    {
        pos_name = stream_word_u(ass->tables, row_addr + cur_pos);
        cur_pos += 4;
    }
    memberref->str_name = string_from_cstring(INTERP, ass->strings + pos_name, 0);

    /* Get signature. */
    if (ass->blobs_ptr_size == 2)
        memberref->signature = stream_short_u(ass->tables, row_addr + cur_pos);
    else
        memberref->signature = stream_word_u(ass->tables, row_addr + cur_pos);

    /* Return the PMC. */
    return mr;
}


/* This looks up a row in the AssemblyRef metadata table and makes a assemblyref
   PMC from it. */
static PMC* make_assemblyref_pmc(Interp *INTERP, PMC *self, dotnet_assembly *ass,
                                 FILE *in, Parrot_UInt4 row)
{
    Parrot_UInt4 row_addr;
    Parrot_UInt4 pos_name;
    Parrot_UInt4 pos_culture;
    Parrot_UInt4 cur_pos;

    /*  Create new PMC, underlying struct and tie 'em together. */
    PMC *ar = pmc_new(INTERP, dynclass_DotNetAssemblyRefMetadata);
    dotnet_assemblyref *assemblyref =
        mem_allocate_zeroed_typed(dotnet_assemblyref);
    PMC_struct_val(ar) = assemblyref;

    /* Sanity check. */
    if (row > ass->rows[Table_AssemblyRef])
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
            "AssemblyRef row out of range.");

    /* Calculate table row address. */
    row_addr = ass->positions[Table_AssemblyRef] +
        ((row - 1) * Table_AssemblyRef_RL(ass));

    /* Get version and flags. */
    assemblyref->version_major = stream_short_u(ass->tables, row_addr);
    assemblyref->version_minor = stream_short_u(ass->tables, row_addr + 2);
    assemblyref->version_build = stream_short_u(ass->tables, row_addr + 4);
    assemblyref->version_revision = stream_short_u(ass->tables, row_addr + 6);
    assemblyref->flags = stream_word_u(ass->tables, row_addr + 8);

    /* Get public key blob index. */
    if (ass->blobs_ptr_size == 2)
    {
        assemblyref->public_key = stream_short_u(ass->tables, row_addr + 12);
        cur_pos = 14;
    }
    else
    {
        assemblyref->public_key = stream_word_u(ass->tables, row_addr + 12);
        cur_pos = 16;
    }

    /* Get name. */
    if (ass->strings_ptr_size == 2)
    {
        pos_name = stream_short_u(ass->tables, row_addr + cur_pos);
        cur_pos += 2;
    }
    else
    {
        pos_name = stream_word_u(ass->tables, row_addr + cur_pos);
        cur_pos += 4;
    }
    assemblyref->str_name = string_from_cstring(INTERP, ass->strings + pos_name, 0);

    /* Get culture. */
    if (ass->strings_ptr_size == 2)
    {
        pos_culture = stream_short_u(ass->tables, row_addr + cur_pos);
        cur_pos += 2;
    }
    else
    {
        pos_culture = stream_word_u(ass->tables, row_addr + cur_pos);
        cur_pos += 4;
    }
    assemblyref->str_culture = string_from_cstring(INTERP, ass->strings + pos_culture, 0);

    /* Get hash value blob index. */
    if (ass->blobs_ptr_size == 2)
        assemblyref->hash_value = stream_short_u(ass->tables, row_addr + cur_pos);
    else
        assemblyref->hash_value = stream_word_u(ass->tables, row_addr + cur_pos);

    /* Return the PMC. */
    return ar;
}


/* This produces an ordering for classes so those highest in the inheritance
   hierachy come first, since this is the order we need to generate them in
   the output file. */
static void sort_classes(Interp *INTERP, dotnet_assembly *ass)
{
    /* Bit of background. Classes in the .NET metadata can appear in any order
       but in the PIR we emit they must appear so those that are higher in an
       inheritance hierachy, either of classes or interfaces, come before sub-
       classes. This routine produces a list of class IDs that are ordered in
       such a way.

       We'll use an array of byte flags, one per class, that tracks which of
       the classes we have put in the ordered list so far. A class can only be
       put in the target list if its parents are flagged in the main list as
       done. */
    PMC  *target_list = pmc_new(INTERP, enum_class_FixedIntegerArray);
    int   num_classes = VTABLE_elements(INTERP, ass->classes);
    char *class_flags = (char *)mem_sys_allocate_zeroed(num_classes + 1);
    int   iters       = 0;
    int   done        = 0;
    int   i;

    /* Order list has as many elements as there are classes. */
    VTABLE_set_integer_native(INTERP, target_list, num_classes);

    /* Iterate to pick up all classes, with a check for infinite looping. */
    while (done < num_classes && iters <= num_classes + 1)
    {
        for (i = 0; i < num_classes; i++)
        {
            /* Get the class. */
            PMC *c = VTABLE_get_pmc_keyed_int(INTERP, ass->classes, i);
            dotnet_class *type = (dotnet_class *)PMC_struct_val(c);
            int have_all_parents = 1;

            /* If we've already got this class in the list, skip. */
            if (class_flags[i])
                continue;

            /* Checked inherited class parent type is not a reference or is
               in the list already. */
            if (type->parent_id != 0 && type->parent_type == 0 &&
                !class_flags[type->parent_id - 2])
            {
                have_all_parents = 0;
            }
            else
            {
                /* Parent class is OK, now check interfaces. */
                int int_count = VTABLE_elements(INTERP, type->interface_types);
                int j;
                for (j = 0; j < int_count; j++)
                {
                    int int_type = VTABLE_get_integer_keyed_int(INTERP,
                        type->interface_types, j);
                    int int_id = VTABLE_get_integer_keyed_int(INTERP,
                        type->interface_ids, j);
                    if (int_type == 0 && !class_flags[int_id - 2])
                    {
                        have_all_parents = 0;
                    }
                }
            }

            /* If all the parents are external or available, add this class to the list. */
            if (have_all_parents)
            {
                VTABLE_set_integer_keyed_int(INTERP, target_list, done, i);
                class_flags[i] = 1;
                done++;
            }
        }

        /* Bump up iteration count. */
        iters++;
    }

    /* If we iternated num_classes + 1 times, we failed to complete the sort.
       Throw our toys out of the pram. Oh, and an exception. Also do this if
       we did not order all classes. */
    if (iters == num_classes + 1 || done != num_classes)
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
            "Unable to order inheritance/interfaces.");


    /* Put sorted list in place. */
    ass->class_order = target_list;

    /* Free up memory we used. */
    mem_sys_free(class_flags);
}


/* This turns data in metadata tables into a bunch of PMCs. */
static int metadata_to_pmcs(Interp *INTERP, PMC *self, FILE *in,
                            dotnet_assembly *ass)
{
    Parrot_UInt4 row;

    /* Ensure we have a typedef table. */
    if (ass->rows[Table_TypeDef] == 0)
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
            "Type definitions table is empty.");

    /* First entry is global stuff. */
    ass->globals = make_class_pmc(INTERP, self, ass, in, 1);
    if (ass->globals == NULL)
        return 0;

    /* Now we need a PMC array for storing class PMCs in. */
    ass->classes = pmc_new(INTERP, enum_class_FixedPMCArray);
    VTABLE_set_integer_native(INTERP, ass->classes,
        ass->rows[Table_TypeDef] - 1); /* Set size of array. */

    /* Also, we're going to build up tables of field and method PMCs. */
    ass->fields = pmc_new(INTERP, enum_class_FixedPMCArray);
    VTABLE_set_integer_native(INTERP, ass->fields,
        ass->rows[Table_Field]);
    ass->methods = pmc_new(INTERP, enum_class_FixedPMCArray);
    VTABLE_set_integer_native(INTERP, ass->methods,
        ass->rows[Table_MethodDef]);

    /* Loop over rows and create PMC for the class. */
    for (row = 2; row <= ass->rows[Table_TypeDef]; row++)
    {
        PMC *c = make_class_pmc(INTERP, self, ass, in, row);
        if (c == NULL)
        {
            ass->globals = NULL;
            ass->classes = NULL; /* Oh, GC, you reap so good... */
            ass->fields = NULL;
            ass->methods = NULL;
            return 0;
        }
        VTABLE_set_pmc_keyed_int(INTERP, ass->classes,
            row - 2, c);
    }

    /* Now we need to sort the list of classes by the inheritance hierachy. */
    sort_classes(INTERP, ass);

    /* We also need to grab linkage information. First, TypeRefs. */
    ass->typerefs = pmc_new(INTERP, enum_class_FixedPMCArray);
    VTABLE_set_integer_native(INTERP, ass->typerefs,
        ass->rows[Table_TypeRef]);
    for (row = 1; row <= ass->rows[Table_TypeRef]; row++)
    {
        PMC *tr = make_typeref_pmc(INTERP, self, ass, in, row);
        if (tr == NULL)
        {
            ass->globals = NULL;
            ass->classes = NULL;
            ass->fields = NULL;
            ass->methods = NULL;
            ass->typerefs = NULL;
            return 0;
        }
        VTABLE_set_pmc_keyed_int(INTERP, ass->typerefs, row - 1, tr);
    }

    /* Second, memberrefs. Gee, ain't this fun... */
    ass->memberrefs = pmc_new(INTERP, enum_class_FixedPMCArray);
    VTABLE_set_integer_native(INTERP, ass->memberrefs,
        ass->rows[Table_MemberRef]);
    for (row = 1; row <= ass->rows[Table_MemberRef]; row++)
    {
        PMC *mr = make_memberref_pmc(INTERP, self, ass, in, row);
        if (mr == NULL)
        {
            ass->globals = NULL;
            ass->classes = NULL;
            ass->fields = NULL;
            ass->methods = NULL;
            ass->typerefs = NULL;
            ass->memberrefs = NULL;
            return 0;
        }
        VTABLE_set_pmc_keyed_int(INTERP, ass->memberrefs, row - 1, mr);
    }

    /* Third, assemblyrefs. */
    ass->assemblyrefs = pmc_new(INTERP, enum_class_FixedPMCArray);
    VTABLE_set_integer_native(INTERP, ass->assemblyrefs,
        ass->rows[Table_AssemblyRef]);
    for (row = 1; row <= ass->rows[Table_AssemblyRef]; row++)
    {
        PMC *ar = make_assemblyref_pmc(INTERP, self, ass, in, row);
        if (ar == NULL)
        {
            ass->globals = NULL;
            ass->classes = NULL;
            ass->fields = NULL;
            ass->methods = NULL;
            ass->typerefs = NULL;
            ass->memberrefs = NULL;
            ass->assemblyrefs = NULL;
            return 0;
        }
        VTABLE_set_pmc_keyed_int(INTERP, ass->assemblyrefs, row - 1, ar);
    }

    /* Success. */
    return 1;
}


/* The class itself. */
pmclass DotNetAssembly dynpmc group dotnet {

    /* Class initialization. Look up other PMCs. */
    void class_init()
    {
        if (pass)
        {
            dynclass_DotNetClassMetadata = pmc_type(INTERP,
                string_from_literal(INTERP, "DotNetClassMetadata"));
            dynclass_DotNetMethodMetadata = pmc_type(INTERP,
                string_from_literal(INTERP, "DotNetMethodMetadata"));
            dynclass_DotNetFieldMetadata = pmc_type(INTERP,
                string_from_literal(INTERP, "DotNetFieldMetadata"));
            dynclass_DotNetParamMetadata = pmc_type(INTERP,
                string_from_literal(INTERP, "DotNetParamMetadata"));
            dynclass_DotNetBytecode = pmc_type(INTERP,
                string_from_literal(INTERP, "DotNetBytecode"));
            dynclass_DotNetEH = pmc_type(INTERP,
                string_from_literal(INTERP, "DotNetEH"));
            dynclass_DotNetTypeRefMetadata = pmc_type(INTERP,
                string_from_literal(INTERP, "DotNetTypeRefMetadata"));
            dynclass_DotNetMemberRefMetadata = pmc_type(INTERP,
                string_from_literal(INTERP, "DotNetMemberRefMetadata"));
            dynclass_DotNetAssemblyRefMetadata = pmc_type(INTERP,
                string_from_literal(INTERP, "DotNetAssemblyRef"));
        }
    }


    /* Instance initialization. We need a custom DOD marking and destroy. */
    void init()
    {
        PObj_custom_mark_SET(SELF);
        PObj_active_destroy_SET(SELF);
        PMC_struct_val(SELF) = NULL;
    }


    /* Get string vtable function; returns the filename, if one is set. */
    STRING* get_string()
    {
        dotnet_assembly *ass = (dotnet_assembly *)PMC_struct_val(SELF);
        if (ass)
            return ass->filename;
        else
            return NULL;
    }


    /* Set string vtable function; sets the filename. Invalidates any data
     * held relating to any previously loaded file. */
    void set_string_native(STRING* s)
    {
        /* Out with the old... */
        destroy_dotnet_assembly((dotnet_assembly *)PMC_struct_val(SELF));

        /* ...in with the (name of) the new. :-) */
        PMC_struct_val(SELF) = new_dotnet_assembly(s);
    }


    /* Stirb nicht vor mir */
    void mark()
    {
        /* Tell the GC about stuff we're holding on to. */
        dotnet_assembly *ass = (dotnet_assembly *)PMC_struct_val(SELF);
        if (ass)
        {
            if (ass->filename)
                pobject_lives(INTERP, (PObj*)ass->filename);
            if (ass->classes)
                pobject_lives(INTERP, (PObj*)ass->classes);
            if (ass->globals)
                pobject_lives(INTERP, (PObj*)ass->globals);
            if (ass->class_order)
                pobject_lives(INTERP, (PObj*)ass->class_order);
            if (ass->fields)
                pobject_lives(INTERP, (PObj*)ass->fields);
            if (ass->methods)
                pobject_lives(INTERP, (PObj*)ass->methods);
            if (ass->typerefs)
                pobject_lives(INTERP, (PObj*)ass->typerefs);
            if (ass->memberrefs)
                pobject_lives(INTERP, (PObj*)ass->memberrefs);
            if (ass->assemblyrefs)
                pobject_lives(INTERP, (PObj*)ass->assemblyrefs);
        }
    }


    /* Ich will ein guter Junge sein
       Doch das Verlangen holt mich ein
       Ich muss zerstören */
    void destroy()
    {
        /* Cleanup any memory we're using. */
        destroy_dotnet_assembly((dotnet_assembly *)PMC_struct_val(SELF));
    }


    /* The load method. */
    METHOD void load()
    {
        dotnet_assembly *ass = (dotnet_assembly *)PMC_struct_val(SELF);
        FILE *in;
        char *filename;

        /* If we don't have an assembly, throw an esception and leave. */
        if (!ass)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
                "No filename string set");

        /* If we've already loaded, we needn't do it again. */
        if (ass->loaded)
            return;

        /* Otherwise, we need to load it. */
        filename = string_to_cstring(INTERP, ass->filename);
        in = fopen(filename, "rb");
        free(filename);

        if (!in)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_PIO_ERROR,
                "Unable to open file %s", filename);

        /* Attempt to load the PE parts of the file; this locates the CLI
           header and positions us at the start of it. */
        if (!load_pe_file(INTERP, in, ass))
        {
            fclose(in);
            return;
        }

        /* Attempt to load the CLI header. This positions us at the root of
           the metadata. */
        if (!load_cli_header(INTERP, in, ass))
        {
            fclose(in);
            return;
        }

        /* Attempt to load the CLI streams into memory. */
        if (!load_cli_streams(INTERP, in, ass))
        {
            fclose(in);
            return;
        }

        /* Now locate the table start positions and their lengths. */
        if (!locate_tables(INTERP, ass))
        {
            fclose(in);
            return;
        }

        /* We create some PMCs that aren't reachable for a while, and GC
           runs will eat them. Thus we'll block DOD while we do it (and
           unblock it again later). */
        Parrot_block_GC_mark(INTERP);

        /* Finally, we're ready to do something useful. Turn metadata into
           a myriad of PMCs. Woohoo! */
        if (!metadata_to_pmcs(INTERP, SELF, in, ass))
        {
            fclose(in);
            Parrot_unblock_GC_mark(INTERP);
            return;
        }
        Parrot_unblock_GC_mark(INTERP);

        /* We're done. Close file and set loaded flag. */
        fclose(in);
        ass->loaded = 1;
    }


    METHOD INTVAL is_dll()
    {
        /* Provided we have an assembly, return the flag. */
        dotnet_assembly *ass = (dotnet_assembly *)PMC_struct_val(SELF);
        if (ass) {
            INTVAL result = ass->is_dll;
            RETURN(INTVAL result);
        }
        else
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
                "No .NET CLI file loaded");
    }


    METHOD STRING* get_internal_string(int i)
    {
        dotnet_assembly *ass     = (dotnet_assembly *)PMC_struct_val(SELF);
        Parrot_UInt4     cur_pos = i;
        STRING          *result;

        /* If we don't have an assembly, throw an exception and leave. */
        if (!ass)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "No .NET CLI file loaded");

        /* Otherwise, check string is within range. */
        if ((Parrot_UInt4)i >= ass->strings_size)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "String position out of range");

        /* Create and return a new string (pass 0 as length; end found
           automatically). */

        result = string_from_cstring(INTERP, ass->strings + i, 0);
        RETURN(STRING *result);
    }


    METHOD STRING* get_user_string(int i)
    {
        dotnet_assembly *ass      = (dotnet_assembly *)PMC_struct_val(SELF);
        const ENCODING  *encoding = Parrot_find_encoding(INTERP, "utf16");
        const CHARSET   *charset  = Parrot_find_charset(INTERP, "unicode");
        STRING          *result;
        Parrot_UInt4     length;
        Parrot_UInt4     start;

        /* If we don't have an assembly, throw an esception and leave. */
        if (!ass)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "No .NET CLI file loaded");

        /* Otherwise, check string is within range. */
        if ((Parrot_UInt4)i >= ass->user_strings_size)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "User string position out of range");

        /* Get size of string (Partition II Clause 23.2.4). */
        if ((ass->user_strings[i] & 0x80) == 0)
        {
            length = ass->user_strings[i];
            start  = i + 1;
        }
        else if ((ass->user_strings[i] & 0x40) == 0)
        {
            length = ((ass->user_strings[i] & 0x3F) << 8) +
                     ass->user_strings[i+1];
            start  = i + 2;
        }
        else if ((ass->user_strings[i] & 0x20) == 0)
        {
            length = ((ass->user_strings[i] & 0x1F) << 24) +
                      (ass->user_strings[i + 1]     << 16) +
                      (ass->user_strings[i + 2]     << 8) +
                       ass->user_strings[i + 3];
            start  = i + 4;
        }
        else
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "Unable to decode user string length");

        /* Sanity check. */
        if (start + length > ass->user_strings_size)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "Blob position and length out of range");

        /* Return string. */
        result = string_make_direct(INTERP, ass->user_strings + start,
                    length - 1, encoding, charset, 0);
        RETURN(STRING *result);
    }


    METHOD STRING* get_blob(int i)
    {
        dotnet_assembly *ass = (dotnet_assembly *)PMC_struct_val(SELF);
        unsigned int length;
        unsigned int start;
        STRING *out;

        /* If we don't have an assembly, throw an esception and leave. */
        if (!ass)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "No .NET CLI file loaded");

        /* Otherwise, check blob is within range. */
        if ((Parrot_UInt4)i >= ass->blobs_size)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "Blob position out of range");

        /* Get size of blob (Partition II Clause 23.2.4). */
        if ((ass->blobs[i] & 0x80) == 0)
        {
            length = ass->blobs[i];
            start  = i + 1;
        }
        else if ((ass->blobs[i] & 0x40) == 0)
        {
            length = ((ass->blobs[i] & 0x3F) << 8) + ass->blobs[i+1];
            start  = i + 2;
        }
        else if ((ass->blobs[i] & 0x20) == 0)
        {
            length = ((ass->blobs[i] & 0x1F) << 24) + (ass->blobs[i+1] << 16) +
                     (ass->blobs[i+2] << 8) + ass->blobs[i+3];
            start  = i + 4;
        }
        else
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "Unable to decode blob length");

        /* Sanity check. */
        if (start + length > ass->blobs_size)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "Blob position and length out of range");

        /* Return string containing blob. */
        out = string_from_cstring(INTERP, ass->blobs + start, length);
        if (!out)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "Unable to create blob string");

        RETURN(STRING *out);
    }


    METHOD PMC* get_global()
    {
        dotnet_assembly *ass = (dotnet_assembly *)PMC_struct_val(SELF);

        /* If we don't have an assembly or nothing is loaded, throw an
           exception and leave. Otherwise, return classes PMC array. */
        if (!ass || !ass->loaded)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "No .NET CLI file loaded");
        else {
            PMC *result = ass->globals;
            RETURN(PMC *result);
        }
    }


    METHOD PMC* get_classes()
    {
        dotnet_assembly *ass = (dotnet_assembly *)PMC_struct_val(SELF);

        /* If we don't have an assembly or nothing is loaded, throw an
           exception and leave. Otherwise, return classes PMC array. */
        if (!ass || !ass->loaded)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "No .NET CLI file loaded");
        else {
            PMC *result = ass->classes;
            RETURN(PMC *result);
        }
    }


    METHOD PMC* get_class_order()
    {
        dotnet_assembly *ass = (dotnet_assembly *)PMC_struct_val(SELF);

        /* If we don't have an assembly or nothing is loaded, throw an
           exception and leave. Otherwise, return class order PMC array. */
        if (!ass || !ass->loaded)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "No .NET CLI file loaded");
        else {
            PMC *result = ass->class_order;
            RETURN(PMC *result);
        }
    }


    METHOD PMC* get_typerefs()
    {
        dotnet_assembly *ass = (dotnet_assembly *)PMC_struct_val(SELF);

        /* If we don't have an assembly or nothing is loaded, throw an
           exception and leave. */
        if (!ass || !ass->loaded)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "No .NET CLI file loaded");
        else {
            PMC *result = ass->typerefs;
            RETURN(PMC *result);
        }
    }


    METHOD PMC* get_memberrefs()
    {
        dotnet_assembly *ass = (dotnet_assembly *)PMC_struct_val(SELF);

        /* If we don't have an assembly or nothing is loaded, throw an
           exception and leave. */
        if (!ass || !ass->loaded)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "No .NET CLI file loaded");
        else {
            PMC *result = ass->memberrefs;
            RETURN(PMC *result);
        }
    }


    METHOD PMC* get_assemblyrefs()
    {
        dotnet_assembly *ass = (dotnet_assembly *)PMC_struct_val(SELF);

        /* If we don't have an assembly or nothing is loaded, throw an
           exception and leave. */
        if (!ass || !ass->loaded)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "No .NET CLI file loaded");
        else {
            PMC *result = ass->assemblyrefs;
            RETURN(PMC *result);
        }
    }


    METHOD PMC* get_field(int i)
    {
        dotnet_assembly *ass = (dotnet_assembly *)PMC_struct_val(SELF);

        /* If we don't have an assembly or nothing is loaded, throw an
           exception and leave. Otherwise, return appropriate element of
           fields PMC array. */
        if (!ass || !ass->loaded)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "No .NET CLI file loaded");
        else {
            PMC *result = VTABLE_get_pmc_keyed_int(INTERP, ass->fields, i - 1);
            RETURN(PMC *result);
        }
    }


    METHOD PMC* get_method(int i)
    {
        dotnet_assembly *ass = (dotnet_assembly *)PMC_struct_val(SELF);

        /* If we don't have an assembly or nothing is loaded, throw an
           exception and leave. Otherwise, return appropriate element of
           methods PMC array. */
        if (!ass || !ass->loaded)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "No .NET CLI file loaded");
        else {
            PMC *result = VTABLE_get_pmc_keyed_int(INTERP, ass->methods, i - 1);
            RETURN(PMC *result);
        }
    }


    METHOD PMC* get_entry_method()
    {
        dotnet_assembly *ass = (dotnet_assembly *)PMC_struct_val(SELF);

        /* If we don't have an assembly or nothing is loaded, throw an
           exception and leave. */
        if (!ass || !ass->loaded)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INVALID_OPERATION,
               "No .NET CLI file loaded");
        else {
            PMC *result = VTABLE_get_pmc_keyed_int(INTERP, ass->methods,
                ass->entry_point);
            RETURN(PMC *result);
        }
    }
}


/*
 * Local variables:
 *   c-file-style: "parrot"
 * End:
 * vim: expandtab shiftwidth=4:
 */
