ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.60
Committed: Sun Aug 25 17:15:54 2019 UTC (4 years, 8 months ago) by root
Branch: MAIN
Changes since 1.59: +8 -3 lines
Log Message:
*** empty log message ***

File Contents

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