/*
* PERWAPI - An API for Reading and Writing PE Files
*
* Copyright (c) Diane Corney, Queensland University of Technology, 2004.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the PERWAPI Copyright as included with this
* distribution in the file PERWAPIcopyright.rtf.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY as is explained in the copyright notice.
*
* The author may be contacted at d.corney@qut.edu.au
*
* Version Date: 26/01/07
*/
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
namespace QUT.PERWAPI
{
///
/// (Unmanaged) Resource Elements consist of PEResourceDirectories
/// or PEResourceData elements. Resource directories may be nested up
/// to three deep sorted on Type, Name and Language in that order.
///
public abstract class PEResourceElement
{
private int id;
private string name;
public PEResourceElement() { }
public int Id
{
get { return id; }
set { id = value; }
}
public string Name
{
get { return name; }
set { name = value; }
}
protected internal abstract uint Size();
///
/// Write out the unmanaged resource data.
///
/// The Binary Writer
/// File position at start of .rsrc section
/// RVA of .rsrc section when loaded
protected internal abstract void Write(BinaryWriter dest, uint baseOffset, uint currentOffset, uint RVA);
protected internal long offset;
protected const uint HeaderSize = 16;
protected const uint EntrySize = 8;
}
///
/// ResourceDirectory entries, as defined in Winnt.h
/// as type struct _IMAGE_RESOURCE_DIRECTORY.
///
public class PEResourceDirectory : PEResourceElement {
private uint date = 0;
private ushort majver = 1;
private ushort minver = 0;
public uint Date { get { return date; } set { date = value; } }
public ushort MajVer { get { return majver; } set { majver = value; } }
public ushort MinVer { get { return minver; } set { minver = value; } }
//private ArrayList subItems = new ArrayList();
private List elements = new List();
public int Count() { return elements.Count; }
///
/// Programmatically create unmanaged resource.
///
public PEResourceDirectory() { }
///
/// Read unmanged resource directory structure from PE-file.
///
///
internal void PopulateResourceDirectory(PEReader reader, long baseOffset) {
PEResourceElement resElement = null;
PEResourceDirectory resDirectory;
PEResourceData resData;
int junk = reader.ReadInt32(); // Must be zero.
this.date = reader.ReadUInt32(); // Time stamp.
this.majver = reader.ReadUInt16();
this.minver = reader.ReadUInt16();
int numNameEntries = reader.ReadUInt16(); // Number of named entries.
int numIdntEntries = reader.ReadUInt16(); // Number of ID entries.
for (int i = 0; i < numNameEntries; i++) {
uint nameOrId = reader.ReadUInt32();
uint elemOfst = reader.ReadUInt32();
if ((elemOfst & 0x80000000) != 0) // Element is subdirectory.
resElement = new PEResourceDirectory();
else
resElement = new PEResourceData();
resElement.Name = this.ReadName(reader, baseOffset + nameOrId & 0x7fffffff);
resElement.offset = baseOffset + (long)(elemOfst & 0x7fffffff);
this.AddElement(resElement);
}
// Read the Ident entries.
for (int i = 0; i < numIdntEntries; i++) {
uint nameOrId = reader.ReadUInt32();
uint elemOfst = reader.ReadUInt32();
if ((elemOfst & 0x80000000) != 0) // Element is subdirectory.
resElement = new PEResourceDirectory();
else
resElement = new PEResourceData();
resElement.Id = (ushort)nameOrId;
resElement.offset = baseOffset + (long)(elemOfst & 0x7fffffff);
this.AddElement(resElement);
}
// Now recurse to get subdirectories/the real data.
foreach (PEResourceElement elem in this.elements) {
if ((resDirectory = elem as PEResourceDirectory) != null) {
reader.BaseStream.Seek(resDirectory.offset, SeekOrigin.Begin);
resDirectory.PopulateResourceDirectory(reader, baseOffset);
}
else if ((resData = elem as PEResourceData) != null) {
reader.BaseStream.Seek(resData.offset, SeekOrigin.Begin);
resData.PopulateResourceData(reader, baseOffset);
}
}
}
private string ReadName(BinaryReader rdr, long offset) {
long savedPos = rdr.BaseStream.Position;
rdr.BaseStream.Seek(offset, SeekOrigin.Begin);
ushort nLength = rdr.ReadUInt16();
char[] name = new char[nLength];
for (int i = 0; i < nLength; i++)
name[i] = (char)rdr.ReadUInt16();
rdr.BaseStream.Seek(savedPos, SeekOrigin.Begin);
return new string(name);
}
public bool HasData() {
return elements.Count > 0;
}
public void AddElement(PEResourceElement el) {
//subItems.Add(el);
elements.Add(el);
}
///
/// Total file-space size of all child elements
///
private uint subSize;
///
/// File-space needed for all names of this directory
///
private uint nameSize;
///
/// File-space taken up by this directory
///
private uint dirSize;
///
/// Number of named elements. These come first in list.
///
private uint numNamed;
private uint numIds { get { return (uint)elements.Count - numNamed; } }
protected internal override uint Size() {
nameSize = 0;
numNamed = 0;
subSize = 0;
//for (int i = 0; i < subItems.Count; i++)
foreach (PEResourceElement elem in this.elements) {
subSize += elem.Size();
if (elem.Name != null) {
nameSize += 2 + (uint)elem.Name.Length * 2;
numNamed++;
}
}
dirSize = (uint)elements.Count * EntrySize + HeaderSize;
return dirSize + nameSize + subSize;
}
///
/// Write out the unmanaged resource rooted at this directory.
///
/// The Binary Writer
/// RVA of this .rsrc section
internal void Write(BinaryWriter dest, uint RVA) {
Size();
dest.Flush();
uint baseOffset = (uint)dest.BaseStream.Position;
this.Write(dest, baseOffset, 0, RVA);
}
protected internal override void Write(BinaryWriter dest, uint baseOffset, uint currentOffset, uint RVA) {
uint nameOffset = currentOffset + this.dirSize;
uint targetOffset = currentOffset + this.dirSize;
dest.Write((uint)0); // characteristics
dest.Write(date); // datetime
dest.Write(majver);
dest.Write(minver);
dest.Write((ushort)numNamed);
dest.Write((ushort)numIds);
currentOffset += HeaderSize;
// Write out the named items.
foreach (PEResourceElement elem in elements) {
if (elem.Name != null) {
dest.Write((uint)(nameOffset | 0x80000000));
if (elem is PEResourceDirectory)
dest.Write((uint)(targetOffset | 0x80000000));
else
dest.Write((uint)targetOffset);
nameOffset += 2 + (uint)elem.Name.Length * 2;
targetOffset += (uint)elem.Size();
currentOffset += EntrySize;
}
}
// Write out the items with ID.
foreach (PEResourceElement elem in elements) {
if (elem.Name == null) {
dest.Write(elem.Id);
if (elem is PEResourceDirectory)
dest.Write((uint)(targetOffset | 0x80000000));
else
dest.Write((uint)targetOffset);
currentOffset += EntrySize;
targetOffset += elem.Size();
}
}
// Write out the name strings.
foreach (PEResourceElement elem in elements) {
string s = elem.Name;
if (s != null) {
dest.Write((ushort)s.Length);
byte[] b = System.Text.Encoding.Unicode.GetBytes(s);
dest.Write(b);
}
}
currentOffset += this.nameSize;
// Now recurse to the children.
foreach (PEResourceElement elem in elements) {
elem.Write(dest, baseOffset, currentOffset, RVA);
currentOffset += elem.Size();
}
}
}
public class PEResourceData : PEResourceElement
{
public PEResourceData() { }
int codepage = 0;
byte[] data;
public int CodePage { get { return codepage; } set { codepage = value; } }
public byte[] Data { get { return data; } set { data = value; } }
protected internal override uint Size()
{
return 16 + (uint)Data.Length;
}
///
/// Read the binary data from the PE file.
///
///
internal void PopulateResourceData(PEReader reader, long baseOffset) {
uint dataRVA = reader.ReadUInt32();
int dataLength = reader.ReadInt32();
this.codepage = reader.ReadInt32();
uint junk = reader.ReadUInt32(); // Must be zero.
reader.BaseStream.Seek(reader.GetOffset(dataRVA), SeekOrigin.Begin);
data = new byte[dataLength];
int numberRead = reader.BaseStream.Read(data, 0, dataLength);
}
protected internal override void Write(BinaryWriter dest, uint baseOffset, uint currentOffset, uint RVA)
{
dest.Write((uint)(currentOffset + HeaderSize) + RVA);
dest.Write((uint)data.Length);
dest.Write((uint)codepage);
dest.Write((uint)0);
dest.Write(data);
}
}
}