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