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