ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.51
Committed: Thu Aug 22 07:44:08 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.50: +2 -0 lines
Log Message:
*** empty log message ***

File Contents

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