ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.68
Committed: Sun Sep 1 15:57:48 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.67: +277 -277 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.7 #!/usr/bin/perl
2 root 1.1
3     #
4     # PBCDEDIT - Copyright 2019 Marc A. Lehmann <pbcbedit@schmorp.de>
5     #
6     # SPDX-License-Identifier: GPL-3.0-or-later
7     #
8     # This program is free software: you can redistribute it and/or modify
9     # it under the terms of the GNU General Public License as published by
10     # the Free Software Foundation, either version 3 of the License, or
11     # (at your option) any later version.
12     #
13     # This program is distributed in the hope that it will be useful,
14     # but WITHOUT ANY WARRANTY; without even the implied warranty of
15     # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16     # GNU General Public License for more details.
17     #
18     # You should have received a copy of the GNU General Public License
19     # along with this program. If not, see <https://www.gnu.org/licenses/>.
20     #
21    
22 root 1.34 use 5.016; # numerous features need 5.14, __SUB__ needs 5.16
23 root 1.1
24 root 1.59 our $VERSION = '1.4';
25 root 1.40 our $JSON_VERSION = 3; # the version of the json objects generated by this program
26 root 1.37
27     our $CHANGELOG = <<EOF;
28 root 1.40
29 root 1.60 - work around lsblk bug sometimes giving dos pttype for gpt partitions.
30 root 1.61 - bootmenupolicy in synopsis must be set to 0 for text menu.
31 root 1.60
32 root 1.56 1.4 Thu Aug 22 10:48:22 CEST 2019
33     - new "create" subcommand.
34     - "create" and "edit" try to save and restore ownership/permissions
35     of bcd hives when writing the new file.
36 root 1.51 - editorial fixes to the documentation.
37 root 1.59 - add mininmal hive creation example.
38 root 1.51
39 root 1.45 1.3 Sat Aug 17 07:04:15 CEST 2019
40 root 1.40 - output of pbcdedit elements --json has changed, as it didn't
41     take the reorganisation by classes fully into account.
42     - json schema bumped to 3.
43 root 1.43 - new "bcd-device" and "bcd-legacy-device" subcommands.
44 root 1.44 - implement --json option for lsblk.
45 root 1.40
46 root 1.37 1.2 Fri Aug 16 00:20:41 CEST 2019
47 root 1.46 - bcd element names now depend on the bcd object type they are in,
48 root 1.37 also affects "elements" output.
49     - json schema bumped to 2.
50     - new version command.
51     - numerous minor bugfixes.
52    
53     EOF
54 root 1.1
55     =head1 NAME
56    
57     pbcdedit - portable boot configuration data (BCD) store editor
58    
59     =head1 SYNOPSIS
60    
61     pbcdedit help # output manual page
62 root 1.37 pbcdedit version # output version and changelog
63 root 1.28
64 root 1.1 pbcdedit export path/to/BCD # output BCD hive as JSON
65 root 1.28 pbcdedit import path/to/BCD # convert standard input to BCD hive
66 root 1.1 pbcdedit edit path/to/BCD edit-instructions...
67    
68     pbcdedit objects # list all supported object aliases and types
69     pbcdedit elements # list all supported bcd element aliases
70    
71 root 1.52 # Example: enable text-based boot menu.
72 root 1.61 pbcdedit edit /my/BCD set '{default}' bootmenupolicy 0
73 root 1.52
74     # Example change system device to first partition containing winload.
75     pbcdedit edit /my/BCD \
76     set '{default}' device 'locate=<null>,element,path' \
77     set '{default}' osdevice 'locate=<null>,element,path'
78    
79    
80 root 1.1 =head1 DESCRIPTION
81    
82     This program allows you to create, read and modify Boot Configuration Data
83     (BCD) stores used by Windows Vista and newer versions of Windows.
84    
85 root 1.9 At this point, it is in relatively early stages of development and has
86     received little to no real-world testing.
87    
88 root 1.1 Compared to other BCD editing programs it offers the following unique
89     features:
90    
91     =over
92    
93     =item Can create BCD hives from scratch
94    
95     Practically all other BCD editing programs force you to copy existing BCD
96     stores, which might or might not be copyrighted by Microsoft.
97    
98     =item Does not rely on Windows
99    
100     As the "portable" in the name implies, this program does not rely on
101     C<bcdedit> or other windows programs or libraries, it works on any system
102 root 1.34 that supports at least perl version 5.16.
103 root 1.1
104     =item Decodes and encodes BCD device elements
105    
106     PBCDEDIT can concisely decode and encode BCD device element contents. This
107     is pretty unique, and offers a lot of potential that can't be realised
108     with C<bcdedit> or any programs relying on it.
109    
110     =item Minimal files
111    
112     BCD files written by PBCDEDIT are always "minimal", that is, they don't
113     contain unused data areas and therefore don't contain old and potentially
114     sensitive data.
115    
116     =back
117    
118     The target audience for this program is professionals and tinkerers who
119 root 1.11 are ready to invest time into learning how it works. It is not an easy
120 root 1.26 program to use and requires patience and a good understanding of BCD
121 root 1.1 stores.
122    
123    
124     =head1 SUBCOMMANDS
125    
126 root 1.11 PBCDEDIT expects a subcommand as first argument that tells it what to
127 root 1.1 do. The following subcommands exist:
128    
129     =over
130    
131 root 1.20 =item C<help>
132 root 1.1
133 root 1.11 Displays the whole manual page (this document).
134 root 1.1
135 root 1.37 =item C<version>
136    
137     This outputs the PBCDEDIT version, the JSON schema version it uses and the
138     full log of changes.
139    
140 root 1.20 =item C<export> F<path>
141 root 1.1
142     Reads a BCD data store and writes a JSON representation of it to standard
143     output.
144    
145     The format of the data is explained later in this document.
146    
147 root 1.11 Example: read a BCD store, modify it with an external program, write it
148     again.
149 root 1.1
150     pbcdedit export BCD | modify-json-somehow | pbcdedit import BCD
151    
152 root 1.20 =item C<import> F<path>
153 root 1.1
154     The reverse of C<export>: Reads a JSON representation of a BCD data store
155     from standard input, and creates or replaces the given BCD data store.
156    
157 root 1.20 =item C<edit> F<path> I<instructions...>
158 root 1.1
159 root 1.6 Load a BCD data store, apply some instructions to it, and save it again.
160    
161 root 1.26 See the section L<EDITING BCD STORES>, below, for more info.
162 root 1.6
163 root 1.20 =item C<parse> F<path> I<instructions...>
164 root 1.6
165     Same as C<edit>, above, except it doesn't save the data store again. Can
166     be useful to extract some data from it.
167 root 1.1
168 root 1.56 =item C<create> F<path> I<instructions...>
169    
170     Same as C<edit>, above, except it creates a new data store from scratch if
171     needed. An existing store will be emptied completely.
172    
173 root 1.44 =item C<lsblk> [C<--json>]
174 root 1.1
175     On a GNU/Linux system, you can get a list of partition device descriptors
176     using this command - the external C<lsblk> command is required, as well as
177     a mounted C</sys> file system.
178    
179     The output will be a list of all partitions in the system and C<partition>
180     descriptors for GPT and both C<legacypartition> and C<partition>
181 root 1.11 descriptors for MBR partitions.
182 root 1.1
183 root 1.53 With C<--json> it will print similar information as C<lsblk --json>, but
184 root 1.44 with extra C<bcd_device> and C<bcd_legacy_device> attributes.
185    
186 root 1.43 =item C<bcd-device> F<path>
187    
188     Tries to find the BCD device element for the given device, which currently
189     must be a a partition of some kind. Prints the C<partition=> descriptor as
190     a result, or nothing. Exit status will be true on success, and false on
191     failure.
192    
193     Like C<lsblk>, above, this likely only works on GNU/Linux systems.
194    
195     Example: print the partition descriptor of tghe partition with label DATA.
196    
197     $ pbcdedit bcd-device /dev/disk/by-label/DATA
198     partition=<null>,harddisk,mbr,47cbc08a,213579202560
199    
200     =item C<bcd-legacy-device> F<path>
201    
202     Like above, but uses a C<legacypartition> descriptor instead.
203    
204 root 1.20 =item C<objects> [C<--json>]
205 root 1.1
206 root 1.11 Outputs two tables: a table listing all type aliases with their hex BCD
207 root 1.1 element ID, and all object name aliases with their GUID and default type
208     (if any).
209    
210     With C<--json> it prints similar information as a JSON object, for easier parsing.
211    
212 root 1.20 =item C<elements> [C<--json>]
213 root 1.1
214     Outputs a table of known element aliases with their hex ID and the format
215     type.
216    
217     With C<--json> it prints similar information as a JSON object, for easier parsing.
218    
219 root 1.20 =item C<export-regf> F<path>
220 root 1.1
221 root 1.31 This has nothing to do with BCD stores, but simply exposes PCBEDIT's
222 root 1.30 internal registry hive reader - it takes a registry hive file as argument
223     and outputs a JSON representation of it to standard output.
224 root 1.1
225     Hive versions 1.2 till 1.6 are supported.
226    
227 root 1.20 =item C<import-regf> F<path>
228 root 1.1
229     The reverse of C<export-regf>: reads a JSON representation of a registry
230 root 1.20 hive from standard input and creates or replaces the registry hive file
231     given as argument.
232 root 1.1
233     The written hive will always be in a slightly modified version 1.3
234     format. It's not the format windows would generate, but it should be
235     understood by any conformant hive reader.
236    
237     Note that the representation chosen by PBCDEDIT currently throws away
238 root 1.11 classname data (often used for feeble attempts at hiding stuff by
239 root 1.1 Microsoft) and security descriptors, so if you write anything other than
240     a BCD hive you will most likely destroy it.
241    
242     =back
243    
244    
245 root 1.27 =head1 BCD STORE REPRESENTATION FORMAT
246 root 1.1
247     A BCD data store is represented as a JSON object with one special key,
248     C<meta>, and one key per BCD object. That is, each BCD object becomes
249     one key-value pair in the object, and an additional key called C<meta>
250     contains meta information.
251    
252     Here is an abridged example of a real BCD store:
253    
254     {
255     "meta" : {
256     "version" : 1
257     },
258     "{7ae02178-821d-11e7-8813-1c872c5f5ab0}" : {
259     "type" : "application::osloader",
260     "description" : "Windows 10",
261     "device" : "partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,3ce6aceb-e90c-4fd2-9fba-47cab15f6faf",
262     "osdevice" : "partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,3ce6aceb-e90c-4fd2-9fba-47cab15f6faf",
263     "path" : "\\Windows\\system32\\winload.exe",
264     "systemroot" : "\\Windows"
265     },
266     "{bootloadersettings}" : {
267     "inherit" : "{globalsettings} {hypervisorsettings}"
268     },
269     "{bootmgr}" : {
270     "description" : "Windows Boot Manager",
271     "device" : "partition=<null>,harddisk,mbr,ff3ba63b,1048576",
272     "displayorder" : "{7ae02178-821d-11e7-8813-1c872c5f5ab0}",
273     "inherit" : "{globalsettings}",
274     "displaybootmenu" : 0,
275     "timeout" : 30
276     },
277     "{globalsettings}" : {
278     "inherit" : "{dbgsettings} {emssettings} {badmemory}"
279     },
280     "{hypervisorsettings}" : {
281     "hypervisorbaudrate" : 115200,
282     "hypervisordebugport" : 1,
283     "hypervisordebugtype" : 0
284     },
285     # ...
286     }
287    
288 root 1.3 =head2 Minimal BCD to boot windows
289    
290     Experimentally I found the following BCD is the minimum required to
291 root 1.38 successfully boot any post-XP version of Windows (assuming suitable
292 root 1.39 C<device> and C<osdevice> values, of course, and assuming a BIOS boot -
293     for UEFI, you should use F<winload.efi> instead of F<winload.exe>):
294 root 1.3
295     {
296     "{bootmgr}" : {
297 root 1.36 "default" : "{45b547a7-8ca6-4417-9eb0-a257b61f35b4}"
298 root 1.3 },
299    
300     "{45b547a7-8ca6-4417-9eb0-a257b61f35b1}" : {
301     "type" : "application::osloader",
302     "description" : "Windows Boot",
303     "device" : "legacypartition=<null>,harddisk,mbr,47cbc08a,1",
304     "osdevice" : "legacypartition=<null>,harddisk,mbr,47cbc08a,1",
305     "path" : "\\Windows\\system32\\winload.exe",
306     "systemroot" : "\\Windows"
307     },
308     }
309    
310     Note that minimal doesn't mean recommended - Windows itself will add stuff
311     to this during or after boot, and you might or might not run into issues
312     when installing updates as it might not be able to find the F<bootmgr>.
313    
314 root 1.58 This is how you would create a minimal hive with PBCDEDIT from within
315     GNU/Linux, assuming F</dev/sdc3> is the windows partition, using
316     a random GUID for the osloader and using C<partition> instead of
317     C<legacypartition>:
318 root 1.57
319     osldr="{$(uuidgen)}"
320     part=$(pbcdedit bcd-device /dev/sdc3)
321     pbcdedit create minimal.bcd \
322     set '{bootmgr}' default "$osldr" \
323     set "$osldr" type application::osloader \
324     set "$osldr" description 'Windows Boot' \
325     set "$osldr" device "$part" \
326     set "$osldr" osdevice "$part" \
327 root 1.58 set "$osldr" path '\Windows\system32\winload.exe' \
328 root 1.57 set "$osldr" systemroot '\Windows'
329    
330 root 1.1 =head2 The C<meta> key
331    
332     The C<meta> key is not stored in the BCD data store but is used only
333     by PBCDEDIT. It is always generated when exporting, and importing will
334     be refused when it exists and the version stored inside doesn't store
335 root 1.11 the JSON schema version of PBCDEDIT. This ensures that different and
336     incompatible versions of PBCDEDIT will not read and misinterpret each
337 root 1.1 others data.
338    
339     =head2 The object keys
340    
341     Every other key is a BCD object. There is usually a BCD object for the
342     boot manager, one for every boot option and a few others that store common
343     settings inherited by these.
344    
345     Each BCD object is represented by a GUID wrapped in curly braces. These
346 root 1.11 are usually random GUIDs used only to distinguish BCD objects from each
347 root 1.1 other. When adding a new boot option, you can simply generate a new GUID.
348    
349     Some of these GUIDs are fixed well known GUIDs which PBCDEDIT will decode
350     into human-readable strings such as C<{globalsettings}>, which is the same
351     as C<{7ea2e1ac-2e61-4728-aaa3-896d9d0a9f0e}>.
352    
353     Each BCD, object has an associated type. For example,
354     C<application::osloader> for objects loading Windows via F<winload.exe>,
355     C<application::bootsector> for real mode applications and so on.
356    
357     The type of a object is stored in the pseudo BCD element C<type> (see next
358     section).
359    
360     Some well-known objects have a default type. If an object type matches
361     its default type, then the C<type> element will be omitted. Similarly, if
362     the C<type> element is missing and the BCD object has a default type, the
363     default type will be used when writing a BCD store.
364    
365     Running F<pbcdedit objects> will give you a list of object types,
366     well-known object aliases and their default types.
367    
368     If different string keys in a JSON BCD store map to the same BCD object
369     then a random one will "win" and the others will be discarded. To avoid
370     this, you should always use the "canonical" name of a BCD object, which is
371     the human-readable form (if it exists).
372    
373     =head2 The object values - BCD elements
374    
375     The value of each BCD object entry consists of key-value pairs called BCD
376     elements.
377    
378     BCD elements are identified by a 32 bit number, but to make things
379     simpler PBCDEDIT will replace these with well-known strings such as
380     C<description>, C<device> or C<path>.
381    
382     When PBCDEDIT does not know the BCD element, it will use
383     C<custom:HHHHHHHH>, where C<HHHHHHHH> is the 8-digit hex number of the
384     BCD element. For example, C<device> would be C<custom::11000001>. You can
385     get a list of all BCD elements known to PBCDEDIT by running F<pbcdedit
386     elements>.
387    
388     What was said about duplicate keys mapping to the same object is true for
389     elements as well, so, again, you should always use the canonical name,
390 root 1.11 which is the human readable alias, if known.
391 root 1.1
392     =head3 BCD element types
393    
394     Each BCD element has a type such as I<string> or I<boolean>. This type
395     determines how the value is interpreted, and most of them are pretty easy
396     to explain:
397    
398     =over
399    
400     =item string
401    
402     This is simply a unicode string. For example, the C<description> and
403     C<systemroot> elements both are of this type, one storing a human-readable
404     name for this boot option, the other a file path to the windows root
405     directory:
406    
407     "description" : "Windows 10",
408     "systemroot" : "\\Windows",
409    
410     =item boolean
411    
412 root 1.11 Almost as simple are booleans, which represent I<true>/I<false>,
413 root 1.1 I<on>/I<off> and similar values. In the JSON form, true is represented
414     by the number C<1>, and false is represented by the number C<0>. Other
415     values will be accepted, but PBCDEDIT doesn't guarantee how these are
416     interpreted.
417    
418     For example, C<displaybootmenu> is a boolean that decides whether to
419     enable the C<F8> boot menu. In the example BCD store above, this is
420     disabled:
421    
422     "displaybootmenu" : 0,
423    
424     =item integer
425    
426 root 1.41 Again, very simple, this is a 64 bit integer. It can be either specified
427 root 1.1 as a decimal number, as a hex number (by prefixing it with C<0x>) or as a
428 root 1.11 binary number (prefix C<0b>).
429 root 1.1
430     For example, the boot C<timeout> is an integer, specifying the automatic
431     boot delay in seconds:
432    
433     "timeout" : 30,
434    
435     =item integer list
436    
437     This is a list of 64 bit integers separated by whitespace. It is not used
438 root 1.54 much, so here is a somewhat artificial and untested example of using
439 root 1.1 C<customactions> to specify a certain custom, eh, action to be executed
440     when pressing C<F10> at boot:
441    
442     "customactions" : "0x1000044000001 0x54000001",
443    
444     =item guid
445    
446 root 1.11 This represents a single GUID value wrapped in curly braces. It is used a
447 root 1.1 lot to refer from one BCD object to other one.
448    
449     For example, The C<{bootmgr}> object might refer to a resume boot option
450 root 1.36 using C<default>:
451 root 1.1
452 root 1.36 "default" : "{7ae02178-821d-11e7-8813-1c872c5f5ab0}",
453 root 1.1
454     Human readable aliases are used and allowed.
455    
456     =item guid list
457    
458 root 1.11 Similar to the GUID type, this represents a list of such GUIDs, separated
459 root 1.1 by whitespace from each other.
460    
461     For example, many BCD objects can I<inherit> elements from other BCD
462 root 1.11 objects by specifying the GUIDs of those other objects in a GUID list
463 root 1.1 called surprisingly called C<inherit>:
464    
465     "inherit" : "{dbgsettings} {emssettings} {badmemory}",
466    
467     This example also shows how human readable aliases can be used.
468    
469     =item device
470    
471     This type is why I write I<most> are easy to explain earlier: This type
472     is the pinnacle of Microsoft-typical hacks layered on top of other
473     hacks. Understanding this type took more time than writing all the rest of
474     PBCDEDIT, and because it is so complex, this type has its own subsection
475     below.
476 root 1.54
477 root 1.1 =back
478    
479 root 1.50 =head3 The BCD "device" element type
480 root 1.1
481     Device elements specify, well, devices. They are used for such diverse
482 root 1.11 purposes such as finding a TFTP network boot image, serial ports or VMBUS
483 root 1.1 devices, but most commonly they are used to specify the disk (harddisk,
484 root 1.11 cdrom, ramdisk, vhd...) to boot from.
485 root 1.1
486     The device element is kind of a mini-language in its own which is much
487     more versatile then the limited windows interface to it - BCDEDIT -
488     reveals.
489    
490     While some information can be found on the BCD store and the windows
491     registry, there is pretty much no public information about the device
492     element, so almost everything known about it had to be researched first
493     in the process of writing this script, and consequently, support for BCD
494     device elements is partial only.
495    
496     On the other hand, the expressive power of PBCDEDIT in specifying devices
497 root 1.55 is much greater than BCDEDIT and therefore more can be done with it. The
498 root 1.1 downside is that BCD device elements are much more complicated than what
499     you might think from reading the BCDEDIT documentation.
500    
501     In other words, simple things are complicated, and complicated things are
502     possible.
503    
504     Anyway, the general syntax of device elements is an optional GUID,
505 root 1.11 followed by a device type, optionally followed by hexadecimal flags in
506 root 1.1 angle brackets, optionally followed by C<=> and a comma-separated list of
507     arguments, some of which can be (and often are) in turn devices again.
508    
509     [{GUID}]type[<flags>][=arg,arg...]
510    
511     Here are some examples:
512    
513     boot
514     {b097d29f-bc00-11e9-8a9a-525400123456}block=file,<boot>,\\EFI"
515     locate=<null>,element,systemroot
516     partition=<null>,harddisk,mbr,47cbc08a,1048576
517     partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,76d39e5f-ad1b-407e-9c05-c81eb83b57dd
518     block<1>=ramdisk,<partition=<null>,harddisk,mbr,47cbc08a,68720525312>,0,0,0,\Recovery\b097d29e-bc00-11e9-8a9a-525400123456\Winre.wim
519     block=file,<partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,ee3a393a-f0de-4057-9946-88584245ed48>,\
520     binary=050000000000000048000000000000000000000000000000000000000000000000000000000000000
521    
522     I hope you are suitably impressed. I was, too, when I realized decoding
523     these binary blobs is not as easy as I had assumed.
524    
525     The optional prefixed GUID seems to refer to a device BCD object, which
526     can be used to specify more device-specific BCD elements (for example
527     C<ramdisksdidevice> and C<ramdisksdpath>).
528    
529     The flags after the type are omitted when they are C<0>. The only known
530     flag is C<1>, which seems to indicate that the parent device is invalid. I
531     don't claim to fully understand it, but it seems to indicate that the
532     boot manager has to search the device itself. Why the device is specified
533     in the first place escapes me, but a lot of this device stuff seems to be
534     badly hacked together...
535    
536     The types understood and used by PBCDEDIT are as follows (keep in mind
537     that not of all the following is necessarily supported in PBCDEDIT):
538    
539     =over
540    
541 root 1.14 =item C<binary=>I<hex...>
542 root 1.1
543     This type isn't actually a real BCD element type, but a fallback for those
544     cases where PBCDEDIT can't perfectly decode a device element (except for
545     the leading GUID, which it can always decode). In such cases, it will
546     convert the device into this type with a hexdump of the element data.
547    
548 root 1.14 =item C<null>
549 root 1.1
550 root 1.42 This is another special type - sometimes, a device is all zero-filled,
551     which is not valid. This can mark the absence of a device or something
552     PBCDEDIT does not understand, so it decodes it into this special "all
553     zero" type called C<null>.
554 root 1.1
555     It's most commonly found in devices that can use an optional parent
556     device, when no parent device is used.
557    
558 root 1.14 =item C<boot>
559 root 1.1
560     Another type without parameters, this refers to the device that was booted
561     from (nowadays typically the EFI system partition).
562    
563 root 1.14 =item C<vmbus=>I<interfacetype>,I<interfaceinstance>
564 root 1.1
565     This specifies a VMBUS device with the given interface type and interface
566     instance, both of which are "naked" (no curly braces) GUIDs.
567    
568     Made-up example (couldn't find a single example on the web):
569    
570     vmbus=c376c1c3-d276-48d2-90a9-c04748072c60,12345678-a234-b234-c234-d2345678abcd
571    
572 root 1.14 =item C<partition=><I<parent>>,I<devicetype>,I<partitiontype>,I<diskid>,I<partitionid>
573 root 1.1
574 root 1.18 This designates a specific partition on a block device. I<parent> is an
575     optional parent device on which to search on, and is often C<null>. Note
576     that the angle brackets around I<parent> are part of the syntax.
577 root 1.1
578 root 1.17 I<devicetypes> is one of C<harddisk>, C<floppy>, C<cdrom>, C<ramdisk>,
579 root 1.1 C<file> or C<vhd>, where the first three should be self-explaining,
580 root 1.21 C<file> is usually used to locate a file to be used as a disk image,
581     and C<vhd> is used to treat files as virtual harddisks, i.e. F<vhd> and
582     F<vhdx> files.
583 root 1.1
584 root 1.17 The I<partitiontype> is either C<mbr>, C<gpt> or C<raw>, the latter being
585 root 1.1 used for devices without partitions, such as cdroms, where the "partition"
586     is usually the whole device.
587    
588 root 1.17 The I<diskid> identifies the disk or device using a unique signature, and
589     the same is true for the I<partitionid>. How these are interpreted depends
590     on the I<partitiontype>:
591 root 1.1
592     =over
593    
594 root 1.13 =item C<mbr>
595 root 1.1
596     The C<diskid> is the 32 bit disk signature stored at offset 0x1b8 in the
597     MBR, interpreted as a 32 bit unsigned little endian integer and written as
598     hex number. That is, the bytes C<01 02 03 04> would become C<04030201>.
599    
600 root 1.11 Diskpart (using the C<DETAIL> command) and the C<lsblk> command typically
601 root 1.1 found on GNU/Linux systems (using e.g. C<lsblk -o NAME,PARTUUID>) can
602 root 1.18 display the I<diskid>.
603 root 1.1
604 root 1.18 The I<partitionid> is the byte offset(!) of the partition counting from
605 root 1.1 the beginning of the MBR.
606    
607 root 1.18 Example, use the partition on the harddisk with I<diskid> C<47cbc08a>
608 root 1.1 starting at sector C<2048> (= 1048576 / 512).
609    
610     partition=<null>,harddisk,mbr,47cbc08a,1048576
611    
612 root 1.13 =item C<gpt>
613 root 1.1
614 root 1.18 The I<diskid> is the disk GUID/disk identifier GUID from the partition
615     table (as displayed e.g. by F<gdisk>), and the I<partitionid> is the
616     partition unique GUID (displayed using e.g. the F<gdisk> F<i> command).
617 root 1.1
618     Example: use the partition C<76d39e5f-ad1b-407e-9c05-c81eb83b57dd> on GPT
619     disk C<9742e468-9206-48a0-b4e4-c4e9745a356a>.
620    
621     partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,76d39e5f-ad1b-407e-9c05-c81eb83b57dd
622    
623 root 1.14 =item C<raw>
624 root 1.1
625 root 1.18 Instead of I<diskid> and I<partitionid>, this type only accepts a decimal
626 root 1.11 disk number and signifies the whole disk. BCDEDIT cannot display the
627     resulting device, and I am doubtful whether it has a useful effect.
628 root 1.1
629     =back
630    
631 root 1.14 =item C<legacypartition=><I<parent>>,I<devicetype>,I<partitiontype>,I<diskid>,I<partitionid>
632 root 1.1
633     This is exactly the same as the C<partition> type, except for a tiny
634     detail: instead of using the partition start offset, this type uses the
635     partition number for MBR disks. Behaviour other partition types should be
636     the same.
637    
638     The partition number starts at C<1> and skips unused partition, so if
639     there are two primary partitions and another partition inside the extended
640     partition, the primary partitions are number C<1> and C<2> and the
641 root 1.11 partition inside the extended partition is number C<3>, regardless of any
642 root 1.1 gaps.
643    
644 root 1.14 =item C<locate=><I<parent>>,I<locatetype>,I<locatearg>
645 root 1.1
646     This device description will make the bootloader search for a partition
647     with a given path.
648    
649 root 1.18 The I<parent> device is the device to search on (angle brackets are
650     still part of the syntax!) If it is C<null>, then C<locate> will
651 root 1.1 search all disks it can find.
652    
653 root 1.18 I<locatetype> is either C<element> or C<path>, and merely distinguishes
654 root 1.1 between two different ways to specify the path to search for: C<element>
655 root 1.18 uses an element ID (either as hex or as name) as I<locatearg> and C<path>
656     uses a relative path as I<locatearg>.
657 root 1.1
658 root 1.18 Example: find any partition which has the F<magicfile.xxx> path in the
659 root 1.1 root.
660    
661     locate=<null>,path,\magicfile.xxx
662    
663     Example: find any partition which has the path specified in the
664 root 1.18 C<systemroot> element (typically F<\Windows>).
665 root 1.1
666     locate=<null>,element,systemroot
667    
668 root 1.14 =item C<block=>I<devicetype>,I<args...>
669 root 1.1
670     Last not least, the most complex type, C<block>, which... specifies block
671     devices (which could be inside a F<vhdx> file for example).
672    
673 root 1.18 I<devicetypes> is one of C<harddisk>, C<floppy>, C<cdrom>, C<ramdisk>,
674 root 1.47 C<file> or C<vhd> - the same as for C<partition=>.
675 root 1.1
676 root 1.18 The remaining arguments change depending on the I<devicetype>:
677 root 1.1
678     =over
679    
680 root 1.14 =item C<block=file>,<I<parent>>,I<path>
681 root 1.1
682 root 1.18 Interprets the I<parent> device (typically a partition) as a
683 root 1.1 filesystem and specifies a file path inside.
684    
685 root 1.14 =item C<block=vhd>,<I<parent>>
686 root 1.1
687 root 1.18 Pretty much just changes the interpretation of I<parent>, which is
688 root 1.1 usually a disk image (C<block=file,...)>) to be a F<vhd> or F<vhdx> file.
689    
690 root 1.14 =item C<block=ramdisk>,<I<parent>>,I<base>,I<size>,I<offset>,I<path>
691 root 1.1
692 root 1.18 Interprets the I<parent> device as RAM disk, using the (decimal)
693 root 1.1 base address, byte size and byte offset inside a file specified by
694 root 1.18 I<path>. The numbers are usually all C<0> because they can be extracted
695 root 1.1 from the RAM disk image or other parameters.
696    
697     This is most commonly used to boot C<wim> images.
698    
699 root 1.14 =item C<block=floppy>,I<drivenum>
700 root 1.1
701     Refers to a removable drive identified by a number. BCDEDIT cannot display
702 root 1.14 the resulting device, and it is not clear what effect it will have.
703 root 1.1
704 root 1.14 =item C<block=cdrom>,I<drivenum>
705 root 1.1
706     Pretty much the same as C<floppy> but for CD-ROMs.
707    
708     =item anything else
709    
710     Probably not yet implemented. Tell me of your needs...
711    
712     =back
713    
714 root 1.49 =head4 Examples
715 root 1.1
716     This concludes the syntax overview for device elements, but probably
717 root 1.50 leaves many questions open. I can't help with most of them, as I also have
718 root 1.14 many questions, but I can walk you through some actual examples using more
719 root 1.1 complex aspects.
720    
721 root 1.15 =item C<< locate=<block=vhd,<block=file,<locate=<null>,path,\disk.vhdx>,\disk.vhdx>>,element,path >>
722 root 1.1
723 root 1.4 Just like with C declarations, you best treat device descriptors as
724     instructions to find your device and work your way from the inside out:
725    
726     locate=<null>,path,\disk.vhdx
727    
728     First, the innermost device descriptor searches all partitions on the
729     system for a file called F<\disk.vhdx>:
730    
731 root 1.16 block=file,<see above>,\disk.vhdx
732 root 1.4
733     Next, this takes the device locate has found and finds a file called
734     F<\disk.vhdx> on it. This is the same file locate was using, but that is
735     only because we find the device using the same path as finding the disk
736     image, so this is purely incidental, although quite common.
737    
738 root 1.15 Next, this file will be opened as a virtual disk:
739 root 1.4
740 root 1.16 block=vhd,<see above>
741 root 1.4
742     And finally, inside this disk, another C<locate> will look for a partition
743     with a path as specified in the C<path> element, which most likely will be
744     F<\Windows\system32\winload.exe>:
745    
746 root 1.16 locate=<see above>,element,path
747 root 1.4
748     As a result, this will boot the first Windows it finds on the first
749     F<disk.vhdx> disk image it can find anywhere.
750 root 1.1
751 root 1.15 =item C<< locate=<block=vhd,<block=file,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,\win10.vhdx>>,element,path >>
752 root 1.1
753 root 1.15 Pretty much the same as the previous case, but with a bit of
754     variance. First, look for a specific partition on an MBR-partitioned disk:
755 root 1.4
756     partition=<null>,harddisk,mbr,47cbc08a,242643632128
757    
758     Then open the file F<\win10.vhdx> on that partition:
759    
760 root 1.16 block=file,<see above>,\win10.vhdx
761 root 1.4
762     Then, again, the file is opened as a virtual disk image:
763    
764 root 1.16 block=vhd,<see above>
765 root 1.4
766     And again the windows loader (or whatever is in C<path>) will be searched:
767    
768 root 1.16 locate=<see above>,element,path
769 root 1.1
770 root 1.15 =item C<< {b097d2b2-bc00-11e9-8a9a-525400123456}block<1>=ramdisk,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,0,0,0,\boot.wim >>
771 root 1.1
772 root 1.4 This is quite different. First, it starts with a GUID. This GUID belongs
773     to a BCD object of type C<device>, which has additional parameters:
774    
775     "{b097d2b2-bc00-11e9-8a9a-525400123456}" : {
776     "type" : "device",
777     "description" : "sdi file for ramdisk",
778     "ramdisksdidevice" : "partition=<null>,harddisk,mbr,47cbc08a,1048576",
779     "ramdisksdipath" : "\boot.sdi"
780     },
781    
782     I will not go into many details, but this specifies a (presumably empty)
783 root 1.15 template ramdisk image (F<\boot.sdi>) that is used to initialize the
784     ramdisk. The F<\boot.wim> file is then extracted into it. As you can also
785 root 1.4 see, this F<.sdi> file resides on a different C<partition>.
786    
787 root 1.15 Continuing, as always, from the inside out, first this device descriptor
788 root 1.4 finds a specific partition:
789    
790     partition=<null>,harddisk,mbr,47cbc08a,242643632128
791    
792     And then specifies a C<ramdisk> image on this partition:
793    
794 root 1.16 block<1>=ramdisk,<see above>,0,0,0,\boot.wim
795 root 1.4
796 root 1.5 I don't know what the purpose of the C<< <1> >> flag value is, but it
797 root 1.4 seems to be always there on this kind of entry.
798 root 1.1
799 root 1.5 If you have some good examples to add here, feel free to mail me.
800    
801 root 1.1
802 root 1.26 =head1 EDITING BCD STORES
803 root 1.6
804     The C<edit> and C<parse> subcommands allow you to read a BCD data store
805 root 1.15 and modify it or extract data from it. This is done by executing a series
806 root 1.6 of "editing instructions" which are explained here.
807    
808     =over
809    
810 root 1.22 =item C<get> I<object> I<element>
811 root 1.6
812     Reads the BCD element I<element> from the BCD object I<object> and writes
813     it to standard output, followed by a newline. The I<object> can be a GUID
814     or a human-readable alias, or the special string C<{default}>, which will
815     refer to the default BCD object.
816    
817     Example: find description of the default BCD object.
818    
819     pbcdedit parse BCD get "{default}" description
820    
821 root 1.22 =item C<set> I<object> I<element> I<value>
822 root 1.6
823     Similar to C<get>, but sets the element to the given I<value> instead.
824    
825 root 1.15 Example: change the bootmgr default too
826 root 1.6 C<{b097d2ad-bc00-11e9-8a9a-525400123456}>:
827    
828 root 1.36 pbcdedit edit BCD set "{bootmgr}" default "{b097d2ad-bc00-11e9-8a9a-525400123456}"
829 root 1.6
830 root 1.22 =item C<eval> I<perlcode>
831 root 1.6
832     This takes the next argument, interprets it as Perl code and
833     evaluates it. This allows you to do more complicated modifications or
834     extractions.
835    
836     The following variables are predefined for your use:
837    
838     =over
839    
840     =item C<$PATH>
841    
842     The path to the BCD data store, as given to C<edit> or C<parse>.
843    
844     =item C<$BCD>
845    
846     The decoded BCD data store.
847    
848     =item C<$DEFAULT>
849    
850     The default BCD object name.
851    
852     =back
853    
854     The example given for C<get>, above, could be expressed like this with
855     C<eval>:
856    
857     pbcdedit edit BCD eval 'say $BCD->{$DEFAULT}{description}'
858    
859 root 1.15 The example given for C<set> could be expressed like this:
860 root 1.6
861 root 1.36 pbcdedit edit BCD eval '$BCD->{"{bootmgr}"{default} = "{b097d2ad-bc00-11e9-8a9a-525400123456}"'
862 root 1.6
863 root 1.22 =item C<do> I<path>
864 root 1.6
865     Similar to C<eval>, above, but instead of using the argument as perl code,
866     it loads the perl code from the given file and executes it. This makes it
867     easier to write more complicated or larger programs.
868    
869     =back
870    
871 root 1.22
872 root 1.1 =head1 SEE ALSO
873    
874 root 1.25 For ideas on what you can do with BCD stores in
875     general, and some introductory material, try
876 root 1.1 L<http://www.mistyprojects.co.uk/documents/BCDEdit/index.html>.
877    
878 root 1.23 For good reference on which BCD objects and
879 root 1.24 elements exist, see Geoff Chappell's pages at
880 root 1.23 L<http://www.geoffchappell.com/notes/windows/boot/bcd/index.htm>.
881 root 1.1
882     =head1 AUTHOR
883    
884 root 1.10 Written by Marc A. Lehmann L<pbcdedit@schmorp.de>.
885 root 1.1
886     =head1 REPORTING BUGS
887    
888 root 1.11 Bugs can be reported directly the author at L<pcbedit@schmorp.de>.
889 root 1.1
890     =head1 BUGS AND SHORTCOMINGS
891    
892     This should be a module. Of a series of modules, even.
893    
894     Registry code should preserve classname and security descriptor data, and
895     whatever else is necessary to read and write any registry hive file.
896    
897     I am also not happy with device descriptors being strings rather than a
898     data structure, but strings are probably better for command line usage. In
899 root 1.15 any case, device descriptors could be converted by simply "splitting" at
900 root 1.1 "=" and "," into an array reference, recursively.
901    
902     =head1 HOMEPAGE
903    
904     Original versions of this program can be found at
905     L<http://software.schmorp.de/pkg/pbcdedit>.
906    
907     =head1 COPYRIGHT
908    
909     Copyright 2019 Marc A. Lehmann, licensed under GNU GPL version 3 or later,
910     see L<https://gnu.org/licenses/gpl.html>. This is free software: you are
911     free to change and redistribute it. There is NO WARRANTY, to the extent
912     permitted by law.
913    
914     =cut
915    
916 root 1.32 # common sense is optional, but recommended
917 root 1.34 BEGIN { eval { require "common/sense.pm"; } && common::sense->import }
918 root 1.1
919 root 1.37 no warnings 'portable'; # avoid 32 bit integer warnings
920    
921 root 1.1 use Encode ();
922     use List::Util ();
923     use IO::Handle ();
924     use Time::HiRes ();
925    
926     eval { unpack "Q", pack "Q", 1 }
927     or die "perl with 64 bit integer supported required.\n";
928    
929     our $JSON = eval { require JSON::XS; JSON::XS:: }
930     // eval { require JSON::PP; JSON::PP:: }
931     // die "either JSON::XS or JSON::PP must be installed\n";
932    
933     our $json_coder = $JSON->new->utf8->pretty->canonical->relaxed;
934    
935     # hack used for debugging
936     sub xxd($$) {
937     open my $xxd, "| xxd | sed -e 's/^/\Q$_[0]\E: /'";
938     syswrite $xxd, $_[1];
939     }
940    
941 root 1.56 # get some meta info on a file (uid, gid, perms)
942     sub stat_get($) {
943     [(stat shift)[4, 5, 2]]
944     }
945    
946     # set stat info on a file
947     sub stat_set($$) {
948     my ($fh_or_path, $stat) = @_;
949    
950     return unless $stat;
951     chown $stat->[0], $stat->[1], $fh_or_path;
952     chmod +($stat->[2] & 07777), $fh_or_path;
953     }
954    
955 root 1.6 sub file_load($) {
956     my ($path) = @_;
957    
958     open my $fh, "<:raw", $path
959     or die "$path: $!\n";
960     my $size = -s $fh;
961     $size = read $fh, my $buf, $size
962     or die "$path: short read\n";
963    
964     $buf
965     }
966    
967 root 1.56 sub file_save($$;$) {
968     my ($path, $data, $stat) = @_;
969    
970     open my $fh, ">:raw", "$path~"
971     or die "$path~: $!\n";
972     print $fh $data
973     or die "$path~: short write\n";
974     stat_set $fh, $stat;
975     $fh->sync;
976     close $fh;
977    
978     rename "$path~", $path;
979     }
980    
981 root 1.29 # sources and resources used for writing pbcdedit
982     #
983 root 1.1 # registry:
984     # https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md
985     # http://amnesia.gtisc.gatech.edu/~moyix/suzibandit.ltd.uk/MSc/
986     # bcd:
987     # http://www.geoffchappell.com/notes/windows/boot/bcd/index.htm
988     # https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn653287(v=vs.85)
989     # bcd devices:
990     # reactos' boot/environ/include/bl.h
991     # windows .mof files
992    
993     #############################################################################
994     # registry stuff
995    
996     # we use a hardcoded securitya descriptor - full access for everyone
997     my $sid = pack "H*", "010100000000000100000000"; # S-1-1-0 everyone
998     my $ace = pack "C C S< L< a*", 0, 2, 8 + (length $sid), 0x000f003f, $sid; # type flags size mask sid
999     my $sacl = "";
1000     my $dacl = pack "C x S< S< x2 a*", 2, 8 + (length $ace), 1, $ace; # rev size count ace*
1001     my $sd = pack "C x S< L< L< L< L< a* a* a* a*",
1002     # rev flags(SE_DACL_PRESENT SE_SELF_RELATIVE) owner group sacl dacl
1003     1, 0x8004,
1004     20 + (length $sacl) + (length $dacl),
1005     20 + (length $sacl) + (length $dacl) + (length $sid),
1006     0, 20,
1007     $sacl, $dacl, $sid, $sid;
1008     my $sk = pack "a2 x2 x4 x4 x4 L< a*", sk => (length $sd), $sd;
1009    
1010     sub NO_OFS() { 0xffffffff } # file pointer "NULL" value
1011    
1012     sub KEY_HIVE_ENTRY() { 0x0004 }
1013     sub KEY_NO_DELETE () { 0x0008 }
1014     sub KEY_COMP_NAME () { 0x0020 }
1015    
1016     sub VALUE_COMP_NAME() { 0x0001 }
1017    
1018     my @regf_typename = qw(
1019     none sz expand_sz binary dword dword_be link multi_sz
1020     resource_list full_resource_descriptor resource_requirements_list
1021     qword qword_be
1022     );
1023    
1024     my %regf_dec_type = (
1025     sz => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
1026     expand_sz => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
1027     link => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
1028     multi_sz => sub { $_[0] =~ s/(?:\x00\x00)?\x00\x00$//; [ split /\x00/, (Encode::decode "UTF-16LE", $_[0]), -1 ] },
1029     dword => sub { unpack "L<", shift },
1030     dword_be => sub { unpack "L>", shift },
1031     qword => sub { unpack "Q<", shift },
1032     qword_be => sub { unpack "Q>", shift },
1033     );
1034    
1035     my %regf_enc_type = (
1036     sz => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
1037     expand_sz => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
1038     link => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
1039     multi_sz => sub { (join "", map +(Encode::encode "UTF-16LE", $_) . "\x00\x00", @{ $_[0] }) . "\x00\x00" },
1040     dword => sub { pack "L<", shift },
1041     dword_be => sub { pack "L>", shift },
1042     qword => sub { pack "Q<", shift },
1043     qword_be => sub { pack "Q>", shift },
1044     );
1045    
1046     # decode a registry hive
1047     sub regf_decode($) {
1048     my ($hive) = @_;
1049    
1050     "regf" eq substr $hive, 0, 4
1051     or die "not a registry hive\n";
1052    
1053     my ($major, $minor) = unpack "\@20 L< L<", $hive;
1054    
1055     $major == 1
1056     or die "registry major version is not 1, but $major\n";
1057    
1058     $minor >= 2 && $minor <= 6
1059     or die "registry minor version is $minor, only 2 .. 6 are supported\n";
1060    
1061     my $bins = substr $hive, 4096;
1062    
1063     my $decode_key = sub {
1064     my ($ofs) = @_;
1065    
1066     my @res;
1067    
1068     my ($sze, $sig) = unpack "\@$ofs l< a2", $bins;
1069    
1070     $sze < 0
1071     or die "key node points to unallocated cell\n";
1072    
1073     $sig eq "nk"
1074     or die "expected key node at $ofs, got '$sig'\n";
1075    
1076     my ($flags, $snum, $sofs, $vnum, $vofs, $knamesze) = unpack "\@$ofs ( \@6 S< \@24 L< x4 L< x4 L< L< \@76 S< )", $bins;
1077    
1078     my $kname = unpack "\@$ofs x80 a$knamesze", $bins;
1079    
1080     # classnames, security descriptors
1081     #my ($cofs, $xofs, $clen) = unpack "\@$ofs ( \@44 L< L< \@72 S< )", $bins;
1082     #if ($cofs != NO_OFS && $clen) {
1083     # #warn "cofs $cofs+$clen\n";
1084     # xxd substr $bins, $cofs, 16;
1085     #}
1086    
1087     $kname = Encode::decode "UTF-16LE", $kname
1088     unless $flags & KEY_COMP_NAME;
1089    
1090     if ($vnum && $vofs != NO_OFS) {
1091     for ($vofs += 4; $vnum--; $vofs += 4) {
1092     my $kofs = unpack "\@$vofs L<", $bins;
1093    
1094     my ($sze, $sig) = unpack "\@$kofs l< a2", $bins;
1095    
1096     $sig eq "vk"
1097     or die "key values list contains invalid node (expected vk got '$sig')\n";
1098    
1099     my ($nsze, $dsze, $dofs, $type, $flags) = unpack "\@$kofs x4 x2 S< L< L< L< L<", $bins;
1100    
1101     my $name = substr $bins, $kofs + 24, $nsze;
1102    
1103     $name = Encode::decode "UTF-16LE", $name
1104     unless $flags & VALUE_COMP_NAME;
1105    
1106     my $data;
1107     if ($dsze & 0x80000000) {
1108     $data = substr $bins, $kofs + 12, $dsze & 0x7;
1109     } elsif ($dsze > 16344 && $minor > 3) { # big data
1110     my ($bsze, $bsig, $bnum, $bofs) = unpack "\@$dofs l< a2 S< L<", $bins;
1111    
1112     for ($bofs += 4; $bnum--; $bofs += 4) {
1113     my $dofs = unpack "\@$bofs L<", $bins;
1114     my $dsze = unpack "\@$dofs l<", $bins;
1115     $data .= substr $bins, $dofs + 4, -$dsze - 4;
1116     }
1117     $data = substr $data, 0, $dsze; # cells might be longer than data
1118     } else {
1119     $data = substr $bins, $dofs + 4, $dsze;
1120     }
1121    
1122     $type = $regf_typename[$type] if $type < @regf_typename;
1123    
1124     $data = ($regf_dec_type{$type} || sub { unpack "H*", shift })
1125     ->($data);
1126    
1127     $res[0]{$name} = [$type, $data];
1128     }
1129     }
1130    
1131     if ($sofs != NO_OFS) {
1132     my $decode_key = __SUB__;
1133    
1134     my $decode_subkeylist = sub {
1135     my ($sofs) = @_;
1136    
1137     my ($sze, $sig, $snum) = unpack "\@$sofs l< a2 S<", $bins;
1138    
1139     if ($sig eq "ri") { # index root
1140     for (my $lofs = $sofs + 8; $snum--; $lofs += 4) {
1141     __SUB__->(unpack "\@$lofs L<", $bins);
1142     }
1143     } else {
1144     my $inc;
1145    
1146     if ($sig eq "li") { # subkey list
1147     $inc = 4;
1148     } elsif ($sig eq "lf" or $sig eq "lh") { # subkey list with name hints or hashes
1149     $inc = 8;
1150     } else {
1151     die "expected subkey list at $sofs, found '$sig'\n";
1152     }
1153    
1154     for (my $lofs = $sofs + 8; $snum--; $lofs += $inc) {
1155     my ($name, $data) = $decode_key->(unpack "\@$lofs L<", $bins);
1156     $res[1]{$name} = $data;
1157     }
1158     }
1159     };
1160    
1161     $decode_subkeylist->($sofs);
1162     }
1163    
1164     ($kname, \@res);
1165     };
1166    
1167     my ($rootcell) = unpack "\@36 L<", $hive;
1168    
1169     my ($rname, $root) = $decode_key->($rootcell);
1170    
1171     [$rname, $root]
1172     }
1173    
1174 root 1.62 # return a binary windows FILETIME struct
1175 root 1.1 sub filetime_now {
1176     my ($s, $ms) = Time::HiRes::gettimeofday;
1177    
1178 root 1.64 pack "Q<", ($s * 1_000_000 + $ms) * 10
1179     + 116_444_736_000_000_000 # 1970-01-01 00:00:00
1180 root 1.1 }
1181    
1182     # encode a registry hive
1183     sub regf_encode($) {
1184     my ($hive) = @_;
1185    
1186     my %typeval = map +($regf_typename[$_] => $_), 0 .. $#regf_typename;
1187    
1188     # the filetime is apparently used to verify log file validity,
1189     # so by generating a new timestamp the log files *should* automatically
1190     # become invalidated and windows would "self-heal" them.
1191     # (update: has been verified by reverse engineering)
1192 root 1.65 # possibly the fact that the two sequence numbers match might also
1193 root 1.1 # make windows think that the hive is not dirty and ignore logs.
1194     # (update: has been verified by reverse engineering)
1195    
1196     my $now = filetime_now;
1197    
1198     # we only create a single hbin
1199     my $bins = pack "a4 L< L< x8 a8 x4", "hbin", 0, 0, $now;
1200    
1201     # append cell to $bind, return offset
1202     my $cell = sub {
1203     my ($cell) = @_;
1204    
1205     my $res = length $bins;
1206    
1207     $cell .= "\x00" while 4 != (7 & length $cell); # slow and ugly
1208    
1209     $bins .= pack "l<", -(4 + length $cell);
1210     $bins .= $cell;
1211    
1212     $res
1213     };
1214    
1215     my $sdofs = $cell->($sk); # add a dummy security descriptor
1216     my $sdref = 0; # refcount
1217     substr $bins, $sdofs + 8, 4, pack "L<", $sdofs; # flink
1218     substr $bins, $sdofs + 12, 4, pack "L<", $sdofs; # blink
1219    
1220     my $encode_key = sub {
1221     my ($kname, $kdata, $flags) = @_;
1222     my ($values, $subkeys) = @$kdata;
1223    
1224     if ($kname =~ /[^\x00-\xff]/) {
1225     $kname = Encode::encode "UTF-16LE", $kname;
1226     } else {
1227     $flags |= KEY_COMP_NAME;
1228     }
1229    
1230     # encode subkeys
1231    
1232     my @snames =
1233     map $_->[1],
1234     sort { $a->[0] cmp $b->[0] }
1235     map [(uc $_), $_],
1236     keys %$subkeys;
1237    
1238     # normally, we'd have to encode each name, but we assume one char is at most two utf-16 cp's
1239     my $maxsname = 4 * List::Util::max map length, @snames;
1240    
1241     my @sofs = map __SUB__->($_, $subkeys->{$_}, 0), @snames;
1242    
1243     # encode values
1244     my $maxvname = 4 * List::Util::max map length, keys %$values;
1245     my @vofs;
1246     my $maxdsze = 0;
1247    
1248     while (my ($vname, $v) = each %$values) {
1249     my $flags = 0;
1250    
1251     if ($vname =~ /[^\x00-\xff]/) {
1252     $vname = Encode::encode "UTF-16LE", $kname;
1253     } else {
1254     $flags |= VALUE_COMP_NAME;
1255     }
1256    
1257     my ($type, $data) = @$v;
1258    
1259     $data = ($regf_enc_type{$type} || sub { pack "H*", shift })->($data);
1260    
1261     my $dsze;
1262     my $dofs;
1263    
1264     if (length $data <= 4) {
1265     $dsze = 0x80000000 | length $data;
1266     $dofs = unpack "L<", pack "a4", $data;
1267     } else {
1268     $dsze = length $data;
1269     $dofs = $cell->($data);
1270     }
1271    
1272     $type = $typeval{$type} // ($type =~ /^[0-9]+\z/ ? $type : die "cannot encode type '$type'");
1273    
1274     push @vofs, $cell->(pack "a2 S< L< L< L< S< x2 a*",
1275     vk => (length $vname), $dsze, $dofs, $type, $flags, $vname);
1276    
1277     $maxdsze = $dsze if $maxdsze < $dsze;
1278     }
1279    
1280     # encode key
1281    
1282     my $slist = @sofs ? $cell->(pack "a2 S< L<*", li => (scalar @sofs), @sofs) : NO_OFS;
1283     my $vlist = @vofs ? $cell->(pack "L<*", @vofs) : NO_OFS;
1284    
1285     my $kdata = pack "
1286     a2 S< a8 x4 x4
1287     L< L< L< L< L< L<
1288     L< L< L< L< L< L<
1289     x4 S< S< a*
1290     ",
1291     nk => $flags, $now,
1292     (scalar @sofs), 0, $slist, NO_OFS, (scalar @vofs), $vlist,
1293     $sdofs, NO_OFS, $maxsname, 0, $maxvname, $maxdsze,
1294     length $kname, 0, $kname;
1295     ++$sdref;
1296    
1297     my $res = $cell->($kdata);
1298    
1299     substr $bins, $_ + 16, 4, pack "L<", $res
1300     for @sofs;
1301    
1302     $res
1303     };
1304    
1305     my ($rname, $root) = @$hive;
1306    
1307     my $rofs = $encode_key->($rname, $root, KEY_HIVE_ENTRY | KEY_NO_DELETE); # 4 = root key
1308    
1309     if (my $pad = -(length $bins) & 4095) {
1310     $pad -= 4;
1311     $bins .= pack "l< x$pad", $pad + 4;
1312     }
1313    
1314     substr $bins, $sdofs + 16, 4, pack "L<", $sdref; # sd refcount
1315     substr $bins, 8, 4, pack "L<", length $bins;
1316    
1317     my $base = pack "
1318     a4 L< L< a8 L< L< L< L<
1319     L< L< L<
1320     a64
1321     x396
1322     ",
1323     regf => 1974, 1974, $now, 1, 3, 0, 1,
1324     $rofs, length $bins, 1,
1325     (Encode::encode "UTF-16LE", "\\pbcdedit.reg");
1326    
1327     my $chksum = List::Util::reduce { $a ^ $b } unpack "L<*", $base;
1328     $chksum = 0xfffffffe if $chksum == 0xffffffff;
1329     $chksum = 1 if $chksum == 0;
1330    
1331     $base .= pack "L<", $chksum;
1332    
1333     $base = pack "a* \@4095 x1", $base;
1334    
1335     $base . $bins
1336     }
1337    
1338     # load and parse registry from file
1339     sub regf_load($) {
1340     my ($path) = @_;
1341    
1342 root 1.6 regf_decode file_load $path
1343 root 1.1 }
1344    
1345     # encode and save registry to file
1346 root 1.56 sub regf_save($$;$) {
1347     my ($path, $hive, $stat) = @_;
1348 root 1.1
1349     $hive = regf_encode $hive;
1350    
1351 root 1.56 file_save $path, $hive, $stat;
1352 root 1.1 }
1353    
1354     #############################################################################
1355     # bcd stuff
1356    
1357 root 1.66 # human-readable aliases for GUID object identifiers
1358 root 1.1 our %bcd_objects = (
1359     '{0ce4991b-e6b3-4b16-b23c-5e0d9250e5d9}' => '{emssettings}',
1360     '{1afa9c49-16ab-4a5c-4a90-212802da9460}' => '{resumeloadersettings}',
1361     '{1cae1eb7-a0df-4d4d-9851-4860e34ef535}' => '{default}',
1362     '{313e8eed-7098-4586-a9bf-309c61f8d449}' => '{kerneldbgsettings}',
1363     '{4636856e-540f-4170-a130-a84776f4c654}' => '{dbgsettings}',
1364     '{466f5a88-0af2-4f76-9038-095b170dc21c}' => '{ntldr}',
1365     '{5189b25c-5558-4bf2-bca4-289b11bd29e2}' => '{badmemory}',
1366     '{6efb52bf-1766-41db-a6b3-0ee5eff72bd7}' => '{bootloadersettings}',
1367     '{7254a080-1510-4e85-ac0f-e7fb3d444736}' => '{ssetupefi}',
1368     '{7ea2e1ac-2e61-4728-aaa3-896d9d0a9f0e}' => '{globalsettings}',
1369     '{7ff607e0-4395-11db-b0de-0800200c9a66}' => '{hypervisorsettings}',
1370     '{9dea862c-5cdd-4e70-acc1-f32b344d4795}' => '{bootmgr}',
1371     '{a1943bbc-ea85-487c-97c7-c9ede908a38a}' => '{ostargettemplatepcat}',
1372     '{a5a30fa2-3d06-4e9f-b5f4-a01df9d1fcba}' => '{fwbootmgr}',
1373     '{ae5534e0-a924-466c-b836-758539a3ee3a}' => '{ramdiskoptions}',
1374     '{b012b84d-c47c-4ed5-b722-c0c42163e569}' => '{ostargettemplateefi}',
1375     '{b2721d73-1db4-4c62-bf78-c548a880142d}' => '{memdiag}',
1376     '{cbd971bf-b7b8-4885-951a-fa03044f5d71}' => '{setuppcat}',
1377     '{fa926493-6f1c-4193-a414-58f0b2456d1e}' => '{current}',
1378     );
1379    
1380     # default types
1381     our %bcd_object_types = (
1382     '{fwbootmgr}' => 0x10100001,
1383     '{bootmgr}' => 0x10100002,
1384     '{memdiag}' => 0x10200005,
1385     '{ntldr}' => 0x10300006,
1386     '{badmemory}' => 0x20100000,
1387     '{dbgsettings}' => 0x20100000,
1388     '{emssettings}' => 0x20100000,
1389     '{globalsettings}' => 0x20100000,
1390     '{bootloadersettings}' => 0x20200003,
1391     '{hypervisorsettings}' => 0x20200003,
1392     '{kerneldbgsettings}' => 0x20200003,
1393     '{resumeloadersettings}' => 0x20200004,
1394     '{ramdiskoptions}' => 0x30000000,
1395     );
1396    
1397     # object types
1398     our %bcd_types = (
1399     0x10100001 => 'application::fwbootmgr',
1400     0x10100002 => 'application::bootmgr',
1401     0x10200003 => 'application::osloader',
1402     0x10200004 => 'application::resume',
1403     0x10100005 => 'application::memdiag',
1404     0x10100006 => 'application::ntldr',
1405     0x10100007 => 'application::setupldr',
1406     0x10400008 => 'application::bootsector',
1407     0x10400009 => 'application::startup',
1408     0x1020000a => 'application::bootapp',
1409     0x20100000 => 'settings',
1410     0x20200001 => 'inherit::fwbootmgr',
1411     0x20200002 => 'inherit::bootmgr',
1412     0x20200003 => 'inherit::osloader',
1413     0x20200004 => 'inherit::resume',
1414     0x20200005 => 'inherit::memdiag',
1415     0x20200006 => 'inherit::ntldr',
1416     0x20200007 => 'inherit::setupldr',
1417     0x20200008 => 'inherit::bootsector',
1418     0x20200009 => 'inherit::startup',
1419     0x20300000 => 'inherit::device',
1420     0x30000000 => 'device',
1421     );
1422    
1423     our %rbcd_objects = reverse %bcd_objects;
1424    
1425     our $RE_GUID = qr<([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})>i;
1426    
1427     sub dec_guid($) {
1428     my ($p1, $p2, $p3, $p4, $p5) = unpack "VvvH4H12", shift;
1429     sprintf "%08x-%04x-%04x-%s-%s", $p1, $p2, $p3, $p4, $p5;
1430     }
1431    
1432     sub enc_guid($) {
1433     $_[0] =~ /^$RE_GUID\z/o
1434     or return;
1435    
1436     pack "VvvH4H12", hex $1, hex $2, hex $3, $4, $5
1437     }
1438    
1439     # "wguid" are guids wrapped in curly braces {...} also supporting aliases
1440     sub dec_wguid($) {
1441     my $guid = "{" . (dec_guid shift) . "}";
1442    
1443     $bcd_objects{$guid} // $guid
1444     }
1445    
1446     sub enc_wguid($) {
1447     my ($guid) = @_;
1448    
1449     if (my $alias = $rbcd_objects{$guid}) {
1450     $guid = $alias;
1451     }
1452    
1453     $guid =~ /^\{($RE_GUID)\}\z/o
1454     or return;
1455    
1456     enc_guid $1
1457     }
1458    
1459     sub BCDE_CLASS () { 0xf0000000 }
1460     sub BCDE_CLASS_LIBRARY () { 0x10000000 }
1461     sub BCDE_CLASS_APPLICATION () { 0x20000000 }
1462     sub BCDE_CLASS_DEVICE () { 0x30000000 }
1463     sub BCDE_CLASS_TEMPLATE () { 0x40000000 }
1464    
1465     sub BCDE_FORMAT () { 0x0f000000 }
1466     sub BCDE_FORMAT_DEVICE () { 0x01000000 }
1467     sub BCDE_FORMAT_STRING () { 0x02000000 }
1468     sub BCDE_FORMAT_GUID () { 0x03000000 }
1469     sub BCDE_FORMAT_GUID_LIST () { 0x04000000 }
1470     sub BCDE_FORMAT_INTEGER () { 0x05000000 }
1471     sub BCDE_FORMAT_BOOLEAN () { 0x06000000 }
1472     sub BCDE_FORMAT_INTEGER_LIST () { 0x07000000 }
1473    
1474     sub enc_integer($) {
1475     my $value = shift;
1476     $value = oct $value if $value =~ /^0[bBxX]/;
1477     unpack "H*", pack "Q<", $value
1478     }
1479    
1480 root 1.37 sub enc_device($$);
1481     sub dec_device($$);
1482    
1483 root 1.1 our %bcde_dec = (
1484     BCDE_FORMAT_DEVICE , \&dec_device,
1485     # # for round-trip verification
1486     # BCDE_FORMAT_DEVICE , sub {
1487     # my $dev = dec_device $_[0];
1488     # $_[0] eq enc_device $dev
1489     # or die "bcd device decoding does not round trip for $_[0]\n";
1490     # $dev
1491     # },
1492     BCDE_FORMAT_STRING , sub { shift },
1493     BCDE_FORMAT_GUID , sub { dec_wguid enc_wguid shift },
1494     BCDE_FORMAT_GUID_LIST , sub { join " ", map dec_wguid enc_wguid $_, @{+shift} },
1495     BCDE_FORMAT_INTEGER , sub { unpack "Q", pack "a8", pack "H*", shift }, # integer might be 4 or 8 bytes - caused by ms coding bugs
1496     BCDE_FORMAT_BOOLEAN , sub { shift eq "00" ? 0 : 1 },
1497 root 1.67 BCDE_FORMAT_INTEGER_LIST, sub { join " ", unpack "Q*", pack "H*", shift }, # not sure if this can be 4 bytes
1498 root 1.1 );
1499    
1500     our %bcde_enc = (
1501 root 1.37 BCDE_FORMAT_DEVICE , sub { binary => enc_device $_[0], $_[1] },
1502 root 1.1 BCDE_FORMAT_STRING , sub { sz => shift },
1503     BCDE_FORMAT_GUID , sub { sz => "{" . (dec_guid enc_wguid shift) . "}" },
1504     BCDE_FORMAT_GUID_LIST , sub { multi_sz => [map "{" . (dec_guid enc_wguid $_) . "}", split /\s+/, shift ] },
1505     BCDE_FORMAT_INTEGER , sub { binary => enc_integer shift },
1506     BCDE_FORMAT_BOOLEAN , sub { binary => shift ? "01" : "00" },
1507     BCDE_FORMAT_INTEGER_LIST, sub { binary => join "", map enc_integer $_, split /\s+/, shift },
1508     );
1509    
1510     # BCD Elements
1511 root 1.37 our %bcde_byclass = (
1512     any => {
1513 root 1.68 0x11000001 => 'device',
1514     0x12000002 => 'path',
1515     0x12000004 => 'description',
1516     0x12000005 => 'locale',
1517     0x14000006 => 'inherit',
1518     0x15000007 => 'truncatememory',
1519     0x14000008 => 'recoverysequence',
1520     0x16000009 => 'recoveryenabled',
1521     0x1700000a => 'badmemorylist',
1522     0x1600000b => 'badmemoryaccess',
1523     0x1500000c => 'firstmegabytepolicy',
1524     0x1500000d => 'relocatephysical',
1525     0x1500000e => 'avoidlowmemory',
1526     0x1600000f => 'traditionalkseg',
1527     0x16000010 => 'bootdebug',
1528     0x15000011 => 'debugtype',
1529     0x15000012 => 'debugaddress',
1530     0x15000013 => 'debugport',
1531     0x15000014 => 'baudrate',
1532     0x15000015 => 'channel',
1533     0x12000016 => 'targetname',
1534     0x16000017 => 'noumex',
1535     0x15000018 => 'debugstart',
1536     0x12000019 => 'busparams',
1537     0x1500001a => 'hostip',
1538     0x1500001b => 'port',
1539     0x1600001c => 'dhcp',
1540     0x1200001d => 'key',
1541     0x1600001e => 'vm',
1542     0x16000020 => 'bootems',
1543     0x15000022 => 'emsport',
1544     0x15000023 => 'emsbaudrate',
1545     0x12000030 => 'loadoptions',
1546     0x16000040 => 'advancedoptions',
1547     0x16000041 => 'optionsedit',
1548     0x15000042 => 'keyringaddress',
1549     0x11000043 => 'bootstatdevice',
1550     0x12000044 => 'bootstatfilepath',
1551     0x16000045 => 'preservebootstat',
1552     0x16000046 => 'graphicsmodedisabled',
1553     0x15000047 => 'configaccesspolicy',
1554     0x16000048 => 'nointegritychecks',
1555     0x16000049 => 'testsigning',
1556     0x1200004a => 'fontpath',
1557     0x1500004b => 'integrityservices',
1558     0x1500004c => 'volumebandid',
1559     0x16000050 => 'extendedinput',
1560     0x15000051 => 'initialconsoleinput',
1561     0x15000052 => 'graphicsresolution',
1562     0x16000053 => 'restartonfailure',
1563     0x16000054 => 'highestmode',
1564     0x16000060 => 'isolatedcontext',
1565     0x15000065 => 'displaymessage',
1566     0x15000066 => 'displaymessageoverride',
1567     0x16000068 => 'nobootuxtext',
1568     0x16000069 => 'nobootuxprogress',
1569     0x1600006a => 'nobootuxfade',
1570     0x1600006b => 'bootuxreservepooldebug',
1571     0x1600006c => 'bootuxdisabled',
1572     0x1500006d => 'bootuxfadeframes',
1573     0x1600006e => 'bootuxdumpstats',
1574     0x1600006f => 'bootuxshowstats',
1575     0x16000071 => 'multibootsystem',
1576     0x16000072 => 'nokeyboard',
1577     0x15000073 => 'aliaswindowskey',
1578     0x16000074 => 'bootshutdowndisabled',
1579     0x15000075 => 'performancefrequency',
1580     0x15000076 => 'securebootrawpolicy',
1581     0x17000077 => 'allowedinmemorysettings',
1582     0x15000079 => 'bootuxtransitiontime',
1583     0x1600007a => 'mobilegraphics',
1584     0x1600007b => 'forcefipscrypto',
1585     0x1500007d => 'booterrorux',
1586     0x1600007e => 'flightsigning',
1587     0x1500007f => 'measuredbootlogformat',
1588     0x15000080 => 'displayrotation',
1589     0x15000081 => 'logcontrol',
1590     0x16000082 => 'nofirmwaresync',
1591     0x11000084 => 'windowssyspart',
1592     0x16000087 => 'numlock',
1593     0x26000202 => 'skipffumode',
1594     0x26000203 => 'forceffumode',
1595     0x25000510 => 'chargethreshold',
1596     0x26000512 => 'offmodecharging',
1597     0x25000aaa => 'bootflow',
1598     0x45000001 => 'devicetype',
1599     0x42000002 => 'applicationrelativepath',
1600     0x42000003 => 'ramdiskdevicerelativepath',
1601     0x46000004 => 'omitosloaderelements',
1602     0x47000006 => 'elementstomigrate',
1603     0x46000010 => 'recoveryos',
1604 root 1.37 },
1605     bootapp => {
1606 root 1.68 0x26000145 => 'enablebootdebugpolicy',
1607     0x26000146 => 'enablebootorderclean',
1608     0x26000147 => 'enabledeviceid',
1609     0x26000148 => 'enableffuloader',
1610     0x26000149 => 'enableiuloader',
1611     0x2600014a => 'enablemassstorage',
1612     0x2600014b => 'enablerpmbprovisioning',
1613     0x2600014c => 'enablesecurebootpolicy',
1614     0x2600014d => 'enablestartcharge',
1615     0x2600014e => 'enableresettpm',
1616 root 1.37 },
1617     bootmgr => {
1618 root 1.68 0x24000001 => 'displayorder',
1619     0x24000002 => 'bootsequence',
1620     0x23000003 => 'default',
1621     0x25000004 => 'timeout',
1622     0x26000005 => 'resume',
1623     0x23000006 => 'resumeobject',
1624     0x24000007 => 'startupsequence',
1625     0x24000010 => 'toolsdisplayorder',
1626     0x26000020 => 'displaybootmenu',
1627     0x26000021 => 'noerrordisplay',
1628     0x21000022 => 'bcddevice',
1629     0x22000023 => 'bcdfilepath',
1630     0x26000024 => 'hormenabled',
1631     0x26000025 => 'hiberboot',
1632     0x22000026 => 'passwordoverride',
1633     0x22000027 => 'pinpassphraseoverride',
1634     0x26000028 => 'processcustomactionsfirst',
1635     0x27000030 => 'customactions',
1636     0x26000031 => 'persistbootsequence',
1637     0x26000032 => 'skipstartupsequence',
1638     0x22000040 => 'fverecoveryurl',
1639     0x22000041 => 'fverecoverymessage',
1640 root 1.37 },
1641     device => {
1642 root 1.68 0x35000001 => 'ramdiskimageoffset',
1643     0x35000002 => 'ramdisktftpclientport',
1644     0x31000003 => 'ramdisksdidevice',
1645     0x32000004 => 'ramdisksdipath',
1646     0x35000005 => 'ramdiskimagelength',
1647     0x36000006 => 'exportascd',
1648     0x35000007 => 'ramdisktftpblocksize',
1649     0x35000008 => 'ramdisktftpwindowsize',
1650     0x36000009 => 'ramdiskmcenabled',
1651     0x3600000a => 'ramdiskmctftpfallback',
1652     0x3600000b => 'ramdisktftpvarwindow',
1653 root 1.37 },
1654     memdiag => {
1655 root 1.68 0x25000001 => 'passcount',
1656     0x25000002 => 'testmix',
1657     0x25000003 => 'failurecount',
1658     0x26000003 => 'cacheenable',
1659     0x25000004 => 'testtofail',
1660     0x26000004 => 'failuresenabled',
1661     0x25000005 => 'stridefailcount',
1662     0x26000005 => 'cacheenable',
1663     0x25000006 => 'invcfailcount',
1664     0x25000007 => 'matsfailcount',
1665     0x25000008 => 'randfailcount',
1666     0x25000009 => 'chckrfailcount',
1667 root 1.37 },
1668     ntldr => {
1669 root 1.68 0x22000001 => 'bpbstring',
1670 root 1.37 },
1671     osloader => {
1672 root 1.68 0x21000001 => 'osdevice',
1673     0x22000002 => 'systemroot',
1674     0x23000003 => 'resumeobject',
1675     0x26000004 => 'stampdisks',
1676     0x26000010 => 'detecthal',
1677     0x22000011 => 'kernel',
1678     0x22000012 => 'hal',
1679     0x22000013 => 'dbgtransport',
1680     0x25000020 => 'nx',
1681     0x25000021 => 'pae',
1682     0x26000022 => 'winpe',
1683     0x26000024 => 'nocrashautoreboot',
1684     0x26000025 => 'lastknowngood',
1685     0x26000026 => 'oslnointegritychecks',
1686     0x26000027 => 'osltestsigning',
1687     0x26000030 => 'nolowmem',
1688     0x25000031 => 'removememory',
1689     0x25000032 => 'increaseuserva',
1690     0x25000033 => 'perfmem',
1691     0x26000040 => 'vga',
1692     0x26000041 => 'quietboot',
1693     0x26000042 => 'novesa',
1694     0x26000043 => 'novga',
1695     0x25000050 => 'clustermodeaddressing',
1696     0x26000051 => 'usephysicaldestination',
1697     0x25000052 => 'restrictapiccluster',
1698     0x22000053 => 'evstore',
1699     0x26000054 => 'uselegacyapicmode',
1700     0x26000060 => 'onecpu',
1701     0x25000061 => 'numproc',
1702     0x26000062 => 'maxproc',
1703     0x25000063 => 'configflags',
1704     0x26000064 => 'maxgroup',
1705     0x26000065 => 'groupaware',
1706     0x25000066 => 'groupsize',
1707     0x26000070 => 'usefirmwarepcisettings',
1708     0x25000071 => 'msi',
1709     0x25000072 => 'pciexpress',
1710     0x25000080 => 'safeboot',
1711     0x26000081 => 'safebootalternateshell',
1712     0x26000090 => 'bootlog',
1713     0x26000091 => 'sos',
1714     0x260000a0 => 'debug',
1715     0x260000a1 => 'halbreakpoint',
1716     0x260000a2 => 'useplatformclock',
1717     0x260000a3 => 'forcelegacyplatform',
1718     0x260000a4 => 'useplatformtick',
1719     0x260000a5 => 'disabledynamictick',
1720     0x250000a6 => 'tscsyncpolicy',
1721     0x260000b0 => 'ems',
1722     0x250000c0 => 'forcefailure',
1723     0x250000c1 => 'driverloadfailurepolicy',
1724     0x250000c2 => 'bootmenupolicy',
1725     0x260000c3 => 'onetimeadvancedoptions',
1726     0x260000c4 => 'onetimeoptionsedit',
1727     0x250000e0 => 'bootstatuspolicy',
1728     0x260000e1 => 'disableelamdrivers',
1729     0x250000f0 => 'hypervisorlaunchtype',
1730     0x220000f1 => 'hypervisorpath',
1731     0x260000f2 => 'hypervisordebug',
1732     0x250000f3 => 'hypervisordebugtype',
1733     0x250000f4 => 'hypervisordebugport',
1734     0x250000f5 => 'hypervisorbaudrate',
1735     0x250000f6 => 'hypervisorchannel',
1736     0x250000f7 => 'bootux',
1737     0x260000f8 => 'hypervisordisableslat',
1738     0x220000f9 => 'hypervisorbusparams',
1739     0x250000fa => 'hypervisornumproc',
1740     0x250000fb => 'hypervisorrootprocpernode',
1741     0x260000fc => 'hypervisoruselargevtlb',
1742     0x250000fd => 'hypervisorhostip',
1743     0x250000fe => 'hypervisorhostport',
1744     0x250000ff => 'hypervisordebugpages',
1745     0x25000100 => 'tpmbootentropy',
1746     0x22000110 => 'hypervisorusekey',
1747     0x22000112 => 'hypervisorproductskutype',
1748     0x25000113 => 'hypervisorrootproc',
1749     0x26000114 => 'hypervisordhcp',
1750     0x25000115 => 'hypervisoriommupolicy',
1751     0x26000116 => 'hypervisorusevapic',
1752     0x22000117 => 'hypervisorloadoptions',
1753     0x25000118 => 'hypervisormsrfilterpolicy',
1754     0x25000119 => 'hypervisormmionxpolicy',
1755     0x2500011a => 'hypervisorschedulertype',
1756     0x25000120 => 'xsavepolicy',
1757     0x25000121 => 'xsaveaddfeature0',
1758     0x25000122 => 'xsaveaddfeature1',
1759     0x25000123 => 'xsaveaddfeature2',
1760     0x25000124 => 'xsaveaddfeature3',
1761     0x25000125 => 'xsaveaddfeature4',
1762     0x25000126 => 'xsaveaddfeature5',
1763     0x25000127 => 'xsaveaddfeature6',
1764     0x25000128 => 'xsaveaddfeature7',
1765     0x25000129 => 'xsaveremovefeature',
1766     0x2500012a => 'xsaveprocessorsmask',
1767     0x2500012b => 'xsavedisable',
1768     0x2500012c => 'kerneldebugtype',
1769     0x2200012d => 'kernelbusparams',
1770     0x2500012e => 'kerneldebugaddress',
1771     0x2500012f => 'kerneldebugport',
1772     0x25000130 => 'claimedtpmcounter',
1773     0x25000131 => 'kernelchannel',
1774     0x22000132 => 'kerneltargetname',
1775     0x25000133 => 'kernelhostip',
1776     0x25000134 => 'kernelport',
1777     0x26000135 => 'kerneldhcp',
1778     0x22000136 => 'kernelkey',
1779     0x22000137 => 'imchivename',
1780     0x21000138 => 'imcdevice',
1781     0x25000139 => 'kernelbaudrate',
1782     0x22000140 => 'mfgmode',
1783     0x26000141 => 'event',
1784     0x25000142 => 'vsmlaunchtype',
1785     0x25000144 => 'hypervisorenforcedcodeintegrity',
1786     0x21000150 => 'systemdatadevice',
1787     0x21000151 => 'osarcdevice',
1788     0x21000153 => 'osdatadevice',
1789     0x21000154 => 'bspdevice',
1790     0x21000155 => 'bspfilepath',
1791 root 1.37 },
1792     resume => {
1793 root 1.68 0x21000001 => 'filedevice',
1794     0x22000002 => 'filepath',
1795     0x26000003 => 'customsettings',
1796     0x26000004 => 'pae',
1797     0x21000005 => 'associatedosdevice',
1798     0x26000006 => 'debugoptionenabled',
1799     0x25000007 => 'bootux',
1800     0x25000008 => 'bootmenupolicy',
1801     0x26000024 => 'hormenabled',
1802 root 1.37 },
1803     startup => {
1804 root 1.68 0x26000001 => 'pxesoftreboot',
1805     0x22000002 => 'applicationname',
1806 root 1.37 },
1807     );
1808    
1809     # mask, value => class
1810     our @bcde_typeclass = (
1811     [0x00000000, 0x00000000, 'any'],
1812     [0xf00fffff, 0x1000000a, 'bootapp'],
1813     [0xf0ffffff, 0x2020000a, 'bootapp'],
1814     [0xf00fffff, 0x10000001, 'bootmgr'],
1815     [0xf00fffff, 0x10000002, 'bootmgr'],
1816     [0xf0ffffff, 0x20200001, 'bootmgr'],
1817     [0xf0ffffff, 0x20200002, 'bootmgr'],
1818     [0xf0f00000, 0x20300000, 'device'],
1819     [0xf0000000, 0x30000000, 'device'],
1820     [0xf00fffff, 0x10000005, 'memdiag'],
1821     [0xf0ffffff, 0x20200005, 'memdiag'],
1822     [0xf00fffff, 0x10000006, 'ntldr'],
1823     [0xf00fffff, 0x10000007, 'ntldr'],
1824     [0xf0ffffff, 0x20200006, 'ntldr'],
1825     [0xf0ffffff, 0x20200007, 'ntldr'],
1826     [0xf00fffff, 0x10000003, 'osloader'],
1827     [0xf0ffffff, 0x20200003, 'osloader'],
1828     [0xf00fffff, 0x10000004, 'resume'],
1829     [0xf0ffffff, 0x20200004, 'resume'],
1830     [0xf00fffff, 0x10000009, 'startup'],
1831     [0xf0ffffff, 0x20200009, 'startup'],
1832 root 1.1 );
1833    
1834 root 1.37 our %rbcde_byclass;
1835    
1836     while (my ($k, $v) = each %bcde_byclass) {
1837     $rbcde_byclass{$k} = { reverse %$v };
1838     }
1839    
1840     # decodes (numerical elem, type) to name
1841     sub dec_bcde_id($$) {
1842     for my $class (@bcde_typeclass) {
1843     if (($_[1] & $class->[0]) == $class->[1]) {
1844     if (my $id = $bcde_byclass{$class->[2]}{$_[0]}) {
1845     return $id;
1846     }
1847     }
1848     }
1849 root 1.1
1850 root 1.37 sprintf "custom:%08x", $_[0]
1851 root 1.1 }
1852    
1853 root 1.37 # encodes (elem as name, type)
1854     sub enc_bcde_id($$) {
1855     $_[0] =~ /^custom:(?:0x)?([0-9a-fA-F]{8}$)/
1856     and return hex $1;
1857    
1858     for my $class (@bcde_typeclass) {
1859     if (($_[1] & $class->[0]) == $class->[1]) {
1860     if (my $value = $rbcde_byclass{$class->[2]}{$_[0]}) {
1861     return $value;
1862     }
1863     }
1864     }
1865    
1866     undef
1867 root 1.1 }
1868    
1869     # decode/encode bcd device element - the horror, no documentaion
1870     # whatsoever, supercomplex, superinconsistent.
1871    
1872     our @dev_type = qw(block type1 legacypartition serial udp boot partition vmbus locate);
1873     our @block_type = qw(harddisk floppy cdrom ramdisk type4 file vhd);
1874     our @part_type = qw(gpt mbr raw);
1875    
1876     our $NULL_DEVICE = "\x00" x 16;
1877    
1878     # biggest bitch to decode, ever
1879     # this decoded a device portion after the GUID
1880 root 1.37 sub dec_device_($$);
1881     sub dec_device_($$) {
1882     my ($device, $type) = @_;
1883 root 1.1
1884     my $res;
1885    
1886     my ($type, $flags, $length, $pad) = unpack "VVVV", substr $device, 0, 4 * 4, "";
1887    
1888     $pad == 0
1889     or die "non-zero reserved field in device descriptor\n";
1890    
1891     if ($length == 0 && $type == 0 && $flags == 0) {
1892     return ("null", $device);
1893     }
1894    
1895     $length >= 16
1896     or die "device element size too small ($length)\n";
1897    
1898     $type = $dev_type[$type] // die "$type: unknown device type\n";
1899     #d# warn "t<$type,$flags,$length,$pad>\n";#d#
1900    
1901     $res .= $type;
1902     $res .= sprintf "<%x>", $flags if $flags;
1903    
1904     my $tail = substr $device, $length - 4 * 4, 1e9, "";
1905    
1906     $length == 4 * 4 + length $device
1907     or die "device length mismatch ($length != " . (16 + length $device) . ")\n";
1908    
1909     my $dec_path = sub {
1910     my ($path, $error) = @_;
1911    
1912     $path =~ /^((?:..)*)\x00\x00\z/s
1913     or die "$error\n";
1914    
1915     $path = Encode::decode "UTF-16LE", $1;
1916    
1917     $path
1918     };
1919    
1920     if ($type eq "partition" or $type eq "legacypartition") {
1921     my $partdata = substr $device, 0, 16, "";
1922     my ($blocktype, $parttype) = unpack "VV", substr $device, 0, 4 * 2, "";
1923    
1924     $blocktype = $block_type[$blocktype] // die "unknown block device type '$blocktype'\n";
1925     $parttype = $part_type[$parttype] // die "unknown partition type\n";
1926    
1927     my $diskid = substr $device, 0, 16, "";
1928    
1929     $diskid = $parttype eq "gpt"
1930     ? dec_guid substr $diskid, 0, 16
1931     : sprintf "%08x", unpack "V", $diskid;
1932    
1933     my $partid = $parttype eq "gpt" ? dec_guid $partdata
1934     : $type eq "partition" ? unpack "Q<", $partdata # byte offset to partition start
1935     : unpack "L<", $partdata; # partition number, one-based
1936    
1937 root 1.37 (my $parent, $device) = dec_device_ $device, $type;
1938 root 1.1
1939     $res .= "=";
1940     $res .= "<$parent>";
1941     $res .= ",$blocktype,$parttype,$diskid,$partid";
1942    
1943     # PartitionType (gpt, mbr, raw)
1944     # guid | partsig | disknumber
1945    
1946     } elsif ($type eq "boot") {
1947     $device =~ s/^\x00{56}\z//
1948     or die "boot device type with extra data not supported\n";
1949    
1950     } elsif ($type eq "block") {
1951     my $blocktype = unpack "V", substr $device, 0, 4, "";
1952    
1953     $blocktype = $block_type[$blocktype] // die "unknown block device type '$blocktype'\n";
1954    
1955     # decode a "file path" structure
1956     my $dec_file = sub {
1957     my ($fver, $flen, $ftype) = unpack "VVV", substr $device, 0, 4 * 3, "";
1958    
1959     my $path = substr $device, 0, $flen - 12, "";
1960    
1961     $fver == 1
1962     or die "unsupported file descriptor version '$fver'\n";
1963    
1964     $ftype == 5
1965     or die "unsupported file descriptor path type '$type'\n";
1966    
1967 root 1.37 (my $parent, $path) = dec_device_ $path, $type;
1968 root 1.1
1969     $path = $dec_path->($path, "file device without path");
1970    
1971     ($parent, $path)
1972     };
1973    
1974     if ($blocktype eq "file") {
1975     my ($parent, $path) = $dec_file->();
1976    
1977     $res .= "=file,<$parent>,$path";
1978    
1979     } elsif ($blocktype eq "vhd") {
1980     $device =~ s/^\x00{20}//s
1981     or die "virtualdisk has non-zero fields I don't understand\n";
1982    
1983 root 1.37 (my $parent, $device) = dec_device_ $device, $type;
1984 root 1.1
1985     $res .= "=vhd,<$parent>";
1986    
1987     } elsif ($blocktype eq "ramdisk") {
1988     my ($base, $size, $offset) = unpack "Q< Q< L<", substr $device, 0, 8 + 8 + 4, "";
1989     my ($subdev, $path) = $dec_file->();
1990    
1991     $res .= "=ramdisk,<$subdev>,$base,$size,$offset,$path";
1992    
1993     } else {
1994     die "unsupported block type '$blocktype'\n";
1995     }
1996    
1997     } elsif ($type eq "locate") {
1998     # mode, bcde_id, unknown, string
1999     # we assume locate has _either_ an element id _or_ a path, but not both
2000    
2001     my ($mode, $elem, $parent) = unpack "VVV", substr $device, 0, 4 * 3, "";
2002    
2003     if ($parent) {
2004     # not sure why this is an offset - it must come after the path
2005     $parent = substr $device, $parent - 4 * 3 - 4 * 4, 1e9, "";
2006 root 1.37 ($parent, my $tail) = dec_device_ $parent, $type;
2007 root 1.1 0 == length $tail
2008     or die "trailing data after locate device parent\n";
2009     } else {
2010     $parent = "null";
2011     }
2012    
2013     my $path = $device; $device = "";
2014     $path = $dec_path->($path, "device locate mode without path");
2015    
2016     $res .= "=<$parent>,";
2017    
2018     if ($mode == 0) { # "Element"
2019     !length $path
2020     or die "device locate mode 0 having non-empty path ($mode, $elem, $path)\n";
2021    
2022 root 1.37 $elem = dec_bcde_id $elem, $type;
2023 root 1.1 $res .= "element,$elem";
2024    
2025     } elsif ($mode == 1) { # "String"
2026     !$elem
2027     or die "device locate mode 1 having non-zero element\n";
2028    
2029     $res .= "path,$path";
2030     } else {
2031     # mode 2 maybe called "ElementChild" with element and parent device? example needed
2032     die "device locate mode '$mode' not supported\n";
2033     }
2034    
2035     } elsif ($type eq "vmbus") {
2036     my $type = dec_guid substr $device, 0, 16, "";
2037     my $instance = dec_guid substr $device, 0, 16, "";
2038    
2039     $device =~ s/^\x00{24}\z//
2040     or die "vmbus has non-zero fields I don't understand\n";
2041    
2042     $res .= "=$type,$instance";
2043    
2044     } else {
2045     die "unsupported device type '$type'\n";
2046     }
2047    
2048     warn "unexpected trailing device data($res), " . unpack "H*",$device
2049     if length $device;
2050     #length $device
2051     # and die "unexpected trailing device data\n";
2052    
2053     ($res, $tail)
2054     }
2055    
2056     # decode a full binary BCD device descriptor
2057 root 1.37 sub dec_device($$) {
2058     my ($device, $type) = @_;
2059 root 1.1
2060     $device = pack "H*", $device;
2061    
2062     my $guid = dec_guid substr $device, 0, 16, "";
2063     $guid = $guid eq "00000000-0000-0000-0000-000000000000"
2064     ? "" : "{$guid}";
2065    
2066     eval {
2067 root 1.37 my ($dev, $tail) = dec_device_ $device, $type;
2068 root 1.1
2069     $tail eq ""
2070     or die "unsupported trailing data after device descriptor\n";
2071    
2072     "$guid$dev"
2073     # } // scalar ((warn $@), "$guid$fallback")
2074     } // ($guid . "binary=" . unpack "H*", $device)
2075     }
2076    
2077     sub indexof($@) {
2078     my $value = shift;
2079    
2080     for (0 .. $#_) {
2081     $value eq $_[$_]
2082     and return $_;
2083     }
2084    
2085     undef
2086     }
2087    
2088     # encode the device portion after the GUID
2089 root 1.37 sub enc_device_($$);
2090     sub enc_device_($$) {
2091     my ($device, $type) = @_;
2092 root 1.1
2093     my $enc_path = sub {
2094     my $path = shift;
2095     $path =~ s/\//\\/g;
2096     (Encode::encode "UTF-16LE", $path) . "\x00\x00"
2097     };
2098    
2099     my $enc_file = sub {
2100     my ($parent, $path) = @_; # parent and path must already be encoded
2101    
2102     $path = $parent . $path;
2103    
2104     # fver 1, ftype 5
2105     pack "VVVa*", 1, 12 + length $path, 5, $path
2106     };
2107    
2108     my $parse_path = sub {
2109     s/^([\/\\][^<>"|?*\x00-\x1f]*)//
2110     or die "$_: invalid path\n";
2111    
2112     $enc_path->($1)
2113     };
2114    
2115     my $parse_parent = sub {
2116     my $parent;
2117    
2118     if (s/^<//) {
2119 root 1.37 ($parent, $_) = enc_device_ $_, $type;
2120 root 1.1 s/^>//
2121     or die "$device: syntax error: parent device not followed by '>'\n";
2122     } else {
2123     $parent = $NULL_DEVICE;
2124     }
2125    
2126     $parent
2127     };
2128    
2129     for ($device) {
2130     s/^([a-z]+)//
2131     or die "$_: device does not start with type string\n";
2132    
2133     my $type = $1;
2134     my $flags = s/^<([0-9a-fA-F]+)>// ? hex $1 : 0;
2135     my $payload;
2136    
2137     if ($type eq "binary") {
2138     s/^=([0-9a-fA-F]+)//
2139     or die "binary type must have a hex string argument\n";
2140    
2141     $payload = pack "H*", $1;
2142    
2143     } elsif ($type eq "null") {
2144     return ($NULL_DEVICE, $_);
2145    
2146     } elsif ($type eq "boot") {
2147     $payload = "\x00" x 56;
2148    
2149     } elsif ($type eq "partition" or $type eq "legacypartition") {
2150     s/^=//
2151     or die "$_: missing '=' after $type\n";
2152    
2153     my $parent = $parse_parent->();
2154    
2155     s/^,//
2156     or die "$_: comma missing after partition parent device\n";
2157    
2158     s/^([a-z]+),//
2159     or die "$_: partition does not start with block type (e.g. hd or vhd)\n";
2160     my $blocktype = $1;
2161    
2162     s/^([a-z]+),//
2163     or die "$_: partition block type not followed by partiton type\n";
2164     my $parttype = $1;
2165    
2166     my ($partdata, $diskdata);
2167    
2168     if ($parttype eq "mbr") {
2169     s/^([0-9a-f]{8}),//i
2170     or die "$_: partition mbr disk id malformed (must be e.g. 1234abcd)\n";
2171     $diskdata = pack "Vx12", hex $1;
2172    
2173     s/^([0-9]+)//
2174     or die "$_: partition number or offset is missing or malformed (must be decimal)\n";
2175    
2176     # the following works for both 64 bit offset and 32 bit partno
2177     $partdata = pack "Q< x8", $1;
2178    
2179     } elsif ($parttype eq "gpt") {
2180     s/^($RE_GUID),//
2181     or die "$_: partition disk guid missing or malformed\n";
2182     $diskdata = enc_guid $1;
2183    
2184     s/^($RE_GUID)//
2185     or die "$_: partition guid missing or malformed\n";
2186     $partdata = enc_guid $1;
2187    
2188     } elsif ($parttype eq "raw") {
2189     s/^([0-9]+)//
2190     or die "$_: partition disk number missing or malformed (must be decimal)\n";
2191    
2192     $partdata = pack "L< x12", $1;
2193    
2194     } else {
2195     die "$parttype: partition type not supported\n";
2196     }
2197    
2198     $payload = pack "a16 L< L< a16 a*",
2199     $partdata,
2200     (indexof $blocktype, @block_type),
2201     (indexof $parttype, @part_type),
2202     $diskdata,
2203     $parent;
2204    
2205     } elsif ($type eq "locate") {
2206     s/^=//
2207     or die "$_: missing '=' after $type\n";
2208    
2209     my ($mode, $elem, $path);
2210    
2211     my $parent = $parse_parent->();
2212    
2213     s/^,//
2214     or die "$_: missing comma after locate parent device\n";
2215    
2216     if (s/^element,//) {
2217 root 1.37 s/^([0-9a-z:]+)//i
2218 root 1.1 or die "$_ locate element must be either name or 8-digit hex id\n";
2219 root 1.37 $elem = enc_bcde_id $1, $type;
2220 root 1.1 $mode = 0;
2221     $path = $enc_path->("");
2222    
2223     } elsif (s/^path,//) {
2224     $mode = 1;
2225     $path = $parse_path->();
2226    
2227     } else {
2228     die "$_ second locate argument must be subtype (either element or path)\n";
2229     }
2230    
2231     if ($parent ne $NULL_DEVICE) {
2232     ($parent, $path) = (4 * 4 + 4 * 3 + length $path, "$path$parent");
2233     } else {
2234     $parent = 0;
2235     }
2236    
2237     $payload = pack "VVVa*", $mode, $elem, $parent, $path;
2238    
2239     } elsif ($type eq "block") {
2240     s/^=//
2241     or die "$_: missing '=' after $type\n";
2242    
2243     s/^([a-z]+),//
2244     or die "$_: block device does not start with block type (e.g. disk)\n";
2245     my $blocktype = $1;
2246    
2247     my $blockdata;
2248    
2249     if ($blocktype eq "file") {
2250     my $parent = $parse_parent->();
2251     s/^,// or die "$_: comma missing after file block device parent\n";
2252     my $path = $parse_path->();
2253    
2254     $blockdata = $enc_file->($parent, $path);
2255    
2256     } elsif ($blocktype eq "vhd") {
2257     $blockdata = "\x00" x 20; # ENOTUNDERSTOOD
2258     $blockdata .= $parse_parent->();
2259    
2260     } elsif ($blocktype eq "ramdisk") {
2261     my $parent = $parse_parent->();
2262    
2263     s/^,(\d+),(\d+),(\d+),//a
2264     or die "$_: missing ramdisk base,size,offset after ramdisk parent device\n";
2265    
2266     my ($base, $size, $offset) = ($1, $2, $3);
2267    
2268     my $path = $parse_path->();
2269    
2270     $blockdata = pack "Q< Q< L< a*", $base, $size, $offset, $enc_file->($parent, $path);
2271    
2272     } elsif ($blocktype eq "cdrom" or $blocktype eq "floppy") {
2273     # this is guesswork
2274     s/^(\d+)//a
2275     or die "$_: missing device number for cdrom\n";
2276     $blockdata = pack "V", $1;
2277    
2278     } else {
2279     die "$blocktype: unsupported block type (must be file, vhd, ramdisk, floppy, cdrom)\n";
2280     }
2281    
2282     $payload = pack "Va*",
2283     (indexof $blocktype, @block_type),
2284     $blockdata;
2285    
2286     } elsif ($type eq "vmbus") {
2287     s/^=($RE_GUID)//
2288     or die "$_: malformed or missing vmbus interface type guid\n";
2289     my $type = enc_guid $1;
2290     s/^,($RE_GUID)//
2291     or die "$_: malformed or missing vmbus interface instance guid\n";
2292     my $instance = enc_guid $1;
2293    
2294     $payload = pack "a16a16x24", $type, $instance;
2295    
2296 root 1.56 # } elsif ($type eq "udp") {
2297     # $payload = pack "Va16", 1, "12345678";
2298    
2299 root 1.1 } else {
2300     die "$type: not a supported device type (binary, null, boot, legacypartition, partition, block, locate)\n";
2301     }
2302    
2303     return (
2304     (pack "VVVVa*", (indexof $type, @dev_type), $flags, 16 + length $payload, 0, $payload),
2305     $_
2306     );
2307     }
2308     }
2309    
2310     # encode a full binary BCD device descriptor
2311 root 1.37 sub enc_device($$) {
2312     my ($device, $type) = @_;
2313 root 1.1
2314     my $guid = "\x00" x 16;
2315    
2316     if ($device =~ s/^\{([A-Za-z0-9\-]+)\}//) {
2317     $guid = enc_guid $1
2318     or die "$device: does not start with valid guid\n";
2319     }
2320    
2321 root 1.37 my ($descriptor, $tail) = enc_device_ $device, $type;
2322 root 1.1
2323     length $tail
2324     and die "$device: garbage after device descriptor\n";
2325    
2326     unpack "H*", $guid . $descriptor
2327     }
2328    
2329     # decode a registry hive into the BCD structure used by pbcdedit
2330     sub bcd_decode {
2331     my ($hive) = @_;
2332    
2333     my %bcd;
2334    
2335     my $objects = $hive->[1][1]{Objects}[1];
2336    
2337     while (my ($k, $v) = each %$objects) {
2338     my %kv;
2339     $v = $v->[1];
2340    
2341     $k = $bcd_objects{$k} // $k;
2342    
2343     my $type = $v->{Description}[0]{Type}[1];
2344    
2345     if ($type != $bcd_object_types{$k}) {
2346 root 1.37 $kv{type} = $bcd_types{$type} // sprintf "0x%08x", $type;
2347 root 1.1 }
2348    
2349     my $elems = $v->{Elements}[1];
2350    
2351     while (my ($k, $v) = each %$elems) {
2352     my $k = hex $k;
2353    
2354 root 1.37 my $v = $bcde_dec{$k & BCDE_FORMAT}->($v->[0]{Element}[1], $type);
2355     my $k = dec_bcde_id $k, $type;
2356 root 1.1
2357     $kv{$k} = $v;
2358     }
2359    
2360     $bcd{$k} = \%kv;
2361     }
2362    
2363     $bcd{meta} = { version => $JSON_VERSION };
2364    
2365     \%bcd
2366     }
2367    
2368     # encode a pbcdedit structure into a registry hive
2369     sub bcd_encode {
2370     my ($bcd) = @_;
2371    
2372     if (my $meta = $bcd->{meta}) {
2373     $meta->{version} eq $JSON_VERSION
2374     or die "BCD meta version ($meta->{version}) does not match executable version ($JSON_VERSION)\n";
2375     }
2376    
2377     my %objects;
2378     my %rbcd_types = reverse %bcd_types;
2379    
2380     while (my ($k, $v) = each %$bcd) {
2381     my %kv;
2382    
2383     next if $k eq "meta";
2384    
2385     $k = lc $k; # I know you windows types!
2386    
2387     my $type = $v->{type};
2388    
2389     if ($type) {
2390     $type = $type =~ /^(?:0x)[0-9a-fA-F]+$/
2391     ? hex $type
2392     : $rbcd_types{$type} // die "$type: unable to parse bcd object type\n";
2393     }
2394    
2395     my $guid = enc_wguid $k
2396     or die "$k: invalid bcd object identifier\n";
2397    
2398     # default type if not given
2399     $type //= $bcd_object_types{dec_wguid $guid} // die "$k: unable to deduce bcd object type\n";
2400    
2401     my %elem;
2402    
2403     while (my ($k, $v) = each %$v) {
2404     next if $k eq "type";
2405    
2406 root 1.37 $k = (enc_bcde_id $k, $type) // die "$k: invalid bcde element name or id\n";
2407 root 1.1 $elem{sprintf "%08x", $k} = [{
2408     Element => [ ($bcde_enc{$k & BCDE_FORMAT} // die "$k: unable to encode unknown bcd element type}")->($v)]
2409     }];
2410     }
2411    
2412     $guid = dec_guid $guid;
2413    
2414     $objects{"{$guid}"} = [undef, {
2415     Description => [{ Type => [dword => $type] }],
2416     Elements => [undef, \%elem],
2417     }];
2418     }
2419    
2420     [NewStoreRoot => [undef, {
2421     Description => [{
2422     KeyName => [sz => "BCD00000001"],
2423     System => [dword => 1],
2424     pbcdedit => [sz => $VERSION],
2425     # other values seen: GuidCache => ..., TreatAsSystem => 0x00000001
2426     }],
2427     Objects => [undef, \%objects],
2428     }]]
2429     }
2430    
2431     #############################################################################
2432 root 1.29 # edit instructions
2433 root 1.1
2434 root 1.6 sub bcd_edit_eval {
2435     package pbcdedit;
2436    
2437     our ($PATH, $BCD, $DEFAULT);
2438    
2439     eval shift;
2440     die "$@" if $@;
2441     }
2442    
2443     sub bcd_edit {
2444     my ($path, $bcd, @insns) = @_;
2445    
2446 root 1.36 my $default = $bcd->{"{bootmgr}"}{default};
2447 root 1.6
2448     # prepare "officially visible" variables
2449     local $pbcdedit::PATH = $path;
2450     local $pbcdedit::BCD = $bcd;
2451     local $pbcdedit::DEFAULT = $default;
2452    
2453     while (@insns) {
2454     my $insn = shift @insns;
2455    
2456     if ($insn eq "get") {
2457     my $object = shift @insns;
2458     my $elem = shift @insns;
2459    
2460 root 1.15 $object = $object eq "{default}" ? $default : dec_wguid enc_wguid $object;
2461 root 1.6
2462     print $bcd->{$object}{$elem}, "\n";
2463    
2464     } elsif ($insn eq "set") {
2465     my $object = shift @insns;
2466     my $elem = shift @insns;
2467     my $value = shift @insns;
2468    
2469 root 1.15 $object = $object eq "{default}" ? $default : dec_wguid enc_wguid $object;
2470 root 1.6
2471     $bcd->{$object}{$elem} = $value;
2472    
2473     } elsif ($insn eq "eval") {
2474 root 1.35 my $perl = shift @insns;
2475     bcd_edit_eval "#line 1 'eval'\n$perl";
2476 root 1.6
2477     } elsif ($insn eq "do") {
2478     my $path = shift @insns;
2479     my $file = file_load $path;
2480     bcd_edit_eval "#line 1 '$path'\n$file";
2481    
2482     } else {
2483     die "$insn: not a recognized instruction for edit/parse\n";
2484     }
2485     }
2486    
2487     }
2488    
2489     #############################################################################
2490 root 1.43 # other utilities
2491 root 1.6
2492 root 1.1 # json to stdout
2493     sub prjson($) {
2494     print $json_coder->encode ($_[0]);
2495     }
2496    
2497     # json from stdin
2498     sub rdjson() {
2499     my $json;
2500     1 while read STDIN, $json, 65536, length $json;
2501     $json_coder->decode ($json)
2502     }
2503    
2504 root 1.43 sub lsblk() {
2505     my $lsblk = $json_coder->decode (scalar qx<lsblk --json -o PATH,KNAME,MAJ:MIN,TYPE,PTTYPE,PTUUID,PARTUUID,LABEL,FSTYPE>);
2506    
2507     for my $dev (@{ $lsblk->{blockdevices} }) {
2508     if ($dev->{type} eq "part") {
2509 root 1.60
2510     # lsblk sometimes gives a bogus pttype, so we recreate it here
2511     $dev->{pttype} = $dev->{ptuuid} =~ /^$RE_GUID\z/
2512     ? "gpt" : "dos";
2513    
2514 root 1.43 if ($dev->{pttype} eq "gpt") {
2515     $dev->{bcd_device} = "partition=<null>,harddisk,gpt,$dev->{ptuuid},$dev->{partuuid}";
2516     } elsif ($dev->{pttype} eq "dos") { # why not "mbr" :(
2517     if ($dev->{partuuid} =~ /^([0-9a-f]{8})-([0-9a-f]{2})\z/i) {
2518     my ($diskid, $partno) = ($1, hex $2);
2519     $dev->{bcd_legacy_device} = "legacypartition=<null>,harddisk,mbr,$diskid,$partno";
2520     if (open my $fh, "/sys/class/block/$dev->{kname}/start") {
2521     my $start = 512 * readline $fh;
2522     $dev->{bcd_device} = "partition=<null>,harddisk,mbr,$diskid,$start";
2523     }
2524     }
2525     }
2526     }
2527     }
2528    
2529     $lsblk->{blockdevices}
2530     }
2531    
2532     sub prdev($$) {
2533     my ($path, $attribute) = @_;
2534    
2535     # rather than stat'ing and guessing how devices are encoded, we use lsblk for this
2536 root 1.60 my $mm = $json_coder->decode (scalar qx<lsblk -d -o MAJ:MIN -J \Q$path\E>)->{blockdevices}[0]{"maj:min"};
2537 root 1.43
2538     my $lsblk = lsblk;
2539    
2540     for my $dev (@$lsblk) {
2541     if ($dev->{"maj:min"} eq $mm && $dev->{$attribute}) {
2542     say $dev->{$attribute};
2543     exit 0;
2544     }
2545     }
2546    
2547     exit 1;
2548     }
2549    
2550     #############################################################################
2551     # command line parser
2552    
2553 root 1.1 our %CMD = (
2554     help => sub {
2555     require Pod::Usage;
2556     Pod::Usage::pod2usage (-verbose => 2);
2557     },
2558    
2559     objects => sub {
2560     my %rbcd_types = reverse %bcd_types;
2561     $_ = sprintf "%08x", $_ for values %rbcd_types;
2562    
2563     if ($_[0] eq "--json") {
2564     my %default_type = %bcd_object_types;
2565     $_ = sprintf "%08x", $_ for values %default_type;
2566    
2567     prjson {
2568     version => $JSON_VERSION,
2569     object_alias => \%bcd_objects,
2570     object_type => \%rbcd_types,
2571     object_default_type => \%default_type,
2572     };
2573     } else {
2574     my %rbcd_objects = reverse %bcd_objects;
2575    
2576     print "\n";
2577    
2578     printf "%-9s %s\n", "Type", "Alias";
2579     for my $tname (sort keys %rbcd_types) {
2580     printf "%-9s %s\n", $rbcd_types{$tname}, $tname;
2581     }
2582    
2583     print "\n";
2584    
2585     printf "%-39s %-23s %s\n", "Object GUID", "Alias", "(Hex) Default Type";
2586     for my $name (sort keys %rbcd_objects) {
2587 root 1.37 my $guid = $rbcd_objects{$name};
2588     my $type = $bcd_object_types{$name};
2589 root 1.1 my $tname = $bcd_types{$type};
2590    
2591     $type = $type ? sprintf "(%08x) %s", $type, $tname : "-";
2592    
2593     printf "%-39s %-23s %s\n", $guid, $name, $type;
2594     }
2595    
2596     print "\n";
2597     }
2598     },
2599    
2600     elements => sub {
2601     my $json = $_[0] eq "--json";
2602    
2603     my %format_name = (
2604     BCDE_FORMAT_DEVICE , "device",
2605     BCDE_FORMAT_STRING , "string",
2606     BCDE_FORMAT_GUID , "guid",
2607     BCDE_FORMAT_GUID_LIST , "guid list",
2608     BCDE_FORMAT_INTEGER , "integer",
2609     BCDE_FORMAT_BOOLEAN , "boolean",
2610     BCDE_FORMAT_INTEGER_LIST, "integer list",
2611     );
2612    
2613 root 1.40 my @element;
2614 root 1.1
2615 root 1.37 for my $class (sort keys %rbcde_byclass) {
2616     my $rbcde = $rbcde_byclass{$class};
2617    
2618     unless ($json) {
2619     print "\n";
2620     printf "Elements applicable to class(es): $class\n";
2621     printf "%-9s %-12s %s\n", "Element", "Format", "Name Alias";
2622     }
2623     for my $name (sort keys %$rbcde) {
2624     my $id = $rbcde->{$name};
2625     my $format = $format_name{$id & BCDE_FORMAT};
2626 root 1.1
2627 root 1.37 if ($json) {
2628 root 1.40 push @element, [$class, $id * 1, $format, $name];
2629 root 1.37 } else {
2630 root 1.40 $id = sprintf "%08x", $id;
2631 root 1.37 printf "%-9s %-12s %s\n", $id, $format, $name;
2632     }
2633 root 1.1 }
2634     }
2635     print "\n" unless $json;
2636    
2637     prjson {
2638     version => $JSON_VERSION,
2639 root 1.40 element => \@element,
2640 root 1.37 class => \@bcde_typeclass,
2641 root 1.1 } if $json;
2642    
2643     },
2644    
2645     export => sub {
2646     prjson bcd_decode regf_load shift;
2647     },
2648    
2649     import => sub {
2650     regf_save shift, bcd_encode rdjson;
2651     },
2652    
2653 root 1.56 create => sub {
2654     my $path = shift;
2655     my $stat = stat_get $path; # should actually be done at file load time
2656     my $bcd = { };
2657     bcd_edit $path, $bcd, @_;
2658     regf_save $path, bcd_encode $bcd;
2659     stat_set $path, $stat;
2660     },
2661    
2662 root 1.6 edit => sub {
2663     my $path = shift;
2664 root 1.56 my $stat = stat_get $path; # should actually be done at file load time
2665 root 1.6 my $bcd = bcd_decode regf_load $path;
2666     bcd_edit $path, $bcd, @_;
2667     regf_save $path, bcd_encode $bcd;
2668 root 1.56 stat_set $path, $stat;
2669 root 1.6 },
2670    
2671     parse => sub {
2672     my $path = shift;
2673     my $bcd = bcd_decode regf_load $path;
2674     bcd_edit $path, $bcd, @_;
2675     },
2676    
2677 root 1.1 "export-regf" => sub {
2678     prjson regf_load shift;
2679    
2680     },
2681    
2682     "import-regf" => sub {
2683     regf_save shift, rdjson;
2684     },
2685    
2686     lsblk => sub {
2687 root 1.44 my $json = $_[0] eq "--json";
2688    
2689 root 1.43 my $lsblk = lsblk;
2690    
2691 root 1.44 if ($json) {
2692     prjson $lsblk;
2693     } else {
2694     printf "%-10s %-8.8s %-6.6s %-3s %s\n", "DEVICE", "LABEL", "FSTYPE", "PT", "DEVICE DESCRIPTOR";
2695     for my $dev (@$lsblk) {
2696     for my $bcd ($dev->{bcd_device}, $dev->{bcd_legacy_device}) {
2697     printf "%-10s %-8.8s %-6.6s %-3s %s\n",
2698     $dev->{path}, $dev->{label}, $dev->{fstype}, $dev->{pttype}, $bcd
2699     if $bcd;
2700     }
2701 root 1.1 }
2702     }
2703     },
2704 root 1.37
2705 root 1.43 "bcd-device" => sub {
2706     prdev shift, "bcd_device";
2707     },
2708    
2709     "bcd-legacy-device" => sub {
2710     prdev shift, "bcd_legacy_device";
2711     },
2712    
2713 root 1.37 version => sub {
2714     print "\n",
2715     "PBCDEDIT version $VERSION, copyright 2019 Marc A. Lehmann <pbcdedit\@schmorp.de>.\n",
2716     "JSON schema version: $JSON_VERSION\n",
2717     "Licensed under the GNU General Public License Version 3.0, or any later version.\n",
2718     "\n",
2719     $CHANGELOG,
2720     "\n";
2721     },
2722 root 1.1 );
2723    
2724     my $cmd = shift;
2725    
2726     unless (exists $CMD{$cmd}) {
2727     warn "Usage: $0 subcommand args...\nTry $0 help\n";
2728     exit 126;
2729     }
2730    
2731     $CMD{$cmd}->(@ARGV);
2732