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