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