This is an automated email from the git hooks/post-receive script. bengen pushed a commit to annotated tag debian/1.0-1 in repository libsendmail-milter-perl.
commit c46a09d28f38ccef8d1a97f6811218bbc15bb5ae Author: Hilko Bengen <ben...@debian.org> Date: Thu Dec 3 10:19:24 2009 +0100 Imported Upstream version 0.51 --- Changes | 27 + MANIFEST | 4 + META.yml | 2 +- Makefile.PL | 5 + bin/regcompare.pl | 448 ++++++++++-- bin/regmultidiff.pl | 10 +- bin/regscope.pl | 1200 ++++++++++++++++++++++++++++++++ bin/regview.pl | 331 ++++++--- lib/Parse/Win32Registry.pm | 64 +- lib/Parse/Win32Registry/Base.pm | 21 +- lib/Parse/Win32Registry/Key.pm | 5 +- lib/Parse/Win32Registry/Win95/Key.pm | 14 + lib/Parse/Win32Registry/Win95/Value.pm | 10 + lib/Parse/Win32Registry/WinNT/Key.pm | 32 + lib/Parse/Win32Registry/WinNT/Value.pm | 14 + t/iterator.t | 253 +++++++ t/use.t | 2 +- t/win95_iter_tests.rf | Bin 0 -> 655 bytes t/winnt_iter_tests.rf | Bin 0 -> 5376 bytes 19 files changed, 2280 insertions(+), 162 deletions(-) diff --git a/Changes b/Changes index 78c31a7..ab802cc 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,32 @@ Revision history for Perl extension Parse::Win32Registry. +** 0.51 2009-10-04 + +Added new regscope.pl script, a GTK+ registry entry viewer that uses color +to highlight different types of registry entries. + +Documented the get_name method of SID objects and the get_value_data +method of Key objects. The as_string method of the ACE object and the +as_stanza method of the SecurityDescriptor object now include the well +known SID names (as returned each SID object's get_name method) for +each SID object. + +Updated the regview.pl and regcompare.pl scripts: regview.pl and +regcompare.pl can now select keys and/or values when searching, +regview.pl can now sort columns (e.g. keys can be sorted by timestamp, +values by type, etc), regcompare.pl can now bookmark keys or values, +and regview.pl now has a basic report view. + +Fixed the get_subtree_iter and make_multiple_subkey_iterator methods +to return the root key(s) of the subtree(s) as the documentation +indicates. regview.pl, regmultidiff.pl, and regcompare.pl amended to +accommodate these changes. + +Fixed redisplay problem closing dialogs using Escape in regview.pl and +regcompare.pl. + +Makefile.pl now includes all scripts as exe_files. + ** 0.50 2009-07-19 Security information is now extracted from Windows NT registry files. diff --git a/MANIFEST b/MANIFEST index 6878b71..3da569f 100644 --- a/MANIFEST +++ b/MANIFEST @@ -10,6 +10,7 @@ bin/regexport.pl bin/regfind.pl bin/regmultidiff.pl bin/regscan.pl +bin/regscope.pl bin/regsecurity.pl bin/regshell.pl bin/regstats.pl @@ -36,6 +37,7 @@ t/constants.t t/entry.t t/errors.t t/file.t +t/iterator.t t/key.t t/misc.t t/security.t @@ -57,9 +59,11 @@ t/invalid_regf_header.rf t/invalid_rgkn_header.rf t/missing_rgkn_header.rf t/win95_error_tests.rf +t/win95_iter_tests.rf t/win95_key_tests.rf t/win95_value_tests.rf t/winnt_error_tests.rf +t/winnt_iter_tests.rf t/winnt_key_tests.rf t/winnt_security_tests.rf t/winnt_value_tests.rf diff --git a/META.yml b/META.yml index 871403f..6d9c8e1 100644 --- a/META.yml +++ b/META.yml @@ -1,6 +1,6 @@ --- #YAML:1.0 name: Parse-Win32Registry -version: 0.50 +version: 0.51 abstract: Parse Windows Registry Files license: ~ author: diff --git a/Makefile.PL b/Makefile.PL index 747baac..3ed244a 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -16,11 +16,16 @@ WriteMakefile( AUTHOR => 'James Macfarlane', EXE_FILES => [ 'bin/regclassnames.pl', + 'bin/regcompare.pl', 'bin/regdiff.pl', 'bin/regdump.pl', 'bin/regexport.pl', 'bin/regfind.pl', + 'bin/regmultidiff.pl', 'bin/regscan.pl', + 'bin/regscope.pl', + 'bin/regsecurity.pl', + 'bin/regshell.pl', 'bin/regstats.pl', 'bin/regtimeline.pl', 'bin/regtree.pl', diff --git a/bin/regcompare.pl b/bin/regcompare.pl index b6e5ee6..05452b5 100755 --- a/bin/regcompare.pl +++ b/bin/regcompare.pl @@ -5,9 +5,15 @@ use warnings; use Glib ':constants'; use Gtk2 -init; +my $screen = Gtk2::Gdk::Screen->get_default; +my $window_width = $screen->get_width * 0.9; +my $window_height = $screen->get_height * 0.8; +$window_width = 1100 if $window_width > 1100; +$window_height = 900 if $window_height > 900; + use File::Basename; use File::Spec; -use Parse::Win32Registry 0.50 qw( make_multiple_subtree_iterator +use Parse::Win32Registry 0.51 qw( make_multiple_subtree_iterator make_multiple_subkey_iterator make_multiple_value_iterator compare_multiple_keys @@ -176,6 +182,7 @@ my @actions = ( # name, stock id, label ['FileMenu', undef, '_File'], ['SearchMenu', undef, '_Search'], + ['BookmarksMenu', undef, '_Bookmarks'], ['ViewMenu', undef, '_View'], ['HelpMenu', undef, '_Help'], # name, stock-id, label, accelerator, tooltip, callback @@ -184,8 +191,11 @@ my @actions = ( ['Quit', 'gtk-quit', '_Quit', '<control>Q', undef, \&quit], ['Find', 'gtk-find', '_Find', '<control>F', undef, \&find], ['FindNext', undef, 'Find Next', '<control>G', undef, \&find_next], + ['FindNext2', undef, 'Find Next', 'F3', undef, \&find_next], ['FindChange', 'gtk-find', 'Find _Change', '<control>N', undef, \&find_change], ['FindNextChange', undef, 'Find Next Change', '<control>M', undef, \&find_next_change], + ['AddBookmark', 'gtk-add', '_Add Bookmark', '<control>D', undef, \&add_bookmark], + ['EditBookmarks', undef, '_Edit Bookmarks', '<control>B', undef, \&edit_bookmarks], ['About', 'gtk-about', '_About', undef, undef, \&about], ); @@ -194,11 +204,17 @@ $action_group->add_actions(\@actions, undef); my @toggle_actions = ( # name, stock id, label, accelerator, tooltip, callback, active - ['ShowDetail', undef, 'Show _Detail', '<control>X', undef, \&toggle_item_detail, TRUE], + ['ShowToolbar', undef, 'Show _Toolbar', '<control>T', undef, \&toggle_toolbar, TRUE], + ['ShowDetail', 'gtk-edit', 'Show _Detail', '<control>X', undef, \&toggle_item_detail, TRUE], ); $action_group->add_toggle_actions(\@toggle_actions, undef); +my $action_group2 = Gtk2::ActionGroup->new('actions2'); # bookmarks +my $bookmarks_merge_id = $uimanager->new_merge_id; +my $action_name = 1; # unique action name + $uimanager->insert_action_group($action_group, 0); +$uimanager->insert_action_group($action_group2, 1); my $ui_info = <<END_OF_UI; <ui> @@ -216,8 +232,15 @@ my $ui_info = <<END_OF_UI; <menuitem action='FindChange'/> <menuitem action='FindNextChange'/> </menu> + <menu action='BookmarksMenu'> + <menuitem action='AddBookmark'/> + <menuitem action='EditBookmarks'/> + <separator/> + </menu> <menu action='ViewMenu'> + <menuitem action='ShowToolbar'/> <menuitem action='ShowDetail'/> + <separator/> </menu> <menu action='HelpMenu'> <menuitem action='About'/> @@ -232,6 +255,7 @@ my $ui_info = <<END_OF_UI; <separator/> <toolitem action='Quit'/> </toolbar> + <accelerator action='FindNext2'/> </ui> END_OF_UI @@ -239,6 +263,7 @@ $uimanager->add_ui_from_string($ui_info); my $menubar = $uimanager->get_widget('/MenuBar'); my $toolbar = $uimanager->get_widget('/ToolBar'); +my $bookmarks_menu = $uimanager->get_widget('/MenuBar/BookmarksMenu')->get_submenu; ### STATUSBAR @@ -246,7 +271,7 @@ my $statusbar = Gtk2::Statusbar->new; ### VBOX -my $main_vbox = Gtk2::VBox->new; +my $main_vbox = Gtk2::VBox->new(FALSE, 0); $main_vbox->pack_start($menubar, FALSE, FALSE, 0); $main_vbox->pack_start($toolbar, FALSE, FALSE, 0); $main_vbox->pack_start($vpaned1, TRUE, TRUE, 0); @@ -255,7 +280,7 @@ $main_vbox->pack_start($statusbar, FALSE, FALSE, 0); ### WINDOW my $window = Gtk2::Window->new; -$window->set_default_size(600, 400); +$window->set_default_size($window_width, $window_height); $window->set_position('center'); $window->signal_connect(destroy => sub { Gtk2->main_quit }); $window->add($main_vbox); @@ -301,10 +326,14 @@ sub build_open_files_dialog { 'gtk-remove' => 50, 'gtk-ok' => 'ok', ); - $dialog->set_size_request(-1, 400); + $dialog->set_size_request($window_width * 0.8, $window_height * 0.8); $dialog->vbox->add($scrolled_file_view); $dialog->set_default_response('ok'); + $dialog->signal_connect(delete_event => sub { + $dialog->hide; + return TRUE; + }); $dialog->signal_connect(response => sub { my ($dialog, $response) = @_; if ($response eq '70') { @@ -347,6 +376,119 @@ sub build_open_files_dialog { my $open_files_dialog = build_open_files_dialog; +### BOOKMARKS STORE + +use constant { + BMCOL_NAME => 0, + BMCOL_LOCATION => 1, + BMCOL_ICON => 2, +}; + +my $bookmark_store = Gtk2::ListStore->new( + 'Glib::String', 'Glib::Scalar', 'Glib::String', +); + +sub build_bookmarks_dialog { + my $bookmark_view = Gtk2::TreeView->new($bookmark_store); + $bookmark_view->set_reorderable(TRUE); + + my $bookmark_icon_cell = Gtk2::CellRendererPixbuf->new; + my $bookmark_name_cell = Gtk2::CellRendererText->new; + my $bookmark_column0 = Gtk2::TreeViewColumn->new; + $bookmark_column0->set_title('Bookmark'); + $bookmark_column0->pack_start($bookmark_icon_cell, FALSE); + $bookmark_column0->pack_start($bookmark_name_cell, TRUE); + $bookmark_column0->set_attributes($bookmark_icon_cell, + 'stock-id', BMCOL_ICON); + $bookmark_column0->set_attributes($bookmark_name_cell, + 'text', BMCOL_NAME); + $bookmark_column0->set_resizable(TRUE); + $bookmark_view->append_column($bookmark_column0); + + my $bookmark_location_cell = Gtk2::CellRendererText->new; + my $bookmark_column1 = $bookmark_view->insert_column_with_data_func( + 1, 'Location', $bookmark_location_cell, + sub { + my ($column, $cell, $model, $iter, $num) = @_; + my $location = $model->get($iter, BMCOL_LOCATION); + if (defined $location) { + my ($subkey_path, $value_name) = @$location; + my $string = $subkey_path; + if (defined $value_name) { + $value_name = '(Default)' if $value_name eq ''; + $string .= ", $value_name"; + } + $cell->set('text', $string); + } + else { + $cell->set('text', '?'); + } + }, + ); + $bookmark_location_cell->set('ellipsize', 'end'); + + my $scrolled_bookmark_view = Gtk2::ScrolledWindow->new; + $scrolled_bookmark_view->set_policy('automatic', 'automatic'); + $scrolled_bookmark_view->set_shadow_type('in'); + $scrolled_bookmark_view->add($bookmark_view); + + my $label = Gtk2::Label->new; + $label->set_markup('<i>Drag bookmarks to reorder them</i>'); + + my $dialog = Gtk2::Dialog->new('Edit Bookmarks', $window, 'modal', + 'gtk-remove' => 50, + 'gtk-ok' => 'ok', + ); + $dialog->resize($window_width * 0.8, $window_height * 0.8); + $dialog->vbox->pack_start($scrolled_bookmark_view, TRUE, TRUE, 0); + $dialog->vbox->pack_start($label, FALSE, FALSE, 5); + $dialog->set_default_response('ok'); + + $dialog->signal_connect(delete_event => sub { + $dialog->hide; + return TRUE; + }); + $dialog->signal_connect(response => sub { + my ($dialog, $response) = @_; + if ($response eq '50') { + # Remove selected bookmark + my $selection = $bookmark_view->get_selection; + my $iter = $selection->get_selected; + if (defined $iter) { + $bookmark_store->remove($iter); + } + } + else { + # Before exiting, move menuitems into current bookmark order + $uimanager->remove_ui($bookmarks_merge_id); + $uimanager->ensure_update; + foreach my $action ($action_group2->list_actions) { + $action_group2->remove_action($action); + } + $action_name = 1; + my $iter = $bookmark_store->get_iter_first; + while (defined $iter) { + my $bookmark_name = $bookmark_store->get($iter, BMCOL_NAME); + my $location = $bookmark_store->get($iter, BMCOL_LOCATION); + my $icon = $bookmark_store->get($iter, BMCOL_ICON); + my $display_name = $bookmark_name; + $display_name =~ s/_/__/g; + $action_group2->add_actions([ + [$action_name, $icon, $display_name, undef, undef, \&go_to_bookmark], + ], $location); + $uimanager->add_ui($bookmarks_merge_id, '/MenuBar/BookmarksMenu', $action_name, $action_name, 'menuitem', FALSE); + $action_name++; + $iter = $bookmark_store->iter_next($iter); + } + $dialog->hide; + } + }); + + return $dialog; +} + +my $bookmarks_dialog = build_bookmarks_dialog; + ######################## GLOBAL SETUP my @filenames = (); @@ -354,7 +496,9 @@ my @root_keys = (); my $last_dir; -my $find_param; +my $search_keys = TRUE; +my $search_values = TRUE; +my $find_param = ''; my $find_iter; my $change_iter; @@ -423,6 +567,16 @@ sub toggle_item_detail { } } +sub toggle_toolbar { + my ($toggle_action) = @_; + if ($toggle_action->get_active) { + $toolbar->show; + } + else { + $toolbar->hide; + } +} + sub tree_item_selected { my ($tree_selection) = @_; @@ -552,11 +706,12 @@ sub compare_files { my $color = $model->get($iter, TREECOL_COLOR); if (defined $changes) { my $diff = substr($changes->[$num], 0, 1); - $cell->set('text', $diff || '.'); + $cell->set('text', $diff || "\x{00bb}"); $cell->set('foreground', $color); } else { - $cell->set('text', '.'); + $cell->set('text', "\x{00b7}"); + $cell->set('foreground', $color); } }, $num, # additional data is passed to callback @@ -607,11 +762,18 @@ sub add_children { while (defined(my $subkeys = $subkeys_iter->get_next)) { my @changes = compare_multiple_keys(@$subkeys); + my $num_changes = grep { $_ } @changes; + # insert a 'blank' change for missing subkeys + for (my $i = 0; $i < @changes; $i++) { + if ($changes[$i] eq '' && !defined $subkeys->[$i]) { + $changes[$i] = ' '; + } + } my $any_subkey = (grep { defined } @$subkeys)[0]; my $iter = $model->append($parent_iter); - my $num_changes = grep { $_ } @changes; + if ($num_changes > 0) { $model->set($iter, TREECOL_NAME, $any_subkey->get_name, @@ -635,13 +797,20 @@ sub add_children { while (defined(my $values = $values_iter->get_next)) { my @changes = compare_multiple_values(@$values); + my $num_changes = grep { $_ } @changes; + # insert a 'blank' change for missing values + for (my $i = 0; $i < @changes; $i++) { + if ($changes[$i] eq '' && !defined $values->[$i]) { + $changes[$i] = " "; + } + } my $any_value = (grep { defined } @$values)[0]; - my $name = $any_value->get_name; $name = "(Default)" if $name eq ''; + my $iter = $model->append($parent_iter); - my $num_changes = grep { $_ } @changes; + if ($num_changes > 0) { $model->set($iter, TREECOL_NAME, $name, @@ -751,6 +920,7 @@ sub show_message { 'ok', $message, ); + $dialog->set_title(ucfirst $type); $dialog->run; $dialog->destroy; } @@ -792,8 +962,8 @@ sub go_to_subkey_and_value { ? ($subkey_path) : split(/\\/, $subkey_path, -1); - my $root_iter = $tree_store->get_iter_first; - my $iter = $root_iter; + my $iter = $tree_store->get_iter_first; + return if !defined $iter; # no registry loaded while (defined(my $subkey_name = shift @path_components)) { my $items = $tree_store->get($iter, TREECOL_ITEMS); @@ -813,10 +983,12 @@ sub go_to_subkey_and_value { if (!defined $iter) { return; # no matching child iter } - } + my $parent_iter = $tree_store->iter_parent($iter); + my $parent_path = $tree_store->get_path($parent_iter); + $tree_view->expand_to_path($parent_path); my $tree_path = $tree_store->get_path($iter); - $tree_view->expand_to_path($tree_path); +# $tree_view->expand_to_path($tree_path); $tree_view->scroll_to_cell($tree_path); $tree_view->set_cursor($tree_path); $window->set_focus($tree_view); @@ -825,19 +997,33 @@ sub go_to_subkey_and_value { } } +sub get_search_message { + my $message; + if ($search_keys && $search_values) { + $message = "Searching registry keys and values..."; + } + elsif ($search_keys) { + $message = "Searching registry keys..."; + } + elsif ($search_values) { + $message = "Searching registry values..."; + } + return $message; +} + sub find_next { if (!defined $find_param || !defined $find_iter) { return; } my $label = Gtk2::Label->new; - $label->set_text("Searching registry..."); + $label->set_text(get_search_message); my $dialog = Gtk2::Dialog->new('Find', $window, 'modal', 'gtk-cancel' => 'cancel', ); - $dialog->vbox->pack_start($label, TRUE, TRUE, 10); + $dialog->vbox->pack_start($label, TRUE, TRUE, 5); $dialog->set_default_response('cancel'); $dialog->show_all; @@ -846,57 +1032,82 @@ sub find_next { if (!defined $keys_ref) { $dialog->response('ok'); - show_message('info', 'Finished searching.'); return FALSE; # stop searching } # Obtain the name and path from the first defined key my $any_key = (grep { defined } @$keys_ref)[0]; my $subkey_path = (split(/\\/, $any_key->get_path, 2))[1]; + if (!defined $subkey_path) { + return TRUE; + } + # Check values (if defined) for a match if (defined $values_ref) { - my $any_value = (grep { defined } @$values_ref)[0]; - my $value_name = $any_value->get_name; - if (index(lc $value_name, lc $find_param) >= 0) { - go_to_subkey_and_value($subkey_path, $value_name); - $dialog->response('ok'); - return FALSE; # stop searching - } - else { - return TRUE; # continue searching + if ($search_values) { + my $any_value = (grep { defined } @$values_ref)[0]; + my $value_name = $any_value->get_name; + if (index(lc $value_name, lc $find_param) >= 0) { + go_to_subkey_and_value($subkey_path, $value_name); + $dialog->response(50); + return FALSE; # stop searching + } } + return TRUE; # continue searching } - my $key_name = $any_key->get_name; - if (index(lc $key_name, lc $find_param) >= 0) { - go_to_subkey_and_value($subkey_path); - $dialog->response('ok'); - return FALSE; # stop searching - } - else { - return TRUE; # continue searching + # Check keys for a match + if ($search_keys) { + my $key_name = $any_key->get_name; + if (index(lc $key_name, lc $find_param) >= 0) { + go_to_subkey_and_value($subkey_path); + $dialog->response(50); + return FALSE; # stop searching + } } + return TRUE; # continue searching }); my $response = $dialog->run; + $dialog->destroy; + if ($response eq 'cancel' || $response eq 'delete-event') { Glib::Source->remove($id); } - $dialog->destroy; + elsif ($response eq 'ok') { + show_message('info', 'Finished searching.'); + } } sub find { return if @root_keys == 0; my $entry = Gtk2::Entry->new; + $entry->set_text($find_param); $entry->set_activates_default(TRUE); + my $check1 = Gtk2::CheckButton->new('Search _Keys'); + $check1->set_active($search_keys); + my $check2 = Gtk2::CheckButton->new('Search _Values'); + $check2->set_active($search_values); + $check1->signal_connect(toggled => sub { + if (!$check1->get_active && !$check2->get_active) { + $check2->set_active(TRUE); + } + }); + $check2->signal_connect(toggled => sub { + if (!$check1->get_active && !$check2->get_active) { + $check1->set_active(TRUE); + } + }); my $dialog = Gtk2::Dialog->new('Find', $window, 'modal', 'gtk-cancel' => 'cancel', 'gtk-ok' => 'ok', ); - $dialog->vbox->pack_start($entry, TRUE, TRUE, 10); + $dialog->vbox->pack_start($entry, TRUE, TRUE, 0); + $dialog->vbox->pack_start($check1, TRUE, TRUE, 0); + $dialog->vbox->pack_start($check2, TRUE, TRUE, 0); $dialog->set_default_response('ok'); $dialog->show_all; @@ -904,6 +1115,8 @@ sub find { $dialog->destroy; if ($response eq 'ok' && @root_keys > 0) { + $search_keys = $check1->get_active; + $search_values = $check2->get_active; $find_param = $entry->get_text; if ($find_param ne '') { $find_iter = make_multiple_subtree_iterator(@root_keys); @@ -918,13 +1131,13 @@ sub find_next_change { } my $label = Gtk2::Label->new; - $label->set_text("Searching registry..."); + $label->set_text(get_search_message); my $dialog = Gtk2::Dialog->new('Find Change', $window, 'modal', 'gtk-cancel' => 'cancel', ); - $dialog->vbox->pack_start($label, TRUE, TRUE, 10); + $dialog->vbox->pack_start($label, TRUE, TRUE, 5); $dialog->set_default_response('cancel'); $dialog->show_all; @@ -933,48 +1146,54 @@ sub find_next_change { if (!defined $keys_ref) { $dialog->response('ok'); - show_message('info', 'Finished searching.'); return FALSE; # stop searching } # Obtain the name and path from the first defined key my $any_key = (grep { defined } @$keys_ref)[0]; my $subkey_path = (split(/\\/, $any_key->get_path, 2))[1]; + if (!defined $subkey_path) { + return TRUE; + } + # Check values (if defined) for changes if (defined $values_ref) { - my $any_value = (grep { defined } @$values_ref)[0]; - my $value_name = $any_value->get_name; - my @changes = compare_multiple_values(@$values_ref); + if ($search_values) { + my $any_value = (grep { defined } @$values_ref)[0]; + my $value_name = $any_value->get_name; + my @changes = compare_multiple_values(@$values_ref); + my $num_changes = grep { $_ } @changes; + if ($num_changes > 0) { + go_to_subkey_and_value($subkey_path, $value_name); + $dialog->response(50); + return FALSE; # stop searching + } + } + return TRUE; # continue searching + } + + if ($search_keys) { + my $key_name = $any_key->get_name; + my @changes = compare_multiple_keys(@$keys_ref); my $num_changes = grep { $_ } @changes; if ($num_changes > 0) { - go_to_subkey_and_value($subkey_path, $value_name); - $dialog->response('ok'); + go_to_subkey_and_value($subkey_path); + $dialog->response(50); return FALSE; # stop searching } - else { - return TRUE; # continue searching - } - } - - my $key_name = $any_key->get_name; - - my @changes = compare_multiple_keys(@$keys_ref); - my $num_changes = grep { $_ } @changes; - if ($num_changes > 0) { - go_to_subkey_and_value($subkey_path); - $dialog->response('ok'); - return FALSE; # stop searching - } - else { - return TRUE; # continue searching } + return TRUE; # continue searching }); my $response = $dialog->run; + $dialog->destroy; + if ($response eq 'cancel' || $response eq 'delete-event') { Glib::Source->remove($id); } - $dialog->destroy; + elsif ($response eq 'ok') { + show_message('info', 'Finished searching.'); + } } sub find_change { @@ -1006,14 +1225,30 @@ sub find_change { my $key_path = $start_key->get_path; my $label = Gtk2::Label->new; - $label->set_text("Find changes starting from\n'$key_path'?"); + $label->set_markup("Find changes starting from\n<b>$key_path</b>?"); + my $check1 = Gtk2::CheckButton->new('Search _Keys'); + $check1->set_active($search_keys); + my $check2 = Gtk2::CheckButton->new('Search _Values'); + $check2->set_active($search_values); + $check1->signal_connect(toggled => sub { + if (!$check1->get_active && !$check2->get_active) { + $check2->set_active(TRUE); + } + }); + $check2->signal_connect(toggled => sub { + if (!$check1->get_active && !$check2->get_active) { + $check1->set_active(TRUE); + } + }); my $dialog = Gtk2::Dialog->new('Find Change', $window, 'modal', 'gtk-cancel' => 'cancel', 'gtk-ok' => 'ok', ); - $dialog->vbox->pack_start($label, TRUE, TRUE, 10); + $dialog->vbox->pack_start($label, TRUE, TRUE, 5); + $dialog->vbox->pack_start($check1, TRUE, TRUE, 0); + $dialog->vbox->pack_start($check2, TRUE, TRUE, 0); $dialog->set_default_response('ok'); $dialog->show_all; @@ -1021,7 +1256,92 @@ sub find_change { $dialog->destroy; if ($response eq 'ok') { + $search_keys = $check1->get_active; + $search_values = $check2->get_active; $change_iter = make_multiple_subtree_iterator(@start_keys); + $change_iter->get_next; find_next_change; } } + +sub get_location { + my @start_keys; + my @start_values; + my ($keys_ref, $values_ref); + my ($model, $iter) = $tree_selection->get_selected; + if (defined $model && defined $iter) { + my $icon = $model->get($iter, TREECOL_ICON); + if ($icon eq 'gtk-directory') { + # Item is a key + $keys_ref = $model->get($iter, TREECOL_ITEMS); + } + else { + # Item is a value + $values_ref = $model->get($iter, TREECOL_ITEMS); + + # Find parent key + $iter = $model->iter_parent($iter); + return if !defined $iter; + + $keys_ref = $model->get($iter, TREECOL_ITEMS); + } + return ($keys_ref, $values_ref); + } + else { + return ($keys_ref, $values_ref); + } +} + +sub add_bookmark { + my ($keys_ref, $values_ref) = get_location; + if (defined $keys_ref) { + my $any_key = (grep { defined } @$keys_ref)[0]; + my $key_path = $any_key->get_path; + my $key_name = $any_key->get_name; + + # Remove root key name to get subkey path + my $subkey_path = (split(/\\/, $key_path, 2))[1]; + return if !defined $subkey_path; + + my $bookmark_name; + my $location; + my $icon; + if (defined $values_ref) { + my $any_value = (grep { defined } @$values_ref)[0]; + my $value_name = $any_value->get_name; + $location = [$subkey_path, $value_name]; + $value_name = '(Default)' if $value_name eq ''; + $bookmark_name = "$value_name [$key_name]"; + $icon = 'gtk-file'; + } + else { + $bookmark_name = $key_name; + $location = [$subkey_path]; + $icon = 'gtk-directory'; + } + my $display_name = $bookmark_name; + $display_name =~ s/_/__/g; + $action_group2->add_actions([ + [$action_name, $icon, $display_name, undef, undef, \&go_to_bookmark], + ], $location); + $uimanager->add_ui($bookmarks_merge_id, '/MenuBar/BookmarksMenu', $action_name, $action_name, 'menuitem', FALSE); + $action_name++; + if (my $iter = $bookmark_store->append) { + $bookmark_store->set($iter, + BMCOL_NAME, $bookmark_name, + BMCOL_LOCATION, $location, + BMCOL_ICON, $icon, + ); + } + } +} + +sub edit_bookmarks { + $bookmarks_dialog->show_all; +} + +sub go_to_bookmark { + my ($menuitem, $location) = @_; + my ($subkey_path, $value_name) = @$location; + go_to_subkey_and_value($subkey_path, $value_name); +} diff --git a/bin/regmultidiff.pl b/bin/regmultidiff.pl index b51da65..66d39a3 100755 --- a/bin/regmultidiff.pl +++ b/bin/regmultidiff.pl @@ -4,7 +4,7 @@ use warnings; use File::Basename; use Getopt::Long; -use Parse::Win32Registry 0.50 qw( make_multiple_subtree_iterator +use Parse::Win32Registry 0.51 qw( make_multiple_subtree_iterator compare_multiple_keys compare_multiple_values hexdump ); @@ -68,9 +68,10 @@ for (my $num = 0; $num < $batch_size; $num++) { } my $key_shown; -my $keys_ref = \@start_keys; -my $values_ref; -do { +#my $keys_ref = \@start_keys; +#my $values_ref; + +while (my ($keys_ref, $values_ref) = $subtree_iter->get_next) { my @keys = @$keys_ref; my $any_key = (grep { defined } @keys)[0]; die "Unexpected error: no keys!" if !defined $any_key; @@ -136,7 +137,6 @@ do { } } } -while (($keys_ref, $values_ref) = $subtree_iter->get_next); sub usage { my $script_name = basename $0; diff --git a/bin/regscope.pl b/bin/regscope.pl new file mode 100755 index 0000000..29d3a87 --- /dev/null +++ b/bin/regscope.pl @@ -0,0 +1,1200 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use constant MAX_SCALE => 10; # maximum scale for hbin maps + +use Glib ':constants'; +use Gtk2 -init; + +my $screen = Gtk2::Gdk::Screen->get_default; +my $window_width = $screen->get_width * 0.9; +my $window_height = $screen->get_height * 0.8; +$window_width = 1100 if $window_width > 1100; +$window_height = 900 if $window_height > 900; + +use Encode; +use File::Basename; +use Parse::Win32Registry 0.51 qw(hexdump qquote :REG_); + +binmode(STDOUT, ':utf8'); + +my $script_name = basename $0; + +### LIST VIEW + +use constant { + COLUMN_HBIN_OFFSET => 0, + COLUMN_HBIN_OBJECT => 1, +}; + +my $hbin_store = Gtk2::ListStore->new( + 'Glib::String', 'Glib::Scalar', +); + +my $hbin_view = Gtk2::TreeView->new($hbin_store); +$hbin_view->set_size_request(120, -1); + +my $hbin_column1 = Gtk2::TreeViewColumn->new_with_attributes( + 'Hbin', Gtk2::CellRendererText->new, + 'text', COLUMN_HBIN_OFFSET, +); +$hbin_view->append_column($hbin_column1); +$hbin_column1->set_resizable(TRUE); + +my $hbin_selection = $hbin_view->get_selection; +$hbin_selection->set_mode('browse'); +$hbin_selection->signal_connect('changed' => \&hbin_selection_changed); + +my $scrolled_hbin_view = Gtk2::ScrolledWindow->new; +$scrolled_hbin_view->set_policy('automatic', 'automatic'); +$scrolled_hbin_view->set_shadow_type('in'); +$scrolled_hbin_view->add($hbin_view); + +### LIST VIEW FOR ENTRY + +use constant { + COLUMN_ENTRY_OFFSET => 0, + COLUMN_ENTRY_LENGTH => 1, + COLUMN_ENTRY_TAG => 2, + COLUMN_ENTRY_IN_USE => 3, + COLUMN_ENTRY_COLOR => 4, + COLUMN_ENTRY_OBJECT => 5, + COLUMN_ENTRY_USED_BY => 6, +}; + +my $entry_store = Gtk2::ListStore->new( + 'Glib::String', 'Glib::String', 'Glib::String', + 'Glib::String', 'Glib::String', 'Glib::Scalar', + 'Glib::String', +); + +my $entry_view = Gtk2::TreeView->new($entry_store); + +my $entry_column0 = Gtk2::TreeViewColumn->new_with_attributes( + 'Entry', my $entry_cell0 = Gtk2::CellRendererText->new, + 'text', COLUMN_ENTRY_OFFSET, + 'background', COLUMN_ENTRY_COLOR, +); +$entry_view->append_column($entry_column0); +$entry_column0->set_resizable(TRUE); + +my $entry_column1 = Gtk2::TreeViewColumn->new_with_attributes( + 'Tag', my $entry_cell1 = Gtk2::CellRendererText->new, + 'text', COLUMN_ENTRY_TAG, + 'background', COLUMN_ENTRY_COLOR, +); +$entry_view->append_column($entry_column1); +$entry_column1->set_resizable(TRUE); + +my $entry_column2 = Gtk2::TreeViewColumn->new_with_attributes( + 'Alloc.', Gtk2::CellRendererText->new, + 'text', COLUMN_ENTRY_IN_USE, + 'background', COLUMN_ENTRY_COLOR, +); +$entry_view->append_column($entry_column2); +$entry_column2->set_resizable(TRUE); + +my $entry_column3 = Gtk2::TreeViewColumn->new_with_attributes( + 'Length', Gtk2::CellRendererText->new, + 'text', COLUMN_ENTRY_LENGTH, + 'background', COLUMN_ENTRY_COLOR, +); +$entry_view->append_column($entry_column3); +$entry_column3->set_resizable(TRUE); + +my $entry_column4 = Gtk2::TreeViewColumn->new_with_attributes( + 'Owner', my $entry_cell4 = Gtk2::CellRendererText->new, + 'text', COLUMN_ENTRY_USED_BY, + 'background', COLUMN_ENTRY_COLOR, +); +$entry_view->append_column($entry_column4); +$entry_column4->set_resizable(TRUE); + +my $entry_selection = $entry_view->get_selection; +$entry_selection->set_mode('browse'); +$entry_selection->signal_connect('changed' => \&entry_selection_changed); + +my $scrolled_entry_view = Gtk2::ScrolledWindow->new; +$scrolled_entry_view->set_policy('automatic', 'automatic'); +$scrolled_entry_view->set_shadow_type('in'); +$scrolled_entry_view->add($entry_view); + +### TEXT VIEW + +my $text_view = Gtk2::TextView->new; +$text_view->set_editable(FALSE); +$text_view->modify_font(Gtk2::Pango::FontDescription->from_string('monospace')); + +my $text_buffer = $text_view->get_buffer; + +my $scrolled_text_view = Gtk2::ScrolledWindow->new; +$scrolled_text_view->set_policy('automatic', 'automatic'); +$scrolled_text_view->set_shadow_type('in'); +$scrolled_text_view->add($text_view); + +### IMAGE + +my $hbin_image = Gtk2::Image->new; +my $eventbox = Gtk2::EventBox->new; +$eventbox->add($hbin_image); +$eventbox->add_events(['button-press-mask']); + +$eventbox->signal_connect('button-press-event' => \&hbin_map_click); + +my $scrolled_hbin_image = Gtk2::ScrolledWindow->new; +$scrolled_hbin_image->set_policy('automatic', 'automatic'); +$scrolled_hbin_image->set_shadow_type('in'); +$scrolled_hbin_image->add_with_viewport($eventbox); + +### NOTEBOOK + +my $notebook = Gtk2::Notebook->new; +my $hbin_map_page = $notebook->append_page($scrolled_hbin_image, + Gtk2::Label->new_with_mnemonic("Hbin _Map")); +my $info_page = $notebook->append_page($scrolled_text_view, + Gtk2::Label->new_with_mnemonic("_Info")); + +### HPANED + +my $hpaned = Gtk2::HPaned->new; +$hpaned->add1($scrolled_entry_view); +$hpaned->add2($notebook); +$hpaned->set_position(320); + +### HBOX + +my $hbox = Gtk2::HBox->new; +$hbox->pack_start($scrolled_hbin_view, FALSE, FALSE, 0); +$hbox->pack_start($hpaned, TRUE, TRUE, 0); + +### UIMANAGER + +my $uimanager = Gtk2::UIManager->new; + +my @actions = ( + # name, stock id, label + ['FileMenu', undef, '_File'], + ['SearchMenu', undef, '_Search'], + ['ViewMenu', undef, '_View'], + ['HelpMenu', undef, '_Help'], + # name, stock-id, label, accelerator, tooltip, callback + ['Open', 'gtk-open', '_Open', '<control>O', undef, \&open_file], + ['Quit', 'gtk-quit', '_Quit', '<control>Q', undef, \&quit], + ['About', 'gtk-about', '_About', undef, undef, \&about], +); + +my $default_actions = Gtk2::ActionGroup->new('actions'); +$default_actions->add_actions(\@actions, undef); + +my @actions2 = ( + # name, stock-id, label, accelerator, tooltip, callback + ['Close', 'gtk-close', '_Close', '<control>W', undef, \&close_file], + ['Find', 'gtk-find', '_Find', '<control>F', undef, \&find], + ['FindNext', undef, 'Find _Next', '<control>G', undef, \&find_next], + ['FindNext2', undef, undef, 'F3', undef, \&find_next], + ['Process1', undef, '_Scan Entries', undef, undef, \&scan_entries], + ['Process2', 'gtk-media-play', 'Identify _Entry Owners', '<control>E', undef, \&scan_tree], + ['GoTo', 'gtk-index', '_Go To Offset', '<control>I', undef, \&go_to_offset], +); + +my $file_actions = Gtk2::ActionGroup->new('actions2'); +$file_actions->add_actions(\@actions2, undef); + +my @actions3 = ( + # name, stock-id, label, accelerator, tooltip, callback + ['ZoomIn', 'gtk-zoom-in', 'Zoom Hbin Map _In', '<control>plus', undef, \&zoom_in], + ['ZoomIn2', undef, undef, '<control>equal', undef, \&zoom_in], + ['ZoomOut', 'gtk-zoom-out', 'Zoom Hbin Map _Out', '<control>minus', undef, \&zoom_out], + ['ZoomFit', 'gtk-zoom-fit', 'Zoom Hbin Map To _Fit', '<control>0', undef, \&zoom_fit], + ['SaveHbinMap', 'gtk-save', '_Save Hbin Map', '<control>S', undef, \&save_hbin_map], +); + +my $hbin_actions = Gtk2::ActionGroup->new('actions3'); +$hbin_actions->add_actions(\@actions3, undef); + +my @actions4 = ( + # name, stock-id, label, accelerator, tooltip, callback + ['Jump', 'gtk-jump-to', '_Jump To Owner', '<control>J', undef, \&jump_to_owner], + ['JumpBack', 'gtk-go-back', 'Jump _Back', 'BackSpace', undef, \&jump_back], +); + +my $owner_actions = Gtk2::ActionGroup->new('actions4'); +$owner_actions->add_actions(\@actions4, undef); + +my @toggle_actions = ( + # name, stock id, label, accelerator, tooltip, callback, active + ['ShowToolbar', undef, 'Show _Toolbar', '<control>T', undef, \&toggle_toolbar, TRUE], + ['ShowHbins', undef, 'Show _Hbins', undef, undef, \&toggle_hbins, TRUE], + ['ShowHbinMap', undef, 'Show Hbin _Map', undef, undef, \&toggle_hbin_map, TRUE], +); +$default_actions->add_toggle_actions(\@toggle_actions, undef); + +$uimanager->insert_action_group($default_actions, 0); +$uimanager->insert_action_group($file_actions, 0); +$uimanager->insert_action_group($hbin_actions, 0); +$uimanager->insert_action_group($owner_actions, 0); + +$file_actions->set_sensitive(FALSE); +$hbin_actions->set_sensitive(FALSE); +$owner_actions->set_sensitive(FALSE); + +my $ui_info = <<END_OF_UI; +<ui> + <menubar name='MenuBar'> + <menu action='FileMenu'> + <menuitem action='Open'/> + <menuitem action='SaveHbinMap'/> + <menuitem action='Close'/> + <separator/> + <menuitem action='Quit'/> + </menu> + <menu action='SearchMenu'> + <menuitem action='Find'/> + <menuitem action='FindNext'/> + <separator/> + <menuitem action='GoTo'/> + <separator/> + <menuitem action='Process2'/> + <menuitem action='Jump'/> + <menuitem action='JumpBack'/> + </menu> + <menu action='ViewMenu'> + <menuitem action='ShowToolbar'/> + <menuitem action='ShowHbins'/> + <menuitem action='ShowHbinMap'/> + <separator/> + <menuitem action='ZoomIn'/> + <menuitem action='ZoomOut'/> + <menuitem action='ZoomFit'/> + </menu> + <menu action='HelpMenu'> + <menuitem action='About'/> + </menu> + </menubar> + <toolbar name='ToolBar'> + <toolitem action='Open'/> + <toolitem action='Close'/> + <separator/> + <toolitem action='Find'/> + <toolitem action='GoTo'/> + <toolitem action='Jump'/> + <separator/> + <toolitem action='Quit'/> + </toolbar> + <accelerator action='FindNext2'/> + <accelerator action='ZoomIn2'/> +</ui> +END_OF_UI + +$uimanager->add_ui_from_string($ui_info); + +my $menubar = $uimanager->get_widget('/MenuBar'); +my $toolbar = $uimanager->get_widget('/ToolBar'); + +### STATUSBAR + +my $statusbar = Gtk2::Statusbar->new; + +### VBOX + +my $main_vbox = Gtk2::VBox->new(FALSE, 0); +$main_vbox->pack_start($menubar, FALSE, FALSE, 0); +$main_vbox->pack_start($toolbar, FALSE, FALSE, 0); +$main_vbox->pack_start($hbox, TRUE, TRUE, 0); +$main_vbox->pack_start($statusbar, FALSE, FALSE, 0); + +### WINDOW + +my $window = Gtk2::Window->new; +$window->set_default_size($window_width, $window_height); +$window->set_position('center'); +$window->signal_connect(destroy => sub { Gtk2->main_quit }); +$window->add($main_vbox); +$window->add_accel_group($uimanager->get_accel_group); +$window->set_title($script_name); +$window->show_all; + +my $filename = shift; +if (defined $filename && -r $filename) { + load_file($filename); +} + +### GLOBALS + +my $registry; + +my $last_dir; + +my $find_param = ''; +my $find_iter; +my $find_hbin; +my $find_hbin_iter; +my $find_entry_iter; + +my $entry_source; # will be a registry for Win95, a hbin for WinNT + +my $map_width; +my $map_height; +my $map_pixbuf; +my $map_scale = 6; + +my %owners = (); + +my @jump_history = (); + +Gtk2->main; + +############################################################################### + +sub load_entries { + return if !defined $entry_source; + + $entry_store->clear; + + # $entry_source is either a WinNT::Hbin or a Win95::File. + my $entry_iter = $entry_source->get_entry_iterator; + while (my $entry = $entry_iter->get_next) { + my $iter = $entry_store->append; + + my $tag = $entry->get_tag; + my $offset = $entry->get_offset; + + # colorize each row according to its tag (NT only) + # '#FF8080' red, sat 50% + # '#80FFFF' cyan, sat 50% + # '#80FF80' green, sat 50% + # '#FF80FF' magenta, sat 50% + my $color = '#E6E6E6'; + if ($tag eq 'nk') { + $color = '#FF8080'; + } + elsif ($tag eq 'sk') { + $color = '#80FFFF'; + } + elsif ($tag eq 'vk') { + $color = '#80FF80'; + } + elsif ($tag =~ /(lf|lh|li|ri)/) { + $color = '#FF80FF'; + } + + $entry_store->set($iter, + COLUMN_ENTRY_OFFSET, sprintf("0x%x", $offset), + COLUMN_ENTRY_LENGTH, $entry->get_length, + COLUMN_ENTRY_TAG, $tag, + COLUMN_ENTRY_IN_USE, $entry->is_allocated, + COLUMN_ENTRY_COLOR, $color, + COLUMN_ENTRY_OBJECT, $entry); + + # If owners have been identified, add this to the Owner column + if (exists $owners{$offset}) { + my $desc = ""; + my $num_referrers = @{$owners{$offset}}; + if ($num_referrers == 1) { + my $rtype = $owners{$offset}[0]{type}; + my $roffset = $owners{$offset}[0]{offset}; + $desc = sprintf "$rtype @ 0x%x", $roffset; + if ($roffset == $offset) { + $desc = "Self"; + } + } + else { + $desc = "$num_referrers referrers"; + } + $entry_store->set($iter, + COLUMN_ENTRY_USED_BY, $desc); + } + } +} + +sub hbin_selection_changed { + my ($model, $iter) = $hbin_selection->get_selected; + if (!defined $model || !defined $iter) { + return; + } + + my $hbin = $model->get($iter, COLUMN_HBIN_OBJECT); + $entry_source = $hbin; # set global entry source + + my $str = $hbin->parse_info . "\n"; + $str .= $hbin->unparsed; + + $text_buffer->set_text($str); + + $statusbar->pop(0); + $statusbar->push(0, sprintf("Hbin @ 0x%x", $hbin->get_offset)); + + load_entries(); + + make_hbin_map(); + zoom_fit(); + + $notebook->set_current_page($hbin_map_page); +} + +sub entry_selection_changed { + my ($model, $iter) = $entry_selection->get_selected; + if (!defined $model || !defined $iter) { + return; + } + + my $entry = $model->get($iter, COLUMN_ENTRY_OBJECT); + my $offset = $entry->get_offset; + + my $desc; + if ($entry->looks_like_key) { + $desc = sprintf "Key @ 0x%x", $offset; + } + elsif ($entry->looks_like_value) { + $desc = sprintf "Value @ 0x%x", $offset; + } + elsif ($entry->looks_like_security) { + $desc = sprintf "Security @ 0x%x", $offset; + } + else { + $desc = sprintf "Entry @ 0x%x", $offset; + } + + my $str = "$desc\n\n" + . $entry->parse_info . "\n" + . $entry->unparsed . "\n"; + + my $status = $desc; + + if ($entry->looks_like_key) { + $str .= $entry->as_string . "\n\n"; + $status .= ' "' . $entry->get_name . '"'; + } + elsif ($entry->looks_like_value) { + my $name = $entry->get_name; + $name = '(Default)' if $name eq ''; + my $type_as_string = $entry->get_type_as_string; + + $str .= "$name ($type_as_string)\n\n"; + $status .= ' "' . $entry->get_name . '"'; + } + + $text_buffer->set_text($str); + $notebook->set_current_page($info_page); + + $statusbar->pop(0); + $statusbar->push(0, $status); +} + +sub show_message { + my $type = shift; + my $message = shift; + + my $dialog = Gtk2::MessageDialog->new( + $window, + 'destroy-with-parent', + $type, + 'ok', + $message, + ); + $dialog->set_title(ucfirst $type); + $dialog->run; + $dialog->destroy; +} + +sub load_file { + my $filename = shift; + + my ($name, $path) = fileparse($filename); + + close_file(); + + if (!-r $filename) { + show_message('error', "Unable to open '$name'."); + } + elsif ($registry = Parse::Win32Registry->new($filename)) { + if (my $root_key = $registry->get_root_key) { + $window->set_title("$name - $script_name"); + + my $hbin_iter = $registry->get_hbin_iterator; + if (defined $hbin_iter) { # WinNT + # load hbins + while (my $hbin = $hbin_iter->get_next) { + my $iter = $hbin_store->append; + $hbin_store->set($iter, + COLUMN_HBIN_OFFSET, sprintf("0x%x", $hbin->{_offset}), + COLUMN_HBIN_OBJECT, $hbin); + } + show_hbin_functions(TRUE); + } + else { # Win95 + $entry_source = $registry; + load_entries(); + show_hbin_functions(FALSE); + } + $file_actions->set_sensitive(TRUE); + } + } + else { + show_message('error', "'$name' is not a registry file."); + } +} + +sub choose_file { + my ($title, $type, $suggested_name) = @_; + + my $file_chooser = Gtk2::FileChooserDialog->new( + $title, + undef, + $type, + 'gtk-cancel' => 'cancel', + 'gtk-ok' => 'ok', + ); + if ($type eq 'save') { + $file_chooser->set_current_name($suggested_name); + } + if (defined $last_dir) { + $file_chooser->set_current_folder($last_dir); + } + my $response = $file_chooser->run; + + my $filename; + if ($response eq 'ok') { + $filename = $file_chooser->get_filename; + } + $last_dir = $file_chooser->get_current_folder; + $file_chooser->destroy; + return $filename; +} + +sub open_file { + my $filename = choose_file('Select Registry File', 'open'); + if (defined $filename) { + load_file($filename); + } +} + +sub save_hbin_map { + return if !defined $map_pixbuf; + + my ($model, $iter) = $hbin_selection->get_selected; + if (!defined $model || !defined $iter) { + return; + } + + my $hbin = $model->get($iter, COLUMN_HBIN_OBJECT); + + my $name = sprintf "hbin\@0x%x.jpg", $hbin->get_offset; + + my $filename = choose_file('Save Hbin Map', 'save', $name); + if (defined $filename) { + my $pixbuf = $map_pixbuf->scale_simple($map_width * MAX_SCALE, + $map_height * MAX_SCALE, + 'tiles'); + $pixbuf->save($filename, 'jpeg', quality => 100); + } +} + +sub close_file { + $hbin_store->clear; + $entry_store->clear; + $registry = undef; + $entry_source = undef; + $hbin_image->clear; + $text_buffer->set_text(''); + $statusbar->pop(0); + $map_pixbuf = undef; + %owners = (); + @jump_history = (); + $file_actions->set_sensitive(FALSE); + $hbin_actions->set_sensitive(FALSE); + $owner_actions->set_sensitive(FALSE); +} + +sub quit { + $window->destroy; +} + +sub about { + Gtk2->show_about_dialog(undef, + 'program-name' => $script_name, + 'version' => $Parse::Win32Registry::VERSION, + 'copyright' => 'Copyright (c) 2009 James Macfarlane', + 'comments' => 'GTK2 Registry Scope for the Parse::Win32Registry module', + ); +} + +sub toggle_hbins { + my ($toggle_action) = @_; + if ($toggle_action->get_active) { + $scrolled_hbin_view->show; + } + else { + $scrolled_hbin_view->hide; + } +} + +sub toggle_hbin_map { + my ($toggle_action) = @_; + if ($toggle_action->get_active) { + $scrolled_hbin_image->show; + } + else { + $scrolled_hbin_image->hide; + } +} + +sub toggle_toolbar { + my ($toggle_action) = @_; + if ($toggle_action->get_active) { + $toolbar->show; + } + else { + $toolbar->hide; + } +} + +sub make_hbin_map { + my ($model, $iter) = $hbin_selection->get_selected; + if (!defined $model || !defined $iter) { + return; + } + + my $hbin = $model->get($iter, COLUMN_HBIN_OBJECT); + + my $hbin_length = $hbin->get_length; + + # Find the nearest power of 2 larger than hbin length + my $n = $hbin_length; + if ($n > 0) { + $n--; + foreach (1..31) { + $n |= $n >> $_; + } + $n++; + } + + # Find the squarest map dimensions + my $h = 1; + my $w = $n; + while ($h < $w) { + $h *= 2; + $w /= 2; + } + + $map_width = $w; + $map_height = int($hbin_length / $w); + + # Initialise the byte sequence with the hbin header + my $data = pack "C*", + map { ($_, $_, $_, 255) } unpack("C*", $hbin->get_raw_bytes); + + # Build the hbin map using colorised byte sequences + my $entry_iter = $hbin->get_entry_iterator; + while (my $entry = $entry_iter->get_next) { + my $tag = $entry->get_tag; + if ($tag eq 'nk') { + $data .= pack "C*", + map { ($_, 0, 0, 255) } unpack("C*", $entry->get_raw_bytes); + } + elsif ($tag eq 'vk') { + $data .= pack "C*", + map { (0, $_, 0, 255) } unpack("C*", $entry->get_raw_bytes); + } + elsif ($tag eq 'sk') { + $data .= pack "C*", + map { (0, $_, $_, 255) } unpack("C*", $entry->get_raw_bytes); + } + elsif ($tag =~ /(lf|lh|ri|li)/) { + $data .= pack "C*", + map { ($_, 0, $_, 255) } unpack("C*", $entry->get_raw_bytes); + } + else { + $data .= pack "C*", + map { ($_, $_, $_, 255) } unpack("C*", $entry->get_raw_bytes); + } + } + + my $padding = ($map_width * $map_height) - int(length($data) / 4); + $data .= pack "C*", (255, 255, 255, 128) x $padding; + + $map_pixbuf = Gtk2::Gdk::Pixbuf->new_from_data( + $data, 'rgb', 1, 8, $map_width, $map_height, $map_width * 4); +} + +sub hbin_map_click { + my ($widget, $event) = @_; + + my ($x, $y) = ($event->x, $event->y); + + my @alloc = $hbin_image->allocation->values; + my $alloc_width = $alloc[2]; + my $alloc_height = $alloc[3]; + my $map_x = $x - ($alloc_width - $map_width*$map_scale) / 2; + my $map_y = $y - ($alloc_height - $map_height*$map_scale) / 2; + $map_x /= $map_scale; + $map_y /= $map_scale; + $map_x = int($map_x); + $map_y = int($map_y); + + if (($map_x >= 0 && $map_x < $map_width) && + ($map_y >= 0 && $map_y < $map_height)) { + my $offset = ($map_y * $map_width) + $map_x; + + my ($model, $iter) = $hbin_selection->get_selected; + if (!defined $model || !defined $iter) { + return; + } + my $hbin = $model->get($iter, COLUMN_HBIN_OBJECT); + $offset += $hbin->get_offset; + + if ($offset < ($hbin->get_offset + 0x20)) { + # First 32 bytes comprise the hbin header + go_to_hbin($offset); + } + else { + go_to_entry($offset); + $notebook->set_current_page($hbin_map_page); + } + } +} + +sub show_owner_functions { + +} + +sub show_hbin_functions { + my $state = shift; + + my $show_hbins_toggle + = $uimanager->get_widget('/MenuBar/ViewMenu/ShowHbins'); + + my $show_hbin_map_toggle + = $uimanager->get_widget('/MenuBar/ViewMenu/ShowHbinMap'); + + if ($state) { + $hbin_actions->set_sensitive(TRUE); + $show_hbins_toggle->set_active(TRUE); + $show_hbin_map_toggle->set_active(TRUE); + } + else { + $hbin_actions->set_sensitive(FALSE); + $show_hbins_toggle->set_active(FALSE); + $show_hbin_map_toggle->set_active(FALSE); + } +} + +sub draw_scaled_map { + return if !defined $map_pixbuf; + + my $scale = shift || $map_scale; # optional override + + my $pixbuf = $map_pixbuf->scale_simple($map_width * $scale, + $map_height * $scale, + 'tiles'); + $hbin_image->set_from_pixbuf($pixbuf); +} + +sub zoom_in { + return if !defined $map_pixbuf; + + $map_scale++; + $map_scale = MAX_SCALE if $map_scale > MAX_SCALE; + draw_scaled_map(); + $notebook->set_current_page($hbin_map_page); +} + +sub zoom_out { + return if !defined $map_pixbuf; + + $map_scale--; + $map_scale = 1 if $map_scale < 1; + draw_scaled_map(); + $notebook->set_current_page($hbin_map_page); +} + +sub zoom_fit { + return if !defined $map_pixbuf; + + my $allocation = $scrolled_hbin_image->allocation; + my ($x, $y, $available_width, $available_height) = $allocation->values; + + for (my $scale = MAX_SCALE; $scale > 0; $scale--) { + my $width = $map_width * $scale; + my $height = $map_height * $scale; + + if ($width < $available_width && $height < $available_height) { + $map_scale = $scale; + last; + } + } + draw_scaled_map(); + $notebook->set_current_page($hbin_map_page); +} + +sub go_to_hbin { + my ($offset) = @_; + + my $iter = $hbin_store->get_iter_first; + while (defined $iter) { + my $hbin = $hbin_store->get($iter, COLUMN_HBIN_OBJECT); + my $hbin_start = $hbin->get_offset; + my $hbin_end = $hbin_start + $hbin->get_length; + if ($offset >= $hbin_start && $offset < $hbin_end) { + my $tree_path = $hbin_store->get_path($iter); + $hbin_view->expand_to_path($tree_path); + $hbin_view->scroll_to_cell($tree_path); + $hbin_view->set_cursor($tree_path); + $window->set_focus($hbin_view); + return; + } + $iter = $hbin_store->iter_next($iter); + } +} + +sub go_to_entry { + my ($offset) = @_; + + my $iter = $entry_store->get_iter_first; + while (defined $iter) { + my $entry = $entry_store->get($iter, COLUMN_ENTRY_OBJECT); + my $entry_start = $entry->get_offset; + my $entry_end = $entry_start + $entry->get_length; + if ($offset >= $entry_start && $offset < $entry_end) { + my $tree_path = $entry_store->get_path($iter); + $entry_view->expand_to_path($tree_path); + $entry_view->scroll_to_cell($tree_path); + $entry_view->set_cursor($tree_path); + $window->set_focus($entry_view); + return; + } + $iter = $entry_store->iter_next($iter); + } +} + +sub find_next { + if (!defined $find_param || !defined $find_entry_iter) { + return; + } + + # Build find next dialog + my $label = Gtk2::Label->new; + $label->set_text("Searching registry entries..."); + my $dialog = Gtk2::Dialog->new('Find', + $window, + 'modal', + 'gtk-cancel' => 'cancel', + ); + $dialog->vbox->pack_start($label, TRUE, TRUE, 5); + $dialog->set_default_response('cancel'); + $dialog->show_all; + + my $id = Glib::Idle->add(sub { + while (1) { + my $entry = $find_entry_iter->get_next; + if (defined $entry) { + my $found = 0; + + if (index($entry->get_raw_bytes, $find_param) > -1) { + $found = 1; + } + else { + my $uni_find_param = encode("UCS-2LE", $find_param); + if (index($entry->get_raw_bytes, $uni_find_param) > -1) { + $found = 1; + } + } + + if ($found) { + if (defined $find_hbin) { + go_to_hbin($find_hbin->get_offset); + } + go_to_entry($entry->get_offset); + + $dialog->response(50); + return FALSE; + } + + return TRUE; # continue searching... + } + else { # no more entries...? + if (defined $find_hbin_iter) { + $find_hbin = $find_hbin_iter->get_next; + if (defined $find_hbin) { + $find_entry_iter = $find_hbin->get_entry_iterator; + if (!defined $find_entry_iter) { + last; # no entry iterator... (WinNT) + } + } + else { + last; # no more hbins... (WinNT) + } + } + else { + last; # no more entries... (Win95) + } + } + } + + $dialog->response('ok'); + return FALSE; + + }); + + my $response = $dialog->run; + $dialog->destroy; + + if ($response eq 'cancel' || $response eq 'delete-event') { + Glib::Source->remove($id); + } + elsif ($response eq 'ok') { + show_message('info', 'Finished searching.'); + } +} + +sub find { + return if !defined $registry; + + my $entry = Gtk2::Entry->new; + $entry->set_text($find_param); + $entry->set_activates_default(TRUE); + my $dialog = Gtk2::Dialog->new('Find', + $window, + 'modal', + 'gtk-cancel' => 'cancel', + 'gtk-ok' => 'ok', + ); + $dialog->vbox->pack_start($entry, TRUE, TRUE, 5); + $dialog->set_default_response('ok'); + $dialog->show_all; + + my $response = $dialog->run; + $dialog->destroy; + + if ($response eq 'ok') { + $find_param = $entry->get_text; + if ($find_param ne '') { + # WinNT: initialise hbin_iter, hbin, entry_iter + # Win95: initialise entry_iter + $find_hbin_iter = $registry->get_hbin_iterator; + if (defined $find_hbin_iter) { # WinNT + $find_hbin = $find_hbin_iter->get_next; + $find_entry_iter = $find_hbin->get_entry_iterator; + } + else { # Win95 + $find_entry_iter = $registry->get_entry_iterator; + } + find_next; + } + } +} + +sub go_to_offset { + return if !defined $registry; + + my $entry = Gtk2::Entry->new; + $entry->set_activates_default(TRUE); + my $dialog = Gtk2::Dialog->new('Go To Offset', + $window, + 'modal', + 'gtk-cancel' => 'cancel', + 'gtk-ok' => 'ok', + ); + $dialog->vbox->pack_start($entry, TRUE, TRUE, 5); + $dialog->set_default_response('ok'); + $dialog->show_all; + + $entry->prepend_text("0x"); + $entry->set_position(-1); + + my $response = $dialog->run; + $dialog->destroy; + + if ($response ne 'ok') { + return; + } + + my $offset; + eval { + my $answer = $entry->get_text; + if ($answer =~ m/^0x[\da-fA-F]+\s*$/) { + $offset = int(eval $answer); + } + }; + + if (defined $offset && $offset < $registry->get_length) { + go_to_hbin($offset); + go_to_entry($offset); + } +} + +sub jump_to_owner { + my ($model, $iter) = $entry_selection->get_selected; + if (!defined $model || !defined $iter) { + return; + } + + if (!%owners) { + show_message('error', "'Identify Entry Owners' has not been run."); + return; + } + + my $entry = $model->get($iter, COLUMN_ENTRY_OBJECT); + my $offset = $entry->get_offset; + + if (exists $owners{$offset}) { + my $num_referrers = @{$owners{$offset}}; + if ($num_referrers >= 1) { + my $roffset = $owners{$offset}[0]{offset}; + if ($roffset != $offset) { + push @jump_history, $offset; + go_to_hbin($roffset); + go_to_entry($roffset); + } + } + } +} + +sub jump_back { + if (@jump_history) { + my $offset = pop @jump_history; + go_to_hbin($offset); + go_to_entry($offset); + } +} + +############################################################################### + +sub scan_entries { + return if !defined $registry; + + my $label = Gtk2::Label->new; + $label->set_text("Searching registry..."); + my $dialog = Gtk2::Dialog->new('Find', + $window, + 'modal', + 'gtk-cancel' => 'cancel', + ); + $dialog->vbox->pack_start($label, TRUE, TRUE, 5); + $dialog->set_default_response('cancel'); + $dialog->show_all; + + my $entry_iter; + my $hbin_iter = $registry->get_hbin_iterator; + if (defined $hbin_iter) { # WinNT + my $hbin = $hbin_iter->get_next; + $entry_iter = $hbin->get_entry_iterator; + } + else { # Win95 + $entry_iter = $registry->get_entry_iterator; + } + + my $id = Glib::Idle->add(sub { + while (1) { + my $entry = $entry_iter->get_next; + if (defined $entry) { + + # do something with entry... + printf "processing entry 0x%x...\n", $entry->get_offset; + + return TRUE; # continue searching... + } + else { # no more entries...? + if (defined $hbin_iter) { + my $hbin = $hbin_iter->get_next; + if (defined $hbin) { + $entry_iter = $hbin->get_entry_iterator; + if (!defined $entry_iter) { + last; # no more entries... (WinNT) + } + } + else { + last; # no more hbins... (WinNT) + } + } + else { + last; # no more entries... (Win95) + } + } + } + + $dialog->response('ok'); + show_message('info', 'Finished long running process.'); + return FALSE; + }); + + my $response = $dialog->run; + $dialog->destroy; + + if ($response eq 'cancel' || $response eq 'delete-event') { + Glib::Source->remove($id); + } +} + +sub scan_tree { + return if !defined $registry; + + %owners = (); + + my $label = Gtk2::Label->new; + $label->set_text("Scanning registry to identify entry owners..."); + my $dialog = Gtk2::Dialog->new('Scanning', + $window, + 'modal', + 'gtk-cancel' => 'cancel', + ); + $dialog->vbox->pack_start($label, TRUE, TRUE, 5); + $dialog->set_default_response('cancel'); + $dialog->show_all; + + my $root_key = $registry->get_root_key; + my $subtree_iter = $root_key->get_subtree_iterator; + my $value_iter; + + my $id = Glib::Idle->add(sub { + if (defined $value_iter) { + my $value = $value_iter->get_next; + if (defined $value) { + my $name = $value->get_name; + $name = '(Default)' if $name eq ''; + + my $self_offset = $value->get_offset; + foreach my $offset ($value->get_associated_offsets) { + push @{$owners{$offset}}, + { type => "Value", offset => $self_offset }; + } + + return TRUE; # continue processing + } + } + if (defined $subtree_iter) { + my $key = $subtree_iter->get_next; + if (defined $key) { + my $self_offset = $key->get_offset; + foreach my $offset ($key->get_associated_offsets) { + push @{$owners{$offset}}, + { type => "Key", offset => $self_offset }; + } + + # Fetch new value iterator for new key + $value_iter = $key->get_value_iterator; + return TRUE; # continue processing + } + } + + $dialog->response('ok'); + return FALSE; # stop processing + }); + + my $response = $dialog->run; + $dialog->destroy; + + if ($response eq 'cancel' || $response eq 'delete-event') { + Glib::Source->remove($id); + %owners = (); + } + elsif ($response eq 'ok') { + $entry_column4->set_visible(TRUE); + show_message('info', "Finished identifying entry owners.\n" + . "Check the Owner column for details."); + $owner_actions->set_sensitive(TRUE); + load_entries(); + } +} + diff --git a/bin/regview.pl b/bin/regview.pl index e2cbc54..a5374ba 100755 --- a/bin/regview.pl +++ b/bin/regview.pl @@ -5,9 +5,15 @@ use warnings; use Glib ':constants'; use Gtk2 -init; +my $screen = Gtk2::Gdk::Screen->get_default; +my $window_width = $screen->get_width * 0.9; +my $window_height = $screen->get_height * 0.8; +$window_width = 1100 if $window_width > 1100; +$window_height = 900 if $window_height > 900; + use File::Basename; use File::Spec; -use Parse::Win32Registry 0.50 qw(hexdump); +use Parse::Win32Registry 0.51 qw(hexdump); binmode(STDOUT, ':utf8'); @@ -37,6 +43,15 @@ for (my $col = 0; $col < @list_column_names; $col++) { 'text', $col); $list_view->append_column($column); $column->set_resizable(TRUE); + $list_store->set_sort_func($col, sub { + my ($model, $itera, $iterb, $col) = @_; + my $a = $model->get($itera, $col); + my $b = $model->get($iterb, $col); + $a = '' if !defined $a; + $b = '' if !defined $b; + return $a cmp $b; + }, $col); + $column->set_sort_column_id($col); } $list_view->set_rules_hint(TRUE); @@ -96,6 +111,15 @@ for (my $col = 0; $col < @tree_column_names; $col++) { $column->set_resizable(TRUE); $tree_view->append_column($column); push @tree_columns, $column; + $tree_store->set_sort_func($col, sub { + my ($model, $itera, $iterb, $col) = @_; + my $a = $model->get($itera, $col); + my $b = $model->get($iterb, $col); + $a = '' if !defined $a; + $b = '' if !defined $b; + return $a cmp $b; + }, $col); + $column->set_sort_column_id($col); } $tree_view->set_rules_hint(TRUE); @@ -120,7 +144,7 @@ my $hpaned = Gtk2::HPaned->new; $hpaned->pack1($scrolled_tree_view, FALSE, FALSE); $hpaned->pack2($vpaned, TRUE, FALSE); -$hpaned->set_position(200); +$hpaned->set_position($window_width * 0.3); ### MENU @@ -133,15 +157,15 @@ my $accel_group = Gtk2::AccelGroup->new; # File Menu my $open_menuitem = Gtk2::MenuItem->new('_Open'); $open_menuitem->signal_connect('activate' => \&open_file); -$open_menuitem->add_accelerator('activate' => $accel_group, +$open_menuitem->add_accelerator('activate', $accel_group, $Gtk2::Gdk::Keysyms{O}, ['control-mask'], ['visible', 'locked']); my $close_menuitem = Gtk2::MenuItem->new('_Close'); $close_menuitem->signal_connect('activate' => \&close_file); -$close_menuitem->add_accelerator('activate' => $accel_group, +$close_menuitem->add_accelerator('activate', $accel_group, $Gtk2::Gdk::Keysyms{W}, ['control-mask'], ['visible', 'locked']); my $quit_menuitem = Gtk2::MenuItem->new('_Quit'); $quit_menuitem->signal_connect('activate' => \&quit); -$quit_menuitem->add_accelerator('activate' => $accel_group, +$quit_menuitem->add_accelerator('activate', $accel_group, $Gtk2::Gdk::Keysyms{Q}, ['control-mask'], ['visible', 'locked']); my $file_menu = Gtk2::Menu->new; @@ -156,7 +180,7 @@ my $recent_separator; # placeholder, becomes separator for recent files # Edit Menu my $copy_menuitem = Gtk2::MenuItem->new('_Copy key path'); $copy_menuitem->signal_connect('activate' => \©_key_path); -$copy_menuitem->add_accelerator('activate' => $accel_group, +$copy_menuitem->add_accelerator('activate', $accel_group, $Gtk2::Gdk::Keysyms{C}, ['control-mask'], ['visible', 'locked']); my $edit_menu = Gtk2::Menu->new; @@ -165,38 +189,27 @@ $edit_menu->append($copy_menuitem); # Search Menu my $find_menuitem = Gtk2::MenuItem->new('_Find'); $find_menuitem->signal_connect('activate' => \&find); -$find_menuitem->add_accelerator('activate' => $accel_group, +$find_menuitem->add_accelerator('activate', $accel_group, $Gtk2::Gdk::Keysyms{F}, ['control-mask'], ['visible', 'locked']); my $find_next_menuitem = Gtk2::MenuItem->new('Find Next'); $find_next_menuitem->signal_connect('activate' => \&find_next); -$find_next_menuitem->add_accelerator('activate' => $accel_group, +$find_next_menuitem->add_accelerator('activate', $accel_group, $Gtk2::Gdk::Keysyms{G}, ['control-mask'], ['visible', 'locked']); +$find_next_menuitem->add_accelerator('activate', $accel_group, + $Gtk2::Gdk::Keysyms{F3}, [], ['visible', 'locked']); my $search_menu = Gtk2::Menu->new; $search_menu->append($find_menuitem); $search_menu->append($find_next_menuitem); -# Test Menu -my $dump_loaded_keys_menuitem = Gtk2::MenuItem->new('Dump loaded keys'); -$dump_loaded_keys_menuitem->signal_connect('activate' => \&dump_loaded_keys); -my $dump_settings_menuitem = Gtk2::MenuItem->new('Dump settings'); -$dump_settings_menuitem->signal_connect('activate' => \&dump_settings); -my $dump_bookmarks_menuitem = Gtk2::MenuItem->new('Dump bookmarks'); -$dump_bookmarks_menuitem->signal_connect('activate' => \&dump_bookmarks); - -my $test_menu = Gtk2::Menu->new; -$test_menu->append($dump_loaded_keys_menuitem); -$test_menu->append($dump_bookmarks_menuitem); -$test_menu->append($dump_settings_menuitem); - # Bookmarks Menu my $add_bookmark_menuitem = Gtk2::MenuItem->new('_Add Bookmark'); $add_bookmark_menuitem->signal_connect('activate' => \&add_bookmark); -$add_bookmark_menuitem->add_accelerator('activate' => $accel_group, +$add_bookmark_menuitem->add_accelerator('activate', $accel_group, $Gtk2::Gdk::Keysyms{D}, ['control-mask'], ['visible', 'locked']); my $edit_bookmarks_menuitem = Gtk2::MenuItem->new('_Edit Bookmarks'); $edit_bookmarks_menuitem->signal_connect('activate' => \&edit_bookmarks); -$edit_bookmarks_menuitem->add_accelerator('activate' => $accel_group, +$edit_bookmarks_menuitem->add_accelerator('activate', $accel_group, $Gtk2::Gdk::Keysyms{B}, ['control-mask'], ['visible', 'locked']); my $bookmarks_menu = Gtk2::Menu->new; @@ -204,6 +217,21 @@ $bookmarks_menu->append($add_bookmark_menuitem); $bookmarks_menu->append($edit_bookmarks_menuitem); $bookmarks_menu->append(Gtk2::SeparatorMenuItem->new); +# Reports Menu +my $view_report_menuitem = Gtk2::MenuItem->new('View Bookmark Report'); +$view_report_menuitem->signal_connect('activate' => \&view_report); +$view_report_menuitem->add_accelerator('activate', $accel_group, + $Gtk2::Gdk::Keysyms{R}, ['control-mask'], ['visible', 'locked']); +#my $dump_loaded_keys_menuitem = Gtk2::MenuItem->new('Dump loaded keys'); +#$dump_loaded_keys_menuitem->signal_connect('activate' => \&dump_loaded_keys); +#my $dump_settings_menuitem = Gtk2::MenuItem->new('Dump settings'); +#$dump_settings_menuitem->signal_connect('activate' => \&dump_settings); + +my $reports_menu = Gtk2::Menu->new; +$reports_menu->append($view_report_menuitem); +#$reports_menu->append($dump_loaded_keys_menuitem); +#$reports_menu->append($dump_settings_menuitem); + # Help Menu my $about_menuitem = Gtk2::MenuItem->new('_About'); $about_menuitem->signal_connect('activate' => \&about); @@ -224,14 +252,14 @@ my $search_menuitem = Gtk2::MenuItem->new('_Search'); $search_menuitem->set_submenu($search_menu); $menubar->append($search_menuitem); -#my $test_menuitem = Gtk2::MenuItem->new('_Test'); -#$test_menuitem->set_submenu($test_menu); -#$menubar->append($test_menuitem); - my $bookmarks_menuitem = Gtk2::MenuItem->new('_Bookmarks'); $bookmarks_menuitem->set_submenu($bookmarks_menu); $menubar->append($bookmarks_menuitem); +my $reports_menuitem = Gtk2::MenuItem->new('_Reports'); +$reports_menuitem->set_submenu($reports_menu); +$menubar->append($reports_menuitem); + my $help_menuitem = Gtk2::MenuItem->new('_Help'); $help_menuitem->set_submenu($help_menu); $menubar->append($help_menuitem); @@ -242,7 +270,7 @@ my $statusbar = Gtk2::Statusbar->new; ### VBOX -my $main_vbox = Gtk2::VBox->new; +my $main_vbox = Gtk2::VBox->new(FALSE, 0); $main_vbox->pack_start($menubar, FALSE, FALSE, 0); $main_vbox->pack_start($hpaned, TRUE, TRUE, 0); $main_vbox->pack_start($statusbar, FALSE, FALSE, 0); @@ -250,7 +278,7 @@ $main_vbox->pack_start($statusbar, FALSE, FALSE, 0); ### WINDOW my $window = Gtk2::Window->new; -$window->set_default_size(600, 400); +$window->set_default_size($window_width, $window_height); $window->set_position('center'); $window->signal_connect(destroy => sub { Gtk2->main_quit }); $window->signal_connect(delete_event => sub { save_settings(); return FALSE; }); @@ -274,27 +302,37 @@ sub build_bookmarks_dialog { my $bookmark_column0 = Gtk2::TreeViewColumn->new_with_attributes( 'Bookmark', Gtk2::CellRendererText->new, 'text', 0); + $bookmark_column0->set_resizable(TRUE); $bookmark_view->append_column($bookmark_column0); - $bookmark_column0->set_resizable(FALSE); + my $bookmark_location_cell = Gtk2::CellRendererText->new; my $bookmark_column1 = Gtk2::TreeViewColumn->new_with_attributes( - 'Location', Gtk2::CellRendererText->new, 'text', 1); - $bookmark_view->append_column($bookmark_column1); + 'Location', $bookmark_location_cell, 'text', 1); + $bookmark_location_cell->set('ellipsize', 'end'); $bookmark_column1->set_resizable(FALSE); + $bookmark_view->append_column($bookmark_column1); my $scrolled_bookmark_view = Gtk2::ScrolledWindow->new; $scrolled_bookmark_view->set_policy('automatic', 'automatic'); $scrolled_bookmark_view->set_shadow_type('in'); $scrolled_bookmark_view->add($bookmark_view); + my $label = Gtk2::Label->new; + $label->set_markup('<i>Drag bookmarks to reorder them</i>'); + my $dialog = Gtk2::Dialog->new('Edit Bookmarks', $window, 'modal', 'gtk-remove' => 50, 'gtk-ok' => 'ok', ); - $dialog->resize(400, 200); - $dialog->vbox->add($scrolled_bookmark_view); + $dialog->resize($window_width * 0.8, $window_height * 0.8); + $dialog->vbox->pack_start($scrolled_bookmark_view, TRUE, TRUE, 0); + $dialog->vbox->pack_start($label, FALSE, FALSE, 5); $dialog->set_default_response('ok'); + $dialog->signal_connect(delete_event => sub { + $dialog->hide; + return TRUE; + }); $dialog->signal_connect(response => sub { my ($dialog, $response) = @_; if ($response eq '50') { @@ -325,9 +363,52 @@ sub build_bookmarks_dialog { my $bookmarks_dialog = build_bookmarks_dialog; +my $report_view; + +sub build_report_dialog { + $report_view = Gtk2::TextView->new; + $report_view->set_editable(FALSE); + $report_view->modify_font(Gtk2::Pango::FontDescription->from_string('monospace')); + + my $text_buffer = $report_view->get_buffer; + + my $scrolled_report_view = Gtk2::ScrolledWindow->new; + $scrolled_report_view->set_policy('automatic', 'automatic'); + $scrolled_report_view->set_shadow_type('in'); + $scrolled_report_view->add($report_view); + + my $dialog = Gtk2::Dialog->new('Report', $window, 'modal', + 'gtk-save' => 50, + 'gtk-ok' => 'ok', + ); + $dialog->resize($window_width * 0.8, $window_height * 0.8); + $dialog->vbox->add($scrolled_report_view); + $dialog->set_default_response('ok'); + + $dialog->signal_connect(delete_event => sub { + $dialog->hide; + return TRUE; + }); + $dialog->signal_connect(response => sub { + my ($dialog, $response) = @_; + if ($response eq '50') { + save_report(); + } + else { + $dialog->hide; + } + }); + + return $dialog; +} + +my $report_dialog = build_report_dialog; + ### GLOBALS -my $find_param; +my $search_keys = TRUE; +my $search_values = TRUE; +my $find_param = ''; my $find_iter; my @recent = (); @@ -564,29 +645,56 @@ sub load_recent { load_file($filename); } -sub open_file { +sub choose_file { + my ($title, $type, $suggested_name) = @_; + my $file_chooser = Gtk2::FileChooserDialog->new( - 'Select Registry File', + $title, undef, - 'open', + $type, 'gtk-cancel' => 'cancel', 'gtk-ok' => 'ok', ); + if ($type eq 'save') { + $file_chooser->set_current_name($suggested_name); + } if (defined $last_dir) { $file_chooser->set_current_folder($last_dir); } - my $filename; my $response = $file_chooser->run; + + my $filename; if ($response eq 'ok') { $filename = $file_chooser->get_filename; } $last_dir = $file_chooser->get_current_folder; $file_chooser->destroy; + return $filename; +} + +sub open_file { + my $filename = choose_file('Select Registry File', 'open'); if ($filename) { load_file($filename); } } +sub save_report { + if (my $filename = choose_file('Save Log File As', 'save', "report.txt")) { + my $basename = basename $filename; + if (open my $fh, ">", $filename) { + my $text_buffer = $report_view->get_buffer; + my $start_iter = $text_buffer->get_start_iter; + my $end_iter = $text_buffer->get_end_iter; + print {$fh} $text_buffer->get_text($start_iter, $end_iter, 0); +# show_message("info", "Report saved to '$basename'"); + } + else { + show_message("error", "Error saving log to '$basename'"); + } + } +} + sub close_file { $tree_store->clear; $list_store->clear; @@ -621,6 +729,7 @@ sub show_message { 'ok', $message, ); + $dialog->set_title(ucfirst $type); $dialog->run; $dialog->destroy; } @@ -628,20 +737,23 @@ sub show_message { sub create_bookmark_menuitem { my ($name, $subkey_path) = @_; - if (my $menuitem = Gtk2::MenuItem->new($name)) { - $bookmarks_menu->append($menuitem); - $bookmarks_menu->show_all; - if (my $iter = $bookmark_store->append) { - $bookmark_store->set($iter, - 0, $name, - 1, $subkey_path, - 2, $menuitem, - ); - } - $menuitem->signal_connect('activate' => \&go_to_bookmark, - $subkey_path); + my $display_name = $name; + $display_name =~ s/_/__/g; + if (my $menuitem = Gtk2::MenuItem->new($display_name)) { + $bookmarks_menu->append($menuitem); + $bookmarks_menu->show_all; + if (my $iter = $bookmark_store->append) { + $bookmark_store->set($iter, + 0, $name, + 1, $subkey_path, + 2, $menuitem, + ); } + $menuitem->signal_connect('activate' => \&go_to_bookmark, + $subkey_path); + } } + sub add_bookmark { my $iter = $tree_selection->get_selected; return if !defined $iter; @@ -755,8 +867,10 @@ sub go_to_subkey { } if (@path_components == 0) { + my $parent_iter = $tree_store->iter_parent($iter); + my $parent_path = $tree_store->get_path($parent_iter); + $tree_view->expand_to_path($parent_path); my $tree_path = $tree_store->get_path($iter); - $tree_view->expand_to_path($tree_path); $tree_view->scroll_to_cell($tree_path); $tree_view->set_cursor($tree_path); $window->set_focus($tree_view); @@ -765,20 +879,33 @@ sub go_to_subkey { } } +sub get_search_message { + my $message; + if ($search_keys && $search_values) { + $message = "Searching registry keys and values..."; + } + elsif ($search_keys) { + $message = "Searching registry keys..."; + } + elsif ($search_values) { + $message = "Searching registry values..."; + } + return $message; +} + sub find_next { if (!defined $find_param || !defined $find_iter) { return; } - # Build find next dialog my $label = Gtk2::Label->new; - $label->set_text("Searching registry..."); + $label->set_text(get_search_message); my $dialog = Gtk2::Dialog->new('Find', $window, 'modal', 'gtk-cancel' => 'cancel', ); - $dialog->vbox->pack_start($label, TRUE, TRUE, 10); + $dialog->vbox->pack_start($label, TRUE, TRUE, 5); $dialog->set_default_response('cancel'); $dialog->show_all; @@ -787,41 +914,44 @@ sub find_next { if (!defined $key) { $dialog->response('ok'); - show_message('info', 'Finished searching.'); return FALSE; # stop searching } # Remove root key name to get subkey path my $subkey_path = (split(/\\/, $key->get_path, 2))[1]; if (!defined $subkey_path) { - return FALSE; # stop searching - # (currently get_subtree_iterator never returns the root key) + # go_to_subkey locates keys based on the subkey path + # and does not support going to the root key. + # Therefore if the subkey path is not defined, + # the subtree iterator has returned the root key, + # so searching it should be skipped. + return TRUE; # continue searching } + # Check value (if defined) for a match if (defined $value) { - # Check value for a match - my $value_name = $value->get_name; - if (index(lc $value_name, lc $find_param) >= 0) { - go_to_subkey($subkey_path); - go_to_value($value_name); - $dialog->response('ok'); - return FALSE; # stop searching - } - else { - return TRUE; # continue searching + if ($search_values) { + my $value_name = $value->get_name; + if (index(lc $value_name, lc $find_param) >= 0) { + go_to_subkey($subkey_path); + go_to_value($value_name); + $dialog->response(50); + return FALSE; # stop searching + } } + return TRUE; # continue searching } # Check key for a match - my $key_name = $key->get_name; - if (index(lc $key_name, lc $find_param) >= 0) { - go_to_subkey($subkey_path); - $dialog->response('ok'); - return FALSE; # stop searching - } - else { - return TRUE; # continue searching + if ($search_keys) { + my $key_name = $key->get_name; + if (index(lc $key_name, lc $find_param) >= 0) { + go_to_subkey($subkey_path); + $dialog->response(50); + return FALSE; # stop searching + } } + return TRUE; # continue searching }); my $response = $dialog->run; @@ -830,19 +960,40 @@ sub find_next { if ($response eq 'cancel' || $response eq 'delete-event') { Glib::Source->remove($id); } + elsif ($response eq 'ok') { + show_message('info', 'Finished searching.'); + } } sub find { - # Build find dialog + return if !defined $tree_store->get_iter_first; + my $entry = Gtk2::Entry->new; + $entry->set_text($find_param); $entry->set_activates_default(TRUE); + my $check1 = Gtk2::CheckButton->new('Search _Keys'); + $check1->set_active($search_keys); + my $check2 = Gtk2::CheckButton->new('Search _Values'); + $check2->set_active($search_values); + $check1->signal_connect(toggled => sub { + if (!$check1->get_active && !$check2->get_active) { + $check2->set_active(TRUE); + } + }); + $check2->signal_connect(toggled => sub { + if (!$check1->get_active && !$check2->get_active) { + $check1->set_active(TRUE); + } + }); my $dialog = Gtk2::Dialog->new('Find', $window, 'modal', 'gtk-cancel' => 'cancel', 'gtk-ok' => 'ok', ); - $dialog->vbox->pack_start($entry, TRUE, TRUE, 10); + $dialog->vbox->pack_start($entry, TRUE, TRUE, 0); + $dialog->vbox->pack_start($check1, TRUE, TRUE, 0); + $dialog->vbox->pack_start($check2, TRUE, TRUE, 0); $dialog->set_default_response('ok'); $dialog->show_all; @@ -856,6 +1007,8 @@ sub find { return if !defined $root_key; if ($response eq 'ok') { + $search_keys = $check1->get_active; + $search_values = $check2->get_active; $find_param = $entry->get_text; $find_iter = undef; if ($find_param ne '') { @@ -939,13 +1092,16 @@ sub dump_loaded_keys { }); } -sub dump_bookmarks { - print "Dumping bookmarks:\n"; +sub view_report { my $root_iter = $tree_store->get_iter_first; if (!defined $root_iter) { print "(no registry file loaded)\n"; return; } + + my $text_buffer = $report_view->get_buffer; + $text_buffer->set_text(''); + my $root_key = $tree_store->get($root_iter, 3); my $iter = $bookmark_store->get_iter_first; while (defined $iter) { @@ -953,8 +1109,21 @@ sub dump_bookmarks { my $path = $bookmark_store->get($iter, 1); if (my $key = $root_key->get_subkey($path)) { - print $key->get_path, "\n"; + my $str = $key->as_string . "\n"; + $text_buffer->insert_at_cursor($str); + foreach my $value ($key->get_list_of_values) { + my $value_name = $value->get_name; + $value_name = "(Default)" if $value_name eq ""; + my $value_type = $value->get_type_as_string; + my $str = "$value_name ($value_type):\n"; + $str .= hexdump($value->get_raw_data); + $text_buffer->insert_at_cursor($str); + } + $text_buffer->insert_at_cursor("\n"); } $iter = $bookmark_store->iter_next($iter); } + + $report_dialog->show_all; } + diff --git a/lib/Parse/Win32Registry.pm b/lib/Parse/Win32Registry.pm index 473d840..dc587ec 100644 --- a/lib/Parse/Win32Registry.pm +++ b/lib/Parse/Win32Registry.pm @@ -3,7 +3,7 @@ package Parse::Win32Registry; use strict; use warnings; -our $VERSION = '0.50'; +our $VERSION = '0.51'; use base qw(Exporter); @@ -359,6 +359,32 @@ The default value (displayed as '(Default)' by REGEDIT) does not actually have a name. It can obtained by supplying an empty string, e.g. $key->get_value(''); +=item $key->get_value_data( 'value name' ) + +Returns the data for the specified value name. +If either the value or the value's data does not exist, +nothing will be returned. + +This is simply a shortcut for accessing the data of a value +without creating an intermediate Value object. + +The following code: + + my $value = $key->get_value('value name'); + if (defined $value) { + my $data = $value->get_data; + if (defined $data) { + ...process data... + } + } + +can be replaced with: + + my $data = $key->get_value_data('value name'); + if (defined $data) { + ...process data... + } + =item $key->get_list_of_subkeys Returns a list of Key objects representing the subkeys of the @@ -610,7 +636,8 @@ REG_MULTI_SZ values will be returned as a list of strings when called in a list context, and as a string with each element separated by the list separator $" when called in a scalar context. -(The list separator defaults to the space character.) +(The list separator defaults to the space character. +See perlvar for further information.) String data will be converted from Unicode (UCS-2LE) for Windows NT based registry files. @@ -829,6 +856,13 @@ A SID object represents a Security Identifier. =over 4 +=item $sid->get_name + +Returns a string containing a name for the SID +(e.g. "Administrators" for S-1-5-32-544) +if it is a "well known" SID. +See Microsoft Knowledge Base Article KB243330. + =item $sid->as_string Returns a string containing the SID formatted for presentation. @@ -1262,7 +1296,8 @@ but with something a little more helpful than a hex editor. They are not designed for pulling data out of keys and values. They are designed for providing technical information about keys and values. -Most of these methods are demonstrated by the supplied regscan.pl script. +Most of these methods are demonstrated by the supplied regscan.pl +and regscope.pl scripts. =head2 Registry Object Methods @@ -1487,7 +1522,7 @@ Type regclassnames.pl on its own to see the help: =head2 regcompare.pl -regview.pl is a GTK+ program for comparing multiple registry files. +regcompare.pl is a GTK+ program for comparing multiple registry files. It displays a tree of the registry keys and values highlighting the changed keys and values, and a table detailing the actual changes. @@ -1500,6 +1535,8 @@ Filenames of registry files to compare can be supplied on the command line: regcompare.pl <filename1> <filename2> <filename3> ... +You can of course use wildcards when running from a Unix shell. + =head2 regdump.pl regdump.pl is used to display the keys and values of a registry file. @@ -1621,6 +1658,23 @@ Type regscan.pl on its own to see the help: -u or --unparsed show the unparsed on-disk entries as a hex dump -w or --warnings display warnings of invalid keys and values +=head2 regscope.pl + +regscope.pl is a GTK+ registry scanner. +It presents all the entries in a registry file returned by the +get_hbin_iterator and get_entry_iterator methods. +When viewing Windows NT registry files, it uses color to highlight +key, value, security, and subkey list entries, and presents the hbin +as a colored map. + +It requires Gtk2-Perl to be installed. +Links to Windows binaries can be found via the project home page at +L<http://gtk2-perl.sourceforge.net/win32/>. + +A filename can also be supplied on the command line: + + regscope.pl <filename> + =head2 regsecurity.pl regsecurity.pl will display the security information @@ -1708,7 +1762,7 @@ A filename can also be supplied on the command line: This would not have been possible without the work of those people who have analysed and documented the structure of Windows Registry files, namely: the WINE Project (see misc/registry.c in older releases), -the Samba Project (see utils/editreg.c and utils/profiles.c), +the Samba Project (see utils/editreg.c and utils/profiles.c in older releases), Petter Nordahl-Hagen (see chntpw's ntreg.h), and B.D. (see WinReg.txt). diff --git a/lib/Parse/Win32Registry/Base.pm b/lib/Parse/Win32Registry/Base.pm index eab07cd..840bd7d 100644 --- a/lib/Parse/Win32Registry/Base.pm +++ b/lib/Parse/Win32Registry/Base.pm @@ -445,7 +445,11 @@ sub make_multiple_subtree_iterator { croak "Usage: make_multiple_subtree_iterator\(\$key1, \$key2, ...\)"; } - push my @subkey_iters, make_multiple_subkey_iterator(@keys); + my @subkeys_queue = (\@keys); + push my (@subkey_iters), Parse::Win32Registry::Iterator->new(sub { + return shift @subkeys_queue; + }); +# make_multiple_subkey_iterator(@keys); my $value_iter; my $subkeys; @@ -881,11 +885,14 @@ sub get_trustee { sub as_string { my $self = shift; + my $sid = $self->{_trustee}; my $string = sprintf "%s 0x%02x 0x%08x %s", _look_up_ace_type($self->{_type}), $self->{_flags}, $self->{_mask}, - $self->{_trustee}->as_string; + $sid->as_string; + my $name = $sid->get_name; + $string .= " [$name]" if defined $name; return $string; } @@ -1087,10 +1094,16 @@ sub as_stanza { my $stanza = ""; if (defined(my $owner = $self->{_owner})) { - $stanza .= "Owner SID: " . $owner->as_string . "\n"; + $stanza .= "Owner SID: " . $owner->as_string; + my $name = $owner->get_name; + $stanza .= " [$name]" if defined $name; + $stanza .= "\n"; } if (defined(my $group = $self->{_group})) { - $stanza .= "Group SID: " . $group->as_string . "\n"; + $stanza .= "Group SID: " . $group->as_string; + my $name = $group->get_name; + $stanza .= " [$name]" if defined $name; + $stanza .= "\n"; } if (defined(my $sacl = $self->{_sacl})) { foreach my $ace ($sacl->get_list_of_aces) { diff --git a/lib/Parse/Win32Registry/Key.pm b/lib/Parse/Win32Registry/Key.pm index c923dce..697c86c 100644 --- a/lib/Parse/Win32Registry/Key.pm +++ b/lib/Parse/Win32Registry/Key.pm @@ -172,7 +172,10 @@ sub get_list_of_values { sub get_subtree_iterator { my $self = shift; - push my @subkey_iters, $self->get_subkey_iterator; + my @start_keys = ($self); + push my (@subkey_iters), Parse::Win32Registry::Iterator->new(sub { + return shift @start_keys; + }); my $value_iter; my $key; diff --git a/lib/Parse/Win32Registry/Win95/Key.pm b/lib/Parse/Win32Registry/Win95/Key.pm index 719cfaf..3ee15bf 100644 --- a/lib/Parse/Win32Registry/Win95/Key.pm +++ b/lib/Parse/Win32Registry/Win95/Key.pm @@ -259,4 +259,18 @@ sub get_value_iterator { }); } +sub get_associated_offsets { + my $self = shift; + + my @owners = (); + + push @owners, $self->{_offset}; + + if (defined $self->{_offset_to_rgdb_entry}) { + push @owners, $self->{_offset_to_rgdb_entry}; + } + + return @owners; +} + 1; diff --git a/lib/Parse/Win32Registry/Win95/Value.pm b/lib/Parse/Win32Registry/Win95/Value.pm index a8a5f5d..de08361 100644 --- a/lib/Parse/Win32Registry/Win95/Value.pm +++ b/lib/Parse/Win32Registry/Win95/Value.pm @@ -177,4 +177,14 @@ sub parse_info { return $info; } +sub get_associated_offsets { + my $self = shift; + + my @owners = (); + + push @owners, $self->{_offset}; + + return @owners; +} + 1; diff --git a/lib/Parse/Win32Registry/WinNT/Key.pm b/lib/Parse/Win32Registry/WinNT/Key.pm index dcffdfd..cbd7ec5 100644 --- a/lib/Parse/Win32Registry/WinNT/Key.pm +++ b/lib/Parse/Win32Registry/WinNT/Key.pm @@ -480,4 +480,36 @@ sub get_value_iterator { }); } +sub get_associated_offsets { + my $self = shift; + + my @owners = (); + + push @owners, $self->{_offset}; + + if ($self->{_offset_to_security}) { + push @owners, $self->{_offset_to_security}; + } + + if ($self->{_offset_to_class_name}) { + push @owners, $self->{_offset_to_class_name}; + } + + if ($self->{_num_subkeys}) { + push @owners, $self->{_offset_to_subkey_list}; + } + + # Indirect offsets must be added after _get_offsets_to_subkeys + # has been called (as this populates the _indirect_offsets field) + if ($self->{_indirect_offsets}) { + push @owners, keys %{ $self->{_indirect_offsets} }; + } + + if ($self->{_num_values}) { + push @owners, $self->{_offset_to_value_list}; + } + + return @owners; +} + 1; diff --git a/lib/Parse/Win32Registry/WinNT/Value.pm b/lib/Parse/Win32Registry/WinNT/Value.pm index e36b593..71e9056 100644 --- a/lib/Parse/Win32Registry/WinNT/Value.pm +++ b/lib/Parse/Win32Registry/WinNT/Value.pm @@ -237,4 +237,18 @@ sub parse_info { return $info; } +sub get_associated_offsets { + my $self = shift; + + my @owners = (); + + push @owners, $self->{_offset}; + + if (!$self->{_data_inline}) { + push @owners, $self->{_offset_to_data}; + } + + return @owners; +} + 1; diff --git a/t/iterator.t b/t/iterator.t new file mode 100644 index 0000000..d464557 --- /dev/null +++ b/t/iterator.t @@ -0,0 +1,253 @@ +use strict; +use warnings; + +use Test::More 'no_plan'; +use Data::Dumper; +use Parse::Win32Registry 0.51 qw(:REG_ make_multiple_subtree_iterator); + +$Data::Dumper::Useqq = 1; +$Data::Dumper::Terse = 1; +$Data::Dumper::Indent = 0; + +Parse::Win32Registry::disable_warnings; + +sub find_file +{ + my $filename = shift; + return -d 't' ? "t/$filename" : $filename; +} + +sub run_subtree_iterator_tests +{ + my $key = shift; + my @tests = @_; + + my ($os) = ref($key) =~ /Win(NT|95)/; + + # key+value tests + + my $subtree_iter = $key->get_subtree_iterator; + ok(defined $subtree_iter, "$os get_subtree_iterator defined"); + isa_ok($subtree_iter, "Parse::Win32Registry::Iterator"); + for (my $i = 0; $i < @tests; $i++) { + my ($key_path, $value_name) = @{$tests[$i]}; + + my ($key, $value) = $subtree_iter->get_next; + + my $desc = "$os (list) TEST" . ($i + 1); + + ok(defined $key, "$desc key defined (valid key)"); + is($key->get_path, $key_path, + "$desc key get_path eq " . Dumper($key_path)); + + if (defined $value_name) { + ok(defined $value, "$desc value defined (valid value)"); + is($value->get_name, $value_name, + "$desc value get_name eq " . Dumper($value_name)); + } + else { + ok(!defined $value, "$desc value undefined (no value)"); + } + } + my @final = $subtree_iter->get_next; + is(@final, 0, "$os (list) iterator empty"); + + # key tests + + @tests = grep { !defined $_->[1] } @tests; + + $subtree_iter = $key->get_subtree_iterator; + ok(defined $subtree_iter, "$os get_subtree_iterator defined"); + isa_ok($subtree_iter, "Parse::Win32Registry::Iterator"); + for (my $i = 0; $i < @tests; $i++) { + my ($key_path, $value_name) = @{$tests[$i]}; + + my $key = $subtree_iter->get_next; + + my $desc = "$os (scalar) TEST" . ($i + 1); + + ok(defined $key, "$desc key defined (valid key)"); + is($key->get_path, $key_path, + "$desc key get_path eq " . Dumper($key_path)); + } + my $final = $subtree_iter->get_next; + ok(!defined $final, "$os (scalar) iterator empty"); +} + +sub run_multiple_subtree_iterator_tests { + my $key = shift; + my @tests = @_; + + my ($os) = ref($key) =~ /Win(NT|95)/; + + # key+value tests + + my $subtree_iter = make_multiple_subtree_iterator($key); + ok(defined $subtree_iter, + "$os (list) make_multiple_subtree_iterator defined"); + isa_ok($subtree_iter, "Parse::Win32Registry::Iterator"); + for (my $i = 0; $i < @tests; $i++) { + my ($key_path, $value_name) = @{$tests[$i]}; + + my ($keys_ref, $values_ref) = $subtree_iter->get_next; + + my $desc = "$os (list) TEST" . ($i + 1); + + ok(defined $keys_ref, "$desc keys_ref defined"); + is(ref $keys_ref, 'ARRAY', "$desc keys_ref array"); + is($keys_ref->[0]->get_path, $key_path, + "$desc keys_ref->[0] get_path eq " . Dumper($key_path)); + + if (defined $value_name) { + ok(defined $values_ref, "$desc values_ref defined"); + is(ref $values_ref, 'ARRAY', "$desc values_ref array"); + is($values_ref->[0]->get_name, $value_name, + "$desc values_ref->[0] get_name eq " . Dumper($value_name)); + } + else { + ok(!defined $values_ref, "$desc values_ref undefined"); + } + } + my @final = $subtree_iter->get_next; + is(@final, 0, "$os (list) iterator empty"); + + # key tests + + @tests = grep { !defined $_->[1] } @tests; + + $subtree_iter = make_multiple_subtree_iterator($key); + ok(defined $subtree_iter, + "$os (scalar) make_multiple_subtree_iterator defined"); + isa_ok($subtree_iter, "Parse::Win32Registry::Iterator"); + for (my $i = 0; $i < @tests; $i++) { + my ($key_path, $value_name) = @{$tests[$i]}; + + my $keys_ref = $subtree_iter->get_next; + + my $desc = "$os (scalar) TEST" . ($i + 1); + + ok(defined $keys_ref, "$desc keys_ref defined"); + is(ref $keys_ref, 'ARRAY', "$desc keys_ref array"); + is($keys_ref->[0]->get_path, $key_path, + "$desc keys_ref->[0] get_path eq " . Dumper($key_path)); + } + my $final = $subtree_iter->get_next; + ok(!defined $final, "$os (scalar) iterator empty"); +} + +{ + my $filename = find_file('win95_iter_tests.rf'); + + my $registry = Parse::Win32Registry->new($filename); + + my $root_key = $registry->get_root_key; + + my @tests = ( + [""], + ["\\key1"], + ["\\key1", "value1"], + ["\\key1", "value2"], + ["\\key1\\key3"], + ["\\key1\\key3", "value5"], + ["\\key1\\key3", "value6"], + ["\\key1\\key4"], + ["\\key1\\key4", "value7"], + ["\\key1\\key4", "value8"], + ["\\key2"], + ["\\key2", "value3"], + ["\\key2", "value4"], + ["\\key2\\key5"], + ["\\key2\\key5", "value9"], + ["\\key2\\key5", "value10"], + ["\\key2\\key6"], + ["\\key2\\key6", "value11"], + ["\\key2\\key6", "value12"], + ); + + run_subtree_iterator_tests($root_key, @tests); + + @tests = ( + [""], + ["\\key1"], + ["\\key1", "value1"], + ["\\key1", "value2"], + ["\\key1\\key3"], + ["\\key1\\key3", "value5"], + ["\\key1\\key3", "value6"], + ["\\key1\\key4"], + ["\\key1\\key4", "value7"], + ["\\key1\\key4", "value8"], + ["\\key2"], + ["\\key2", "value3"], + ["\\key2", "value4"], + ["\\key2\\key5"], + ["\\key2\\key5", "value10"], + ["\\key2\\key5", "value9"], + ["\\key2\\key6"], + ["\\key2\\key6", "value11"], + ["\\key2\\key6", "value12"], + ); + + run_multiple_subtree_iterator_tests($root_key, @tests); +} + + +{ + my $filename = find_file('winnt_iter_tests.rf'); + + my $registry = Parse::Win32Registry->new($filename); + + my $root_key = $registry->get_root_key; + + my @tests = ( + ["\$\$\$PROTO.HIV"], + ["\$\$\$PROTO.HIV", "value1"], + ["\$\$\$PROTO.HIV", "value2"], + ["\$\$\$PROTO.HIV\\key1"], + ["\$\$\$PROTO.HIV\\key1", "value3"], + ["\$\$\$PROTO.HIV\\key1", "value4"], + ["\$\$\$PROTO.HIV\\key1\\key3"], + ["\$\$\$PROTO.HIV\\key1\\key3", "value7"], + ["\$\$\$PROTO.HIV\\key1\\key3", "value8"], + ["\$\$\$PROTO.HIV\\key1\\key4"], + ["\$\$\$PROTO.HIV\\key1\\key4", "value9"], + ["\$\$\$PROTO.HIV\\key1\\key4", "value10"], + ["\$\$\$PROTO.HIV\\key2"], + ["\$\$\$PROTO.HIV\\key2", "value5"], + ["\$\$\$PROTO.HIV\\key2", "value6"], + ["\$\$\$PROTO.HIV\\key2\\key5"], + ["\$\$\$PROTO.HIV\\key2\\key5", "value11"], + ["\$\$\$PROTO.HIV\\key2\\key5", "value12"], + ["\$\$\$PROTO.HIV\\key2\\key6"], + ["\$\$\$PROTO.HIV\\key2\\key6", "value13"], + ["\$\$\$PROTO.HIV\\key2\\key6", "value14"], + ); + + run_subtree_iterator_tests($root_key, @tests); + + @tests = ( + ["\$\$\$PROTO.HIV"], + ["\$\$\$PROTO.HIV", "value1"], + ["\$\$\$PROTO.HIV", "value2"], + ["\$\$\$PROTO.HIV\\key1"], + ["\$\$\$PROTO.HIV\\key1", "value3"], + ["\$\$\$PROTO.HIV\\key1", "value4"], + ["\$\$\$PROTO.HIV\\key1\\key3"], + ["\$\$\$PROTO.HIV\\key1\\key3", "value7"], + ["\$\$\$PROTO.HIV\\key1\\key3", "value8"], + ["\$\$\$PROTO.HIV\\key1\\key4"], + ["\$\$\$PROTO.HIV\\key1\\key4", "value10"], + ["\$\$\$PROTO.HIV\\key1\\key4", "value9"], + ["\$\$\$PROTO.HIV\\key2"], + ["\$\$\$PROTO.HIV\\key2", "value5"], + ["\$\$\$PROTO.HIV\\key2", "value6"], + ["\$\$\$PROTO.HIV\\key2\\key5"], + ["\$\$\$PROTO.HIV\\key2\\key5", "value11"], + ["\$\$\$PROTO.HIV\\key2\\key5", "value12"], + ["\$\$\$PROTO.HIV\\key2\\key6"], + ["\$\$\$PROTO.HIV\\key2\\key6", "value13"], + ["\$\$\$PROTO.HIV\\key2\\key6", "value14"], + ); + + run_multiple_subtree_iterator_tests($root_key, @tests); +} diff --git a/t/use.t b/t/use.t index cc20b7d..195c05f 100644 --- a/t/use.t +++ b/t/use.t @@ -5,7 +5,7 @@ use Test::More 'no_plan'; BEGIN { use_ok('Parse::Win32Registry') }; -is($Parse::Win32Registry::VERSION, '0.50', 'correct version'); +is($Parse::Win32Registry::VERSION, '0.51', 'correct version'); can_ok('Parse::Win32Registry', 'new'); can_ok('Parse::Win32Registry', 'convert_filetime_to_epoch_time'); can_ok('Parse::Win32Registry', 'iso8601'); diff --git a/t/win95_iter_tests.rf b/t/win95_iter_tests.rf new file mode 100644 index 0000000..618e8da Binary files /dev/null and b/t/win95_iter_tests.rf differ diff --git a/t/winnt_iter_tests.rf b/t/winnt_iter_tests.rf new file mode 100644 index 0000000..ac56a34 Binary files /dev/null and b/t/winnt_iter_tests.rf differ -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-perl/packages/libsendmail-milter-perl.git _______________________________________________ Pkg-perl-cvs-commits mailing list Pkg-perl-cvs-commits@lists.alioth.debian.org http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-perl-cvs-commits