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 		private union Contents
72 		{
73 			Directory directory;
74 			DirectoryData data;
75 		}
76 		private 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 private:
89 	Directory readDirectory(uint offset)
90 	{
91 		enforce(offset + IMAGE_RESOURCE_DIRECTORY.sizeof <= data.length, "Out-of-bounds directory offset");
92 		auto winDir = cast(IMAGE_RESOURCE_DIRECTORY*)(data.ptr + offset);
93 		Directory dir;
94 		dir.characteristics = winDir.Characteristics;
95 		dir.timestamp = winDir.TimeDateStamp;
96 		dir.majorVersion = winDir.MajorVersion;
97 		dir.minorVersion = winDir.MinorVersion;
98 		dir.entries.length = winDir.NumberOfNamedEntries + winDir.NumberOfIdEntries;
99 
100 		offset += IMAGE_RESOURCE_DIRECTORY.sizeof;
101 		enforce(offset + dir.entries.length * IMAGE_RESOURCE_DIRECTORY_ENTRY.sizeof <= data.length, "Not enough data for directory entries");
102 		auto winEntries = cast(IMAGE_RESOURCE_DIRECTORY_ENTRY*)(data.ptr + offset)[0..dir.entries.length];
103 
104 		foreach (n; 0..dir.entries.length)
105 		{
106 			auto winEntry = &winEntries[n];
107 			auto entry = &dir.entries[n];
108 
109 			if (winEntry.NameIsString)
110 				entry.name = readString(winEntry.NameOffset).to!string;
111 			else
112 				entry.id = winEntry.Id;
113 
114 			entry.isDirectory = winEntry.DataIsDirectory;
115 			if (entry.isDirectory)
116 				entry.directory = readDirectory(winEntry.OffsetToDirectory);
117 			else
118 				entry.data = readDirectoryData(winEntry.OffsetToData);
119 		}
120 
121 		return dir;
122 	}
123 
124 	DirectoryData readDirectoryData(uint offset)
125 	{
126 		enforce(offset + IMAGE_RESOURCE_DATA_ENTRY.sizeof <= data.length, "Out-of-bounds directory data header offset");
127 		auto winDirData = cast(IMAGE_RESOURCE_DATA_ENTRY*)(data.ptr + offset);
128 		DirectoryData dirData;
129 		dirData.codePage = winDirData.CodePage;
130 		auto start = winDirData.OffsetToData - rva;
131 		enforce(start + winDirData.Size <= data.length, "Out-of-bounds directory data offset");
132 		dirData.data = data[start .. start + winDirData.Size];
133 
134 		return dirData;
135 	}
136 
137 	WCHAR[] readString(uint offset)
138 	{
139 		enforce(offset + typeof(IMAGE_RESOURCE_DIR_STRING_U.Length).sizeof <= data.length, "Out-of-bounds string offset");
140 		auto winStr = cast(IMAGE_RESOURCE_DIR_STRING_U*)(data.ptr + offset);
141 		offset += typeof(IMAGE_RESOURCE_DIR_STRING_U.Length).sizeof;
142 		enforce(offset + winStr.Length * WCHAR.sizeof <= data.length, "Out-of-bounds string offset");
143 		auto firstChar = &winStr._NameString;
144 		return firstChar[0..winStr.Length];
145 	}
146 }