1 /**
2  * Support for Windows PE resources.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.sys.windows.pe.resources;
15 
16 import std.conv;
17 import std.exception;
18 
19 import ae.sys.windows.imports;
20 mixin(importWin32!q{winnt});
21 
22 struct ResourceParser
23 {
24 	ubyte[] data;
25 	uint rva;
26 
27 	this(void[] data, uint rva)
28 	{
29 		this.data = cast(ubyte[])data;
30 		this.rva = rva;
31 		root = readDirectory(0);
32 	}
33 
34 	struct Directory
35 	{
36 		uint characteristics, timestamp;
37 		ushort majorVersion, minorVersion;
38 		DirectoryEntry[] entries;
39 
40 		ref DirectoryEntry opIndex(uint id)
41 		{
42 			foreach (ref entry; entries)
43 				if (entry.name is null && entry.id == id)
44 					return entry;
45 			throw new Exception("Can't find directory with this ID");
46 		}
47 
48 		ref DirectoryEntry opIndex(string name)
49 		{
50 			foreach (ref entry; entries)
51 				if (entry.name !is null && entry.name == name)
52 					return entry;
53 			throw new Exception("Can't find directory with this name");
54 		}
55 	}
56 	Directory root;
57 
58 	struct DirectoryEntry
59 	{
60 		string name;
61 		uint id;
62 		bool isDirectory;
63 		union Contents
64 		{
65 			Directory directory;
66 			DirectoryData data;
67 		}
68 		Contents contents;
69 		ref @property Directory directory() return { assert(isDirectory); return contents.directory; }
70 		ref @property DirectoryData data() return { assert(!isDirectory); return contents.data; }
71 	}
72 
73 	struct DirectoryData
74 	{
75 		uint codePage;
76 		void[] data;
77 	}
78 
79 	Directory readDirectory(uint offset)
80 	{
81 		enforce(offset + IMAGE_RESOURCE_DIRECTORY.sizeof <= data.length, "Out-of-bounds directory offset");
82 		auto winDir = cast(IMAGE_RESOURCE_DIRECTORY*)(data.ptr + offset);
83 		Directory dir;
84 		dir.characteristics = winDir.Characteristics;
85 		dir.timestamp = winDir.TimeDateStamp;
86 		dir.majorVersion = winDir.MajorVersion;
87 		dir.minorVersion = winDir.MinorVersion;
88 		dir.entries.length = winDir.NumberOfNamedEntries + winDir.NumberOfIdEntries;
89 
90 		offset += IMAGE_RESOURCE_DIRECTORY.sizeof;
91 		enforce(offset + dir.entries.length * IMAGE_RESOURCE_DIRECTORY_ENTRY.sizeof <= data.length, "Not enough data for directory entries");
92 		auto winEntries = cast(IMAGE_RESOURCE_DIRECTORY_ENTRY*)(data.ptr + offset)[0..dir.entries.length];
93 
94 		foreach (n; 0..dir.entries.length)
95 		{
96 			auto winEntry = &winEntries[n];
97 			auto entry = &dir.entries[n];
98 
99 			if (winEntry.NameIsString)
100 				entry.name = readString(winEntry.NameOffset).to!string;
101 			else
102 				entry.id = winEntry.Id;
103 
104 			entry.isDirectory = winEntry.DataIsDirectory;
105 			if (entry.isDirectory)
106 				entry.directory = readDirectory(winEntry.OffsetToDirectory);
107 			else
108 				entry.data = readDirectoryData(winEntry.OffsetToData);
109 		}
110 
111 		return dir;
112 	}
113 
114 	DirectoryData readDirectoryData(uint offset)
115 	{
116 		enforce(offset + IMAGE_RESOURCE_DATA_ENTRY.sizeof <= data.length, "Out-of-bounds directory data header offset");
117 		auto winDirData = cast(IMAGE_RESOURCE_DATA_ENTRY*)(data.ptr + offset);
118 		DirectoryData dirData;
119 		dirData.codePage = winDirData.CodePage;
120 		auto start = winDirData.OffsetToData - rva;
121 		enforce(start + winDirData.Size <= data.length, "Out-of-bounds directory data offset");
122 		dirData.data = data[start .. start + winDirData.Size];
123 
124 		return dirData;
125 	}
126 
127 	WCHAR[] readString(uint offset)
128 	{
129 		enforce(offset + typeof(IMAGE_RESOURCE_DIR_STRING_U.Length).sizeof <= data.length, "Out-of-bounds string offset");
130 		auto winStr = cast(IMAGE_RESOURCE_DIR_STRING_U*)(data.ptr + offset);
131 		offset += typeof(IMAGE_RESOURCE_DIR_STRING_U.Length).sizeof;
132 		enforce(offset + winStr.Length * WCHAR.sizeof <= data.length, "Out-of-bounds string offset");
133 		auto firstChar = &winStr._NameString;
134 		return firstChar[0..winStr.Length];
135 	}
136 }