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 <ae@cy.md>
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 /// Parses Windows PE resources.
23 struct ResourceParser
24 {
25 	ubyte[] data; /// All data.
26 	uint rva; /// RVA of data.
27 
28 	this(void[] data, uint rva)
29 	{
30 		this.data = cast(ubyte[])data;
31 		this.rva = rva;
32 		root = readDirectory(0);
33 	} ///
34 
35 	/// Resource directory.
36 	struct Directory
37 	{
38 		/// Directory properties.
39 		uint characteristics, timestamp;
40 		/// ditto
41 		ushort majorVersion, minorVersion;
42 		/// Directory entries.
43 		DirectoryEntry[] entries;
44 
45 		/// Find an entry by ID.
46 		ref DirectoryEntry opIndex(uint id)
47 		{
48 			foreach (ref entry; entries)
49 				if (entry.name is null && entry.id == id)
50 					return entry;
51 			throw new Exception("Can't find directory with this ID");
52 		}
53 
54 		/// Find an entry by name.
55 		ref DirectoryEntry opIndex(string name)
56 		{
57 			foreach (ref entry; entries)
58 				if (entry.name !is null && entry.name == name)
59 					return entry;
60 			throw new Exception("Can't find directory with this name");
61 		}
62 	}
63 	Directory root; /// The root directory.
64 
65 	/// An entry in a `Directory`.
66 	struct DirectoryEntry
67 	{
68 		string name; /// Entry name (if it is identified by a name).
69 		uint id; /// Entry ID (if it is identified by an ID).
70 		bool isDirectory; /// True if this entry is another directory.
71 		union Contents
72 		{
73 			Directory directory;
74 			DirectoryData data;
75 		}
76 		Contents contents;
77 		ref @property Directory directory() return { assert(isDirectory); return contents.directory; } /// Return the contents as a subdirectory.
78 		ref @property DirectoryData data() return { assert(!isDirectory); return contents.data; } /// Return the contents as data.
79 	}
80 
81 	/// Data contents of a `DirectoryEntry` with `!isDirectory`.
82 	struct DirectoryData
83 	{
84 		uint codePage; ///
85 		void[] data;   ///
86 	}
87 
88 	Directory readDirectory(uint offset)
89 	{
90 		enforce(offset + IMAGE_RESOURCE_DIRECTORY.sizeof <= data.length, "Out-of-bounds directory offset");
91 		auto winDir = cast(IMAGE_RESOURCE_DIRECTORY*)(data.ptr + offset);
92 		Directory dir;
93 		dir.characteristics = winDir.Characteristics;
94 		dir.timestamp = winDir.TimeDateStamp;
95 		dir.majorVersion = winDir.MajorVersion;
96 		dir.minorVersion = winDir.MinorVersion;
97 		dir.entries.length = winDir.NumberOfNamedEntries + winDir.NumberOfIdEntries;
98 
99 		offset += IMAGE_RESOURCE_DIRECTORY.sizeof;
100 		enforce(offset + dir.entries.length * IMAGE_RESOURCE_DIRECTORY_ENTRY.sizeof <= data.length, "Not enough data for directory entries");
101 		auto winEntries = cast(IMAGE_RESOURCE_DIRECTORY_ENTRY*)(data.ptr + offset)[0..dir.entries.length];
102 
103 		foreach (n; 0..dir.entries.length)
104 		{
105 			auto winEntry = &winEntries[n];
106 			auto entry = &dir.entries[n];
107 
108 			if (winEntry.NameIsString)
109 				entry.name = readString(winEntry.NameOffset).to!string;
110 			else
111 				entry.id = winEntry.Id;
112 
113 			entry.isDirectory = winEntry.DataIsDirectory;
114 			if (entry.isDirectory)
115 				entry.directory = readDirectory(winEntry.OffsetToDirectory);
116 			else
117 				entry.data = readDirectoryData(winEntry.OffsetToData);
118 		}
119 
120 		return dir;
121 	}
122 
123 	DirectoryData readDirectoryData(uint offset)
124 	{
125 		enforce(offset + IMAGE_RESOURCE_DATA_ENTRY.sizeof <= data.length, "Out-of-bounds directory data header offset");
126 		auto winDirData = cast(IMAGE_RESOURCE_DATA_ENTRY*)(data.ptr + offset);
127 		DirectoryData dirData;
128 		dirData.codePage = winDirData.CodePage;
129 		auto start = winDirData.OffsetToData - rva;
130 		enforce(start + winDirData.Size <= data.length, "Out-of-bounds directory data offset");
131 		dirData.data = data[start .. start + winDirData.Size];
132 
133 		return dirData;
134 	}
135 
136 	WCHAR[] readString(uint offset)
137 	{
138 		enforce(offset + typeof(IMAGE_RESOURCE_DIR_STRING_U.Length).sizeof <= data.length, "Out-of-bounds string offset");
139 		auto winStr = cast(IMAGE_RESOURCE_DIR_STRING_U*)(data.ptr + offset);
140 		offset += typeof(IMAGE_RESOURCE_DIR_STRING_U.Length).sizeof;
141 		enforce(offset + winStr.Length * WCHAR.sizeof <= data.length, "Out-of-bounds string offset");
142 		auto firstChar = &winStr._NameString;
143 		return firstChar[0..winStr.Length];
144 	}
145 }