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