ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.2
Committed: Wed Aug 14 20:56:50 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.1: +0 -3 lines
Log Message:
*** empty log message ***

File Contents

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