Monday, August 23, 2010

Extracting elements from Win32 resource files with F#

Recently I had the necessity of extracting cursors, bitmaps and icons stored in a Win32 compiled resource script file (.res) file. Although there are tools that could do that, this seems like a good excuse to use F# and code from
a previous post: "Using F# computation expressions to read binary files".

Resource file format



The format for the compiled resource file is documented in MSDN "Resource File Formats" . According with this document each entry of the resource file is prefixed by the following header (RESOURCEHEADER) :

  DWORD DataSize;
DWORD HeaderSize;
DWORD TYPE;
DWORD NAME;
DWORD DataVersion;
WORD MemoryFlags;
WORD LanguageId;
DWORD Version;
DWORD Characteristics;


The DataSize field indicates the size of the resource data following the header. According to "RESOURCEHEADER Structure" The TYPE and NAME fields could be stored in two different ways. It could be a zero terminated unicode string or a int16 identifier prefixed by a -1 int16 value. Also after the type/name and entries and after the data of each resource we need to read extra padding bytes that align the entry to DWORD.

Using the code defined for reading binary files the RESOURCEHEADER structure could be specified as:

type ResourceId =
| Numeric of int16
| Name of char[]

let pBuilder = new BinParserBuilder()

let resourceParser = pBuilder {
let! dataSize = RInt
let! headerSize = RInt
let! typePrefix = RShort
let! resourceType =
if typePrefix = -1s then
wrap(RShort, Numeric)
else
wrap(RZString,
(fun data -> Name(Array.concat
[[|char(typePrefix)|];
data] )))
let! namePrefix = RShort
let! resourceName =
if namePrefix = -1s then
wrap(RShort, Numeric)
else
wrap(RZString,
(fun data -> Name(Array.concat
[[|char(namePrefix)|];
data] )))
let typeAndNameLength = sizeOfResourceId resourceType +
sizeOfResourceId resourceName

let! _ = RDWordAlignData(typeAndNameLength)

let! dataVersion = RInt
let! memoryFlags = RShort
let! languageId = RShort
let! version = RInt
let! characteristics = RInt
let! contents = RByteBlock(dataSize)

let! _ = RDWordAlignData(dataSize)

return (resourceName,resourceType,contents)
}



Using this code we can extract each entry of the resource file by writing the following:

let file = new FileStream(fileName, FileMode.Open)
let binaryReader = new BinaryReader(file, System.Text.Encoding.Unicode)

let resources =
seq {
while(file.Position < file.Length) do
match (resourceParser.Function binaryReader) with
| Success (t,_) -> yield (Some t)
| Failure _ -> printf "Error!"
yield None
} |> List.ofSeq



Extracting Icons



Icons have two entries inside the resource files. The first entry has information about the image and the other contains the actual image data.

The following function writes all the icons in the resource list:

resources |>
filterMap (fun current ->
cursorIconInfo current 14s getIconInfo "ico") |>
filterMap (fun (originalid, (_, i, planes, bitcount, bytes, iconId)) ->
match (List.find (isEntryWithId iconId) resources) with
| Some(_, _, data) ->
Some(originalid, i, planes, bitcount, data)
| _ -> None) |>
Seq.iter (fun (name, (w, h, colorcount), planes, bitcount, contents) ->
writeIcon(name, (w, h, colorcount),
bitcount, planes, contents))


The information about the icon is extracted using cursorIconInfo which works for the cursor and the icon entry:



let cursorIconInfo resourceInfo resourceType infoExtractFunction extension =
match resourceInfo with
| Some(resName,Numeric(rType),data) when rType = resourceType ->
match resName,(infoExtractFunction data) with
| Numeric(id), Success(t,_) ->
Some(sprintf "%s%O.%s" extension id extension,t)
| Name(nameChars), Success(t,_) ->
Some(new String(nameChars) + "." + extension,t)
| _,_ ->
printf "WARNING: Skipping resource: %O\n" id
None
| _ -> None



With the data returned from this function we can get the id number of the resource file entry that contains the image data (iconCursorId). We can then get the resource entry of the icon and extract the image info from there using the following code:

let iconResEntry = pBuilder {
let! reserved = RShort
let! restype = RShort
let! rescount = RShort
let! iconWidth = RByte
let! iconHeight = RByte
let! colorCount = RByte
let! reserved = RByte
let! planes = RShort
let! bitCount = RShort
let! bytesInRes = RInt
let! iconCursorId = RShort
return (reserved, restype, rescount),
(iconWidth, iconHeight, colorCount),
planes, bitCount, bytesInRes, iconCursorId
}

let getIconInfo (data : byte array) =
use str = new MemoryStream(data)
use bReader = new BinaryReader(str)
iconResEntry.Function bReader



Image information stored in the payload section of the resource is almost the same as a .ICO file . So in order to extract the icon what we need to do is to generate part of the header of a valid ICO file. The following function does that:

let writeIcon(fileName,
(width : byte, height : byte, bitcount : byte),
bpp : int16,
planes : int16,
contents : byte array) =
use writer = new FileStream(fileName, FileMode.Create)
use bwriter = new BinaryWriter(writer)
bwriter.Write(0s)
bwriter.Write(1s)
bwriter.Write(1s)
bwriter.Write(byte(width))
bwriter.Write(byte(height))
bwriter.Write(byte(bitcount))
bwriter.Write(byte(0))
bwriter.Write(planes)
bwriter.Write(bpp)
bwriter.Write(contents.Length)
bwriter.Write(int32(writer.Position) + 4)
bwriter.Write(contents)


Extracting Cursors



The process of writing the cursors is almost the same as the process with icons. The following code filters the cursor resources and extract them:

resources |>
filterMap (fun current -> cursorIconInfo current 12s getCursorInfo "cur") |>
filterMap (fun (name, (_, (w, h), _, bitCount, _, cursorId)) ->
match List.find (isEntryWithId cursorId) resources with
| Some(_, _, theData) -> Some(name, bitCount, theData)
| _ -> None) |>
Seq.iter ( fun (name, bitcount, contents) ->
writeCursor(name, byte(bitcount),contents))


The getCursorInfo function extracts data from the cursor specific entry:

let cursorResEntry = pBuilder {
let! reserved = RShort
let! restype = RShort
let! rescount = RShort
let! cursorWidth = RShort
let! cursorHeight = RShort
let! planes = RShort
let! bitCount = RShort
let! bytesInRes = RInt
let! iconCursorId = RShort
return (reserved, restype, rescount),
(cursorWidth, cursorHeight),
planes, bitCount, bytesInRes, iconCursorId
}

let getCursorInfo (data : byte array) =
use str = new MemoryStream(data)
use bReader = new BinaryReader(str, System.Text.Encoding.Unicode)
cursorResEntry.Function bReader


As with the icon entry, the cursor entry has the data in almost the same format as a .CUR file so we need to generate a valid header for this file.

let writeCursor(fileName, bitcount : byte, contents : byte array) =
use writer = new FileStream(fileName, FileMode.Create)
use bwriter = new BinaryWriter(writer)
bwriter.Write(0s)
bwriter.Write(2s)
bwriter.Write(1s)
//Assume 32x32
bwriter.Write(byte(32))
bwriter.Write(byte(32))
bwriter.Write(bitcount)
bwriter.Write(byte(0))
let v1 = uint16(contents.[0]) ||| (uint16(contents.[1] <<< 16))
let v2 = uint16(contents.[2]) ||| (uint16(contents.[3] <<< 16))
bwriter.Write(v1)
bwriter.Write(v2)
bwriter.Write(contents.Length - 4)
bwriter.Write(int32(writer.Position) + 4)
bwriter.Write(contents,4,contents.Length - 4 )



As you can see from the code I had to assume that the resolution of the cursor is 32x32 . This is because the data didn't seem to have the correct information as specified in "CURSORDIR Structure" .



Extracting Bitmaps



Finally writing bitmap file entries is very easy. The bitmap data is stored using the DIB format which is part of the BMP file format. The following fragment shows how we extract these resources.

resources |> 
filterMap (fun res -> match res with
| Some(Numeric id, Numeric 2s, data) ->
Some(sprintf "bmp%O.bmp" id, data)
| Some(Name nameChars, Numeric 2s, data) ->
Some(new String(nameChars)+".bmp", data)
| _ -> None) |>
Seq.iter (fun (name, data) -> writeBmp(name, data))


As with the other elements, the writeBmp function writes the appropriate header according to the BMP file format.

let writeBmp(name,data:byte array) =
use writer = new FileStream(name,FileMode.Create)
use bwriter = new BinaryWriter(writer)
bwriter.Write(0x42uy)
bwriter.Write(0x4Duy)
bwriter.Write(14 + data.Length)
bwriter.Write(5s)
bwriter.Write(5s)
// According to the DIB header the image color depth
// is located ad offset 14
let paletteSize =
if data.[14] < 24uy then
int((2.0 ** float(data.[14])) * 4.0)
else
0
bwriter.Write(14 + 40 + paletteSize)
bwriter.Write(data)


Code for this post can be found here.