ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.61
Committed: Sun Aug 25 20:04:11 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.60: +2 -1 lines
Log Message:
*** empty log message ***

File Contents

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