use strict;
use File::Basename qw( basename dirname );
use Win32API::Registry qw( :ALL );
use Encode qw( decode );

#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
my ($mode, $script1, $db_name, $reg_key,$reg_ke1, $def_out);
my $script = basename($0);
my $usage = "far2 -> far3 settings converter v1.0.10 / far3 build 2465

Usage:
  $script Mode [Opt] -i reg[=RegFile] -o OutType

Mode:
  assoc|colors|highlight|macros|panel|shortcut
  
Opt:
  -e|-a Codepage
  -u UserName

OutType:
  db[=DBpath]|xml[=XmlFile]|sql[=SqlFile]|bat[=BatFile]

Samples:
  $script assoc -i reg -o xml
  $script colors -u User -i reg=FarSettings.User.reg -o sql=colors2x3.sql
  $script highlight -e CP1252 -i reg=Far1.reg -o bat -db MyFar3\\Profile
  $script macros -a CP1251 -i reg -o db
";

die $usage unless scalar(@ARGV) > 0;
$mode = shift @ARGV;
if ( $mode =~ m!^a!oi )
 { $mode = "assoc"; $db_name = "associations.db"; $reg_key = "Associations\\Type%d"; }
elsif ( $mode =~ m!^c!oi )
 { $mode = "colors"; $db_name = "colors.db"; $reg_key = "Colors"; }
elsif ( $mode =~ m!^h!oi )
 { $mode = "highlight"; $db_name = "highlight.db"; $reg_key = "%s\\%s%d"; }
elsif ( $mode =~ m!^m!oi )
 { $mode = "macros"; $db_name = "macros.db"; $reg_key = "KeyMacros"; }
elsif ( $mode =~ m!^p!oi )
 { $mode = "panel"; $db_name = "panelmodes.db"; $reg_key = "Panel\\ViewModes\\Mode%d"; }
elsif ( $mode =~ m!^s!oi )
 { $mode = "shortcut"; $db_name = "shortcuts.db"; ($reg_key,$reg_ke1)=("FolderShortcuts","Shortcuts\\%d"); }
else
 { die $usage; }

$script1 = "2x3-$mode"; $def_out = "far3_$mode";

my $inpType = ''; #'reg'; # 'file'
my $outType = ''; # 'db'; # 'xml', 'sql', 'bat'

my ($regpath, $text) = ('', undef);

my $local =  $mode eq "shortcut" ? 'LOCAL' : '';
my ($varn, $tail) = ( "FAR${local}PROFILE", '');
($varn, $tail) = ("${local}APPDATA", '\Far Manager\Profile') unless exists $ENV{$varn};
($varn, $tail) = ("USERPROFILE", '\Local Settings\Application Data\Far Manager\Profile') unless(exists $ENV{$varn} || !$local);
my $db_path = "$ENV{$varn}$tail"; $db_path =~ s!\\$!!o;
my $db_dflt = "$db_path\\$db_name";
my $db_batn = "\%$varn\%$tail\\$db_name"; $db_batn =~ s!\\\\!\\!go;

my ($outpath, $SQLITE3, $USER, $ACP,$ENC, $I, $DEBUG) = ('', 'sqlite3.exe', '', '','', '  ', 0);
{
   my ($h, $t, $cp, $l);
   RegOpenKeyEx(HKEY_LOCAL_MACHINE,'SYSTEM\CurrentControlSet\Control\Nls\CodePage',0,KEY_READ,$h);
   if ( $h ) {
      $ACP = 'CP' . substr($cp,0,-1) if ( RegQueryValueEx($h, 'ACP', [], $t, $cp, $l) );
      RegCloseKey($h);
   }
}
$ACP ||= 'CP1251';

#---------------------------------------------------------------------------------------------
#  command line parsing (except mode that already processed)
#
my ($i, $o, $v);
for ($i = 0; $i < scalar(@ARGV) - 1; $i += 2) {
   ($o, $v) = ($ARGV[$i], $ARGV[$i+1]);
   if    ( $o eq "-i" ) {
      die $usage unless( $v =~ m!^reg(=(.+))?$!o );
      $inpType = 'reg';
      if ( $2 ) {
         $inpType = 'file'; $regpath = $2;
         $regpath = substr($regpath,1,-1) if ( $regpath =~ m!^".+"$!o );
         unless ( $ENC ) {
            open(REG,"<:encoding($ACP)",$regpath) || die "Couldn't open file: '$regpath' - $!";
            { local $/; $text = <REG> }
            close(REG);
            if ( length($text) > 8 && "REGEDIT4" eq substr($text,0,8) ) { $ENC = $ACP; }
            else { $ENC = 'UTF-16'; $text = undef; }
         }
         unless( $text ) {
            open(REG,"<:encoding($ENC)",$regpath) || die "Couldn't open file: '$regpath' - $!";
            { local $/; $text = <REG> }
            close(REG);
         }
      }
   }
   elsif ( $o eq "-o" ) {
      die $usage unless( $v =~ m!^(db|xml|sql|bat)(=(.+))?$!o );
      $outType = $1;
      unless ( $3 ) {
         $outpath = $def_out . "." . $outType unless ( $outType eq 'db');
      }
      else {
         $outpath = $3;
         $outpath = substr($outpath,1,-1) if ( $outpath =~ m!^".+"$!o );
         $db_path = $outpath if ( $outType eq 'db' );
      }
   }
   elsif ( $o eq "-a"  ) { $ACP = $v; }
   elsif ( $o eq "-e"  ) { $ENC = $v; }
   elsif ( $o eq "-u"  ) { $USER = "\\Users\\$v"; }
   elsif ( $o eq "-db" ) { $db_path = $v; }
   else { die $usage; }
}
die $usage if ( !$inpType || !$outType || $i < scalar(@ARGV) );
if ( $outType eq 'db' )  {
   $outpath = "$db_path\\$db_name" if ( -d $db_path );
   $outpath ||= $db_name;
   die "db file missed: '$outpath'" unless -f $outpath;
}
$db_path = "$db_path\\$db_name" if ( $outType eq 'bat' && ! -f $db_path );
$db_batn = $db_path if ( lc($db_dflt) ne lc($db_path));

print "\nin: $inpType=$regpath\nout: $outType=$outpath [db=$db_path]\n" if ( $DEBUG );

my $tmp_sql = "$ENV{TEMP}\\$script1.sql";

my ($X, $S, $B) = (
'<?xml version="1.0" encoding="UTF-8" ?>
<farconfig>
', '', '@echo off
  set tmp_sql="%TEMP%' . "\\$script1.sql" . '"
  echo -- ' . $script1  . ' tmp file> %tmp_sql%
');

#---------------------------------------------------------------------------------------------
# subroutines
#
my ($KeyMap_ready, %KeyMap, %KeyNames) = (0);

sub OpenKey($)
{
   my ($key) = (@_);
   if ( $inpType eq 'reg' )
   {
      my $params = undef;
      RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Far2${USER}\\$key", 0, KEY_READ, $params);
      return $params;
   }

   my ($p1, $p2, $pe,$pe1, $kk, $k0) = (0);
   unless ( $KeyMap_ready )
   {
      $text =~ s!\r\n!\n!og;
      $text =~ s![ \t]\n!\n!og;
      $text =~ s!\n\[-HKEY\S+\]\n!\n!og;
      $text = "\n\n$text\n\n" ;
      
      for (;;) {
         $p1 = index($text, "\n\n[HKEY_CURRENT_USER\\", $p1);
         last if $p1 < 0;
         $p2 = index($text, "]\n", $p1+21);
         last if $p2 < 0;
         $kk = lc($k0 = substr($text, $p1+21, $p2-$p1-21));
         $p1 = index($text, "\n\n", $p2+1);
         last if $p1 < 0;
         next if $p1 <= $p2+1;
         next unless substr($kk,0,14) eq "software\\far2\\";
         if ( $USER ) {
            my $lu = length($USER);
            next unless length($kk) > 14+$lu;
            next unless substr($kk,14,$lu) ne lc($USER);
            substr($kk,14,$lu) = '';
         }
         next if substr($kk,14,5) eq "saved"; #Saved(Dialog|Folder||View)?History
         next if substr($kk,14,13) eq "pluginscache\\";
         next if substr($kk,14,14) eq "editor\\lastpos";
         next if substr($kk,14,14) eq "viewer\\lastpos";
         $KeyMap{$kk = substr($kk,14)} = $p2 + 2;
         $KeyNames{$kk} = substr($k0, 14);
      }
      ++$KeyMap_ready;
   }
   return undef unless( exists $KeyMap{$key = lc($key)} );

   $p1 = $KeyMap{$key};
   $pe = index($text, "\n\n", $p1);
   return undef if $pe <= $p1;
   
   my %params = ();
   for (;;) {
      last if ( $p1 >= $pe );
      $p2 = index($text, "\n", $p1);
      my ($s, $v, $t) = substr($text, $p1, $p2-$p1);
      $p1 = $p2 + 1;
      next unless $s =~ m!^"(%*\w+)"=(.+)!o;
      $kk = lc($k0 = $1);
      $s = $2;
      if ( $s =~ m!^dword:[0-9a-fA-F]+!o ) {
         $params{$kk} = { n=>$k0, t=>'i', v=> hex(substr($s,6)) };
      }
      elsif ( $s =~ m!^".*"$!o ) {
         $s = substr($s,1,-1);
         my $p = 0;
         while ( ($p = index($s,"\\",$p)) >= 0 && $p < length($s)-1 ) {
            my ($c1, $c2) = (substr($s, $p+1, 1), 0);
            if    ( "\\" eq $c1 ) { $c2 = "\\"; }
            elsif ( "\"" eq $c1 ) { $c2 = "\""; }
            elsif ( "n"  eq $c1 ) { $c2 = "\n"; }
            elsif ( "r"  eq $c1 ) { $c2 = "\r"; }
            elsif ( "t"  eq $c1 ) { $c2 = "\t"; }
            elsif ( "a"  eq $c1 ) { $c2 = "\a"; }
            substr($s, $p, 2) = $c2 if ( $c2 );
            ++$p;
         }
         $params{$kk} = { n=>$k0, t=>'s', v=>$s };
      }
      elsif ( $s =~ m!^hex\(b\):([0-9a-fA-F]{2}),([0-9a-fA-F]{2}),([0-9a-fA-F]{2}),([0-9a-fA-F]{2}),([0-9a-fA-F]{2}),([0-9a-fA-F]{2}),([0-9a-fA-F]{2}),([0-9a-fA-F]{2})\s*$!o ) {
         $t = 'I'; $v = "$8$7$6$5$4$3$2$1"; # as string
         $params{$kk} = { n=>$k0, t=>$t, v=> $v };
      }
      elsif ( $s =~ m!^hex(\(7\))?:!o ) {
         my $multi_sz = $1;
         while (substr($s,-1) eq "\\" && $p1 < $pe ) {
            $p2 = index($text, "\n", $p1);
            $s = substr($s,0,-1) . substr($text, $p1, $p2-$p1);
            $p1 = $p2 + 1;
         }
         my $v = '';
         ($p2, $pe1) = ($multi_sz ? 7 : 4, length($s));
         while ( $p2 <= $pe1-2 ) {
            my $c = ord(substr($s,$p2,1)) & 255;
            if ($c <= 0x20) { ++$p2; next; }
            $v .= chr(hex(substr($s,$p2,2)));
            last if ( substr($s,$p2+2,1) ne ',');
            $p2 += 3;
         }
         if ( !$multi_sz ) {
            $params{$kk} = { n=>$k0, t=>'b', v=>$v };
         }
         else {
            my $s = "";
            for (my $i = 0; $i < length($v)-2; $i += 2) {
               my $uc = (ord(substr($v,$i,1)) & 255) + 256*(ord(substr($v,$i+1,1)) & 255);
               $uc ||= 10;
               $s .= chr($uc);
            }
            $params{$kk} = { n=>$k0, t=>'s', v=>$s };
         }
      }
   }
   return \%params;
}

my ($SubKeys_Ready, %SubKeys) = (0);

sub EnumKeys($)
{
   my ($key) = @_;
   my @keys;
   if ( $inpType eq 'reg' ) {
      my $h = OpenKey($key);
      if ( $h ) {
         my ($l, $n) = (40);
         for (my $i = 0; RegEnumKeyEx($h, $i, $n, $l, [], [], [], []); ++$i) {
            push @keys, $n;
         }
         RegCloseKey($h);
      }
   }
   else {
      unless ( $SubKeys_Ready ) {
         %SubKeys = ();
         for my $k (keys %KeyMap) {
            if ( $k =~ m!^(.+\\)?([^\\]+)\s*$!o ) {
               my($base, $subk) = ($1 ? substr($1,0,length($1)-1) : '', $2);
               my $aa = $SubKeys{$base} || [];
               push @$aa, substr($KeyNames{$k}, -length($subk));
               $SubKeys{$base} = $aa;
            }
         }
         $SubKeys_Ready = 1;
      }
      my $a = $SubKeys{lc($key)};
      @keys = @$a if ( $a );
   }
   return @keys;
}

sub norm_regdata($$$)
{
   my ($t, $v, $vl) = @_;
   if ( $t == REG_DWORD ) {
      return ('i', unpack("V", $v));
   }
   elsif ( $t == REG_BINARY ) {
      return ('b', $v);
   }
   elsif ( $t == REG_SZ ) {
      $v = substr($v, 0, $vl-1) if ( $vl > 0);
      $v = decode($ACP, $v);
      return ('s', $v);
   }
   elsif ( $t == REG_MULTI_SZ ) {
      $v = substr($v, 0, $vl-1) if ( $vl > 0);
      $v = decode($ACP, $v);
      $v =~ s!\0!\n!og;
      return ('s', $v);      
   }
   elsif ( $t == 11 ) { # REG_QWORD
      return ('I', sprintf("%02x%02x%02x%02x%02x%02x%02x%02x",
         255 & ord(substr($v,7,1)), 255 & ord(substr($v,6,1)),
         255 & ord(substr($v,5,2)), 255 & ord(substr($v,4,1)),
         255 & ord(substr($v,3,1)), 255 & ord(substr($v,2,1)),
         255 & ord(substr($v,1,2)), 255 & ord(substr($v,0,1)))
      );
   }
   else {
      return (undef, undef);
   }
}

sub EnumValues($)
{
   my ($h) = @_;
   if ( $inpType eq 'reg' ) {
      my ($nl,$n, $v,$vl,$vv, $ii, $t,$tt, %vals) = (64);
      for ($ii = 0; RegEnumValue($h, $ii, $n, $nl, [], $t, $v, $vl); ++$ii) {
         ($tt, $vv) = norm_regdata($t, $v, $vl);
         $vals{$n} = { n=>$n, t=>$tt, v=>$vv, l=>$vl } if ( $t );
      }
      return %vals;
   }
   else {
      return %$h;
   }
}

sub QueryValue($$;$)
{
   my ($h, $k, $t) = @_;
   return undef unless $h;
   
   $t ||= 's';
   if ( $inpType eq 'reg' )
   {
      my ($v, $tt, $vl) = (undef, undef);
      if ( RegQueryValueEx($h, $k, [], $tt, $v, $vl) ) {
         ($tt, $v) = norm_regdata($tt, $v, $vl);
      }
      $v = undef if ( $t ne $tt );
      return $v;
   }
   
   $k = lc($k);
   return undef unless( exists( $h->{$k} ) );
   $h = $h->{$k};
   return undef if ( $h->{t} ne $t );
   return $h->{v};
}

sub CloseKey($)
{
   my ($h) = @_;
   RegCloseKey($h) if ( $h && $inpType eq 'reg' );
}

sub get_values($)
{
   my ($k) = @_;
   my $vals = OpenKey($k);
   return () unless $vals;
   my %values = EnumValues($vals);
   CloseKey($vals);
   return %values;
}

#---------------------------------------------------------------------------------------------
sub norm_xml($)
{
   my ($v) = @_;
   $v =~ s!\&!\&amp;!og;
   $v =~ s!"!\&quot;!og;
   $v =~ s!>!\&gt;!og;
   $v =~ s!<!\&lt;!og;
   return $v;
}

sub esc_bat($)
{
   my ($v) = @_;
   return "" unless defined($v) && $v ne '';
   $v =~ s/\&/^\&/og;
   $v =~ s/\|/^\|/og;
   $v =~ s/</^</og;
   $v =~ s/>/^>/og;
   $v =~ s/=/^=/og;
   $v =~ s/!/^!/og;
   $v =~ s/;/^;/og;
   return $v;
}
sub norm_bat($) # should (NOT MUST) work in many cases
{
   my ($s) = @_;
   return "" unless defined($s) && $s ne '';
   $s =~ s/\%/\%\%/og;
   my ($v, $n, $p2, $p1) = ("", length($s), 0);
   while ( $p2 < $n ) {
      $p1 = index($s, "\"", $p2);
      if ( $p1 < 0 ) {
         $v .= esc_bat(substr($s,$p2)); last;
      }
      $v .= esc_bat(substr($s, $p2, $p1-$p2));
      $p2 = index($s, "\"", $p1+1);
      if ( $p2 < 0 ) {
         $v .= '^"' . esc_bat(substr($s, $p1+1)); last;
      }
      $v .= substr($s, $p1, ++$p2-$p1);
   }
   return $v;
}

sub sql3_str($)
{
   my ($v) = @_;
   $v = "" unless defined($v) && $v ne '';
   $v =~ s!'!''!og;
   $v =~ s!\n!'||x'0a00'||'!og;
   $v =~ s!\r!'||x'0d00'||'!og;
   return "'$v'";
}

#---------------------------------------------------------------------------------------------
sub o1($)
{
   my ($s) = @_;
   $S .= "$s\n";
   $s = norm_bat($s);
   $B .= "  echo $s>> \%tmp_sql\%\n";
}

sub ov($\%)
{
   my ($m, $h) = @_;
   if ( scalar(keys %$h) <= 0 ) { $X .= "$I$I<${m}s />\n"; } else {
      $X .= "$I$I<${m}s>\n";
      for my $k (sort keys %$h) {
         my $v = $h->{$k}->{v};
         my $t = $h->{$k}->{t};
         my $k = $h->{$k}->{n};
         if    ( 'i' eq $t ) { $t = "integer"; $v = sprintf("%016x", $v); }
         elsif ( 'I' eq $t ) { $t = "integer"; }
         else  { $t = "text"; $v = "<![CDATA[$v]]>" if index($v,'<') >= 0; }
         $X .= sprintf("$I$I$I<${m} name=\"%s\" type=\"%s\"><text>%s</text></${m}>\n", $k,$t,$v);
         $v = sql3_str($v);
         o1("INSERT INTO ${m}s (name,value,type) VALUES ('$k',$v,'$t');");
      };   
      $X .= "$I$I</${m}s>\n";
   }
}

#---------------------------------------------------------------------------------------------
sub form($$)
{
   my ($nb, $o, $ty, $v) = (0, '', @_);
   
   if ( $ty eq 'i' ) {    #INTEGER
      $o = sprintf($outType ne 'xml' ? "%d" : "%016X", $v);
      substr($o, 0, 8) = "FFFFFFFF"  if ($v < 0 && substr($o,0,1) eq '0'); # fix perl-32
   }
   elsif ( $ty eq 's' ) { #TEXT
      $o = ( $outType ne 'xml' ) ? sql3_str($v) : norm_xml($v);
   }
   elsif ( $ty eq 'D' ) { #DATE
      $nb = 8;
   }
   elsif ( $ty eq 'C' ) { #COLOR
      my $tbg = (0 != ($v & 0x0000f000)) ? "\x00" : "\xff";
      my $tfg = (0 != ($v & 0x00000f00)) ? "\x00" : "\xff";
      $v = "\x03\x00\x00\x00\x00\x00\x00\x00" .
      chr($v & 15) . "\x00\x00" . $tfg . chr(($v >> 4) & 15) . "\x00\x00" . $tbg .
      "\x00\x00\x00\x00\x00\x00\x00\x00";
      $nb = 24;
   }
   else {
      die "unsupported field type: $ty";
   }
   if ( $nb ) {
      my ($b,$s,$e) = $outType ne 'xml' ? ("x'","","'") : ("",",","");
      for ( my $i = 0; $i < $nb; ++$i ) {
         $o .= ($i ? $s : $b) . sprintf("%02X", ord(substr($v,$i,1)));
      }
      $o .= $e;
   }
   return $o;
}

sub load_data($$\@)
{
   my ($class, $name, $ref_fields) = @_;
   my @Group = ();

   for ( $i = 0; $i < 100; ++$i )
   {
      my ($group, %group);
      $group = OpenKey(sprintf($reg_key,$class,$name,$i));
      next unless $group;
      for $o ( @$ref_fields ) {
         my $ty = $o->{ty};
         my $rt = $ty eq 'D' ? 'b' : $ty eq 'C' ? 'i' : $ty;
         $v = QueryValue($group, $o->{db}, $rt);
         $group{$o->{db}} = form($ty, $v) if ( defined $v );  
      }
      CloseKey($group);
      if ( $class eq 'SortGroups' && !exists($group{Mask}) ) {
         if ( $group = OpenKey($class) ) {
            $v = QueryValue($group, "$name$i", 's');
            $group{Mask} = form('s', $v) if ( defined $v );  
            CloseKey($group);            
         }
      }
      push @Group, \%group;
   }
   return @Group;
}

sub save_data(\@$$\@$)
{
   my ($ref_data, $group, $parent_id, $ref_fields, $ref_id) = @_;
   my @Data = @$ref_data;
   my @F = @$ref_fields;
   my $id = $$ref_id;
   
   for ( $i = 0; $i < scalar(@Data); ++$i )
   {
      my $a = $Data[$i];
      
      $X .= "$I$I$I$I<key name=\"$group$i\">\n";
      o1("INSERT INTO table_keys (id,parent_id,name) VALUES ($id,$parent_id,'$group$i');");
      for ( my $j = 0; $j < scalar(@F); ++$j )
      {
         my ($f, $ty, $xt) = ($F[$j]->{db}, $F[$j]->{ty}, $F[$j]->{xt});
         next unless exists $a->{$f};
         my $v = $a->{$f};
         o1("INSERT INTO table_values (key_id,name,value) VALUES ($id,'$f',$v);");         
         if ( $xt eq 'color' ) {
            my $fg = substr($v,33,2) . substr($v,30,2) . substr($v,27,2) . substr($v,24,2);
            my $bg = substr($v,45,2) . substr($v,42,2) . substr($v,39,2) . substr($v,36,2);
            $v = "background=\"$bg\" foreground=\"$fg\" flags=\"bg4bit|fg4bit\"";
         }
         else {
            $v = "value=\"$v\"";
         }
         $X .= "$I$I$I$I$I<value name=\"$f\" type=\"$xt\" $v />\n";
      }
      $X .= "$I$I$I$I</key>\n";
      ++$id;
   }
   $$ref_id = $id;
}

#---------------------------------------------------------------------------------------------
my $COL_PANELTEXT             =  10;
my $COL_PANELSELECTEDTEXT     =  11;
my $COL_LASTPALETTECOLOR      = 146;
my $FAR2_COL_RESERVED0        =  71;
my $FAR2_COL_LASTPALETTECOLOR = 147;

sub F_BLACK()        { 0x0000 }
sub F_BLUE()         { 0x0001 }
sub F_GREEN()        { 0x0002 }
sub F_CYAN()         { 0x0003 }
sub F_RED()          { 0x0004 }
sub F_MAGENTA()      { 0x0005 }
sub F_BROWN()        { 0x0006 }
sub F_LIGHTGRAY()    { 0x0007 }
sub F_DARKGRAY()     { 0x0008 }
sub F_LIGHTBLUE()    { 0x0009 }
sub F_LIGHTGREEN()   { 0x000A }
sub F_LIGHTCYAN()    { 0x000B }
sub F_LIGHTRED()     { 0x000C }
sub F_LIGHTMAGENTA() { 0x000D }
sub F_YELLOW()       { 0x000E }
sub F_WHITE()        { 0x000F }
sub B_BLACK()        { 0x0000 }
sub B_BLUE()         { 0x0010 }
sub B_GREEN()        { 0x0020 }
sub B_CYAN()         { 0x0030 }
sub B_RED()          { 0x0040 }
sub B_MAGENTA()      { 0x0050 }
sub B_BROWN()        { 0x0060 }
sub B_LIGHTGRAY()    { 0x0070 }
sub B_DARKGRAY()     { 0x0080 }
sub B_LIGHTBLUE()    { 0x0090 }
sub B_LIGHTGREEN()   { 0x00A0 }
sub B_LIGHTCYAN()    { 0x00B0 }
sub B_LIGHTRED()     { 0x00C0 }
sub B_LIGHTMAGENTA() { 0x00D0 }
sub B_YELLOW()       { 0x00E0 }
sub B_WHITE()        { 0x00F0 }

sub far3_color($$$)
{
   my ($palette, $i, $sep) = @_;

   my $v = ord(substr($palette, $i, 1));
   my ($fg, $bg, $tr) = ($v & 15, ($v >> 4) & 15, $i==$COL_PANELTEXT || $i==$COL_PANELSELECTEDTEXT);
   my ($fgc, $bgc, $trc) = (sprintf("%02X",$fg), sprintf("%02X",$bg), $tr ? '00' : 'FF');

   my @p3 =
   (  '03', '00', '00', '00', '00', '00', '00', '00' # FarColorFlags FG-4bit, BG-4bit
   ,  $fgc, '00', '00', 'FF'                         # foreground COLORREF
   ,  $bgc, '00', '00', $trc                         # background COLORREF
   ,  '00', '00', '00', '00', '00', '00', '00', '00' # Reserved and alignment
   );
   return join('', @p3) unless $sep;
   return "background=\"${trc}0000$bgc\" foreground=\"FF0000$fgc\" flags=\"bg4bit|fg4bit\"";
}

o1("BEGIN TRANSACTION;");

#---------------------------------------------------------------------------------------------
if ( 'assoc' eq $mode ) {{{ #-----------------------------------------------------------------
#---------------------------------------------------------------------------------------------
my @F = 
( { reg=>"Mask",        db=>"mask",        n=>0 }
, { reg=>"Description", db=>"description", n=>0 }
, { reg=>"Execute",     db=>"type",        n=>0 }
, { reg=>"AltExec",     db=>"type",        n=>1 }
, { reg=>"View",        db=>"type",        n=>2 }
, { reg=>"AltView",     db=>"type",        n=>3 }
, { reg=>"Edit",        db=>"type",        n=>4 }
, { reg=>"AltEdit",     db=>"type",        n=>5 }
);

my (@Assoc);
for ( $i = 0; $i < 100; ++$i )
{
   my %assoc;
   my $assoc = OpenKey(sprintf($reg_key,$i));
   next unless $assoc;
   for $o ( @F ) {
      $assoc{$o->{reg}} = QueryValue($assoc, $o->{reg});
   }
   CloseKey($assoc);
   if ( $assoc{Mask} ) { push @Assoc, \%assoc; }
}
die "assoc -- No associations were found" if ( scalar(@Assoc) <= 0 );
print "assoc -- ", scalar(@Assoc), " associations found\n";

# build Far3 Associations
#
$X .= "$I<associations>\n";

o1($_) for
( "DELETE FROM filetypes;"
, "DELETE FROM commands;"
);

for ( $i = 0; $i < scalar(@Assoc); ++$i )
{
   my ($id, $a) = ($i+1, $Assoc[$i]);
   my ($m, $d, $n) = ($a->{Mask}, $a->{Description});
   $X .= sprintf("$I$I<filetype mask=\"%s\" description=\"%s\">\n", norm_xml($m), norm_xml($d));
   $m = sql3_str($m); $d = sql3_str($d);
   o1("INSERT INTO filetypes (id,mask,weight,description) VALUES ($id,$m,1,$d);");
   for ( my $j = 0; $j < scalar(@F); ++$j )
   {
      my ($f, $n, $db) = ($F[$j]->{reg}, $F[$j]->{n}, $F[$j]->{db});
      next unless ( $db eq 'type' );
      my $c = $a->{$f};
      my $e = $c ? 1 : 0;
      $X .= sprintf("$I$I$I<command type=\"$n\" enabled=\"$e\" command=\"%s\" />\n", norm_xml($c));
      $c = sql3_str($c);
      o1("INSERT INTO commands (ft_id,type,enabled,command) VALUES ($id,$n,$e,$c);");
   }
   $X .= "$I$I</filetype>\n";
}
$X .= "$I</associations>\n";

#---------------------------------------------------------------------------------------------
}}} elsif ( 'colors' eq $mode ) {{{ #---------------------------------------------------------
#---------------------------------------------------------------------------------------------

my @FAR3_COLORS = ( 
["Menu.Text",                                   F_WHITE|B_CYAN        ],# COL_MENUTEXT,
["Menu.Text.Selected",                          F_WHITE|B_BLACK       ],# COL_MENUSELECTEDTEXT,
["Menu.Highlight",                              F_YELLOW|B_CYAN       ],# COL_MENUHIGHLIGHT,
["Menu.Highlight.Selected",                     F_YELLOW|B_BLACK      ],# COL_MENUSELECTEDHIGHLIGHT,
["Menu.Box",                                    F_WHITE|B_CYAN        ],# COL_MENUBOX,
["Menu.Title",                                  F_WHITE|B_CYAN        ],# COL_MENUTITLE,
["HMenu.Text",                                  F_BLACK|B_CYAN        ],# COL_HMENUTEXT,
["HMenu.Text.Selected",                         F_WHITE|B_BLACK       ],# COL_HMENUSELECTEDTEXT,
["HMenu.Highlight",                             F_YELLOW|B_CYAN       ],# COL_HMENUHIGHLIGHT,
["HMenu.Highlight.Selected",                    F_YELLOW|B_BLACK      ],# COL_HMENUSELECTEDHIGHLIGHT,
["Panel.Text",                                  F_LIGHTCYAN|B_BLUE    ],# COL_PANELTEXT,
["Panel.Text.Selected",                         F_YELLOW|B_BLUE       ],# COL_PANELSELECTEDTEXT,
["Panel.Text.Highlight",                        F_LIGHTGRAY|B_BLUE    ],# COL_PANELHIGHLIGHTTEXT,
["Panel.Text.Info",                             F_YELLOW|B_BLUE       ],# COL_PANELINFOTEXT,
["Panel.Cursor",                                F_BLACK|B_CYAN        ],# COL_PANELCURSOR,
["Panel.Cursor.Selected",                       F_YELLOW|B_CYAN       ],# COL_PANELSELECTEDCURSOR,
["Panel.Title",                                 F_LIGHTCYAN|B_BLUE    ],# COL_PANELTITLE,
["Panel.Title.Selected",                        F_BLACK|B_CYAN        ],# COL_PANELSELECTEDTITLE,
["Panel.Title.Column",                          F_YELLOW|B_BLUE       ],# COL_PANELCOLUMNTITLE,
["Panel.Info.Total",                            F_LIGHTCYAN|B_BLUE    ],# COL_PANELTOTALINFO,
["Panel.Info.Selected",                         F_YELLOW|B_CYAN       ],# COL_PANELSELECTEDINFO,
["Dialog.Text",                                 F_BLACK|B_LIGHTGRAY   ],# COL_DIALOGTEXT,
["Dialog.Text.Highlight",                       F_YELLOW|B_LIGHTGRAY  ],# COL_DIALOGHIGHLIGHTTEXT,
["Dialog.Box",                                  F_BLACK|B_LIGHTGRAY   ],# COL_DIALOGBOX,
["Dialog.Box.Title",                            F_BLACK|B_LIGHTGRAY   ],# COL_DIALOGBOXTITLE,
["Dialog.Box.Title.Highlight",                  F_YELLOW|B_LIGHTGRAY  ],# COL_DIALOGHIGHLIGHTBOXTITLE,
["Dialog.Edit",                                 F_BLACK|B_CYAN        ],# COL_DIALOGEDIT,
["Dialog.Button",                               F_BLACK|B_LIGHTGRAY   ],# COL_DIALOGBUTTON,
["Dialog.Button.Selected",                      F_BLACK|B_CYAN        ],# COL_DIALOGSELECTEDBUTTON,
["Dialog.Button.Highlight",                     F_YELLOW|B_LIGHTGRAY  ],# COL_DIALOGHIGHLIGHTBUTTON,
["Dialog.Button.Highlight.Selected",            F_YELLOW|B_CYAN       ],# COL_DIALOGHIGHLIGHTSELECTEDBUTTON,
["Dialog.List.Text",                            F_BLACK|B_LIGHTGRAY   ],# COL_DIALOGLISTTEXT,
["Dialog.List.Text.Selected",                   F_WHITE|B_BLACK       ],# COL_DIALOGLISTSELECTEDTEXT,
["Dialog.List.Highlight",                       F_YELLOW|B_LIGHTGRAY  ],# COL_DIALOGLISTHIGHLIGHT,
["Dialog.List.Highlight.Selected",              F_YELLOW|B_BLACK      ],# COL_DIALOGLISTSELECTEDHIGHLIGHT,
["WarnDialog.Text",                             F_WHITE|B_RED         ],# COL_WARNDIALOGTEXT,
["WarnDialog.Text.Highlight",                   F_YELLOW|B_RED        ],# COL_WARNDIALOGHIGHLIGHTTEXT,
["WarnDialog.Box",                              F_WHITE|B_RED         ],# COL_WARNDIALOGBOX,
["WarnDialog.Box.Title",                        F_WHITE|B_RED         ],# COL_WARNDIALOGBOXTITLE,
["WarnDialog.Box.Title.Highlight",              F_YELLOW|B_RED        ],# COL_WARNDIALOGHIGHLIGHTBOXTITLE,
["WarnDialog.Edit",                             F_BLACK|B_CYAN        ],# COL_WARNDIALOGEDIT,
["WarnDialog.Button",                           F_WHITE|B_RED         ],# COL_WARNDIALOGBUTTON,
["WarnDialog.Button.Selected",                  F_BLACK|B_LIGHTGRAY   ],# COL_WARNDIALOGSELECTEDBUTTON,
["WarnDialog.Button.Highlight",                 F_YELLOW|B_RED        ],# COL_WARNDIALOGHIGHLIGHTBUTTON,
["WarnDialog.Button.Highlight.Selected",        F_YELLOW|B_LIGHTGRAY  ],# COL_WARNDIALOGHIGHLIGHTSELECTEDBUTTON,
["Keybar.Num",                                  F_LIGHTGRAY|B_BLACK   ],# COL_KEYBARNUM,
["Keybar.Text",                                 F_BLACK|B_CYAN        ],# COL_KEYBARTEXT,
["Keybar.Background",                           F_LIGHTGRAY|B_BLACK   ],# COL_KEYBARBACKGROUND,
["CommandLine",                                 F_LIGHTGRAY|B_BLACK   ],# COL_COMMANDLINE,
["Clock",                                       F_BLACK|B_CYAN        ],# COL_CLOCK,
["Viewer.Text",                                 F_LIGHTCYAN|B_BLUE    ],# COL_VIEWERTEXT,
["Viewer.Text.Selected",                        F_BLACK|B_CYAN        ],# COL_VIEWERSELECTEDTEXT,
["Viewer.Status",                               F_BLACK|B_CYAN        ],# COL_VIEWERSTATUS,
["Editor.Text",                                 F_LIGHTCYAN|B_BLUE    ],# COL_EDITORTEXT,
["Editor.Text.Selected",                        F_BLACK|B_CYAN        ],# COL_EDITORSELECTEDTEXT,
["Editor.Status",                               F_BLACK|B_CYAN        ],# COL_EDITORSTATUS,
["Help.Text",                                   F_BLACK|B_CYAN        ],# COL_HELPTEXT,
["Help.Text.Highlight",                         F_WHITE|B_CYAN        ],# COL_HELPHIGHLIGHTTEXT,
["Help.Topic",                                  F_YELLOW|B_CYAN       ],# COL_HELPTOPIC,
["Help.Topic.Selected",                         F_WHITE|B_BLACK       ],# COL_HELPSELECTEDTOPIC,
["Help.Box",                                    F_BLACK|B_CYAN        ],# COL_HELPBOX,
["Help.Box.Title",                              F_BLACK|B_CYAN        ],# COL_HELPBOXTITLE,
["Panel.DragText",                              F_YELLOW|B_CYAN       ],# COL_PANELDRAGTEXT,
["Dialog.Edit.Unchanged",                       F_LIGHTGRAY|B_CYAN    ],# COL_DIALOGEDITUNCHANGED,
["Panel.Scrollbar",                             F_LIGHTCYAN|B_BLUE    ],# COL_PANELSCROLLBAR,
["Help.Scrollbar",                              F_BLACK|B_CYAN        ],# COL_HELPSCROLLBAR,
["Panel.Box",                                   F_LIGHTCYAN|B_BLUE    ],# COL_PANELBOX,
["Panel.ScreensNumber",                         F_LIGHTCYAN|B_BLACK   ],# COL_PANELSCREENSNUMBER,
["Dialog.Edit.Selected",                        F_WHITE|B_BLACK       ],# COL_DIALOGEDITSELECTED,
["CommandLine.Selected",                        F_BLACK|B_CYAN        ],# COL_COMMANDLINESELECTED,
["Viewer.Arrows",                               F_YELLOW|B_BLUE       ],# COL_VIEWERARROWS,
["Dialog.List.Scrollbar",                       F_BLACK|B_LIGHTGRAY   ],# COL_DIALOGLISTSCROLLBAR,
["Menu.Scrollbar",                              F_WHITE|B_CYAN        ],# COL_MENUSCROLLBAR,
["Viewer.Scrollbar",                            F_LIGHTCYAN|B_BLUE    ],# COL_VIEWERSCROLLBAR,
["CommandLine.Prefix",                          F_LIGHTGRAY|B_BLACK   ],# COL_COMMANDLINEPREFIX,
["Dialog.Disabled",                             F_DARKGRAY|B_LIGHTGRAY],# COL_DIALOGDISABLED,
["Dialog.Edit.Disabled",                        F_DARKGRAY|B_CYAN     ],# COL_DIALOGEDITDISABLED,
["Dialog.List.Disabled",                        F_DARKGRAY|B_LIGHTGRAY],# COL_DIALOGLISTDISABLED,
["WarnDialog.Disabled",                         F_DARKGRAY|B_RED      ],# COL_WARNDIALOGDISABLED,
["WarnDialog.Edit.Disabled",                    F_DARKGRAY|B_CYAN     ],# COL_WARNDIALOGEDITDISABLED,
["WarnDialog.List.Disabled",                    F_DARKGRAY|B_RED      ],# COL_WARNDIALOGLISTDISABLED,
["Menu.Text.Disabled",                          F_DARKGRAY|B_CYAN     ],# COL_MENUDISABLEDTEXT,
["Editor.Clock",                                F_BLACK|B_CYAN        ],# COL_EDITORCLOCK,
["Viewer.Clock",                                F_BLACK|B_CYAN        ],# COL_VIEWERCLOCK,
["Dialog.List.Title",                           F_BLACK|B_LIGHTGRAY   ],# COL_DIALOGLISTTITLE
["Dialog.List.Box",                             F_BLACK|B_LIGHTGRAY   ],# COL_DIALOGLISTBOX,
["WarnDialog.Edit.Selected",                    F_WHITE|B_BLACK       ],# COL_WARNDIALOGEDITSELECTED,
["WarnDialog.Edit.Unchanged",                   F_LIGHTGRAY|B_CYAN    ],# COL_WARNDIALOGEDITUNCHANGED,
["Dialog.Combo.Text",                           F_WHITE|B_CYAN        ],# COL_DIALOGCBOXTEXT,
["Dialog.Combo.Text.Selected",                  F_WHITE|B_BLACK       ],# COL_DIALOGCBOXSELECTEDTEXT,
["Dialog.Combo.Highlight",                      F_YELLOW|B_CYAN       ],# COL_DIALOGCBOXHIGHLIGHT,
["Dialog.Combo.Highlight.Selected",             F_YELLOW|B_BLACK      ],# COL_DIALOGCBOXSELECTEDHIGHLIGHT,
["Dialog.Combo.Box",                            F_WHITE|B_CYAN        ],# COL_DIALOGCBOXBOX,
["Dialog.Combo.Title",                          F_WHITE|B_CYAN        ],# COL_DIALOGCBOXTITLE,
["Dialog.Combo.Disabled",                       F_DARKGRAY|B_CYAN     ],# COL_DIALOGCBOXDISABLED,
["Dialog.Combo.Scrollbar",                      F_WHITE|B_CYAN        ],# COL_DIALOGCBOXSCROLLBAR,
["WarnDialog.List.Text",                        F_WHITE|B_RED         ],# COL_WARNDIALOGLISTTEXT,
["WarnDialog.List.Text.Selected",               F_BLACK|B_LIGHTGRAY   ],# COL_WARNDIALOGLISTSELECTEDTEXT,
["WarnDialog.List.Highlight",                   F_YELLOW|B_RED        ],# COL_WARNDIALOGLISTHIGHLIGHT,
["WarnDialog.List.Highlight.Selected",          F_YELLOW|B_LIGHTGRAY  ],# COL_WARNDIALOGLISTSELECTEDHIGHLIGHT,
["WarnDialog.List.Box",                         F_WHITE|B_RED         ],# COL_WARNDIALOGLISTBOX,
["WarnDialog.List.Title",                       F_WHITE|B_RED         ],# COL_WARNDIALOGLISTTITLE,
["WarnDialog.List.Scrollbar",                   F_WHITE|B_RED         ],# COL_WARNDIALOGLISTSCROLLBAR,
["WarnDialog.Combo.Text",                       F_WHITE|B_CYAN        ],# COL_WARNDIALOGCBOXTEXT,
["WarnDialog.Combo.Text.Selected",              F_WHITE|B_BLACK       ],# COL_WARNDIALOGCBOXSELECTEDTEXT,
["WarnDialog.Combo.Highlight",                  F_YELLOW|B_CYAN       ],# COL_WARNDIALOGCBOXHIGHLIGHT,
["WarnDialog.Combo.Highlight.Selected",         F_YELLOW|B_BLACK      ],# COL_WARNDIALOGCBOXSELECTEDHIGHLIGHT,
["WarnDialog.Combo.Box",                        F_WHITE|B_CYAN        ],# COL_WARNDIALOGCBOXBOX,
["WarnDialog.Combo.Title",                      F_WHITE|B_CYAN        ],# COL_WARNDIALOGCBOXTITLE,
["WarnDialog.Combo.Disabled",                   F_DARKGRAY|B_CYAN     ],# COL_WARNDIALOGCBOXDISABLED,
["WarnDialog.Combo.Scrollbar",                  F_WHITE|B_CYAN        ],# COL_WARNDIALOGCBOXSCROLLBAR,
["Dialog.List.Arrows",                          F_YELLOW|B_LIGHTGRAY  ],# COL_DIALOGLISTARROWS,
["Dialog.List.Arrows.Disabled",                 F_DARKGRAY|B_LIGHTGRAY],# COL_DIALOGLISTARROWSDISABLED,
["Dialog.List.Arrows.Selected",                 F_YELLOW|B_BLACK      ],# COL_DIALOGLISTARROWSSELECTED,
["Dialog.Combo.Arrows",                         F_YELLOW|B_CYAN       ],# COL_DIALOGCOMBOARROWS,
["Dialog.Combo.Arrows.Disabled",                F_DARKGRAY|B_CYAN     ],# COL_DIALOGCOMBOARROWSDISABLED,
["Dialog.Combo.Arrows.Selected",                F_YELLOW|B_BLACK      ],# COL_DIALOGCOMBOARROWSSELECTED,
["WarnDialog.List.Arrows",                      F_YELLOW|B_RED        ],# COL_WARNDIALOGLISTARROWS,
["WarnDialog.List.Arrows.Disabled",             F_DARKGRAY|B_RED      ],# COL_WARNDIALOGLISTARROWSDISABLED,
["WarnDialog.List.Arrows.Selected",             F_YELLOW|B_LIGHTGRAY  ],# COL_WARNDIALOGLISTARROWSSELECTED,
["WarnDialog.Combo.Arrows",                     F_YELLOW|B_CYAN       ],# COL_WARNDIALOGCOMBOARROWS,
["WarnDialog.Combo.Arrows.Disabled",            F_DARKGRAY|B_CYAN     ],# COL_WARNDIALOGCOMBOARROWSDISABLED,
["WarnDialog.Combo.Arrows.Selected",            F_YELLOW|B_BLACK      ],# COL_WARNDIALOGCOMBOARROWSSELECTED,
["Menu.Arrows",                                 F_YELLOW|B_CYAN       ],# COL_MENUARROWS,
["Menu.Arrows.Disabled",                        F_DARKGRAY|B_CYAN     ],# COL_MENUARROWSDISABLED,
["Menu.Arrows.Selected",                        F_YELLOW|B_BLACK      ],# COL_MENUARROWSSELECTED,
["CommandLine.UserScreen",                      F_LIGHTGRAY|B_BLACK   ],# COL_COMMANDLINEUSERSCREEN,
["Editor.Scrollbar",                            F_LIGHTCYAN|B_BLUE    ],# COL_EDITORSCROLLBAR,
["Menu.GrayText",                               F_DARKGRAY|B_CYAN     ],# COL_MENUGRAYTEXT,
["Menu.GrayText.Selected",                      F_LIGHTGRAY|B_BLACK   ],# COL_MENUSELECTEDGRAYTEXT,
["Dialog.Combo.GrayText",                       F_DARKGRAY|B_CYAN     ],# COL_DIALOGCOMBOGRAY,
["Dialog.Combo.GrayText.Selected",              F_LIGHTGRAY|B_BLACK   ],# COL_DIALOGCOMBOSELECTEDGRAYTEXT,
["Dialog.List.GrayText",                        F_DARKGRAY|B_LIGHTGRAY],# COL_DIALOGLISTGRAY,
["Dialog.List.GrayText.Selected",               F_LIGHTGRAY|B_BLACK   ],# COL_DIALOGLISTSELECTEDGRAYTEXT,
["WarnDialog.Combo.GrayText",                   F_DARKGRAY|B_CYAN     ],# COL_WARNDIALOGCOMBOGRAY,
["WarnDialog.Combo.GrayText.Selected",          F_LIGHTGRAY|B_BLACK   ],# COL_WARNDIALOGCOMBOSELECTEDGRAYTEXT,
["WarnDialog.List.GrayText",                    F_DARKGRAY|B_RED      ],# COL_WARNDIALOGLISTGRAY,
["WarnDialog.List.GrayText.Selected",           F_BLACK|B_LIGHTGRAY   ],# COL_WARNDIALOGLISTSELECTEDGRAYTEXT,
["Dialog.DefaultButton",                        F_BLACK|B_LIGHTGRAY   ],# COL_DIALOGDEFAULTBUTTON,
["Dialog.DefaultButton.Selected",               F_BLACK|B_CYAN        ],# COL_DIALOGSELECTEDDEFAULTBUTTON,
["Dialog.DefaultButton.Highlight",              F_YELLOW|B_LIGHTGRAY  ],# COL_DIALOGHIGHLIGHTDEFAULTBUTTON,
["Dialog.DefaultButton.Highlight.Selected",     F_YELLOW|B_CYAN       ],# COL_DIALOGHIGHLIGHTSELECTEDDEFAULTBUTTON,
["WarnDialog.DefaultButton",                    F_WHITE|B_RED         ],# COL_WARNDIALOGDEFAULTBUTTON,
["WarnDialog.DefaultButton.Selected",           F_BLACK|B_LIGHTGRAY   ],# COL_WARNDIALOGSELECTEDDEFAULTBUTTON,
["WarnDialog.DefaultButton.Highlight",          F_YELLOW|B_RED        ],# COL_WARNDIALOGHIGHLIGHTDEFAULTBUTTON,
["WarnDialog.DefaultButton.Highlight.Selected", F_YELLOW|B_LIGHTGRAY  ])# COL_WARNDIALOGHIGHLIGHTSELECTEDDEFAULTBUTTON,
;    

my %FAR3_COLOR_INDEX = ();
my $my_default_palette = "\x30" x $FAR2_COL_LASTPALETTECOLOR;
for ($i = 0; $i < $FAR2_COL_LASTPALETTECOLOR; ++$i) {
   next if ( $i == $FAR2_COL_RESERVED0 );
   my $j = ( $i < $FAR2_COL_RESERVED0 ? $i : $i-1 );
   substr($my_default_palette, $i, 1) = chr($FAR3_COLORS[$j]->[1]);
   $FAR3_COLOR_INDEX{$FAR3_COLORS[$j]->[0]} = $j;
}                                                                
my $colors = OpenKey($reg_key);
my $palette = QueryValue($colors, "CurrentPalette", 'b') || '';
CloseKey($colors);

$i = length($palette);
if ( $i < $FAR2_COL_LASTPALETTECOLOR ) {
  warn("palette too short -- default colors added\n");
  $palette .= substr($my_default_palette, $i);
  $i = length($palette);
}
die "Bad Far2 palette length: $i" if ( $i != $FAR2_COL_LASTPALETTECOLOR );
substr($palette,$FAR2_COL_RESERVED0,1) = '';

$X .= "$I<colors>\n";
for $i ( sort keys %FAR3_COLOR_INDEX ) {
   $v = far3_color($palette, $FAR3_COLOR_INDEX{$i}, ',');
   $X .= "$I$I<object name=\"$i\" $v />\n";
}
$X .= "$I</colors>\n";

o1("DELETE FROM colors;");
for ( $i = 0; $i < $COL_LASTPALETTECOLOR; ++$i ) {
   my $n = $FAR3_COLORS[$i]->[0];
   $v = far3_color($palette, $i, '');
   o1("INSERT INTO colors (name,value) VALUES ('$n',x'$v');");
}

#---------------------------------------------------------------------------------------------
}}} elsif ( 'highlight' eq $mode ) {{{ #------------------------------------------------------
#---------------------------------------------------------------------------------------------
my @F = 
( { db=>"AttrClear",                   ty=>'i', xt=>'qword' } # - S
, { db=>"AttrSet",                     ty=>'i', xt=>'qword' } # - S
, { db=>"ContinueProcessing",          ty=>'i', xt=>'qword' } # H S
, { db=>"CursorColor",                 ty=>'C', xt=>'color' } # H S
, { db=>"DateAfter",                   ty=>'D', xt=>'hex'   } # H S
, { db=>"DateBefore",                  ty=>'D', xt=>'hex'   } # H S
, { db=>"DateRelative",                ty=>'i', xt=>'qword' } # H S
, { db=>"DateType",                    ty=>'i', xt=>'qword' } # H S
, { db=>"ExcludeAttributes",           ty=>'i', xt=>'qword' } # H -
, { db=>"IgnoreMask",                  ty=>'i', xt=>'qword' } # H -
, { db=>"IncludeAttributes",           ty=>'i', xt=>'qword' } # H -
, { db=>"MarkChar",                    ty=>'i', xt=>'qword' } # H S
, { db=>"MarkCharCursorColor",         ty=>'C', xt=>'color' } # H S
, { db=>"MarkCharNormalColor",         ty=>'C', xt=>'color' } # H S
, { db=>"MarkCharSelectedColor",       ty=>'C', xt=>'color' } # H S
, { db=>"MarkCharSelectedCursorColor", ty=>'C', xt=>'color' } # H S
, { db=>"Mask",                        ty=>'s', xt=>'text'  } # H S
, { db=>"NormalColor",                 ty=>'C', xt=>'color' } # H S
, { db=>"SelectedColor",               ty=>'C', xt=>'color' } # H S
, { db=>"SelectedCursorColor",         ty=>'C', xt=>'color' } # H S
, { db=>"SizeAboveS",                  ty=>'s', xt=>'text'  } # H S
, { db=>"SizeBelowS",                  ty=>'s', xt=>'text'  } # H S
, { db=>"UseAttr",                     ty=>'i', xt=>'qword' } # H S
, { db=>"UseDate",                     ty=>'i', xt=>'qword' } # H S
, { db=>"UseMask",                     ty=>'i', xt=>'qword' } # - S
, { db=>"UseSize",                     ty=>'i', xt=>'qword' } # H S
);
my %I; for (my $i = 0; $i < scalar(@F); ++$i ) { $I{lc($F[$i]->{db})} = $i; }

my (@Hlite1,@Hlite2, @Sorts1,@Sorts2);

my $nh1 = scalar(@Hlite1 = load_data("Colors\\Highlight",    "Group", @F));
my $nh2 = scalar(@Hlite2 = load_data("Colors\\Highlight","LastGroup", @F));
my $ns1 = scalar(@Sorts1 = load_data("SortGroups",      "UpperGroup", @F));
my $ns2 = scalar(@Sorts2 = load_data("SortGroups",      "LowerGroup", @F));
print "Highlight: $nh1/$nh2 SortGroups: $ns1/$ns2\n";

$X .= "$I<highlight>\n$I$I<hierarchicalconfig>\n";

o1($_) for
( "DELETE FROM table_values;"
, "DELETE FROM table_keys WHERE id != 0;"
, "INSERT INTO table_keys (id,parent_id,name) VALUES (1,0,'Highlight');"
, "INSERT INTO table_keys (id,parent_id,name) VALUES (2,0,'SortGroups');"
);
my ($h_id, $s_id, $id) = (1, 2, 3);

$X .= "$I$I$I<key name=\"Highlight\">\n"  if ($nh1 > 0 || $nh2 > 0);
save_data(@Hlite1, "Group",     $h_id, @F, \$id) if ($nh1 > 0);
save_data(@Hlite2, "LastGroup", $h_id, @F, \$id) if ($nh2 > 0);
$X .= "$I$I$I</key>\n"                    if ($nh1 > 0 || $nh2 > 0);

$X .= "$I$I$I<key name=\"SortGroups\">\n" if ($ns1 > 0 || $ns2 > 0);
save_data(@Sorts1, "UpperGroup", $s_id, @F, \$id) if ($ns1 > 0);
save_data(@Sorts2, "LowerGroup", $s_id, @F, \$id) if ($ns2 > 0);
$X .= "$I$I$I</key>\n"                    if ($ns1 > 0 || $ns2 > 0);

$X .= "$I$I</hierarchicalconfig>\n$I</highlight>\n";

#---------------------------------------------------------------------------------------------
}}} elsif ( 'macros' eq $mode ) {{{ #---------------------------------------------------------
#---------------------------------------------------------------------------------------------
my @Areas = qw(
   Other Shell Viewer Editor Dialog Search Disks MainMenu Menu Help Info QView Tree FindFolder
   UserMenu AutoCompletion Common
);
my %Flags =
(  DisableOutput       => 0x00000100
,  NoSendKeysToPlugins => 0x00000200
,  RunAfterFARStart    => 0x00000800
,  EmptyCommandLine    => 0x00001000
,  NotEmptyCommandLine => 0x00002000
,  EVSelection         => 0x00004000
,  NoEVSelection       => 0x00008000
,  Selection           => 0x00010000
,  PSelection          => 0x00020000
,  NoSelection         => 0x00040000
,  NoPSelection        => 0x00080000
,  NoFilePanels        => 0x00100000
,  NoFilePPanels       => 0x00200000
,  NoPluginPanels      => 0x00400000
,  NoPluginPPanels     => 0x00800000
,  NoFolders           => 0x01000000
,  NoPFolders          => 0x02000000
,  NoFiles             => 0x04000000
,  NoPFiles            => 0x08000000
);

my %Consts = get_values("$reg_key\\Consts");
my %Vars   = get_values("$reg_key\\Vars");
my %Macros = ();

for my $a (@Areas) {
   my @keys = EnumKeys("$reg_key\\$a");
   next unless scalar(@keys) > 0;
   for my $k (@keys) {
      my $vals = OpenKey("$reg_key\\$a\\$k");
      next unless $vals;
      my $seq = QueryValue($vals, "Sequence");
      my $desc = QueryValue($vals, "Description");
      my ($flags, $flagi) = ("", 0);
      for my $f (keys %Flags) {
         my $v = QueryValue($vals, $f, 'i');
         if ( defined $v && $v ) {
            $flagi |= $Flags{$f}; $flags = $flags ? "$flags|$f" : $f;
         }
      }
      CloseKey($vals);
      next unless $seq;
      $Macros{"$a\\$k"} = { seq=>$seq, desc=>$desc, flags=>$flags, flagi=>$flagi };
   }
}

printf( "$mode -- Consts: %d; Vars: %d; Macros: %d\n",
   scalar(keys %Consts), scalar(keys %Vars), scalar(keys %Macros)
);

# build Far3 Macroses
#
$X .= "$I<macros>\n";

o1($_) for
( "DELETE FROM constants;"
, "DELETE FROM variables;"
, "DELETE FROM key_macros;"
);

ov("constant", %Consts);
ov("variable", %Vars);

if ( scalar(keys %Macros) <= 0 ) { $X .= "$I$I<keymacros />\n"; } else {
   $X .= "$I$I<keymacros>\n";
   for my $k (sort keys %Macros) {
      my ($p, $area, $key, $desc, $seq, $flags ) = index($k, "\\");
      $area = substr($k, 0, $p);
      $key = substr($k, $p+1);
      $seq = $Macros{$k}->{seq};
      $p = index($seq, "<"); if ( $p < 0 ) { $p = index($seq, "&"); }
      $flags = $Macros{$k}->{flags} || "";
      $desc = $desc ? " description=\"" . norm_xml($Macros{$k}->{desc}) . "\"" : "";
      $X .= sprintf("$I$I$I<macro area=\"%s\" key=\"%s\" flags=\"%s\"%s>\n", $area,norm_xml($key),$flags,$desc);
      $X .= "$I$I$I$I<text>";
      $X .= "<![CDATA[" if ( $p >= 0 );
      $X .= $seq;
      $X .= "]]>" if ( $p >= 0 );
      $X .= "</text>\n$I$I$I</macro>\n";
      my $v = "'$area'," . sql3_str($key) . ",'$flags'," . sql3_str($desc) . "," . sql3_str($seq);
      o1("INSERT INTO key_macros (area,key,flags,description,sequence) VALUES ($v);");
   };   
   $X .= "$I$I</keymacros>\n";
}

$X .= "$I</macros>\n";

#---------------------------------------------------------------------------------------------
}}} elsif ( 'panel' eq $mode ) {{{ #----------------------------------------------------------
#---------------------------------------------------------------------------------------------
my @F = 
( { reg=>"Columns",               flag=>0,  ty=>'s', xt=>'text'  } # ="NM,SC,D"
, { reg=>"ColumnWidths",          flag=>0,  ty=>'s', xt=>'text'  } # ="0,10,0"
, { reg=>"StatusColumns",         flag=>0,  ty=>'s', xt=>'text'  } # ="NR"
, { reg=>"StatusColumnWidths",    flag=>0,  ty=>'s', xt=>'text'  } # ="0"
, { reg=>"FullScreen",            flag=>1,  ty=>'i', xt=>'qword' } # =dword:00000000
, { reg=>"AlignExtensions",       flag=>2,  ty=>'i', xt=>'qword' } # =dword:00000000
, { reg=>"FolderAlignExtensions", flag=>4,  ty=>'i', xt=>'qword' } # =dword:00000000
, { reg=>"FolderUpperCase",       flag=>8,  ty=>'i', xt=>'qword' } # =dword:00000000
, { reg=>"FileLowerCase",         flag=>16, ty=>'i', xt=>'qword' } # =dword:00000000
, { reg=>"FileUpperToLowerCase",  flag=>32, ty=>'i', xt=>'qword' } # =dword:00000000
, { reg=>"Flags",                 flag=>-1, ty=>'i', xt=>'qword' }
);

my @Modes;
for ( $i = 0; $i < 100; ++$i )
{
   my ($flags, %mode) = (0);
   my $mode = OpenKey(sprintf($reg_key,$i));
   next unless $mode;
   for $o ( @F ) {
      my ($nm, $ty, $fl) = ( $o->{reg}, $o->{ty}, $o->{flag} );
      my $rt = $ty eq 'D' ? 'b' : $ty eq 'C' ? 'i' : $ty;
      my $v = QueryValue($mode, $nm, $rt) || 0;
      if ( $fl ) { $flags += $fl if ($v > 0 && $fl > 0); }
      else {
         $mode{$nm} = form($ty, $v);  
      }
   }
   CloseKey($mode);
   $mode{Flags} = $flags;
   push @Modes, \%mode;
}
print "panel -- ", scalar(@Modes), " panel modes found\n";

$X .= "$I<panelmodes>\n$I$I<hierarchicalconfig>\n";

o1($_) for
( "DELETE FROM table_values;"
, "DELETE FROM table_keys WHERE id != 0;"
);
my $id = 1;

for ( $i = 0; $i < scalar(@Modes); ++$i )
{
   my $a = $Modes[$i];
      
   $X .= "$I$I$I<key name=\"$i\">\n";
   o1("INSERT INTO table_keys (id,parent_id,name) VALUES ($id,0,'$i');");
   for ( my $j = 0; $j < scalar(@F); ++$j )
   {
      my ($nm, $ty, $xt, $fl) = ($F[$j]->{reg}, $F[$j]->{ty}, $F[$j]->{xt}, $F[$j]->{flag});
      next unless ($fl <= 0) && (exists $a->{$nm});
      my $v = $a->{$nm};
      $X .= "$I$I$I$I<value name=\"$nm\" type=\"$xt\" value=\"$v\" />\n";
      o1("INSERT INTO table_values (key_id,name,value) VALUES ($id,'$nm',$v);");         
   }
   $X .= "$I$I$I</key>\n";
   ++$id;
}
$X .= "$I$I</hierarchicalconfig>\n$I</panelmodes>\n";

#---------------------------------------------------------------------------------------------
}}} elsif ( 'shortcut' eq $mode ) {{{ #-------------------------------------------------------
#---------------------------------------------------------------------------------------------
my ($ns, $data, %Data) = (0);

$data = OpenKey($reg_key); #std
for ( $i = 0; $i <= 9; ++$i )
{
   my (@data, $data1);
   $v = QueryValue($data, "Shortcut$i");
   push @data, $v if defined $v;
   $data1 = OpenKey(sprintf($reg_ke1, $i)); # bis?
   if ( $data1 ) {
      for ( my $j = 0; $j <= 9; ++$j ) {
         $v = QueryValue($data1, "Shortcut$j");
         for ( my $k = 0; defined($v) && $k < scalar(@data); ++$k ) {
            if ( lc($v) eq lc($data[$k]) ) { $v = undef; last; }
         }
         push(@data, $v) if defined $v;
      }
      CloseKey($data1);
   }
   $Data{$i} = \@data if ( @data );
   $ns += @data;
}
CloseKey($data) if ( $data );   
   
print "shortcut -- ", scalar(keys %Data), "/$ns Shortcuts found\n";
exit 0 if ( $ns <= 0 );

$X .= "$I<shortcuts>\n$I$I<hierarchicalconfig>\n$I$I$I<key name=\"Shortcuts\">\n";

o1($_) for
( "DELETE FROM table_values;"
, "DELETE FROM table_keys WHERE id != 0;"
, "INSERT INTO table_keys (id,parent_id,name) VALUES (1,0,'Shortcuts');"
);
my ($p_id, $id) = (1, 2);

for my $s (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
{
   $X .= "$I$I$I$I<key name=\"$s\">\n";
   o1("INSERT INTO table_keys (id,parent_id,name) VALUES ($id,$p_id,'$s');");
   my @d = ();
   if ( exists $Data{$s} ) { my $d = $Data{$s}; @d = @$d; }
   for (my $n = 0; $n < scalar(@d); ++$n) {
      $X .= sprintf("$I$I$I$I$I<value name=\"Shortcut$n\" type=\"text\" value=\"%s\" />\n", norm_xml($d[$n]));
      my $d = sql3_str($d[$n]);
      o1("INSERT INTO table_values (key_id,name,value) VALUES ($id,'Shortcut$n',$d);");         
   }
   $X .= "$I$I$I$I</key>\n";
   ++$id;
}
$X .= "$I$I$I</key>\n$I$I</hierarchicalconfig>\n$I</shortcuts>\n";

#---------------------------------------------------------------------------------------------
}}} #-----------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------

# store settings
#
$X .= "</farconfig>\n";
o1("END TRANSACTION;");

$db_batn = decode($ACP, $db_batn);
$B .= '  for /f "tokens=*" %%c in (' . "'chcp'" . ') do call :save_cp %%c
  chcp 65001 >NUL
  ' . "\"$SQLITE3\" \"$db_batn\"" . ' <%tmp_sql% && echo Ok || echo failed
  chcp %saved_cp% >NUL
goto :EOF
:save_cp
  if not "" == "%~2" shift& goto :save_cp
  set saved_cp=%~1
goto :EOF
';

if ( $outType ne 'db' ) {
   open(OUT, ">:encoding(UTF-8)", $outpath) || die "Can't create: '$outpath' -- $!";
   $X = $S if ( $outType eq 'sql' );
   $X = $B if ( $outType eq 'bat' );
   print OUT $X;
   close OUT;
}
else {
   open( TMP, ">:encoding(UTF-8)", $tmp_sql) || die "Can't create file: '$tmp_sql' -- $!";
   print TMP $S;
   close TMP;
   my $out = `\"$SQLITE3\" \"$outpath\" <\"$tmp_sql\"`;
   my $res = $?;
   print "\n", $res ? $out : "OK\n"
}

#---------------------------------------------------------------------------------------------
exit 0;
