/*
 * System.Reflection-like API for access to .NET assemblies (DLL & EXE)
 */


package ch.epfl.lamp.compiler.msil;

import ch.epfl.lamp.compiler.msil.util.*;
import ch.epfl.lamp.compiler.msil.util.Table.*;

import ch.epfl.lamp.compiler.msil.Type;
import ch.epfl.lamp.compiler.msil.Module;

import java.io.File;
import java.io.RandomAccessFile;
import java.io.PrintStream;
import java.io.IOException;
import java.io.FileNotFoundException;

import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.MappedByteBuffer;

import java.util.Date;

/**
 * A class that represents a .NET PE/COFF image.
 *
 * @author Nikolay Mihaylov
 * @version 1.0
 * @see <a href="http://www.ecma-international.org/publications/standards/Ecma-335.htm">Standard ECMA-335:  Common Language Infrastructure (CLI), 4th edition (June 2006)</a>
 */
public class PEFile {

    //##########################################################################

    public static final int INT_SIZE = 4;

    protected final int PE_SIGNATURE_OFFSET;
    protected final int COFF_HEADER_OFFSET;
    protected final int PE_HEADER_OFFSET;

    protected final int numOfSections;
    protected final int CLI_RVA;
    protected final int CLI_Length;
    public final int rvaMetadata;
    public final int posMetadata;
    protected final int numOfStreams;
    protected final int optHeaderSize;

    protected final File underlyingFile;
    protected final RandomAccessFile file;
    protected final MappedByteBuffer buf;

    protected final PESection [] sections;

    public PEStream Meta, Strings, US, Blob, GUID;

    private final Table [] tables = new Table[Table.MAX_NUMBER];

    public final boolean isDLL;

    protected final int heapSizes;
    public final boolean StringIsShort, BlobIsShort, GUIDIsShort;

    protected PEModule pemodule = null;

    //##########################################################################
    // PEFile constructor

    private static void fileFormatCheck(boolean cond, String s) {
	if (cond)
	    throw new RuntimeException(s);
    }

    /**
     */
    public PEFile(String filename) throws FileNotFoundException {
	this.underlyingFile = new File(filename);
	this.file = new RandomAccessFile(underlyingFile, "r");
	FileChannel fc = file.getChannel();
	MappedByteBuffer bb = null;
	try {
	    bb = fc.map(FileChannel.MapMode.READ_ONLY, 0L, fc.size());
 	} catch (IOException e) { throw new RuntimeException(e); }

    /** Ecma 335, 25 File format extensions to PE:
     *
     *  "Unless stated otherwise, all binary values are stored in little-endian format."
     */

	bb.order(java.nio.ByteOrder.LITTLE_ENDIAN);
	this.buf = bb;

    /** Ecma 335, 25.2.1 MS-DOS header:
     *
     *  "The PE format starts with an MS-DOS stub of exactly the following 128 bytes to
     *   be placed at the front of the module."
     *
     *  We are only checking for MZ (Mark Zbikowski)
     */

	seek(0);
	fileFormatCheck(readByte() != 0x4d, "Invalid PE file format: " + filename); // 'M'
	fileFormatCheck(readByte() != 0x5a, "Invalid PE file format: " + filename); // 'Z'

    /** Ecma 335, 25.2.1 MS-DOS header:
     *
     *  "At offset 0x3c in the DOS header is a 4-byte unsigned integer offset, lfanew,
     *   to the PE signature (shall be "PE\0\0"), immediately followed by the PE file header."
     */

	seek(0x3c);
	PE_SIGNATURE_OFFSET = readInt();
	seek(PE_SIGNATURE_OFFSET);
    // start of PE signature (a signature that is just 4 bytes long)
	fileFormatCheck(readByte() != 0x50, "Invalid PE file format: " + filename); // 'P'
	fileFormatCheck(readByte() != 0x45, "Invalid PE file format: " + filename); // 'E'
    fileFormatCheck(readByte() != 0x00, "Invalid PE file format: " + filename); //  0
    fileFormatCheck(readByte() != 0x00, "Invalid PE file format: " + filename); //  0

	//trace("PE signature offset = 0x" + Table.int2hex(PE_SIGNATURE_OFFSET));

	COFF_HEADER_OFFSET = PE_SIGNATURE_OFFSET + 4;
	PE_HEADER_OFFSET = COFF_HEADER_OFFSET + 20;

	seek(COFF_HEADER_OFFSET);

    /* start of PE file header, Sec. 25.2.2 in Partition II  */
	skip(2); // Machine (always 0x14c)
    numOfSections = readShort(); // Number of sections; indicates size of the Section Table
	Date timeStamp = new Date(readInt() * 1000L);
	skip(2 * INT_SIZE); // skip Pointer to Symbol Table (always 0) and Number of Symbols (always 0)
	optHeaderSize = readShort();
	int characteristics = readShort();
	isDLL = (characteristics & 0x2000) != 0;

	seek(PE_HEADER_OFFSET + 208); // p.157, Partition II

 	CLI_RVA = readInt();    // called "Data Directory Table" in Ch. 4 of Expert IL book
	CLI_Length = readInt();
	//trace("CLI_RVA = 0x" + Table.int2hex(CLI_RVA));
	//trace("CLI_Length = 0x" + Table.int2hex(CLI_Length));

	sections = new PESection[numOfSections];

	seek(PE_HEADER_OFFSET + optHeaderSize); // go to the sections descriptors

	for (int i = 0; i < numOfSections; i++) {
	    seek(PE_HEADER_OFFSET + optHeaderSize + i * 40);
	    sections[i] = new PESection(this);
	    //sections[i].dump(System.out);
	}

	seek(fromRVA(CLI_RVA));
	skip(8);
	rvaMetadata = readInt();
	posMetadata = fromRVA(rvaMetadata);
	//trace("rvaMetadata = 0x" + Table.int2hex(rvaMetadata));
	//trace("posMetadata = 0x" + Table.int2hex(posMetadata));

	seek(posMetadata);
	int magic = readInt();
	//trace("Magic metadata signature = 0x" + Table.int2hex(magic));
	fileFormatCheck(magic != 0x424a5342, "Invalid metadata signature!");
	skip(8);

	int strlength = readInt();
	//trace("version name string length = " + strlength);
	skip(strlength);
	align(INT_SIZE, posMetadata);
	//trace("position of flags = 0x" + Table.int2hex((int)pos()));
	skip(2); // ignore the flags
	numOfStreams = readShort();
	//trace("Number of metadata streams = " + numOfStreams);

	for (int i = 0; i < numOfStreams; i++) {
	    PEStream strm = new PEStream(this);
	    //strm.dump(System.out);
	    if (strm.name.equals("#~")
		|| strm.name.equals("#-"))    Meta = strm;
	    if (strm.name.equals("#Strings")) Strings = strm;
	    if (strm.name.equals("#US"))      US = strm;
	    if (strm.name.equals("#Blob"))    Blob = strm;
	    if (strm.name.equals("#GUID"))    GUID = strm;
	}

	seek(Meta.offset);
	skip(6);
	heapSizes = readByte();
	StringIsShort = (heapSizes & 0x01) == 0;
	GUIDIsShort   = (heapSizes & 0x02) == 0;
	BlobIsShort   = (heapSizes & 0x04) == 0;

	skip(1);
	long tablesMask = readLong();
	long nonStandardTables = tablesMask & ~Table.VALID_TABLES_MASK;
	skip(8); //go to the list of number of rows
	for (int i = 0; i < tables.length; i++) {
	    tables[i] = Table.newTable
		(this, i, ((tablesMask >> i) & 0x01) != 0 ? readInt() : 0);
	}

	initIndexSize();
	initTableRefs();
	// populate the tables from the CLI image file
	long start = pos();
	for (int i = 0; i < tables.length; i++)
	    start = tables[i].init(start);

    } // PEFile()


    public final int[] indexSize = new int[Table.TABLE_SET_LENGTH];

    private void initIndexSize() {
	for (int i = 0; i < Table.TABLE_SET_LENGTH; i++) {
	    indexSize[i] = 2;
	    int[] tableSet = Table.TableSet[i];
	    int treshold = (65536 >> Table.NoBits[i]);
	    for (int j = 0; j < tableSet.length; j++) {
		if (tableSet[j] >= 0) {
		    Table t = tables[tableSet[j]];
		    if (t.rows >= treshold) {
			indexSize[i] = 4;
			break;
		    }
		}
	    }
	}
    }

    protected void initModule(PEModule module) {
	if (pemodule != null)
	    throw new RuntimeException("File " + this
				       + " has already been assigned module "
				       + pemodule + "; new module is " + module);
	this.pemodule = module;
    }

    //##########################################################################

    public ModuleDef ModuleDef;
    public ModuleDef ModuleDef(int i) {
	ModuleDef.readRow(i);
	return ModuleDef;
    }

    public TypeRef TypeRef;

    public TypeDef TypeDef;
    public TypeDef TypeDef(int i) {
	TypeDef.readRow(i);
	return TypeDef;
    }

    public FieldTrans FieldTrans;
    public FieldTrans FieldTrans(int i) {
	FieldTrans.readRow(i);
	return FieldTrans;
    }

    public FieldDef FieldDef;
    public FieldDef FieldDef(int i) {
	FieldDef.readRow(i);
	return FieldDef;
    }

    public MethodTrans MethodTrans;
    public MethodTrans MethodTrans(int i) {
	MethodTrans.readRow(i);
	return MethodTrans;
    }

    public MethodDef MethodDef;
    public MethodDef MethodDef(int i) { MethodDef.readRow(i); return MethodDef; }


    public ParamDef ParamDef;
    public ParamDef ParamDef(int i) { ParamDef.readRow(i); return ParamDef; }

    public GenericParam GenericParam;

    public GenericParam GenericParam(int i) {
        GenericParam.readRow(i);
        return GenericParam;
    }

    public MethodSpec MethodSpec;

    public MethodSpec MethodSpec(int i) {
        MethodSpec.readRow(i);
        return MethodSpec;
    }

    public GenericParamConstraint GenericParamConstraint;

    public GenericParamConstraint GenericParamConstraint(int i) {
        GenericParamConstraint.readRow(i);
        return GenericParamConstraint;
    }

    public InterfaceImpl InterfaceImpl;
    public MemberRef MemberRef;
    public Constant Constant;
    public CustomAttribute CustomAttribute;
    public FieldMarshal FieldMarshal;
    public DeclSecurity DeclSecurity;
    public ClassLayout ClassLayout;
    public FieldLayout FieldLayout;
    public StandAloneSig StandAloneSig;
    public EventMap EventMap;
    public EventDef EventDef;
    public PropertyMap PropertyMap;
    public PropertyDef PropertyDef;
    public MethodSemantics MethodSemantics;
    public MethodImpl MethodImpl;
    public ModuleRef ModuleRef;
    public TypeSpec TypeSpec;
    public ImplMap ImplMap;
    public FieldRVA FieldRVA;
    public AssemblyDef AssemblyDef;
    public AssemblyRef AssemblyRef;
    public FileDef FileDef;
    public ExportedType ExportedType;
    public ManifestResource ManifestResource;
    public NestedClass NestedClass;


    private void initTableRefs() {
	ModuleDef = (ModuleDef) getTable(Table.ModuleDef.ID);
	TypeRef = (TypeRef) getTable(Table.TypeRef.ID);
	TypeDef = (TypeDef) getTable(Table.TypeDef.ID);
	FieldTrans = (FieldTrans) getTable(Table.FieldTrans.ID);
	FieldDef = (FieldDef) getTable(Table.FieldDef.ID);
	MethodTrans = (MethodTrans) getTable(Table.MethodTrans.ID);
	MethodDef = (MethodDef) getTable(Table.MethodDef.ID);
	ParamDef = (ParamDef) getTable(Table.ParamDef.ID);
	InterfaceImpl = (InterfaceImpl) getTable(Table.InterfaceImpl.ID);
	MemberRef = (MemberRef) getTable(Table.MemberRef.ID);
	Constant = (Constant) getTable(Table.Constant.ID);
	CustomAttribute = (CustomAttribute) getTable(Table.CustomAttribute.ID);
	FieldMarshal = (FieldMarshal) getTable(Table.FieldMarshal.ID);
	DeclSecurity = (DeclSecurity) getTable(Table.DeclSecurity.ID);
	ClassLayout = (ClassLayout) getTable(Table.ClassLayout.ID);
	FieldLayout = (FieldLayout) getTable(Table.FieldLayout.ID);
	StandAloneSig = (StandAloneSig) getTable(Table.StandAloneSig.ID);
	EventMap = (EventMap) getTable(Table.EventMap.ID);
	EventDef = (EventDef) getTable(Table.EventDef.ID);
	PropertyMap = (PropertyMap) getTable(Table.PropertyMap.ID);
	PropertyDef = (PropertyDef) getTable(Table.PropertyDef.ID);
	MethodSemantics = (MethodSemantics) getTable(Table.MethodSemantics.ID);
	MethodImpl = (MethodImpl) getTable(Table.MethodImpl.ID);
	ModuleRef = (ModuleRef) getTable(Table.ModuleRef.ID);
	TypeSpec = (TypeSpec) getTable(Table.TypeSpec.ID);
	ImplMap = (ImplMap) getTable(Table.ImplMap.ID);
	FieldRVA = (FieldRVA) getTable(Table.FieldRVA.ID);
	AssemblyDef = (AssemblyDef) getTable(Table.AssemblyDef.ID);
	AssemblyRef = (AssemblyRef) getTable(Table.AssemblyRef.ID);
	FileDef = (FileDef) getTable(Table.FileDef.ID);
	ExportedType = (ExportedType) getTable(Table.ExportedType.ID);
	NestedClass = (NestedClass) getTable(Table.NestedClass.ID);
	ManifestResource =
	    (ManifestResource) getTable(Table.ManifestResource.ID);
        GenericParam = (GenericParam) getTable(Table.GenericParam.ID);
        MethodSpec = (MethodSpec) getTable(Table.MethodSpec.ID);
        GenericParamConstraint = (GenericParamConstraint) getTable(Table.GenericParamConstraint.ID);
    }

    public static String long2hex(long a) {
	StringBuffer str = new StringBuffer("0000000000000000");
	str.append(Long.toHexString(a));
	int l = str.length();
	return str.substring(l - 16, l);
    }

    public static String int2hex(int a) {
	StringBuffer str = new StringBuffer("00000000");
	str.append(Integer.toHexString(a));
	int l = str.length();
	return str.substring(l - 8, l);
    }

    public static String short2hex(int a) {
	StringBuffer str = new StringBuffer("0000");
	str.append(Integer.toHexString(a));
	int l = str.length();
	return str.substring(l - 4, l);
    }

    public static String byte2hex(int a) {
	StringBuffer str = new StringBuffer("00");
	str.append(Integer.toHexString(a));
	int l = str.length();
	return str.substring(l - 2, l);
    }

    public static String bytes2hex(byte[] buf) {
	StringBuffer str = new StringBuffer();
	for (int i = 0; i < buf.length; i++) {
	    str.append(byte2hex(buf[i]));
	    if (i < buf.length - 1)
		str.append(" ");
	}
	return str.toString();
    }

    //##########################################################################
    // filename

    public File getUnderlyingFile() {
	return underlyingFile;
    }

    /**
     * @return the absolute path of the file
     */
    public String getAbsolutePath() {
	return underlyingFile.getAbsolutePath();
    }

    /**
     * @return the name of this file
     */
    public String getName() {
	return underlyingFile.getName();
    }

    /**
     * @return
     */
    public String getParent() {
	return underlyingFile.getParent();
    }

    /**
     * @return the file representing the directory the file belongs to
     */
    public File getParentFile() {
	return underlyingFile.getParentFile();
    }

    public String toString() {
	return getAbsolutePath();
    }

    //##########################################################################
    // file pointer manipulation methods

    /** Returns the current position in the file. */
    public int pos() {
	return buf.position();
    }

    /** Go to the specified position in the file. */
    public void seek(int pos) {
	buf.position(pos);
    }


    /** Align the current position in the file. */
    public void align(int base) { align(base, 0); }

    /** Align the current position in a section starting at offset. */
    public void align(int base, int offset) {
	int p = pos() - offset;
	seek( offset + ((p % base) == 0 ? p : (p/base + 1) * base));
    }

    /** Computes the position in the file that corresponds to the given RVA. */
    public int fromRVA(int rva) {
	int i;
	for(i = 0; i < numOfSections; i++)
	    if(sections[i].virtAddr <= rva &&
	       rva <= (sections[i].virtAddr + sections[i].virtSize))
		return rva - sections[i].virtAddr + sections[i].realAddr;
	throw new RuntimeException("RVA 0x" + Integer.toHexString(rva) +
				   " is not within this file's sections!");
    }

    /** Go to the specified RVA (Relative Virtual Address). */
    public void gotoRVA(int rva) {
	seek(fromRVA(rva));
    }

    /** Move the forward in the file by the specified number of bytes. */
    public void skip(int n) {
	buf.position(buf.position() + n);
    }

    /**
     * Returns a memory mapped little-endian buffer
     * for the specified region of the file.
     */
    public MappedByteBuffer mapBuffer(long offset, int size) {
 	try {
	    MappedByteBuffer b = file.getChannel()
		.map(FileChannel.MapMode.READ_ONLY, offset, size);
	    b.order(java.nio.ByteOrder.LITTLE_ENDIAN);
	    return b;
 	} catch (IOException e) { throw new RuntimeException(e); }
    }

    /** Returns a buffer from the given offset to the end of the file. */
    public ByteBuffer getBuffer(long offset, int size) {
	buf.mark();
	buf.position((int)offset);
	ByteBuffer bb = buf.slice();
	buf.reset();
	bb.limit(size);
	bb.order(java.nio.ByteOrder.LITTLE_ENDIAN);
	return bb;
    }

    //##########################################################################
    // file read methods

    /**
     * Read bs.length number of bytes
     */
    public void read(byte[] bs) {
	buf.get(bs);
    }

    /**
     * Read 1-byte integer from the current position in the file.
     */
    public int readByte() {
	return buf.get();
    }

    /**
     * Read 2-byte integer from the current position in the file.
     */
    public int readShort() {
	return buf.getShort();
    }

    /**
     * Read 4-byte integer from the current position in the file.
     */
    public int readInt() {
	return buf.getInt();
    }

    /**
     * Read 8-byte integer from the current position in the file.
     */
    public long readLong() {
	return buf.getLong();
    }

    /**
     * @return the size of string indeces for this file.
     */
    public int getStringIndexSize() {
	return StringIsShort ? 2 : 4;
    }

    /**
     * @return the size of GUID indeces for this file.
     */
    public int getGUIDIndexSize() {
	return GUIDIsShort ? 2 : 4;
    }

    /**
     * @return the size of Blob indeces for this file.
     */
    public int getBlobIndexSize() {
	return BlobIsShort ? 2 : 4;
    }

    /**
     * @return the size of the index to tableID for this file;
     * @param tableID the ID of the table
     */
    public int getTableIndexSize(int tableID) {
	return tables[tableID].isShort ? 2 : 4;
    }

    /**
     * @return the size of the index to a set of tables with the given @param TableSetID
     * @param tableSetID the ID of the table set
     */
    public int getTableSetIndexSize(int tableSetID) {
	return indexSize[tableSetID];
    }

    /**
     * Read a String index from the current position in the file.
     * @return an index into the String stream
     */
    public int readStringIndex() {
	return StringIsShort ? readShort() : readInt();
    }

    /**
     * Read a GUID index from the current position in the file.
     * @return an index in to the GUID stream
     */
    public int readGUIDIndex() {
	return GUIDIsShort ? readShort() : readInt();
    }

    /**
     * Read a Blob index from the current position in the file.
     * @return an index into the Blob stream
     */
    public int readBlobIndex() {
	return BlobIsShort ? readShort() : readInt();
    }

    /** Read an entry interpreted as index into table @param tableID. */
    public int readTableIndex(int tableId) {
	return tables[tableId].isShort ? readShort() : readInt();
    }

    /***/
    public int readTableSetIndex(int tableSetId) {
	return indexSize[tableSetId] == 2 ? readShort() : readInt();
    }

    /**
     * Read a string from the String stream
     * @return the string at the given position
     * @param pos the position of the string in the String stream
     */
    public String getString(int pos) {
	String s = Strings.getString(pos);
	return s;//.length() == 0 ? null : s;
    }

    /**
     * Read a string from the US (User Strings) stream
     * @return the string at the given position
     * @param pos the position of the string in the US stream
     */
    public String getUString(int pos) {
	return US.getString(pos);
    }

    /**
     * Read a blob from the Blob Stream
     * @return the blob at the given position
     * @param pos the position of the blob in the Blob stream
     */
    public byte[] getBlob(int pos) {
	return Blob.getBlob(pos);
    }

    /***/
    public Sig getSignature(int pos) {
	//return new Sig(getBlob(pos));
	return Blob.getSignature(pos);
    }

    /***/
    public byte[] getGUID(int pos) {
	return GUID.getGUID(pos);
    }

    /**
     * @return the table with the corresponding ID.
     */
    public final Table getTable(int tableID) {
	return tables[tableID];
    }

    //##########################################################################

    /***/
    void trace(String msg) {
	System.out.println("[trace] " + msg);
    }

    //##########################################################################

    public Sig newSignature(ByteBuffer buf) {
	return new Sig(buf);
    }

    /**
     */
    public class Sig implements Signature {

	//######################################################################
	// instance members

	protected final ByteBuffer buf;
	protected final int pos;
	protected final int length;

	public Sig(ByteBuffer buf) {
	    this.buf = buf;
	    //int tmpPos = buf.position();
	    length = decodeInt();
	    this.pos = buf.position();
	}

	public String toString() {
	    StringBuffer b = new StringBuffer("(");
        int savedPos = buf.position();
	    reset();
	    for (int i = 0; i < length; i++) {
		b.append(byte2hex(readByte()));
		if (i < length - 1)
		    b.append(" ");
	    }
        buf.position(savedPos);
	    return b.append(")").toString();
	}

	public Sig reset() { buf.position(pos); return this; }

	public int pos() { return buf.position() - pos; }

	/** @return the byte at the current position in the signature Blob.
	 *  Stay at the same position
	 */
	public int getByte() {
	    return (buf.get(buf.position()) + 0x100) & 0xff;
	}

	/** @return the byte at the current position in the signature Blob.
	 *  Move to the next byte.
	 */
	public int readByte() { return (buf.get() + 0x100) & 0xff; }

	/** Skip the current byte if equal to the given value. */
	public void skipByte(int b) { if (b == getByte()) buf.get(); }

	/** Decodes an integer from the signature Blob.
	 *  @return the decoded integer
	 */
	public int decodeInt() {
	    int res = readByte();
	    if ((res & 0x80) != 0) {
		res = ((res & 0x7f) << 8) | readByte();
		if ((res & 0x4000) != 0)
		    res = ((res & 0x3fff)<<16) | (readByte()<<8) | readByte();
	    }
	    return res;
	}

	/** @return - the type encoded at the current position in the signature
         *         according to 23.2.12
	 */
	public Type decodeType() {
	    try { return decodeType0(); }
	    catch (RuntimeException e) {
		System.out.println("" + pos() + "@" + this);
		throw e;
	    }
	}

	public Type decodeType0() {
	    Type type = null;
	    int desc = readByte();
	    switch (desc) {
	    case ELEMENT_TYPE_BOOLEAN:type = Type.GetType("System.Boolean"); break;
	    case ELEMENT_TYPE_CHAR:   type = Type.GetType("System.Char"); break;
	    case ELEMENT_TYPE_I1:     type = Type.GetType("System.SByte"); break;
	    case ELEMENT_TYPE_U1:     type = Type.GetType("System.Byte"); break;
	    case ELEMENT_TYPE_I2:     type = Type.GetType("System.Int16"); break;
	    case ELEMENT_TYPE_U2:     type = Type.GetType("System.UInt16"); break;
	    case ELEMENT_TYPE_I4:     type = Type.GetType("System.Int32"); break;
	    case ELEMENT_TYPE_U4:     type = Type.GetType("System.UInt32"); break;
	    case ELEMENT_TYPE_I8:     type = Type.GetType("System.Int64"); break;
	    case ELEMENT_TYPE_U8:     type = Type.GetType("System.UInt64"); break;
	    case ELEMENT_TYPE_R4:     type = Type.GetType("System.Single"); break;
	    case ELEMENT_TYPE_R8:     type = Type.GetType("System.Double"); break;
	    case ELEMENT_TYPE_OBJECT: type = Type.GetType("System.Object"); break;
	    case ELEMENT_TYPE_STRING: type = Type.GetType("System.String"); break;
	    case ELEMENT_TYPE_I:      type = Type.GetType("System.IntPtr"); break;
	    case ELEMENT_TYPE_U:      type = Type.GetType("System.UIntPtr"); break;
	    case ELEMENT_TYPE_PTR:        // Followed by <type> token.
		if (getByte() == ELEMENT_TYPE_VOID) {
		    readByte();
		    type = Type.mkPtr(Type.GetType("System.Void"));
		} else type = Type.mkPtr(decodeType());
		break;
        case ELEMENT_TYPE_BYREF:      /* although BYREF is not listed in 23.2.12. as possible alternative, this method is also called when parsing the signatures of a method param and a method return, which do allow for BYREF */
            type = Type.mkByRef(decodeType());
            break;
        case ELEMENT_TYPE_VALUETYPE:  // Followed by TypeDefOrRefEncoded
            assert true;
	    case ELEMENT_TYPE_CLASS:
		// Followed by <type> token
		type = pemodule.getTypeDefOrRef(decodeInt());
		if (type == null) throw new RuntimeException();
		break;

	    case ELEMENT_TYPE_SZARRAY:    // Single-dim array with 0 lower bound.
		skipCustomMods();
		type = Type.mkArray(decodeType(), 1);
		break;
	    case ELEMENT_TYPE_ARRAY:
		// <type> <rank> <boundsCount> <bound1> ... <loCount> <lo1> ...
                    // ArrayShape defined in 23.2.13 ArrayShape
		Type elem = decodeType();
		int rank = decodeInt();
		int numSizes = decodeInt();
		for (int i = 0; i < numSizes; i++)
            decodeInt(); // TODO don't ignore
		int numLoBounds = decodeInt();
		for (int i = 0; i < numLoBounds; i++)
            decodeInt(); // TODO don't ignore
		type = Type.mkArray(elem, rank);
		break;

        // a grammar production from 23.2.12 Type
        // GENERICINST (CLASS | VALUETYPE) TypeDefOrRefEncoded GenArgCount Type*
        case ELEMENT_TYPE_GENERICINST:
            int b = readByte();
            /*- TODO don't ignore b as done above. Should .NET valuetypes be represented as Scala case classes? */
            Type instantiatedType = pemodule.getTypeDefOrRef(decodeInt());
            int numberOfTypeArgs = decodeInt();
            Type[] typeArgs = new Type[numberOfTypeArgs];
            for (int iarg = 0; iarg < numberOfTypeArgs; iarg++) {
                typeArgs[iarg] = decodeType();
            }
            type = new ConstructedType(instantiatedType, typeArgs);
            break;

        // another grammar production from 23.2.12 Type
        // ELEMENT_TYPE_VAR number The number non-terminal following MVAR
        // or VAR is an unsigned integer value (compressed).
        /* See also duplicate code in PEModule.java  */
        case ELEMENT_TYPE_VAR:
            int typeArgAsZeroBased = decodeInt();
            type = new Type.TMVarUsage(typeArgAsZeroBased, true);
            break;

        // another grammar production from 23.2.12 Type
        // ELEMENT_TYPE_MVAR number The number non-terminal following MVAR
        // or VAR is an unsigned integer value (compressed).
        /* See also duplicate code in PEModule.java  */
        case ELEMENT_TYPE_MVAR:
            typeArgAsZeroBased = decodeInt();
            type = new Type.TMVarUsage(typeArgAsZeroBased, false);
            break;

	    case ELEMENT_TYPE_FNPTR:
             // Followed MethodDefSig or by MethodRefSig.
	    case ELEMENT_TYPE_END:
		// Marks end of a list
	    case ELEMENT_TYPE_CMOD_REQD:
		// Required modifier : followed by a TypeDef or TypeRef token.
	    case ELEMENT_TYPE_CMOD_OPT:
		// Optional modifier : followed by a TypeDef or TypeRef token.
	    case ELEMENT_TYPE_INTERNAL:
		// Implemented within the CLI.
	    case ELEMENT_TYPE_MODIFIER:
		// Or'd with following element types.
	    case ELEMENT_TYPE_SENTINEL:
		// Sentinel for varargs method signature.
	    case ELEMENT_TYPE_PINNED:
		// Denotes a local variable that points at a pinned object.
	    default:
		throw new RuntimeException(byte2hex(desc) +
					   "@" + pos() + " in " + this);

	    }
	    if (type == null) throw new RuntimeException();
	    return type;
    } // decodeType0()

	public PECustomMod decodeFieldType() {
	    skipByte(FIELD); // 0x06
	    CustomModifier[] cmods = getCustomMods();
        Type fieldType = decodeType();
	    return new PECustomMod(fieldType, cmods);
	}

	/** decodes the return type of a method signature (22.2.11). */
	public Type decodeRetType() {
	    skipCustomMods();
	    switch (getByte()) {
	    case ELEMENT_TYPE_VOID:
		readByte();
		return Type.GetType("System.Void");
	    case ELEMENT_TYPE_TYPEDBYREF:
		return Type.GetType("System.TypedReference");
	    case ELEMENT_TYPE_BYREF:
		return decodeType();
	    default:
		return decodeType();
	    }
	}

	public Type decodeParamType() {
	    skipCustomMods();
	    switch (getByte()) {
	    case ELEMENT_TYPE_BYREF:
		    return decodeType();
	    case ELEMENT_TYPE_TYPEDBYREF:
		    return Type.GetType("System.TypedReference");
	    default:
		    return decodeType();
	    }
	}

	public void skipCustomMods() {
	    while (getByte() == ELEMENT_TYPE_CMOD_OPT /* 0x20 */
		   || getByte() == ELEMENT_TYPE_CMOD_REQD /* 0x1f */ )
		{
            boolean isREQD = (getByte() == ELEMENT_TYPE_CMOD_REQD); // 0x1f
                    // skip the tag 23.2.7
                    readByte();
                    // skip the TypeDefOrRefEncoded (23.2.8)
            Type ignored = pemodule.getTypeDefOrRef(decodeInt());
            if(isREQD) {
                // System.err.println("ELEMENT_TYPE_CMOD_REQD: " + ignored);
                // throw new RuntimeException("Reqired CMOD: " + ignored);
		}
	}
	}

    /**
     * @see CustomModifier
     */
	public CustomModifier[] getCustomMods() {
      java.util.List/*<CustomModifier>*/ cmods = new java.util.LinkedList();
      while (getByte() == ELEMENT_TYPE_CMOD_OPT || getByte() == ELEMENT_TYPE_CMOD_REQD) {
        boolean isReqd = (getByte() == ELEMENT_TYPE_CMOD_REQD);
        readByte(); // tag 23.2.7
        Type t = pemodule.getTypeDefOrRef(decodeInt()); // TypeDefOrRefEncoded (23.2.8)
        cmods.add(new CustomModifier(isReqd, t));
      }
      CustomModifier[] res = (CustomModifier[])cmods.toArray(new CustomModifier[0]);
      return res;
	}

    //######################################################################

    }  // class Sig

    //##########################################################################

}  // class PEFile
