Holy cow, I wrote a book!
A few random links that I've collected.
And then the obligatory plug for my column in TechNet Magazine:
And then the obligatory book plug: The electronic-only PDF version is now available for purchase, and it's cheaper than the dead tree edition. (Don't forget that the dead tree edition comes with 45 days of free access to the electronic edition.)
My prediction? "Jim Allchin will retire on November 13, 2006." I was wrong. I based my prediction on the fact that Microsoft announced that Allchin "plans to retire at the end of calendar year 2006 following the commercial availability of Windows Vista." The year was set; the rest was trying to guess the day. I haven't seen any announcement saying that plans have changed, so I assume it's still operative. The year isn't over yet, but there's not much time left.
(Then again, what is commercial availability? Apparently, the PR people think it means "the date the product is available to the general public at retail," but to me it means "the date the product can be purchased." So-called business availability was last month, and selling stuff to businesses is commerce, so that counts as commercial availability in my book. But apparently my interpretation of the English language doesn't count for anything, so I'm expecting to see Jim's letter of resignation on January 31st.)
As a follow-up to the difference between My Documents and Application Data, I'd like to rant about all the subdirectories of My Documents that programs create because they think they're so cool.
I'm sure there are more.
Everything in the My Documents folder the user should be able to point to and say, "I remember creating that file on such-and-such date when I did a 'Save' from Program Q." If it doesn't pass that test, then don't put it into My Documents. Use Application Data.
And don't create subdirectories off of My Documents. If the user wants to organize their documents into subdirectories, that's their business. You just ask them where they want their documents and let it go at that.
(Yes, I'm not a fan of My Music, My Videos, and My Pictures, either.)
Omar Shahine points out that Apple has similar guidelines for the Macintosh. I wonder how well people follow them.
Academy-award winning actress Linda Hunt reminds newer Oscar-winners, "It's not about you."
Some years ago, a group different from the one I worked in invited me to "volunteer" to help them with serious problems they were having with their product. They asked to "borrow" me for one week so that I could magically resolve all their issues. I wasn't really that familiar with their product, and I certainly didn't know how it worked internally, so I didn't think I could actually be much help given just one week. But they kept insisting that I "volunteer" to help them, until I finally wrote,
I have a lot of vacation days and I'm not afraid to use them.
That one line catapulted me into thermonuclear device territory.
The Seattle Times reports that the National Weather Service is inviting suggestions from the public on what to call the recent windstorm. They're baffled because "the storm didn't fall on a holiday."
Huh?
The storm blew in on the evening of the 14th, but its effects weren't really felt until the morning of the 15th, and the first dark night for everyone was... the first night of Hanukkah, the Festival of Lights. Everybody in the Seattle area was lighting candles and celebrating Hanukkah (mostly unwittingly).
Gosh, I wonder what we should call this storm? If you have any ideas, send your suggestion to namethewindstorm@noaa.gov.
(By the way, in case you missed it, here's the picture of the house that had ten trees fall on it.)
The documentation for the VerQueryValue function states that the first parameter is a "pointer to the buffer containing the version-information resource returned by the GetFileVersionInfo function." Some people, however, decide to bypass this step and pass a pointer to data that was obtained some other way, and then wonder why VerQueryValue doesn't work.
VerQueryValue
GetFileVersionInfo
The documentation says that the first parameter to VerQueryValue must be a buffer returned by the GetFileVersionInfo function for a reason. The buffer returned by GetFileVersionInfo is an opaque data block specifically formatted so that VerQueryValue will work. You're not supposed to look inside that buffer, and you certainly can't try to "obtain the data some other way". Because if you do, VerQueryValue will look for something in a buffer that is not formatted in the manner the function expects.
(And it can't even detect that the buffer is improperly formatted because there is no cbSize parameter to the VerQueryValue function that tells it how big the buffer is. Without that information, VerQueryValue can't check that, say, all the internal values are in range, since it doesn't know what "out-of-range" is! Is a value of 4000 out of range? It would be if the buffer were only 3000 bytes long. But it would be okay if the buffer were 5000 bytes long. Since there is no size parameter, the the VerQueryValue function is forced to assume that everything is okay.)
cbSize
If it wasn't obvious enough from the documentation that you can't just pass a pointer to a version resource obtained "some other way", it's even more obvious once you see the format of 32-bit version resources. Notice that all strings are stored in Unicode. But if you call the ANSI version VerQueryValueA to request a string, the function has to give you a pointer to an ANSI string. There is no ANSI version of the string in the raw version resource, so what can it possibly return? You can't return a pointer to something that doesn't exist. VerQueryValueA needs to produce an ANSI string, and it does so from memory that GetFileVersionInfo prepared when the resources were extracted.
VerQueryValueA
That there is no cbSize parameter to VerQueryValue was a failure of the original design of the function, but a slightly more understandable one when you look at the original design of 16-bit version resources: Since 16-bit Windows didn't have Unicode support, there was only one version of VerQueryValue, and it operated on ANSI strings. The 16-bit version resource format used ANSI strings, so VerQueryValue could just return a pointer into the raw version resource. No character set conversion was necessary, and therefore no need to reserve additional space as a string conversion buffer, no need for GetFileVersionInfo to "prepare" a buffer so that VerQueryValue could convert from one character set to another.
We interrupt this religious holiday for an important commercial announcement. As a special Christmas present, my new best friends at Addison-Wesley sent me an advance copy of the dead-tree edition of my book. (Obligatory plug: Order it from Amazon. You know I want you to.)
One commenter asked whether my book would be available electronically. On the copyright page of the book (that's the back side of the title page) there is a section titled This Book Is Safari Enabled. You can follow the instructions there to gain access to the online edition of the book for 45 days. I'm told that the electronic edition of the book will also be available in multiple formats including PDF and something called eMobipocket, whatever that is. (The ISBN for the electronic edition is 0321491955.)
Last time we looked at the format of 32-bit version resources, but I ended with the remark that what you saw purported to be the resources of shell32.dll but actually weren't. What's going on here?
shell32.dll
The resources I presented last time were what the resources of shell32.dll should have been, but in fact they aren't.
A common mistake in generating 32-bit resources is to mistreat the cbData field of the structure I called a VERSIONNODE as a count of characters rather than a count of bytes if the type is Unicode text. Even Microsoft's own Resource Compiler has fallen into this trap! For example, consider this VERSIONNODE I presented last time:
cbData
VERSIONNODE
0098 4C 00 // cbNode (node ends at 0x0088 + 0x004C = 0x00D40) 009A 2C 00 // cbData 009C 01 00 // wType = 1 (string data) 009E 43 00 6F 00 6D 00 70 00 61 00 6E 00 79 00 4E 00 61 00 6D 00 65 00 00 00 // L"CompanyName" + null terminator 00B6 00 00 // padding to restore alignment 00B8 4D 00 69 00 63 00 72 00 6F 00 73 00 6F 00 66 00 74 00 20 00 43 00 6F 00 72 00 70 00 6F 00 72 00 61 00 74 00 69 00 6F 00 6E 00 00 00 // L"Microsoft Corporation" + null terminator 00E4 // no padding needed
In real life, the data take the following form:
0098 4C 00 // cbNode (node ends at 0x0088 + 0x004C = 0x00D40) 009A 16 00 // cchData (!) 009C 01 00 // wType = 1 (string data) ...
These malformed version resources manage to get away without crashing too horribly because the standard format of version resources uses string data only in leaf nodes. Therefore, the incorrect cbData affects only the node itself and doesn't cause the child nodes to be parsed incorrectly (since there are no child nodes).
Until somebody tries to read, say, \StringFileInfo\040904B0\CompanyName\oops. After the VerQueryValue function locates the VERSIONNODE corresponding to CompanyName, it tries to locate the first child node and, due to the incorrect cbData, ends up misinterpreting the middle of the string as if it were the start of a child VERSIONNODE. Things only go downhill from there.
\StringFileInfo\040904B0\CompanyName\oops
CompanyName
They're just lucky that nobody actually asks for that.
But wait, there's more. Somebody who calls the VerQueryValueA function expects to have the version string returned as ANSI, so VerQueryValueA needs to know how many characters to convert from Unicode to ANSI. If VerQueryValue trusted the erroneous cbData value, then ANSI callers would get only half the data they were expecting.
As a result of this mess, the VerQueryValue function keeps its eyes open and anticipates that the version resource it was given to parse may have been generated by one of these buggy version resource compilers and goes to some extra effort to accommodate those bugs.
Slate's Explainer column answers questions about current events. Sometimes they do it multiple times, as they did when I asked them how to pronounce the name Pinochet and they said Pee-no-CHAY, then later corrected themselves with pin-oh-CHET, and then again re-corrected themselves with Yes.
Yesterday, the Explainer provided a selection of the questions they received and didn't answer and invited the readers to vote on which one they would like to see answered. My favorite question was the one that begins "I have a sister that stresses the hell out of me," and goes on and on about the car accident, how her sister complains and complains, how she had to tell the sister to stop bugging her with her problems, I mean, I was in a car accident, and concludes, "I really need to know if a person can really stress you out with the same old thing over and over and over again. PLEASE ANSWER BACK ASAP." Comedy gold. If you made this stuff up people would say it was too unrealistic.
As for which one I would actually like answered, I don't know. They're all so goofy, and the answer wouldn't be all that enlightening.
Last time we looked at the format of 16-bit version resources. The 32-bit version is nearly identical, except that everything is now in Unicode. Each node is stored in the following structure (in pseudo-C):
struct VERSIONNODE { WORD cbNode; WORD cbData; WORD wType; WCHAR wszName[]; BYTE rgbPadding1[]; // DWORD alignment union { BYTE rgbData[cbData]; WCHAR wszValue[cbData / sizeof(WCHAR)]; }; BYTE rgbPadding2[]; // DWORD alignment VERSIONNODE rgvnChildren[]; };
In words, each version node begins with a 16-bit value describing the size of the nodes in bytes (including its children), followed by a 16-bit value that specifies how much data (either binary or text) are associated with the node. The wType is zero if the node data is binary or one if the node data is a string. Other values of wType are reserved for future use. The rest is the same as before: A null-terminated key name, padding for DWORD alignment, the node data, more padding, and then the child nodes.
wType
DWORD
The only change beyond the conversion to Unicode is the introduction of the wType field. This field is necessary so that the system knows whether to convert the node data from Unicode to ANSI when somebody calls the VerQueryStringA. If it's binary data, then no conversion is done; if it's string data, then the string is converted.
VerQueryStringA
We illustrate the 32-bit resource format by looking at the resources for the 32-bit shell32.dll.
0000 98 03 34 00 00 00 56 00-53 00 5F 00 56 00 45 00 ..4...V.S._.V.E. 0010 52 00 53 00 49 00 4F 00-4E 00 5F 00 49 00 4E 00 R.S.I.O.N._.I.N. 0020 46 00 4F 00 00 00 00 00-BD 04 EF FE 00 00 01 00 F.O............. 0030 00 00 06 00 35 0B 54 0B-00 00 06 00 35 0B 54 0b ....5.T.....5.T. 0040 3F 00 00 00 00 00 00 00-04 00 04 00 02 00 00 00 ?............... 0050 00 00 00 00 00 00 00 00-00 00 00 00 F6 02 00 00 ................ 0060 01 00 53 00 74 00 72 00-69 00 6E 00 67 00 46 00 ..S.t.r.i.n.g.F. 0070 69 00 6C 00 65 00 49 00-6E 00 66 00 6F 00 00 00 i.l.e.I.n.f.o... 0080 D2 02 00 00 01 00 30 00-34 00 30 00 39 00 30 00 ......0.4.0.9.0. 0090 34 00 42 00 30 00 00 00-4C 00 2C 00 01 00 43 00 4.B.0...L.....C. 00A0 6F 00 6D 00 70 00 61 00-6E 00 79 00 4E 00 61 00 o.m.p.a.n.y.N.a. 00B0 6D 00 65 00 00 00 00 00-4D 00 69 00 63 00 72 00 m.e.....M.i.c.r. 00C0 6F 00 73 00 6F 00 66 00-74 00 20 00 43 00 6F 00 o.s.o.f.t. .C.o. 00D0 72 00 70 00 6F 00 72 00-61 00 74 00 69 00 6F 00 r.p.o.r.a.t.i.o. 00E0 6E 00 00 00 5A 00 32 00-01 00 46 00 69 00 6C 00 n...Z.....F.i.l. 00F0 65 00 44 00 65 00 73 00-63 00 72 00 69 00 70 00 e.D.e.s.c.r.i.p. 0100 74 00 69 00 6F 00 6E 00-00 00 00 00 57 00 69 00 t.i.o.n.....W.i. 0110 6E 00 64 00 6F 00 77 00-73 00 20 00 53 00 68 00 n.d.o.w.s. .S.h. 0120 65 00 6C 00 6C 00 20 00-43 00 6F 00 6D 00 6D 00 e.l.l. .C.o.m.m. 0130 6F 00 6E 00 20 00 44 00-6C 00 6C 00 00 00 00 00 o.n. .D.l.l..... 0140 74 00 54 00 01 00 46 00-69 00 6C 00 65 00 56 00 t.*...F.i.l.e.V. 0150 65 00 72 00 73 00 69 00-6F 00 6E 00 00 00 00 00 e.r.s.i.o.n..... 0160 36 00 2E 00 30 00 30 00-2E 00 32 00 39 00 30 00 6...0.0...2.9.0. 0170 30 00 2E 00 32 00 38 00-36 00 39 00 20 00 28 00 0...2.8.6.9. .(. 0180 78 00 70 00 73 00 70 00-5F 00 73 00 70 00 32 00 x.p.s.p._.s.p.2. 0190 5F 00 67 00 64 00 72 00-2E 00 30 00 36 00 30 00 _.g.d.r...0.6.0. 01A0 33 00 31 00 36 00 2D 00-31 00 35 00 31 00 32 00 3.1.6.-.1.5.1.2. 01B0 29 00 00 00 30 00 10 00-01 00 49 00 6E 00 74 00 )...0.....I.n.t. 01C0 65 00 72 00 6E 00 61 00-6C 00 4E 00 61 00 6D 00 e.r.n.a.l.N.a.m. 01D0 65 00 00 00 53 00 48 00-45 00 4C 00 4C 00 33 00 e...S.H.E.L.L.3. 01E0 32 00 00 00 80 00 5C 00-01 00 4C 00 65 00 67 00 2.........L.e.g. 01F0 61 00 6C 00 43 00 6F 00-70 00 79 00 72 00 69 00 a.l.C.o.p.y.r.i. 0200 67 00 68 00 74 00 00 00-A9 00 20 00 4D 00 69 00 g.h.t..... .M.i. 0210 63 00 72 00 6F 00 73 00-6F 00 66 00 74 00 20 00 c.r.o.s.o.f.t. . 0220 43 00 6F 00 72 00 70 00-6F 00 72 00 61 00 74 00 C.o.r.p.o.r.a.t. 0230 69 00 6F 00 6E 00 2E 00-20 00 41 00 6C 00 6C 00 i.o.n... .A.l.l. 0240 20 00 72 00 69 00 67 00-68 00 74 00 73 00 20 00 .r.i.g.h.t.s. . 0250 72 00 65 00 73 00 65 00-72 00 76 00 65 00 64 00 r.e.s.e.r.v.e.d. 0260 2E 00 00 00 40 00 18 00-01 00 4F 00 72 00 69 00 ....@.....O.r.i. 0270 67 00 69 00 6E 00 61 00-6C 00 46 00 69 00 6C 00 g.i.n.a.l.F.i.l. 0280 65 00 6E 00 61 00 6D 00-65 00 00 00 53 00 48 00 e.n.a.m.e...S.H. 0290 45 00 4C 00 4C 00 33 00-32 00 2E 00 44 00 4C 00 E.L.L.3.2...D.L. 02A0 4C 00 00 00 6A 00 25 00-01 00 50 00 72 00 6F 00 L...j.%...P.r.o. 02B0 64 00 75 00 63 00 74 00-4E 00 61 00 6D 00 65 00 d.u.c.t.N.a.m.e. 02C0 00 00 00 00 4D 00 69 00-63 00 72 00 6F 00 73 00 ....M.i.c.r.o.s. 02D0 6F 00 66 00 74 00 AE 00-20 00 57 00 69 00 6E 00 o.f.t... .W.i.n. 02E0 64 00 6F 00 77 00 73 00-AE 00 20 00 4F 00 70 00 d.o.w.s... .O.p. 02F0 65 00 72 00 61 00 74 00-69 00 6E 00 67 00 20 00 e.r.a.t.i.n.g. . 0300 53 00 79 00 73 00 74 00-65 00 6D 00 00 00 00 00 S.y.s.t.e.m..... 0310 42 00 1E 00 01 00 50 00-72 00 6F 00 64 00 75 00 B.....P.r.o.d.u. 0320 63 00 74 00 56 00 65 00-72 00 73 00 69 00 6F 00 c.t.V.e.r.s.i.o. 0330 6E 00 00 00 36 00 2E 00-30 00 30 00 2E 00 32 00 n...6...0.0...2. 0340 39 00 30 00 30 00 2E 00-32 00 38 00 36 00 39 00 9.0.0...2.8.6.9. 0350 00 00 00 00 44 00 00 00-01 00 56 00 61 00 72 00 ....D.....V.a.r. 0360 46 00 69 00 6C 00 65 00-49 00 6E 00 66 00 6F 00 F.i.l.e.I.n.f.o. 0370 00 00 00 00 24 00 04 00-00 00 54 00 72 00 61 00 ....$.....T.r.a. 0380 6E 00 73 00 6C 00 61 00-74 00 69 00 6F 00 6E 00 n.s.l.a.t.i.o.n. 0390 00 00 00 00 09 04 B0 04 ........
As always, we start with the root node.
0000 98 03 // cbNode (node ends at 0x0000 + 0x0398 = 0x0398) 0002 34 00 // cbData = sizeof(VS_FIXEDFILEINFO) 0004 00 00 // wType = 0 (binary data) 0006 56 00 53 00 5F 00 56 00 45 00 52 00 53 00 49 00 4F 00 4E 00 5F 00 49 00 4E 00 46 00 4F 00 00 00 // L"VS_VERSION_INFO" + null terminator 0026 00 00 // padding to restore alignment 0028 BD 04 EF FE // dwSignature 002C 00 00 01 00 // dwStrucVersion 0030 00 00 06 00 // dwFileVersionMS = 6.0 0034 35 0B 54 0B // dwFileVersionLS = 2900.2869 0038 00 00 06 00 // dwProductVersionMS = 6.0 003C 35 0B 54 0B // dwProductVersionLS = 2900.2869 0040 3F 00 00 00 // dwFileFlagsMask 0044 00 00 00 00 // dwFileFlags 0048 04 00 04 00 // dwFileOS = VOS_NT_WINDOWS32 004C 02 00 00 00 // dwFileType = VFT_DLL 0050 00 00 00 00 // dwFileSubtype 0054 00 00 00 00 // dwFileDateMS 0058 00 00 00 00 // dwFileDateLS 005C // no padding needed
As with the 16-bit version resource, the root node is always a binary node consisting of a VS_FIXEDFILEINFO. After the root node come its children.
VS_FIXEDFILEINFO
005C F6 02 // cbNode (node ends at 0x005C + 0x02F6 = 0x0352) 005E 00 00 // cbData (no data) 0060 01 00 // wType = 1 (string data) 0062 53 00 74 00 72 00 69 00 6E 00 67 00 46 00 69 00 6C 00 65 00 49 00 6E 00 66 00 6F 00 00 00 // L"StringFileInfo" + null terminator 007E 00 00 // padding to restore alignment
The StringFileInfo contains no data, so the fact that it's string data is irrelevant. As before, the children of the StringFileInfo are language nodes.
StringFileInfo
0080 D2 02 // cbNode (node ends at 0x0080 + 0x02D2 = 0x0352) 0082 00 00 // cbData (no data) 0084 01 00 // wType = 1 (string data) 0086 30 00 34 00 30 00 39 00 30 00 34 00 42 00 30 00 00 00 // L"040904B0" + null terminator 0098 // no padding needed
The children of the language node are the strings that make up the bulk of the version information.
Notice that for string types, the cbData includes the null terminator.
00E4 5A 00 // cbNode (node ends at 0x00E4 + 0x005A = 0x013E) 00E6 32 00 // cbData 00E8 01 00 // wTypes = 1 (string data) 00EA 46 00 69 00 6C 00 65 00 44 00 65 00 73 00 63 00 72 00 69 00 70 00 74 00 69 00 6F 00 6E 00 00 00 // L"FileDescription" + null terminator 010A 00 00 // padding to restore alignment 010C 57 00 69 00 6E 00 64 00 6F 00 77 00 73 00 20 00 53 00 68 00 65 00 6C 00 6C 00 20 00 43 00 6F 00 6D 00 6D 00 6F 00 6E 00 20 00 44 00 6C 00 6C 00 00 00 // L"Windows Shell Common Dll" + null terminator 013E 00 00 // padding to restore alignment 0140 74 00 // cbNode (node ends at 0x0140 + 0x0074 = 0x01B4) 0142 54 00 // cbData 0144 01 00 // wType = 1 (string data) 0146 46 00 69 00 6C 00 65 00 56 00 65 00 72 00 73 00 69 00 6F 00 6E 00 00 00 // L"FileVersion" + null terminator 015E 00 00 // padding to restore alignment 0160 36 00 2E 00 30 00 30 00 2E 00 32 00 39 00 30 00 30 00 2E 00 32 00 38 00 36 00 39 00 20 00 28 00 78 00 70 00 73 00 70 00 5F 00 73 00 70 00 32 00 5F 00 67 00 64 00 72 00 2E 00 30 00 36 00 30 00 33 00 31 00 36 00 2D 00 31 00 35 00 31 00 32 00 29 00 00 00 // L"6.00.2900.2869 (xpsp_sp2_gdr.060316-1512)" // + null terminator 01B4 // no padding needed 01B4 30 00 // cbNode (node ends at 0x01B4 + 0x0030 = 0x01E4) 01B6 10 00 // cbData 01B8 01 00 // wType = 1 (string data) 01BA 49 00 6E 00 74 00 65 00 72 00 6E 00 61 00 6C 00 4E 00 61 00 6D 00 65 00 00 00 // L"InternalName" + null terminator 01D4 // no padding needed 01D4 53 00 48 00 45 00 4C 00 4C 00 33 00 32 00 00 00 // L"SHELL32" + null terminator 01E4 // no padding needed 01E4 80 00 // cbNode (node ends at 0x01E4 + 0x0080 = 0x0264) 01E6 5C 00 // cbData 01E8 01 00 // wType = 1 (string data) 01EA 4C 00 65 00 67 00 61 00 6C 00 43 00 6F 00 70 00 79 00 72 00 69 00 67 00 68 00 74 00 00 00 // L"LegalCopyright" + null terminator 0208 // no padding needed 0208 A9 00 20 00 4D 00 69 00 63 00 72 00 6F 00 73 00 6F 00 66 00 74 00 20 00 43 00 6F 00 72 00 70 00 6F 00 72 00 61 00 74 00 69 00 6F 00 6E 00 2E 00 20 00 41 00 6C 00 6C 00 20 00 72 00 69 00 67 00 68 00 74 00 73 00 20 00 72 00 65 00 73 00 65 00 72 00 76 00 65 00 64 00 2E 00 00 00 // L"© Microsoft Corporation. " // L"All rights reserved." + null terminator 0264 // no padding needed 0264 40 00 // cbNode (node ends at 0x0264 + 0x0040 = 0x02A4) 0266 18 00 // cbData 0268 01 00 // wType = 1 (string data) 026A 4F 00 72 00 69 00 67 00 69 00 6E 00 61 00 6C 00 46 00 69 00 6C 00 65 00 6E 00 61 00 6D 00 65 00 00 00 // L"OriginalFilename" + null terminator 028C // no padding needed 028C 53 00 48 00 45 00 4C 00 4C 00 33 00 32 00 2E 00 44 00 4C 00 4C 00 00 00 // L"SHELL32.DLL" + null terminator 02A4 // no padding needed 02A4 6A 00 // cbNode (node ends at 0x02A4 + 0x006A = 0x030E) 02A6 25 00 01 00 50 00 72 00 6F 00 64 00 75 00 63 00 74 00 4E 00 61 00 6D 00 65 00 00 00 // L"ProductName" + null terminator 02C2 00 00 // padding to restore alignment 02C4 4D 00 69 00 63 00 72 00 6F 00 73 00 6F 00 66 00 74 00 AE 00 20 00 57 00 69 00 6E 00 64 00 6F 00 77 00 73 00 AE 00 20 00 4F 00 70 00 65 00 72 00 61 00 74 00 69 00 6E 00 67 00 20 00 53 00 79 00 73 00 74 00 65 00 6D 00 00 00 // L"Microsoft® Windows® " // L"Operating System" + null terminator 030E 00 00 // padding to restore alignment 0310 42 00 // cbNode (node ends at 0x0310 + 0x0042 = 0x0352) 0312 1E 00 // cbData 0314 01 00 // wType = 1 (string data) 0316 50 00 72 00 6F 00 64 00 75 00 63 00 74 00 56 00 65 00 72 00 73 00 69 00 6F 00 6E 00 00 00 // L"ProductVersion" + null terminator 0334 // no padding needed 0334 36 00 2E 00 30 00 30 00 2E 00 32 00 39 00 30 00 30 00 2E 00 32 00 38 00 36 00 39 00 00 00 // L"6.00.2900.2869" + null terminator 0352 00 00 // padding to restore alignment
At offset 0x0352, we've reached the end of the ProductVersion node, the language node, and the StringFileInfo node. The next node is therefore a sibilng to StringFileInfo.
0x0352
ProductVersion
0354 44 00 // cbNode (node ends at 0x0354 + 0x0044 = 0x0394) 0356 00 00 // cbData 0358 01 00 // wType = 1 (string data) 035A 56 00 61 00 72 00 46 00 69 00 6C 00 65 00 49 00 6E 00 66 00 6F 00 00 00 // L"VarFileInfo" + null terminator 0372 00 00 // padding to restore alignment
VarFileInfo
0374 24 00 // cbNode (node ends at 0x0374 + 0x0024 = 0x0394) 0376 04 00 // cbData 0378 00 00 // wType = 0 (binary data) 037A 54 00 72 00 61 00 6E 00 73 00 6C 00 61 00 74 00 69 00 6F 00 6E 00 00 00 // L"Translation" + null terminator 0392 00 00 // padding to restore alignment 0394 09 04 B0 04 // 0x0409 = US English // 0x04B0 = 1200 = Unicode UCS-2 little-endian 0398 // no padding needed
We have reached the end of the Translation node, the VarFileInfo node, and the root node. Our parsing of the version data is complete and resulted in the following version resource:
Translation
FILEVERSION 3,0,2900,2869 PRODUCTVERSION 3,0,2900,2869 FILEFLAGSMASK VS_FFI_FILEFLAGSMASK FILEFLAGS 0 FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL FILESUBTYPE VFT_UNKNOWN BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904B0" BEGIN VALUE "CompanyName", "Microsoft Corporation" VALUE "FileDescription", Windows Shell Common Dll" VALUE "FileVersion", "6.00.2900.2869 (xpsp_sp2_gdr.060316-1512)" VALUE "InternalName", "SHELL32" VALUE "LegalCopyright", "\251 Microsoft Corporation. All rights reserved." VALUE "OriginalFilename", "SHELL32.DLL" VALUE "ProductName", "Microsoft\256 Windows\256 Operating System" VALUE "ProductVersion", "6.00.2900.2869" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x0409, 0x04B0 END END
There you have it, the format of 32-bit version resources. Future versions of Windows may extend this format, but that's what it looks like up through Windows Vista.
Now, after all that byte-mashing, I have to confess that I've been lying to you. What you saw was not the actual version resource in shell32.dll. We'll discuss what's really going on next time.