Spin off hivex as a separate upstream project.
[libguestfs.git] / hivex / tools / visualizer.ml
diff --git a/hivex/tools/visualizer.ml b/hivex/tools/visualizer.ml
deleted file mode 100644 (file)
index 5b7ac79..0000000
+++ /dev/null
@@ -1,984 +0,0 @@
-(* Windows Registry reverse-engineering tool.
- * Copyright (C) 2010 Red Hat Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * For existing information on the registry format, please refer
- * to the following documents.  Note they are both incomplete
- * and inaccurate in some respects.
- *
- * http://www.sentinelchicken.com/data/TheWindowsNTRegistryFileFormat.pdf
- * http://pogostick.net/~pnh/ntpasswd/WinReg.txt
- *)
-
-open Bitstring
-open ExtString
-open Printf
-open Visualizer_utils
-open Visualizer_NT_time
-
-let () =
-  if Array.length Sys.argv <> 2 then (
-    eprintf "Error: missing argument.
-Usage: %s hivefile > out
-where
-  'hivefile' is the input hive file from a Windows machine
-  'out' is an output file where we will write all the keys,
-    values etc for extended debugging purposes.
-Errors, inconsistencies and unexpected fields in the hive file
-are written to stderr.
-" Sys.executable_name;
-    exit 1
-  )
-
-let filename = Sys.argv.(1)
-let basename = Filename.basename filename
-
-(* Load the file. *)
-let bits = bitstring_of_file filename
-
-(* Split into header + data at the 4KB boundary. *)
-let header, data = takebits (4096 * 8) bits, dropbits (4096 * 8) bits
-
-(* Define a persistent pattern which matches the header fields.  By
- * using persistent patterns, we can reuse them later in the
- * program.
- *)
-let bitmatch header_fields =
-  { "regf" : 4*8 : string;
-    seq1 : 4*8 : littleendian;
-    seq2 : 4*8 : littleendian;
-    last_modified : 64
-      : littleendian, bind (nt_to_time_t last_modified);
-    major : 4*8 : littleendian;
-    minor : 4*8 : littleendian;
-
-    (* "Type".  Contains 0. *)
-    unknown1 : 4*8 : littleendian;
-
-    (* "Format".  Contains 1. *)
-    unknown2 : 4*8 : littleendian;
-
-    root_key : 4*8
-      : littleendian, bind (get_offset root_key);
-    end_pages : 4*8
-      : littleendian, bind (get_offset end_pages);
-
-    (* "Cluster".  Contains 1. *)
-    unknown3 : 4*8 : littleendian;
-
-    filename : 64*8 : string;
-
-    (* All three GUIDs here confirmed in Windows 7 registries.  In
-     * Windows <= 2003 these GUID fields seem to contain junk.
-     * 
-     * If you write zeroes to the GUID fields, load and unload in Win7
-     * REGEDIT, then Windows 7 writes some random GUIDs.
-     * 
-     * Also (on Win7) unknownguid1 == unknownguid2.  unknownguid3 is
-     * different.
-     *)
-    unknownguid1 : 16*8 : bitstring;
-    unknownguid2 : 16*8 : bitstring;
-
-    (* Wrote zero to unknown4, loaded and unloaded it in Win7 REGEDIT,
-     * and it still contained zero.  In existing registries it seems to
-     * contain random junk.
-     *)
-    unknown4 : 4*8 : littleendian;
-    unknownguid3 : 16*8 : bitstring;
-
-    (* If you write zero to unknown5, load and unload it in REGEDIT,
-     * Windows 7 puts the string "rmtm" here.  Existing registries also
-     * seen containing this string.  However on older Windows it can
-     * be all zeroes.
-     *)
-    unknown5 : 4*8 : string;
-
-    (* This seems to contain junk from other parts of the registry.  I
-     * wrote zeroes here, loaded and unloaded it in Win7 REGEDIT, and
-     * it still contained zeroes.
-     *)
-    unknown6 : 340*8 : bitstring;
-    csum : 4*8
-      : littleendian, save_offset_to (crc_offset),
-    check (assert (crc_offset = 0x1fc * 8); true);
-    unknown7 : (0x1000-0x200)*8 : bitstring }
-
-let fprintf_header chan bits =
-  bitmatch bits with
-  | { :header_fields } ->
-      fprintf chan
-        "HD %6ld %6ld %s %ld.%ld %08lx %08lx %s %s %08lx %s %s %s %08lx %s %s %s %08lx %s\n"
-        seq1 seq2 (print_time last_modified) major minor
-        unknown1 unknown2
-        (print_offset root_key) (print_offset end_pages)
-        unknown3 (print_utf16 filename)
-        (print_guid unknownguid1) (print_guid unknownguid2)
-        unknown4 (print_guid unknownguid3) unknown5
-        (print_bitstring unknown6)
-        csum (print_bitstring unknown7)
-
-(* Parse the header and check it. *)
-let root_key, end_pages =
-  bitmatch header with
-  |  { :header_fields } ->
-       fprintf_header stdout header;
-
-       if major <> 1_l then
-         eprintf "HD hive file major <> 1 (major.minor = %ld.%ld)\n"
-           major minor;
-       if seq1 <> seq2 then
-         eprintf "HD hive file sequence numbers should match (%ld <> %ld)\n"
-           seq1 seq2;
-       if unknown1 <> 0_l then
-         eprintf "HD unknown1 field <> 0 (%08lx)\n" unknown1;
-       if unknown2 <> 1_l then
-         eprintf "HD unknown2 field <> 1 (%08lx)\n" unknown2;
-       if unknown3 <> 1_l then
-         eprintf "HD unknown3 field <> 1 (%08lx)\n" unknown3;
-       if not (equals unknownguid1 unknownguid2) then
-         eprintf "HD unknownguid1 <> unknownguid2 (%s, %s)\n"
-           (print_guid unknownguid1) (print_guid unknownguid2);
-       (* We think this is junk.
-       if unknown4 <> 0_l then
-         eprintf "HD unknown4 field <> 0 (%08lx)\n" unknown4;
-       *)
-       if unknown5 <> "rmtm" && unknown5 <> "\000\000\000\000" then
-         eprintf "HD unknown5 field <> \"rmtm\" & <> zeroes (%s)\n" unknown5;
-       (* We think this is junk.
-       if not (is_zero_bitstring unknown6) then
-         eprintf "HD unknown6 area is not zero (%s)\n"
-           (print_bitstring unknown6);
-       *)
-       if not (is_zero_bitstring unknown7) then
-         eprintf "HD unknown7 area is not zero (%s)\n"
-           (print_bitstring unknown7);
-
-       root_key, end_pages
-  | {_} ->
-      failwithf "%s: this doesn't look like a registry hive file\n" basename
-
-(* Define persistent patterns to match page and block fields. *)
-let bitmatch page_fields =
-  { "hbin" : 4*8 : string;
-    page_offset : 4*8
-      : littleendian, bind (get_offset page_offset);
-    page_size : 4*8
-      : littleendian, check (Int32.rem page_size 4096_l = 0_l),
-        bind (Int32.to_int page_size);
-
-    (* In the first hbin in the file these fields contain something.
-     * In subsequent hbins these fields are all zero.
-     *
-     * From existing hives (first hbin only):
-     *
-     * unknown1     unknown2                               unknown5
-     * 00 00 00 00  00 00 00 00  9C 77 3B 02  6A 7D CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  50 3A 15 07  B5 9B CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  57 86 90 D4  9A 58 CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  52 3F 90 9D  CF 7C CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  E8 86 C1 17  BD 06 CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  4A 77 CE 7A  CF 7C CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  E4 EA 23 FF  69 7D CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  50 13 BA 8D  A2 9A CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  0E 07 93 13  BD 06 CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  9D 55 D0 B3  99 58 CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  46 AC FF 8B  CF 7C CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  80 29 2D 02  6A 7D CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  90 8D 36 07  B5 9B CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  5C 9B 8B B8  6A 06 CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  85 9F BB 99  9A 58 CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  BE 3D 21 02  6A 7D CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  70 53 09 07  B5 9B CA 01  00 00 00 00
-     * 00 00 00 00  00 00 00 00  5B 62 42 B6  9A 58 CA 01  00 00 00 00
-     * 01 00 00 00  00 00 00 00  B2 46 9B 9E  CF 7C CA 01  00 00 00 00
-     * 01 00 00 00  00 00 00 00  CA 88 EE 1A  BD 06 CA 01  00 00 00 00
-     *
-     * From the above we worked out that fields 3 and 4 are an NT
-     * timestamp, which seems to be "last modified" (when REGEDIT
-     * unloads a hive it updates this timestamp even if nothing
-     * has been changed).
-     *)
-    unknown1 : 4*8 : littleendian;  (* usually zero, occasionally 1 *)
-    unknown2 : 4*8 : littleendian;  (* always zero? *)
-    last_modified : 64
-      : littleendian,
-        bind (if page_offset = 0 then nt_to_time_t last_modified
-              else (
-                assert (last_modified = 0_L);
-                0.
-              )
-             );
-    (* The "B.D." document said this field contains the page size, but
-     * this is not true.  This misinformation has been copied to the
-     * sentinelchicken documentation too.
-     *)
-    unknown5 : 4*8 : littleendian;  (* always zero? *)
-
-    (* Now the blocks in this page follow. *)
-    blocks : (page_size - 32) * 8 : bitstring;
-
-    rest : -1 : bitstring }
-
-let fprintf_page chan bits =
-  bitmatch bits with
-  | { :page_fields } ->
-      fprintf chan "HB %s %08x %08lx %08lx %s %08lx\n"
-        (print_offset page_offset)
-        page_size unknown1 unknown2
-        (if page_offset = 0 then print_time last_modified
-         else string_of_float last_modified) unknown5
-
-let bitmatch block_fields =
-  { seg_len : 4*8
-      : littleendian, bind (Int32.to_int seg_len);
-    block_data : (abs seg_len - 4) * 8 : bitstring;
-    rest : -1 : bitstring }
-
-let fprintf_block chan block_offset bits =
-  bitmatch bits with
-  | { :block_fields } ->
-      fprintf chan "BL %s %s %d\n"
-        (print_offset block_offset)
-        (if seg_len < 0 then "used" else "free")
-        (if seg_len < 0 then -seg_len else seg_len)
-
-(* Iterate over the pages and blocks.  In the process we will examine
- * each page (hbin) header.  Also we will build block_list which is a
- * list of (block offset, length, used flag, data).
- *)
-let block_list = ref []
-let () =
-  let rec loop_over_pages data data_offset =
-    if data_offset < end_pages then (
-      bitmatch data with
-      | { rest : -1 : bitstring } when bitstring_length rest = 0 -> ()
-
-      | { :page_fields } ->
-          fprintf_page stdout data;
-
-          assert (page_offset = data_offset);
-
-          if data_offset = 0 then (     (* first hbin only *)
-            if unknown1 <> 0_l then
-              eprintf "HB %s unknown1 field <> 0 (%08lx)\n"
-                (print_offset page_offset) unknown1;
-            if unknown2 <> 0_l then
-              eprintf "HB %s unknown2 field <> 0 (%08lx)\n"
-                (print_offset page_offset) unknown2;
-            if unknown5 <> 0_l then
-              eprintf "HB %s unknown5 field <> 0 (%08lx)\n"
-                (print_offset page_offset) unknown5
-          ) else (                      (* subsequent hbins *)
-            if unknown1 <> 0_l || unknown2 <> 0_l || unknown5 <> 0_l then
-                eprintf "HB %s unknown fields <> 0 (%08lx %08lx %08lx)\n"
-                  (print_offset page_offset)
-                  unknown1 unknown2 unknown5;
-            if last_modified <> 0. then
-                eprintf "HB %s last_modified <> 0. (%g)\n"
-                  (print_offset page_offset) last_modified
-          );
-
-          (* Loop over the blocks in this page. *)
-          loop_over_blocks blocks (data_offset + 32);
-
-          (* Loop over rest of the pages. *)
-          loop_over_pages rest (data_offset + page_size)
-
-      | {_} ->
-          failwithf "%s: invalid hbin at offset %s\n"
-            basename (print_offset data_offset)
-    ) else (
-      (* Reached the end of the official hbins in this file, BUT the
-       * file can be larger than this and might contain stuff.  What
-       * does it contain after the hbins?  We think just junk, but
-       * we're not sure.
-       *)
-      if not (is_zero_bitstring data) then (
-        eprintf "Junk in file after end of pages:\n";
-        let rec loop data data_offset =
-          bitmatch data with
-          | { rest : -1 : bitstring } when bitstring_length rest = 0 -> ()
-          | { :page_fields } ->
-              eprintf "\tjunk hbin %s 0x%08x\n"
-                (print_offset data_offset) page_size;
-              loop rest (data_offset + page_size);
-          | { _ } ->
-              eprintf "\tother junk %s %s\n"
-                (print_offset data_offset) (print_bitstring data)
-        in
-        loop data data_offset
-      )
-    )
-  and loop_over_blocks blocks block_offset =
-    bitmatch blocks with
-    | { rest : -1 : bitstring } when bitstring_length rest = 0 -> ()
-
-    | { :block_fields } ->
-        assert (block_offset mod 8 = 0);
-
-        fprintf_block stdout block_offset blocks;
-
-        let used, seg_len =
-          if seg_len < 0 then true, -seg_len else false, seg_len in
-
-        let block = block_offset, (seg_len, used, block_data) in
-        block_list := block :: !block_list;
-
-        (* Loop over the rest of the blocks in this page. *)
-        loop_over_blocks rest (block_offset + seg_len)
-
-    | {_} ->
-        failwithf "%s: invalid block near offset %s\n"
-          basename (print_offset block_offset)
-  in
-  loop_over_pages data 0
-
-(* Turn the block_list into a map so we can quickly look up a block
- * from its offset.
- *)
-let block_list = !block_list
-let block_map =
-  List.fold_left (
-    fun map (block_offset, block) -> IntMap.add block_offset block map
-  ) IntMap.empty block_list
-let lookup fn offset =
-  try
-    let (_, used, _) as block = IntMap.find offset block_map in
-    if not used then
-      failwithf "%s: %s: lookup: free block %s referenced from hive tree"
-        basename fn (print_offset offset);
-    block
-  with Not_found ->
-    failwithf "%s: %s: lookup: unknown block %s referenced from hive tree"
-      basename fn (print_offset offset)
-
-(* Use this to mark blocks that we've visited.  If the hive contains
- * no unreferenced blocks, then by the end this should just contain
- * free blocks.
- *)
-let mark_visited, is_not_visited, unvisited_blocks =
-  let v = ref block_map in
-  let mark_visited offset = v := IntMap.remove offset !v
-  and is_not_visited offset = IntMap.mem offset !v
-  and unvisited_blocks () = !v in
-  mark_visited, is_not_visited, unvisited_blocks
-
-(* Define persistent patterns to match nk-records, vk-records and
- * sk-records, which are the record types that we especially want to
- * analyze later.  Other blocks types (eg. value lists, lf-records)
- * have no "spare space" so everything is known about them and we don't
- * store these.
- *)
-let bitmatch nk_fields =
-  { "nk" : 2*8 : string;
-    (* Flags stored in the file as a little endian word, hence the
-     * unusual ordering:
-     *)
-    virtmirrored : 1;
-    predefinedhandle : 1; keynameascii : 1; symlinkkey : 1;
-    cannotbedeleted : 1; isroot : 1; ismountpoint : 1; isvolatile : 1;
-    unknownflag8000 : 1; unknownflag4000 : 1;
-    unknownflag2000 : 1; unknownflag1000 : 1;
-    unknownflag0800 : 1; unknownflag0400 : 1;
-    virtualstore : 1; virttarget : 1;
-    timestamp : 64 : littleendian, bind (nt_to_time_t timestamp);
-    unknown1 : 4*8 : littleendian;
-    parent : 4*8 : littleendian, bind (get_offset parent);
-    nr_subkeys : 4*8 : littleendian, bind (Int32.to_int nr_subkeys);
-    nr_subkeys_vol : 4*8;
-    subkeys : 4*8 : littleendian, bind (get_offset subkeys);
-    subkeys_vol : 4*8;
-    nr_values : 4*8 : littleendian, bind (Int32.to_int nr_values);
-    vallist : 4*8 : littleendian, bind (get_offset vallist);
-    sk : 4*8 : littleendian, bind (get_offset sk);
-    classname : 4*8 : littleendian, bind (get_offset classname);
-    (* sentinelchicken.com says this is a single 32 bit field
-     * containing maximum number of bytes in a subkey name, however
-     * that does not seem to be correct.  We think it is several
-     * fields, the first being the maximum number of bytes in the
-     * UTF16-LE encoded version of the subkey names, (since subkey
-     * names are usually ASCII, that would be max length of names * 2).
-     * This is a historical maximum, so it can be greater than the
-     * current maximum name field.
-     * 
-     * The remaining fields are often non-zero, but the purpose is
-     * unknown.
-     * 
-     * In the hives we examined the other fields had values as
-     * follows:
-     *   userflags: 0, 2, 0xa, 0xe
-     *   virtcontrolflags: 0, 1
-     *   debug: always 0
-     *)
-    max_subkey_name_len : 2*8 : littleendian;
-    unknown2_userflags : 4;
-    unknown2_virtcontrolflags : 4;
-    unknown2_debug : 8;
-
-    (* sentinelchicken.com says: maximum subkey CLASSNAME length,
-     * however that does not seem to be correct.  In hives I looked
-     * at, it has value 0, 0xc, 0x10, 0x18, 0x1a, 0x28.
-     *)
-    unknown3 : 4*8 : littleendian;
-    (* sentinelchicken.com says: maximum number of bytes in a value
-     * name, however that does not seem to be correct.  We think it is
-     * the maximum number of bytes in the UTF16-LE encoded version of
-     * the value names (since value names are usually ASCII, that would
-     * be max length of names * 2).  This is a historical maximum, so
-     * it can be greater than the current maximum name field.
-     *)
-    max_vk_name_len : 4*8 : littleendian, bind (Int32.to_int max_vk_name_len);
-    (* sentinelchicken.com says: maximum value data size, and this
-     * agrees with my observations.  It is the largest data size (not
-     * seg_len, but vk.data_len) for any value in this key.  We think
-     * that this field is a historical max, so eg if a maximally sized
-     * value is deleted then this field is not reduced.  Certainly
-     * max_vk_data_len >= the measured maximum in all the hives that we
-     * have observed.
-     *)
-    max_vk_data_len : 4*8 : littleendian, bind (Int32.to_int max_vk_data_len);
-    unknown6 : 4*8 : littleendian;
-    name_len : 2*8 : littleendian;
-    classname_len : 2*8 : littleendian;
-    name : name_len * 8 : string }
-
-let fprintf_nk chan nk =
-  let (_, _, bits) = lookup "fprintf_nk" nk in
-  bitmatch bits with
-  | { :nk_fields } ->
-      fprintf chan
-        "NK %s %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s %s %08lx %s %d %ld %s %08lx %d %s %s %s %d %x %x %x %08lx %d %d %08lx %d %d %s\n"
-        (print_offset nk)
-        (if unknownflag8000 then "8" else ".")
-        (if unknownflag4000 then "4" else ".")
-        (if unknownflag2000 then "2" else ".")
-        (if unknownflag1000 then "1" else ".")
-        (if unknownflag0800 then "8" else ".")
-        (if unknownflag0400 then "4" else ".")
-        (if virtualstore then "s" else ".")
-        (if virttarget then "t" else ".")
-        (if virtmirrored then "m" else ".")
-        (if predefinedhandle then "P" else ".")
-        (if keynameascii then "A" else ".")
-        (if symlinkkey then "S" else ".")
-        (if cannotbedeleted then "N" else ".")
-        (if isroot then "R" else ".")
-        (if ismountpoint then "M" else ".")
-        (if isvolatile then "V" else ".")
-        (print_time timestamp)
-        unknown1 (print_offset parent) nr_subkeys nr_subkeys_vol
-        (print_offset subkeys) subkeys_vol
-        nr_values (print_offset vallist)
-        (print_offset sk) (print_offset classname)
-        max_subkey_name_len
-        unknown2_userflags unknown2_virtcontrolflags unknown2_debug
-        unknown3 max_vk_name_len max_vk_data_len unknown6
-        name_len classname_len name
-
-type data_t = Inline of bitstring | Offset of int
-let bitmatch vk_fields =
-  { "vk" : 2*8 : string;
-    name_len : 2*8 : littleendian;
-    (* Top bit set means that the data is stored inline.  In that case
-     * the data length must be <= 4.  The length can also be 0 (or
-     * 0x80000000) if the data type is NONE.
-     *)
-    data_len : 4*8
-      : littleendian, bind (
-        let is_inline = Int32.logand data_len 0x8000_0000_l = 0x8000_0000_l in
-        let data_len = Int32.to_int (Int32.logand data_len 0x7fff_ffff_l) in
-        if is_inline then assert (data_len <= 4) else assert (data_len > 4);
-        is_inline, data_len
-      );
-    (* The data itself depends on the type field.
-     *
-     * For REG_SZ type, the data always seems to be NUL-terminated, which
-     * means because these strings are often UTF-16LE, that the string will
-     * end with \0\0 bytes.  The termination bytes are included in data_len.
-     *
-     * For REG_MULTI_SZ, see
-     * http://blogs.msdn.com/oldnewthing/archive/2009/10/08/9904646.aspx
-     *)
-    data : 4*8
-      : bitstring, bind (
-        let is_inline, data_len = data_len in
-        if is_inline then
-          Inline (takebits (data_len*8) data)
-        else (
-          let offset =
-            bitmatch data with { offset : 4*8 : littleendian } -> offset in
-          let offset = get_offset offset in
-          Offset offset
-        )
-      );
-    t : 4*8 : littleendian, bind (Int32.to_int t);
-    (* Flags, stored as a little-endian word: *)
-    unknown1 : 7;
-    nameisascii : 1;  (* Clear for default [zero-length] name, always set
-                       * otherwise in registries that we found.  Perhaps this
-                       * is really "nameisdefault" flag?
-                       *)
-    unknown2 : 8;
-    (* Unknown field, usually contains something. *)
-    unknown3 : 2*8 : littleendian;
-    name : name_len * 8 : string }
-
-let fprintf_vk chan vk =
-  let (_, _, bits) = lookup "fprintf_vk" vk in
-  bitmatch bits with
-  | { :vk_fields } ->
-      let real_data =
-        match data with
-        | Inline data -> data
-        | Offset offset ->
-            let (_, _, bits) = lookup "fprintf_vk (data)" offset in
-            bits in
-      let is_inline, data_len = data_len in
-      fprintf chan "VK %s %s %s %d %s%s %s %08x %s %08x %08x\n"
-        (print_offset vk)
-        name (if is_inline then "inline" else "-") data_len
-        (match data with
-         | Inline _ -> ""
-         | Offset offset -> "["^print_offset offset^"]")
-        (print_bitstring real_data)
-        (print_vk_type t)
-        unknown1 (if nameisascii then "A" else "L")
-        unknown2 unknown3
-
-let bitmatch sk_fields =
-  { "sk" : 2*8 : string;
-    unknown1 : 2*8 : littleendian;
-    sk_next : 4*8 : littleendian, bind (get_offset sk_next);
-    sk_prev : 4*8 : littleendian, bind (get_offset sk_prev);
-    refcount : 4*8 : littleendian, bind (Int32.to_int refcount);
-    sec_len : 4*8 : littleendian, bind (Int32.to_int sec_len);
-    sec_desc : sec_len * 8 : bitstring }
-
-let fprintf_sk chan sk =
-  let (_, _, bits) = lookup "fprintf_sk" sk in
-  bitmatch bits with
-  | { :sk_fields } ->
-      fprintf chan "SK %s %04x %s %s %d %d\n"
-        (print_offset sk) unknown1
-        (print_offset sk_next) (print_offset sk_prev)
-        refcount sec_len
-        (* print_bitstring sec_desc -- suppress this *)
-
-(* Store lists of records we encounter (lists of offsets). *)
-let nk_records = ref []
-and vk_records = ref []
-and sk_records = ref []
-
-(* Functions to visit each block, starting at the root.  Each block
- * that we visit is printed.
- *)
-let rec visit_nk ?(nk_is_root = false) nk =
-  let (_, _, bits) = lookup "visit_nk" nk in
-  mark_visited nk;
-  (bitmatch bits with
-   | { :nk_fields } ->
-       fprintf_nk stdout nk;
-
-       nk_records := nk :: !nk_records;
-
-       (* Check the isroot flag is only set on the root node. *)
-       assert (isroot = nk_is_root);
-
-       if unknownflag8000 then
-         eprintf "NK %s unknownflag8000 is set\n" (print_offset nk);
-       if unknownflag4000 then
-         eprintf "NK %s unknownflag4000 is set\n" (print_offset nk);
-       if unknownflag2000 then
-         eprintf "NK %s unknownflag2000 is set\n" (print_offset nk);
-       if unknownflag1000 then
-         eprintf "NK %s unknownflag1000 is set\n" (print_offset nk);
-       if unknownflag0800 then
-         eprintf "NK %s unknownflag0800 is set\n" (print_offset nk);
-       if unknownflag0400 then
-         eprintf "NK %s unknownflag0400 is set\n" (print_offset nk);
-       if unknown1 <> 0_l then
-         eprintf "NK %s unknown1 <> 0 (%08lx)\n" (print_offset nk) unknown1;
-       if unknown2_userflags <> 0 then
-         eprintf "NK %s unknown2_userflags <> 0 (%x)\n"
-           (print_offset nk) unknown2_userflags;
-       if unknown2_virtcontrolflags <> 0 then
-         eprintf "NK %s unknown2_virtcontrolflags <> 0 (%x)\n"
-           (print_offset nk) unknown2_virtcontrolflags;
-       if unknown2_debug <> 0 then
-         eprintf "NK %s unknown2_debug <> 0 (%x)\n"
-           (print_offset nk) unknown2_debug;
-       if unknown3 <> 0_l then
-         eprintf "NK %s unknown3 <> 0 (%08lx)\n" (print_offset nk) unknown3;
-       if unknown6 <> 0_l then
-         eprintf "NK %s unknown6 <> 0 (%08lx)\n" (print_offset nk) unknown6;
-
-       (* -- common, assume it's not an error
-       if classname = -1 then
-         eprintf "NK %s has no classname\n" (print_offset nk);
-       if classname_len = 0 then
-         eprintf "NK %s has zero-length classname\n" (print_offset nk);
-       *)
-       if sk = -1 then
-         eprintf "NK %s has no sk-record\n" (print_offset nk);
-       if name_len = 0 then
-         eprintf "NK %s has zero-length name\n" (print_offset nk);
-
-       (* Visit the values first at this node. *)
-       let max_data_len, max_name_len =
-         if vallist <> -1 then
-           visit_vallist nr_values vallist
-         else
-           0, 0 in
-
-       if max_vk_data_len < max_data_len then
-         eprintf "NK %s nk.max_vk_data_len (%d) < actual max data_len (%d)\n"
-           (print_offset nk) max_vk_data_len max_data_len;
-
-       if max_vk_name_len < max_name_len * 2 then
-         eprintf "NK %s nk.max_vk_name_len (%d) < actual max name_len * 2 (%d)\n"
-           (print_offset nk) max_vk_name_len (max_name_len * 2);
-
-       (* Visit the subkeys of this node. *)
-       if subkeys <> -1 then (
-         let counted, max_name_len, _ = visit_subkeys subkeys in
-
-         if counted <> nr_subkeys then
-           failwithf "%s: incorrect count of subkeys (%d, counted %d) in subkey list at %s\n"
-             basename nr_subkeys counted (print_offset subkeys);
-
-         if max_subkey_name_len < max_name_len * 2 then
-           eprintf "NK %s nk.max_subkey_name_len (%d) < actual max name_len * 2 (%d)\n"
-             (print_offset nk) max_subkey_name_len (max_name_len * 2);
-       );
-
-       (* Visit the sk-record and classname. *)
-       if sk <> -1 then
-         visit_sk sk;
-       if classname <> -1 then
-         visit_classname classname classname_len;
-
-   | {_} ->
-       failwithf "%s: invalid nk block at offset %s\n"
-         basename (print_offset nk)
-  )
-
-and visit_vallist nr_values vallist =
-  let (seg_len, _, bits) = lookup "visit_vallist" vallist in
-  mark_visited vallist;
-  printf "VL %s %d %d\n" (print_offset vallist) nr_values seg_len;
-  visit_values_in_vallist nr_values vallist bits
-
-and visit_values_in_vallist nr_values vallist bits =
-  if nr_values > 0 then (
-    bitmatch bits with
-    | { rest : -1 : bitstring } when bitstring_length rest = 0 ->
-        assert (nr_values = 0);
-        0, 0
-
-    | { value : 4*8 : littleendian, bind (get_offset value);
-        rest : -1 : bitstring } ->
-        let data_len, name_len = visit_vk value in
-        let max_data_len, max_name_len =
-          visit_values_in_vallist (nr_values-1) vallist rest in
-        max max_data_len data_len, max max_name_len name_len
-
-    | {_} ->
-        failwithf "%s: invalid offset in value list at %s\n"
-          basename (print_offset vallist)
-  ) else 0, 0
-
-and visit_vk vk =
-  let (_, _, bits) = lookup "visit_vk" vk in
-  mark_visited vk;
-
-  (bitmatch bits with
-   | { :vk_fields } ->
-       fprintf_vk stdout vk;
-
-       let is_inline, data_len = data_len in
-
-       if unknown1 <> 0 then
-         eprintf "VK %s unknown1 flags set (%02x)\n"
-           (print_offset vk) unknown1;
-       if unknown2 <> 0 then
-         eprintf "VK %s unknown2 flags set (%02x)\n"
-           (print_offset vk) unknown2;
-       if unknown3 <> 0 then
-         eprintf "VK %s unknown3 flags set (%04x)\n"
-           (print_offset vk) unknown3;
-
-       (* Note this is common for default [ie. zero-length] key names. *)
-       if not nameisascii && name_len > 0 then
-         eprintf "VK %s has non-ASCII name flag set (name is %s)\n"
-           (print_offset vk) (print_binary_string name);
-
-       vk_records := vk :: !vk_records;
-       (match data with
-        | Inline data -> ()
-        | Offset offset ->
-            let _ = lookup "visit_vk (data)" offset in
-            mark_visited offset
-       );
-
-       data_len, name_len
-
-   | {_} ->
-       failwithf "%s: invalid vk block at offset %s\n"
-         basename (print_offset vk)
-  )
-
-(* Visits subkeys, recursing through intermediate lf/lh/ri structures,
- * and returns the number of subkeys actually seen.
- *)
-and visit_subkeys subkeys =
-  let (_, _, bits) = lookup "visit_subkeys" subkeys in
-  mark_visited subkeys;
-  (bitmatch bits with
-   | { "lf" : 2*8 : string;
-       len : 2*8 : littleendian; (* number of subkeys of this node *)
-       rest : len*8*8 : bitstring } ->
-       printf "LF %s %d\n" (print_offset subkeys) len;
-       visit_subkeys_in_lf_list false subkeys len rest
-
-   | { "lh" : 2*8 : string;
-       len : 2*8 : littleendian; (* number of subkeys of this node *)
-       rest : len*8*8 : bitstring } ->
-       printf "LF %s %d\n" (print_offset subkeys) len;
-       visit_subkeys_in_lf_list true subkeys len rest
-
-   | { "ri" : 2*8 : string;
-       len : 2*8 : littleendian;
-       rest : len*4*8 : bitstring } ->
-       printf "RI %s %d\n" (print_offset subkeys) len;
-       visit_subkeys_in_ri_list subkeys len rest
-
-   (* In theory you can have an li-record here, but we've never
-    * seen one.
-    *)
-
-   | { "nk" : 2*8 : string } ->
-       visit_nk subkeys;
-       let name, name_len = name_of_nk subkeys in
-       1, name_len, name
-
-   | {_} ->
-       failwithf "%s: invalid subkey node found at %s\n"
-         basename (print_offset subkeys)
-  )
-
-and visit_subkeys_in_lf_list newstyle_hash subkeys_top len bits =
-  if len > 0 then (
-    bitmatch bits with
-    | { rest : -1 : bitstring } when bitstring_length rest = 0 ->
-        assert (len = 0);
-        0, 0, ""
-
-    | { offset : 4*8 : littleendian, bind (get_offset offset);
-        hash : 4*8 : bitstring;
-        rest : -1 : bitstring } ->
-        let c1, name_len1, name = visit_subkeys offset in
-
-        check_hash offset newstyle_hash hash name;
-
-        let c2, name_len2, _ =
-          visit_subkeys_in_lf_list newstyle_hash subkeys_top (len-1) rest in
-        c1 + c2, max name_len1 name_len2, ""
-
-    | {_} ->
-        failwithf "%s: invalid subkey in lf/lh list at %s\n"
-          basename (print_offset subkeys_top)
-  ) else 0, 0, ""
-
-and visit_subkeys_in_ri_list subkeys_top len bits =
-  if len > 0 then (
-    bitmatch bits with
-    | { rest : -1 : bitstring } when bitstring_length rest = 0 ->
-        assert (len = 0);
-        0, 0, ""
-
-    | { offset : 4*8 : littleendian, bind (get_offset offset);
-        rest : -1 : bitstring } ->
-        let c1, name_len1, _ = visit_subkeys offset in
-        let c2, name_len2, _ =
-          visit_subkeys_in_ri_list subkeys_top (len-1) rest in
-        c1 + c2, max name_len1 name_len2, ""
-
-    | {_} ->
-        failwithf "%s: invalid subkey in ri list at %s\n"
-          basename (print_offset subkeys_top)
-  ) else 0, 0, ""
-
-and check_hash offset newstyle_hash hash name =
-  if not newstyle_hash then (
-    (* Old-style lf record hash the first four bytes of the name
-     * as the has.
-     *)
-    let len = String.length name in
-    let name_bits =
-      if len >= 4 then
-        bitstring_of_string (String.sub name 0 4)
-      else (
-        let zeroes = zeroes_bitstring ((4-len)*8) in
-        concat [bitstring_of_string name; zeroes]
-      ) in
-    if not (equals hash name_bits) then
-      eprintf "LF incorrect hash for name %s, expected %s, actual %s\n"
-        name (print_bitstring name_bits) (print_bitstring hash)
-  ) else (
-    (* New-style lh record has a proper hash. *)
-    let actual = bitmatch hash with { hash : 4*8 : littleendian } -> hash in
-    let h = ref 0_l in
-    String.iter (
-      fun c ->
-        h := Int32.mul !h 37_l;
-        h := Int32.add !h (Int32.of_int (Char.code (Char.uppercase c)))
-    ) name;
-    if actual <> !h then
-      eprintf "LH incorrect hash for name %s, expected 0x%08lx, actual 0x%08lx\n"
-        name !h actual
-  )
-
-and name_of_nk nk =
-  let (_, _, bits) = lookup "name_of_nk" nk in
-  bitmatch bits with
-  | { :nk_fields } -> name, name_len
-
-and visit_sk sk =
-  let (_, _, bits) = lookup "visit_sk" sk in
-  if is_not_visited sk then (
-    mark_visited sk;
-    (bitmatch bits with
-     | { :sk_fields } ->
-         fprintf_sk stdout sk;
-
-         if unknown1 <> 0 then
-           eprintf "SK %s unknown1 <> 0 (%04x)\n" (print_offset sk) unknown1;
-
-         sk_records := sk :: !sk_records
-
-     | {_} ->
-         failwithf "%s: invalid sk-record at %s\n"
-           basename (print_offset sk)
-    )
-  )
-
-and visit_classname classname classname_len =
-  let (seg_len, _, bits) = lookup "visit_classname" classname in
-  mark_visited classname;
-  assert (seg_len >= classname_len);
-  printf "CL %s %s\n" (print_offset classname) (print_bitstring bits)
-
-let () =
-  visit_nk ~nk_is_root:true root_key
-
-(* These are immutable now. *)
-let nk_records = !nk_records
-let vk_records = !vk_records
-let sk_records = !sk_records
-
-(* So we can rapidly tell what is an nk/vk/sk offset. *)
-let nk_set =
-  List.fold_left (fun set offs -> IntSet.add offs set) IntSet.empty nk_records
-let vk_set =
-  List.fold_left (fun set offs -> IntSet.add offs set) IntSet.empty vk_records
-let sk_set =
-  List.fold_left (fun set offs -> IntSet.add offs set) IntSet.empty sk_records
-
-(* Now after visiting all the blocks, are there any used blocks which
- * are unvisited?  If there are any then that would indicate either (a)
- * that the hive contains unreferenced blocks, or (b) that there are
- * referenced blocks that we did not visit because we don't have a full
- * understanding of the hive format.
- *
- * Windows 7 registries often contain a few of these -- not clear
- * how serious they are, but don't fail here.
- *)
-let () =
-  let unvisited = unvisited_blocks () in
-  IntMap.iter (
-    fun offset block ->
-      match block with
-      | (_, false, _) -> () (* ignore unused blocks *)
-      | (seg_len, true, _) ->
-          eprintf "used block %s (length %d) is not referenced\n"
-            (print_offset offset) seg_len
-  ) unvisited
-
-(* Check the SKs are:
- * (a) linked into a single circular list through the sk_prev/sk_next
- * pointers
- * (b) refcounts are correct
- *)
-let () =
-  if List.length sk_records > 0 then (
-    let sk0 = List.hd sk_records in (* start at any arbitrary sk *)
-    (* This loop follows the chain of sk pointers until we arrive
-     * back at the original, checking prev/next are consistent.
-     *)
-    let rec loop visited prevsk sk =
-      if sk <> sk0 then (
-        if not (IntSet.mem sk sk_set) then
-          eprintf "SK %s not an sk-record (faulty sk_next somewhere)\n"
-            (print_offset sk)
-        else (
-          let _, _, bits = lookup "loop sk circular list" sk in
-          bitmatch bits with
-          | { :sk_fields } ->
-              if sk_prev <> prevsk then
-                eprintf "SK %s sk_prev != previous sk (%s, %s)\n"
-                  (print_offset sk)
-                  (print_offset sk_prev) (print_offset prevsk);
-              if IntSet.mem sk visited then
-                eprintf "SK %s already visited (bad circular list)\n"
-                  (print_offset sk);
-              let visited = IntSet.add sk visited in
-              loop visited sk sk_next
-        )
-      )
-    in
-    let _, _, bits = lookup "start sk circular list" sk0 in
-    (bitmatch bits with
-     | { :sk_fields } ->
-         loop IntSet.empty sk_prev sk0
-    );
-
-    (* For every nk-record, if it references an sk-record count that,
-     * then check this matches the refcounts in the sk-records
-     * themselves.
-     *)
-    let refcounts = Counter.create () in
-    List.iter (
-      fun nk ->
-        let _, _, bits = lookup "sk refcounter (nk)" nk in
-        (bitmatch bits with
-         | { :nk_fields } ->
-             Counter.incr refcounts sk
-        )
-    ) nk_records;
-
-    List.iter (
-      fun sk ->
-        let _, _, bits = lookup "sk refcounter (sk)" sk in
-        (bitmatch bits with
-         | { :sk_fields } ->
-             let actual = Counter.get refcounts sk in
-             if actual <> refcount then
-               eprintf "SK %s incorrect refcount (actual %d, in file %d)\n"
-                 (print_offset sk) actual refcount
-        )
-    ) sk_records
-  )