1 /**
2  * Support for Windows PE VersionInfo 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.versioninfo;
15 
16 import std.algorithm.searching;
17 import std.conv;
18 import std.exception;
19 import std.string;
20 
21 debug (verparse) import std.stdio : stderr;
22 debug (verparse) import ae.utils.text : hexDump;
23 
24 /// Parses PE VersionInfo resources.
25 struct VersionInfoParser
26 {
27 	ubyte[] data; /// All data.
28 
29 	this(void[] data)
30 	{
31 		enforce((cast(size_t)data.ptr) % 4 == 0, "Data must be DWORD-aligned");
32 		this.data = cast(ubyte[])data;
33 		root = readNode();
34 	} ///
35 
36 	/// A `VersionInfo` node.
37 	struct Node
38 	{
39 		string key; ///
40 		bool isText; ///
41 		void[] value; ///
42 		Node[] children; ///
43 
44 		/// Return the string contents of a text node.
45 		@property string valueText()
46 		{
47 			if (!value.length)
48 				return null;
49 			auto str = cast(wchar[])value;
50 			enforce(str.endsWith("\0"w), "Not null-terminated");
51 			return str[0..$-1].to!string;
52 		}
53 	}
54 	Node root; /// The root node.
55 
56 private:
57 	Node readNode()
58 	{
59 		auto size = read!ushort() - ushort.sizeof;
60 		auto remainingData = data[size..$];
61 		scope(success) data = remainingData;
62 		data = data[0..size];
63 
64 		auto valueLength = read!ushort();
65 		auto type = read!ushort();
66 		if (type)
67 			valueLength *= 2;
68 
69 		wchar[] key;
70 		ubyte[] value;
71 		debug (verparse) scope(failure) stderr.writefln("wLength=%d wValueLength=%d remainder=%d wType=%d key=%(%s%) [error]", size, valueLength, data.length, type, [key]);
72 		if (valueLength < data.length && (cast(wchar[])data[0..$-valueLength]).indexOf('\0') < 0)
73 		{
74 			// Work around resource compiler bug
75 			debug (verparse) stderr.writeln("Resource compiler bug detected");
76 			valueLength += 2; // Make up for the lost null terminator
77 			auto wdata = cast(wchar[])data;
78 			while (wdata.length > 1 && wdata[$-1] == 0 && wdata[$-2] == 0)
79 				wdata = wdata[0..$-1];
80 			auto point = wdata.length - valueLength/wchar.sizeof;
81 			key = wdata[0..point];
82 			value = cast(ubyte[])wdata[point..$];
83 			data = null;
84 		}
85 		else
86 		{
87 			key = readWStringz();
88 			readAlign();
89 			if (valueLength > data.length)
90 				valueLength = data.length.to!ushort; // Work around Borland linker bug (madCHook.dll)
91 			value = readBytes(valueLength);
92 			readAlign();
93 		}
94 
95 		debug (verparse)
96 		{
97 			stderr.writefln("wLength=%d wValueLength=%d remainder=%d wType=%d key=%(%s%)", size, valueLength, data.length, type, [key]);
98 			if (value.length)
99 				stderr.writeln(hexDump(value));
100 		}
101 
102 		Node node;
103 		node.key = to!string(key);
104 		node.isText = type > 0;
105 		node.value = value;
106 
107 		while (data.length)
108 		{
109 			node.children ~= readNode();
110 			readAlign();
111 		}
112 
113 		return node;
114 	}
115 
116 	T read(T)()
117 	{
118 		T value = *cast(T*)data.ptr;
119 		data = data[T.sizeof..$];
120 		return value;
121 	}
122 
123 	wchar[] readWStringz()
124 	{
125 		auto start = cast(wchar*)data.ptr;
126 		size_t count = 0;
127 		while (read!wchar())
128 			count++;
129 		return start[0..count];
130 	}
131 
132 	ubyte[] readBytes(size_t count)
133 	{
134 		scope(success) data = data[count..$];
135 		return data[0..count];
136 	}
137 
138 	void readAlign()
139 	{
140 		while (data.length && (cast(size_t)data.ptr) % 4 != 0)
141 			read!ubyte();
142 	}
143 }
144