File Coverage

blib/lib/HP200LX/DB.pm
Criterion Covered Total %
statement 212 795 26.6
branch 69 294 23.4
condition 37 227 16.3
subroutine 19 53 35.8
pod 0 45 0.0
total 337 1414 23.8


line stmt bran cond sub pod time code
1             #
2             # FILE %gg/perl/HP200LX/DB.pm
3             #
4             # access HP 200LX database files
5             # See POD Section for a few more details
6             #
7             # work area:
8             # decode_type14
9             # dump_type .. export everything in ASCII format
10             # loader .. import everything from ASCII format
11             #
12             # written: 1997-12-28 (c) g.gonter@ieee.org
13             # latest update: 2001-02-09 17:22:39
14             # $Id: DB.pm,v 1.13 2001/03/05 01:52:39 gonter Exp $
15             #
16              
17             package HP200LX::DB;
18              
19 1     1   1548 use strict;
  1         2  
  1         190  
20 1     1   5 use vars qw($VERSION @ISA @EXPORT_OK @REC_TYPE);
  1         2  
  1         115  
21 1     1   6 use Exporter;
  1         80  
  1         119  
22              
23             $VERSION= '0.09';
24             @ISA= qw(Exporter);
25             @EXPORT_OK= qw(openDB saveDB
26             fmt_date fmt_time pack_date hex_dump
27             );
28              
29 1     1   676 use HP200LX::DB::vpt; # view point management, including vpt definition
  1         2  
  1         12428  
30              
31             # ----------------------------------------------------------------------------
32             my $no_note= 65535; # note number if there is no note
33             my $no_val= 65535; # NIL, empty list, -1 etc.
34             my $no_time= 32768; # empty time field
35             my $no_year= 255; # empty year, mon, day elements
36             my $no_mon= 255;
37             my $no_day= 255;
38             my $no_date= 255; # ... no_date values
39             my $delim= '-'x 74; # optic delimiter
40              
41             # ----------------------------------------------------------------------------
42             my @REC_TYPE= # HP's internal record type definitions
43             (
44             'DBHEADER', # 0
45             'PASSWORD', # 1: only present when a password was set
46             '', # 2
47             '', # 3
48             'CARDDEF', # 4
49             'CATEGORY', # 5
50             'FIELDDEF', # 6
51             'VIEWPTDEF', # 7 sort and subset
52             '', # 8
53             'NOTE', # 9
54             'VIEWPTTABLE', # 10 table of viewpoint entries
55             'DATA', # 11
56             'LINKDEF', # 12: usually smart clips
57             'CARDPAGEDEF', # 13
58             '', # 14 APP:
59             # + ADB: appt_info
60             'SMART_CLIP', # 15 APP: smart clip def in appt.adb (GG)
61             # + ADB: appt_list (adbio)
62             '', # 16 APP
63             '', # 17 APP
64             '', # 18 APP
65             '', # 19 APP
66             '', # 20 APP
67             '', # 21 APP
68             '', # 22 APP
69             '', # 23 APP
70             '', # 24 APP
71             '', # 25 APP
72             '', # 26 APP
73             '', # 27 APP
74             '', # 28 APP
75             '', # 29 APP
76             '', # 30 APP
77             'LOOKUPTABLE' # 31
78             # 14..30 application specific!
79             );
80 0 0   0 0 0 sub REC_TYPE { my $num= shift; $REC_TYPE[$num] || "USER_TYPE_$num"; }
  0         0  
81              
82             # ----------------------------------------------------------------------------
83             my @FIELD_TYPE= # HP's internal field type definitions
84             (
85             { 'Desc' => 'BYTEBOOL', 'Size' => 1, }, # 0
86             { 'Desc' => 'WORDBOOL', 'Size' => 2, }, # 1 .. e.g. check box
87             { 'Desc' => 'STRING', 'Size' => 2, }, # 2
88             { 'Desc' => 'PHONE', 'Size' => 2, }, # 3
89             { 'Desc' => 'NUMBER', 'Size' => 2, }, # 4
90             { 'Desc' => 'CURRENCY', 'Size' => 2, }, # 5
91             { 'Desc' => 'CATEGORY', 'Size' => 2, }, # 6
92             { 'Desc' => 'TIME', 'Size' => 2, }, # 7 Test: store
93             { 'Desc' => 'DATE', 'Size' => 3, }, # 8 Test: store
94             { 'Desc' => 'RADIO_BUTTON', 'Size' => 2, }, # 9 Note: should be 1 byte but it uses 2 bytes!
95             { 'Desc' => 'NOTE', 'Size' => 2, }, # 10 Store: seems to work now
96             { 'Desc' => 'GROUP', 'Size' => 0, }, # 11
97             { 'Desc' => 'STATIC', 'Size' => 0, }, # 12: Label
98             { 'Desc' => 'MULTILINE', 'Size' => 0, }, # 13 ??
99             { 'Desc' => 'LIST', 'Size' => 0, }, # 14
100             { 'Desc' => 'COMBO', 'Size' => 0, }, # 15
101             { 'Desc' => 'U16', 'Size' => 0, }, # 16: WDB time zone difference
102             { 'Desc' => 'U17', 'Size' => 0, }, # 17
103             { 'Desc' => 'U18', 'Size' => 1, }, # 18: ADB "Repeat Status"
104             { 'Desc' => 'U19', 'Size' => 3, }, # 19: ADB "Start Date"
105             { 'Desc' => 'U20', 'Size' => 2, }, # 20: ADB "Due Date"
106             { 'Desc' => 'U21', 'Size' => 0, }, # 21
107             { 'Desc' => 'U22', 'Size' => 2, }, # 22: ADB "Priority"
108             { 'Desc' => 'U23', 'Size' => 2, }, # 23: ADB "#consecutive days"
109             { 'Desc' => 'U24', 'Size' => 2, }, # 24: ADB "Leadtime"
110             { 'Desc' => 'U25', 'Size' => 0, }, # 25
111             );
112              
113             # ----------------------------------------------------------------------------
114             # The HP-LX's password protection engine uses a two constant code blocks:
115             # CODE_A is 127 byte long, CODE_B is 17 byte long
116             my @CODE_A=
117             (
118             0xe8, 0xa3, 0xfe, 0x1b, 0x02, 0xce, 0x40, 0x35,
119             0xa4, 0x7b, 0xf2, 0xa1, 0x70, 0xd5, 0x40, 0x65,
120             0x09, 0x42, 0x23, 0xff, 0xaa, 0xed, 0xf0, 0x2a,
121             0xa2, 0xa9, 0x38, 0xd7, 0xe5, 0x95, 0xea, 0x8c,
122             0x46, 0xdd, 0x90, 0x94, 0x5e, 0x6b, 0x5d, 0xa4,
123             0x7b, 0x8c, 0xea, 0x24, 0xa1, 0x7c, 0xaf, 0x30,
124             0x62, 0x2a, 0xa5, 0x8e, 0xad, 0x67, 0xde, 0x3f,
125             0xb3, 0xe3, 0x53, 0xde, 0x19, 0x42, 0xf8, 0x40,
126             0x96, 0xe8, 0x15, 0x75, 0x43, 0x08, 0x2f, 0xe9,
127             0xb1, 0x4f, 0x1d, 0xd5, 0xa9, 0x16, 0x2c, 0xfb,
128             0x9f, 0x0f, 0xb2, 0xcc, 0xe4, 0x27, 0xbc, 0x1b,
129             0x49, 0xa6, 0x90, 0x79, 0x03, 0x9a, 0xa6, 0x1a,
130             0x70, 0x89, 0x9d, 0x35, 0x81, 0xad, 0x80, 0xb0,
131             0x79, 0x45, 0x21, 0x5f, 0x94, 0x1c, 0xd1, 0x3f,
132             0xdf, 0xa8, 0xa3, 0x40, 0x31, 0x34, 0x66, 0x84,
133             0x85, 0x28, 0xf1, 0x8d, 0x82, 0x04, 0xa4
134             );
135              
136             my @CODE_B=
137             (
138             0x09, 0x0b, 0x09, 0x0f, 0x09, 0x0b, 0x09, 0x77,
139             0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
140             0x78
141             );
142             my @DIAG_K; # used for diagnosing the decryption functions
143              
144             # 17 byte code to decrypt the password
145             my @PW_CODE=
146             (
147             0xE1, 0xA8, 0xF4, 0x17, 0x0B, 0xE7, 0x09, 0x75, # 0x00
148             0xD2, 0x6B, 0x9F, 0x84, 0x2D, 0x9A, 0x3F, 0x05, # 0x08
149             0x71
150             );
151              
152             # ----------------------------------------------------------------------------
153             my %XHDR= # debugging: headers that will not be printed
154             (
155             'sig' => 1, 'time' => 1, 'lookup_table_offset' => 1,
156             'recheader' => 1, 'file_type' => 1,
157             );
158              
159             # ----------------------------------------------------------------------------
160             # create a new (empty) database object
161             sub new
162             {
163 1     1 0 2 my $class= shift;
164 1         1 my $fnm= shift;
165 1   33     11 my $apt= shift || &derive_apt ($fnm);
166              
167             # print ">>> NEW: fnm='$fnm' apt='$apt'\n";
168 1         2 my $i;
169 1         2 my $Types= [];
170 1         214 my @t= localtime (time);
171              
172 1         6 for ($i= 0; $i < 32; $i++) { push (@$Types, []); }
  32         93  
173              
174 1         19 my $obj=
175             {
176             'Filename' => $fnm,
177              
178             'APT' => $apt, # application type
179             # GDB: generic database (default)
180             # NDB: note taker (NDB == GDB)
181             # ADB: appointment book
182             # WDB: world time
183             'APT_Data' => {}, # application specific extension data
184              
185             'Header' => # see loader, save
186             {
187             'sig' => "hcD\000",
188             'recheader' =>
189             {
190             'type' => 0,
191             'status' => 0,
192             'length' => 25,
193             'idx' => 0,
194             },
195              
196             'time' =>
197             {
198             'year' => $t[5]+1900,
199             'mon' => $t[4]+1,
200             'day' => $t[3],
201             'min' => $t[2]*60 + $t[1],
202             },
203              
204             # guessed data from other examples
205             'release_version' => 0x0102,
206             'file_type' => &get_apt ($apt),
207             'file_status' => 0,
208             'cur_viewpt' => 0,
209             'num_recs' => 0,
210             'lookup_table_offset' => 0,
211             'viewpt_hash' => 0x8525, # "Magic Code"
212             # 0x8437 for US american 100LX
213             },
214              
215             'Types' => $Types, # DB records of each type
216              
217             # pre-processed internal datatypes
218             'fielddef' => [], # data descriptions of fields
219             'carddef' => [], # window descriptions of fields
220             'cardpagedef' => [], # description for the four cards
221             'viewptdef' => [], # view point definitins; list/sort/filter
222             'viewpttable' => [], # cached view point table
223              
224             'update' => 0, # number of items modified
225             };
226              
227 1         7 bless $obj, $class;
228             }
229              
230             # ----------------------------------------------------------------------------
231             sub get_apt
232             {
233 1   50 1 0 5 my $APT= shift || 'GDB';
234 1         2 my $code= 0x44; # generic database, GDB and PDB
235              
236 1 50       9 if ($APT eq 'ADB') { $code= 0x32; }
  0 50       0  
    50          
237 0         0 elsif ($APT eq 'NDB') { $code= 0x4E; }
238 0         0 elsif ($APT eq 'WDB') { $code= 0x57; }
239             # else: gdb, pdb: GDB (generic data base)
240              
241 1         18 $code;
242             }
243              
244             # ----------------------------------------------------------------------------
245             sub decode_apt
246             {
247 1     1 0 3 my $code= shift;
248 1         2 my $APT= 'GDB';
249              
250 1 50       10 if ($code == 0x32) { $APT= 'ADB'; }
  0 50       0  
    50          
251 0         0 elsif ($code eq 0x4E) { $APT= 'NDB'; }
252 0         0 elsif ($code eq 0x57) { $APT= 'WDB'; }
253              
254 1         4 $APT;
255             }
256              
257             # ----------------------------------------------------------------------------
258             sub derive_apt
259             {
260 1     1 0 2 my $fnm= shift;
261 1         2 my $APT= 'GDB'; # generic database
262              
263 1 50       17 if ($fnm =~ m/\.adb$/i) { $APT= 'ADB'; } # appointment book
  0 50       0  
    50          
264 0         0 elsif ($fnm =~ m/\.ndb$/i) { $APT= 'NDB'; } # note taker
265 0         0 elsif ($fnm =~ m/\.wdb$/i) { $APT= 'WDB'; } # world time application
266             # else: gdb, pdb: GDB (generic data base)
267              
268 1         5 $APT;
269             }
270              
271             # ----------------------------------------------------------------------------
272             # open a given file and read the database into memory
273             sub openDB
274             {
275 1     1 0 12 my $fnm= shift;
276 1         2 my $APT= shift;
277 1         2 my $dont_decrypt= shift;
278              
279 1         10 my $obj= new HP200LX::DB ($fnm, $APT);
280 1         10 $APT= $obj->{APT}; # use application detection logic in new
281 1         22 my $b;
282             my $sig;
283 1         3 local *FI;
284              
285 1 50       35 unless (open (FI, $fnm))
286             {
287 0         0 print "ERROR: could not open DB file '$fnm'!\n";
288 0         0 return undef;
289             }
290 1         5 binmode (FI); # MS-DOS systems need this, T2D: how about Mac?
291              
292 1         26 read (FI, $sig, 4);
293              
294             # BEGIN to read the db header; see save
295 1         5 my $recheader= &get_recheader (*FI);
296 1         3 my $lng= $recheader->{'length'};
297 1 50       6 print "WARNING lng=$lng, 25 expected!\n" unless ($lng == 25);
298              
299 1         4 read (FI, $b, 19); # lng minus length of record header: 19+6= 25
300 1         5 my ($release_version, $file_type, $file_status,
301             $cur_viewpt, $num_recs, $lookup_table_offset,
302             $year, $mon, $day, $min, $viewpt_hash)= unpack ('vCCvvVCCCvv', $b);
303             # END to read the record header
304              
305 1         5 my $time=
306             {
307             'year' => $year+1900,
308             'mon' => $mon+1,
309             'day' => $day+1,
310             'min' => $min,
311             };
312              
313 1         13 my $hdr=
314             {
315             'sig' => $sig,
316             'time' => $time,
317             'recheader' => $recheader,
318              
319             'release_version' => $release_version,
320             'file_type' => $file_type,
321             'file_status' => $file_status,
322             'cur_viewpt' => $cur_viewpt,
323             'num_recs' => $num_recs,
324             'lookup_table_offset' => $lookup_table_offset,
325             'viewpt_hash' => $viewpt_hash,
326             };
327              
328 1         5 $obj->{Header}= $hdr;
329 1         6 $APT= $obj->{APT}= &decode_apt ($file_type);
330             # &hex_dump ($b);
331             # print "APT=$APT file_type=$file_type num_recs=$num_recs",
332             # " cur_viewpt=$cur_viewpt\n";
333             # printf ("lookup_table_offset= 0x%08lX\n", $lookup_table_offset);
334              
335             # read lookup table
336 1         2 my ($v, $i, $xrec);
337 1         2 my @ltbl= (); # lookup table
338 1         4 my @ftbl= (); # "type first" table
339              
340 1 50       3 if ($lookup_table_offset > 0)
341             {
342 1         10 seek (FI, $lookup_table_offset, 0);
343 1         12 $xrec= &get_recheader (*FI);
344             # &print_recheader (*STDOUT, "lookup table (offset=$lookup_table_offset)", $xrec);
345 1         3 $lng= $xrec->{'length'}-6;
346 1         3 $i= read (FI, $b, $lng);
347              
348 1 50       3 print "WARNING: could not read complete lookup table; read=$i lng=$lng\n"
349             unless ($i == $lng);
350              
351 1         2 $i= $num_recs * 8; # 8 byte per lookup table entry
352 1 50       5 print "WARNING: lookup table size seems wrong;",
353             " lng=$lng num_recs=$num_recs $num_recs*8=$i\n"
354             unless ($i == $lng);
355              
356 1         5 for ($i= 0; $i < $num_recs; $i++)
357             {
358 22         57 my ($size, $filters, $flags, $off_low, $off)=
359             unpack ('vvCCv', substr ($b, $i*8, 8));
360 22         25 $off= $off*256+$off_low;
361              
362             # print "lut [$i] off=$off size=$size\n";
363 22         47 my $lut=
364             {
365             'siz' => $size,
366             'off' => $off,
367             'filters' => $filters,
368             'flags' => $flags,
369             } ;
370              
371 22         56 push (@ltbl, $lut);
372             }
373             # $hdr->{lookup_table_header}= $xrec;
374             # $hdr->{lookup_table}= \@ltbl;
375              
376             # typefirst table
377             #
378             # Purpose:
379             # This table points into the lookup table at the position of the
380             # first record of each record type
381             # Example:
382             # lookup data for record 3 of type 4 is at: ltbl [ftbl [4] + 3]
383             # NOTE:
384             # this is not used here!
385             #
386             # printf ("typefirst table: 0x%08lX\n", $lookup_table_offset + $lng + 6);
387 1         2 $i= read (FI, $b, 64);
388 1 50       3 print "WARNING: could not read complete typefirst table; read=$i lng=64\n"
389             unless ($i == 64);
390 1         3 for ($i= 0; $i < 32; $i++)
391             {
392 32         35 $v= unpack ('v', substr ($b, $i*2, 2));
393 32         50 push (@ftbl, $v);
394             # print "ftbl[$i]= $v\n";
395             }
396             # $hdr->{typefirst_table}= \@ftbl;
397             } # lookup table read
398             # else { print "no lookup table present!\n"; }
399              
400 1         2 $obj->{Meta}= 'Plaintext';
401 1         12 $obj->{dont_decrypt}= $dont_decrypt;
402 1         2 my ($CODE, $CODE_SIZE); # used to decrypt data records
403              
404 1         2 for ($i= 0;; $i++)
405             {
406 23         25 my ($off, $siz, $type, $lut);
407              
408 23 50       45 if ($lookup_table_offset > 0)
409             { # use lookup table to seek each record otherwise read file seqentially
410 23 100       43 last if ($i > $#ltbl);
411              
412 22         35 $lut= $ltbl [$i];
413              
414 22         34 $off= $lut->{off};
415 22         27 $siz= $lut->{siz} - 6;
416              
417 22 50 33     110 if ($siz < 0 || $off < 0)
418             { # empty record
419             # print "[$i] type=???? siz=$siz off=$off\n";
420 0         0 next;
421             }
422              
423 22         195 seek (FI, $off, 0);
424             }
425              
426 22 50       609 last unless (defined ($xrec= &get_recheader (*FI)));
427              
428 22         40 $siz= $xrec->{length}- 6;
429 22         33 $type= $xrec->{type};
430             # the real record data!
431 22         33 read (FI, $b, $siz);
432              
433 22 50 33     94 if ($type < 0 || $type >= 32)
434             {
435 0         0 print "WARNING: unknown record type: $type; IGNORED\n";
436 0         0 &print_recheader (*STDOUT,
437             "record [$i] type=$type siz=$siz off=$off",
438             $xrec);
439 0         0 &hex_dump ($b);
440 0         0 next;
441             }
442              
443 22 50       43 if (defined ($lut))
444             { # additional record data from the LUT
445 22         35 $xrec->{off}= $off;
446 22         59 $xrec->{flags}= $lut->{flags};
447 22         38 $xrec->{filters}= $lut->{filters};
448             }
449              
450 22         43 &analyze_record ($obj, $xrec, $i, $b);
451             }
452             # print "LUT table size: i=$i\n";
453              
454 1         19 close (FI);
455              
456 1         18 $obj;
457             }
458              
459             # ----------------------------------------------------------------------------
460             sub analyze_record
461             {
462 22     22 0 38 my ($obj, $xrec, $i, $b)= @_;
463              
464 22         25 my $type= $xrec->{type};
465 22         37 my $siz= $xrec->{length}-6;
466              
467             # $xrec only contains only fields from the LUT
468             # filters:length:type:off:status:flags:idx
469             # inserts only $xrec->{data} which contains the (decrypted) data
470              
471 22 50 66     108 if ($type > 1 && $obj->{Meta} eq 'Encrypted' && !$obj->{dont_decrypt})
      33        
472             {
473             # print "DATA encoded \n"; &hex_dump ($b);
474              
475 0         0 $b= &decrypt_data ($b, $siz, $obj->{Key});
476              
477             # print "DATA decoded\n"; &hex_dump ($b); print "\n";
478             }
479              
480 22         70 $xrec->{data}= $b;
481              
482             # specially handled objects
483 22 100       47 if ($type == 9) # NOTE
484             { # note records may be missing, but they are accessed according
485             # to their index, thus leave the blank entries in the table.
486 1         3 $obj->{Types}->[9]->[$xrec->{idx}]= $xrec;
487 1         4 return;
488             }
489              
490 21         23 push (@{$obj->{Types}->[$type]}, $xrec);
  21         59  
491              
492 21 50 66     104 if ($type > 1
      33        
493             && $obj->{Meta} eq 'Encrypted'
494             && $obj->{dont_decrypt})
495             { # no usuefull data to process if encrypted
496 0         0 return;
497             }
498              
499             # Main DB type decoder
500 21 100       101 if ($type == 0)
    50          
    100          
    100          
    100          
    100          
    50          
501             { # record header; this is actually read twice and was already
502             # decoded, see above
503             # NOTE: The DB header seems to get modified as soon as an
504             # application opens the database to indicate it is busy
505             # by setting the viewpoint table offset to NULL
506             }
507             elsif ($type == 1)
508             { # password record; this code is very experimental!
509 0         0 $obj->{Meta}= 'Encrypted';
510              
511 0 0       0 if ($obj->{dont_decrypt})
512             { # do not attempt to decrypt this password
513 0         0 return;
514             }
515              
516             # decode and print the password
517 0         0 my ($pass, $key)= &decrypt_password ($b, $siz);
518 0         0 $obj->{Password}= $pass;
519 0         0 $obj->{Key}= $key;
520             # print "session key:\n";
521             # &hex_dump ($key);
522             } # END of type == 1 processing; password record
523              
524             elsif ($type == 4) # CARDDEF
525             { # only one record of this type allowed!!
526 1         4 $obj->{carddef}= &get_carddef ($b);
527             }
528             elsif ($type == 6) # FIELDDEF
529             {
530 13         24 my ($fdef, $rec_size)= &get_fielddef ($b);
531 13         17 push (@{$obj->{fielddef}}, $fdef);
  13         29  
532 13 100       42 $obj->{rec_size}= $rec_size if ($rec_size > $obj->{rec_size});
533             }
534             elsif ($type == 7) # VIEWPTDEF
535             {
536             # print ">>> view point defintion\n"; &hex_dump ($b);
537 1         7 my $vptd= &get_viewptdef ($b);
538             # $vptd->show_viewptdef (*STDOUT);
539 1         2 push (@{$obj->{viewptdef}}, $vptd);
  1         3  
540 1         2 $vptd->{index}= $#{$obj->{viewptdef}};
  1         9  
541             }
542             elsif ($type == 10) # VIEWPTTABLE
543             {
544             # print ">>> view point table\n"; &hex_dump ($b);
545 1         2 push (@{$obj->{viewpttable}}, &get_viewpttable ($b));
  1         6  
546             }
547             elsif ($type == 13) # CARDPAGEDEF
548             { # only none or one record of this type allowed!!
549 0         0 $obj->{cardpagedef}= &get_cardpagedef ($b);
550             }
551              
552 21 50       72 unless ($REC_TYPE[$type])
553             {
554             # application specific data
555 0         0 my $APT= $obj->{APT};
556              
557 0 0 0     0 if ($type == 14 && $APT eq 'ADB')
558             {
559 0         0 $obj->decode_type14 (*STDOUT, $b);
560             }
561             else
562             { # dump info about other unknown field types
563 0   0     0 my $off= $xrec->{off} || 'SEQ';
564 0         0 print "[$i] off=$off siz=$siz type=$type APT='$APT'\n";
565 0         0 &print_recheader (*STDOUT, "record [$i]:", $xrec);
566              
567             # print "b='$b'\n";
568 0         0 &hex_dump ($b);
569 0         0 $obj->{has_unknown_records}++;
570             }
571             }
572             }
573              
574             # ----------------------------------------------------------------------------
575             sub has_errors
576             {
577 0     0 0 0 my $self= shift;
578 0 0       0 return 1 if ($self->{has_unknown_records});
579 0         0 0;
580             }
581              
582             # ----------------------------------------------------------------------------
583             sub saveDB
584             {
585 0     0 0 0 my $self= shift;
586 0   0     0 my $fnmo= shift || $self->{Filename};
587              
588 0         0 my $hdr= $self->{Header};
589 0         0 my $Types= $self->{Types};
590              
591 0         0 my ($type, $Data, $rec, $lng, $idx);
592              
593             # fixup header if necessary
594 0         0 $Data= $Types->[0];
595              
596 0         0 my ($off)= 4;
597 0         0 my (@lut, @ftype, $ftype); # lookup table and first type table
598 0         0 my $lut= 0;
599 0         0 my $num_recs= 0;
600              
601             # calculate lookup table and firsttype table
602             # . for each record type: calculate size of each entry
603             # print "lut_size= $#lut $lut\n";
604 0         0 for ($type= 0; $type < 32; $type++)
605             {
606 0         0 push (@ftype, $lut);
607 0         0 $Data= $Types->[$type];
608              
609 0         0 for ($idx= 0; $idx <= $#$Data; $idx++)
610             {
611 0         0 $rec= $Data->[$idx];
612              
613             # print ">>> save: type=$type idx=$idx\n";
614              
615             # T2D, TEST: note records may be blank!!
616 0 0       0 if (defined ($rec))
617             { # populated record to be saved
618 0         0 $lng= length ($rec->{data});
619              
620 0         0 $rec->{off}= $off;
621 0         0 $off += ($rec->{'length'}= $lng + 6); # 6 off ???
622 0         0 $rec->{idx}= $idx;
623              
624 0 0       0 unless (defined ($rec->{type}))
625             { # set type if not alrady done
626 0         0 $rec->{type}= $type;
627             }
628              
629 0 0       0 unless (defined ($rec->{status}))
630             { # set type if not alrady done
631 0         0 $rec->{status}= 2; # T2D: status == 2 means what ???
632             }
633             }
634             else
635             { # empty record, set up an entry for the lookup table
636 0         0 print ">>>>> save rec type=$type idx=$idx undefined!\n";
637              
638 0         0 $rec=
639             {
640             off => 0,
641             'length'=> 0,
642             flags => 0,
643             filters => 0,
644             };
645             }
646              
647 0         0 $lut [$lut++]= $rec;
648 0         0 $num_recs++;
649             }
650             }
651              
652             # print "lut_size= $#lut $lut num_recs=$num_recs off=$off\n";
653              
654 0         0 $hdr->{lookup_table_offset}= $off;
655 0         0 $hdr->{num_recs}= $num_recs;
656              
657 0         0 local *FO;
658 0 0       0 open (FO, ">$fnmo") || die;
659 0         0 binmode (FI); # MS-DOS systems need this, T2D: how about Mac?
660              
661             # save db header; see also loader
662 0         0 print FO $hdr->{sig};
663 0         0 &put_recheader (*FO, $hdr->{recheader});
664 0         0 my $time= $hdr->{'time'};
665 0         0 my $b= pack ('vCCvvVCCCvv',
666             $hdr->{release_version},
667             $hdr->{file_type}, $hdr->{file_status},
668             $hdr->{cur_viewpt}, $hdr->{num_recs},
669             $off, # lookup_table_offset
670             $time->{year}-1900, $time->{mon}-1,
671             $time->{day}-1, $time->{min},
672             $hdr->{viewpt_hash},
673             );
674 0         0 print FO $b;
675              
676             # save each record for each type
677 0         0 for ($type= 1; $type < 32; $type++)
678             {
679 0         0 $Data= $Types->[$type];
680              
681 0         0 for ($idx= 0; $idx <= $#$Data; $idx++)
682             {
683 0         0 $rec= $Data->[$idx];
684              
685 0 0       0 next unless (defined ($rec->{data})); # empty records
686             # print ">>> save data records type=$type idx=$idx\n";
687 0         0 &put_recheader (*FO, $rec);
688 0         0 print FO $rec->{data};
689             }
690             }
691              
692             # print "lut_size= $#lut $lut\n";
693              
694             # save lookup table
695 0         0 $rec=
696             {
697             'type' => 31,
698             'status' => 0,
699             'length' => ($#lut+1)*8+6,
700             'idx' => 0,
701             };
702              
703 0         0 &put_recheader (*FO, $rec);
704 0         0 foreach $lut (@lut)
705             {
706 0         0 my $off_low= $lut->{off}%256;
707 0         0 my $off= $lut->{off}/256;
708              
709 0         0 my $b= pack ('vvCCv',
710             $lut->{'length'},
711             $lut->{filters}, $lut->{flags},
712             $off_low, $off
713             );
714              
715 0         0 print FO $b;
716             }
717              
718             # save firsttype table
719 0         0 foreach $ftype (@ftype)
720             {
721 0         0 my $b= pack ('v', $ftype);
722 0         0 print FO $b;
723             }
724              
725 0         0 close (FO);
726             }
727              
728             # ----------------------------------------------------------------------------
729             sub print_summary
730             {
731 0     0 0 0 my $db= shift;
732 0         0 my $prt_hdr= shift;
733              
734 0         0 my $hdr= $db->{Header};
735 0         0 my $t= $hdr->{time};
736 0         0 my $min= $t->{min};
737 0         0 my $h= int ($min/60);
738 0         0 $min= $min%60;
739              
740 0 0       0 printf ("Type %-24s Recs View Hash %-16s Comment\n",
741             'Filename', 'created')
742             if ($prt_hdr);
743              
744 0         0 my $Comment;
745 0 0       0 $Comment .= ' CORRUPTED!' if ($db->has_errors);
746 0 0       0 $Comment .= ' Password' if ($db->{Meta} eq 'Encrypted');
747              
748 0         0 printf ("%-4s %-24s %5d %4d 0x%04X %4d-%02d-%02d %2d:%02d%s\n",
749             $db->{APT}, $db->{Filename},
750             $hdr->{num_recs},
751             $hdr->{cur_viewpt}, $hdr->{viewpt_hash},
752             $t->{year}, $t->{mon}, $t->{day}, $h, $min,
753             $Comment,
754             );
755             }
756              
757             # ----------------------------------------------------------------------------
758             sub get_field_def
759             {
760 0     0 0 0 my $self= shift;
761 0         0 my $num= shift;
762              
763 0         0 $self->{fielddef}->[$num];
764             }
765              
766             # ----------------------------------------------------------------------------
767             sub show_db_def
768             {
769 0     0 0 0 my $self= shift;
770 0         0 local *FO= shift;
771              
772 0         0 my $Fdef= $self->{'fielddef'};
773 0         0 my $field;
774 0         0 my $num= 0;
775 0         0 my %off= (); # sorted by offset
776 0         0 my $off;
777              
778 0         0 my $hdr= sprintf ("[##] ## %-12s Siz %-24s FID Off Res Flg\n",
779             "Type", "Name");
780 0         0 print FO $delim, "\n";
781 0         0 print FO "DB def by field number\n", $hdr;
782              
783 0         0 foreach $field (@$Fdef)
784             {
785 0         0 $off= &show_field_def (*FO, $field, $num++);
786 0         0 push (@{$off{$off}}, $field);
  0         0  
787             }
788              
789 0         0 $num= 0;
790              
791 0         0 print FO $delim, "\n", "DB def by offset position\n", $hdr;
792 0         0 foreach $off (sort keys %off)
793             {
794 0         0 foreach $field (@{$off{$off}})
  0         0  
795             {
796 0         0 &show_field_def (*FO, $field, $num);
797             }
798 0         0 $num++
799             }
800              
801 0         0 print FO $delim, "\n";
802             }
803              
804             # ----------------------------------------------------------------------------
805             sub show_card_def
806             {
807 0     0 0 0 my $self= shift;
808 0         0 local *FO= shift;
809              
810 0         0 my $Cdef= $self->{'carddef'};
811 0 0       0 return if ($#$Cdef < 0);
812 0         0 my ($field, $f);
813              
814 0         0 print FO "card definition:\n";
815 0         0 my $i= 0;
816 0         0 foreach $field (@$Cdef)
817             {
818             # &show_field_window ($field);
819 0         0 printf FO ("field [%2d]:", $i++);
820 0         0 foreach $f (sort keys %$field)
821             {
822 0 0 0     0 if ($f eq 'Parent' || $f eq 'Style')
823             {
824 0         0 printf (" %s=%8X,", $f, $field->{$f});
825             } else {
826 0         0 printf (" %s=%3d,", $f, $field->{$f});
827             }
828             }
829 0         0 print "\n";
830             }
831             }
832              
833             # ----------------------------------------------------------------------------
834             sub dump_data
835             {
836 0     0 0 0 my $self= shift;
837              
838 0         0 my $APT= $self->{APT};
839 0   0     0 my $T= $self->{Types} || die;
840 0         0 my $D= $T->[11]; # array of data records
841 0         0 my $N= $T->[9]; # array of note records
842              
843 0   0     0 my $rec_beg= shift || 0;
844 0   0     0 my $rec_end= shift || $#$D;
845 0   0     0 my $Fdef= shift || $self->{fielddef}; # array of field definitions
846              
847 0         0 my ($rec, $field);
848              
849 0         0 print "show_data\n";
850 0         0 foreach $rec ($rec_beg .. $rec_end)
851             {
852 0   0     0 my $d= $D->[$rec] || next;
853 0   0     0 my $b= $d->{data} || next;
854              
855 0         0 my ($ok, $o)= &fetch_data ($b, $Fdef, $N, $APT);
856 0         0 &dump_data_record ($b, $ok, $o);
857             }
858             }
859              
860             # ----------------------------------------------------------------------------
861             sub dump_type
862             {
863 0     0 0 0 my $self= shift;
864 0         0 local *FO= shift;
865 0         0 my $Ty= shift; # if undef, dump all items
866 0   0     0 my $Format= shift || 'auto';
867              
868             # print '# ', join (' ', keys %$self), "\n";
869 0         0 my ($T, $Ty_from, $Ty_end);
870              
871 0 0       0 unless (defined ($T= $self->{Types}))
872             {
873 0         0 print STDERR "can't access Type table in $self\n";
874 0         0 return;
875             }
876              
877 0 0       0 if (defined ($Ty)) { $Ty_from= $Ty_end= $Ty; }
  0         0  
878 0         0 else { $Ty_from= 0; $Ty_end= 255; }
  0         0  
879              
880 0         0 for ($Ty= $Ty_from; $Ty <= $Ty_end; $Ty++)
881             {
882 0         0 my $D= $T->[$Ty];
883 0         0 my $c= $#$D;
884 0 0       0 next if ($c == -1);
885              
886 0         0 my $format= $Format;
887 0 0       0 if ($Format eq 'auto')
888             { # see @REC_TYPE
889 0 0 0     0 if ($Ty == 5 || $Ty == 9 || $Ty == 11) { $format= 'QP'; }
  0   0     0  
890 0         0 else { $format= 'HEX'; }
891             }
892              
893 0   0     0 my $ty_str= $REC_TYPE[$Ty] || "USER$Ty";
894              
895 0         0 my ($i, $Dk, $Dv, $cp, $ch, $cv, $lng, $llng);
896 0         0 for ($i= 0; $i <= $c; $i++)
897             {
898 0         0 print FO "$Ty $ty_str $i/$c\n";
899              
900 0         0 $Dv= $D->[$i];
901             # NOTE: fields not written: off (completely redundant)
902             # off, filters, and flags come from the LUT
903             # print FO '# ZZ ', join (' ', keys %$Dv), "\n";
904 0         0 foreach $Dk (qw(type idx length status filters flags))
905             {
906 0 0       0 next unless (defined ($Dv->{$Dk}));
907 0         0 print FO "<$Dk>$Dv->{$Dk}\n";
908             }
909              
910 0         0 print FO "\n";
911 0 0       0 if ($format eq 'HEX')
912             {
913 0         0 &hex_dump ($Dv->{data}, *FO);
914             }
915             else # especially if ($format eq 'QP')
916             {
917 0         0 my $data= $Dv->{data};
918 0         0 $lng= length ($data);
919 0         0 for ($cp= 0; $cp < $lng; $cp++)
920             {
921 0         0 $cv= unpack ('C', $ch= substr ($data, $cp, 1));
922              
923 0 0 0     0 if (($cv >= 0x00 && $cv <= 0x1F)
      0        
      0        
      0        
      0        
924             || ($cv >= 0x3C && $cv <= 0x3E)
925             || ($cv >= 0x7F && $cv <= 0xFF)
926             )
927             {
928 0         0 $ch= sprintf ("=%02X", $cv);
929 0         0 $llng += 3;
930             }
931 0         0 else { $llng++; }
932              
933 0         0 print FO $ch;
934 0 0       0 if ($llng > 72) { print FO "=\n"; $llng= 0; }
  0         0  
  0         0  
935             }
936 0 0       0 if ($llng > 0) { print FO "\n"; $llng= 0; }
  0         0  
  0         0  
937             }
938              
939 0         0 print FO "\n\n\n";
940             }
941             }
942             }
943              
944             # ----------------------------------------------------------------------------
945             # load ASCII file; name should be changed...
946             sub loader
947             {
948 0     0 0 0 my $self= shift;
949 0         0 local *FI= shift;
950              
951 0         0 my $status= 'undef';
952 0         0 my ($rec, $counter, $b, $format);
953 0         0 while ()
954             {
955 0         0 chomp;
956             # print ">>> $_\n";
957              
958 0 0 0     0 if (m##)
    0          
    0          
    0          
    0          
    0          
    0          
959             {
960 0         0 $rec= {};
961 0         0 $status= 'record';
962 0         0 $counter++;
963 0         0 $b= '';
964             }
965             elsif (m##)
966             {
967 0 0 0     0 if ($status ne 'record' && $status ne 'data')
968             {
969 0         0 print "WARNING: unexpected status $status\n";
970             }
971              
972             # analyze header if necessary:
973             # filters:length:type:off:status:flags:idx
974             # print ">>> insert record: ", join (':', %$rec), "\n";
975             # &hex_dump ($b);
976 0         0 &analyze_record ($self, $rec, $counter, $b);
977 0         0 $status= 'undef';
978             }
979             elsif (m##)
980             {
981 0         0 $format= $1;
982 0         0 $status= 'data';
983             }
984             elsif (m##)
985             {
986 0         0 $status= 'record';
987             }
988             elsif (m#<(type|idx|length|status|filters|flags)>(.*)#)
989             {
990 0         0 $rec->{$1}= $2;
991             }
992             elsif ($status eq 'data')
993             {
994 0 0       0 if ($format eq 'QP')
    0          
995             {
996 0         0 s/=$//;
997 0         0 s/=([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
  0         0  
998 0         0 $b .= $_;
999             }
1000             elsif ($format eq 'HEX')
1001             {
1002 0         0 my @x= split (/\|/);
1003 0         0 @x= split (' ', $x[0]);
1004 0         0 shift (@x);
1005             # print "[", join (':', @x), "]\n";
1006 0         0 $b .= pack ("C*", map { hex ($_); } @x);
  0         0  
1007             }
1008             else
1009             {
1010 0         0 print "WARNING: unexpected data format: '$format'\n";
1011             }
1012             }
1013             elsif (/^#/ || /^[ \t]*$/) {} # comment
1014             else
1015             {
1016 0         0 print "WARNING: unexpected data: '$_'\n";
1017             }
1018             }
1019             }
1020              
1021             # ----------------------------------------------------------------------------
1022             sub TIEARRAY
1023             {
1024 1     1   15 return $_[1];
1025             }
1026              
1027             # ----------------------------------------------------------------------------
1028             sub FETCH
1029             {
1030 1     1   11 my $db= shift;
1031 1         2 my $idx= shift;
1032              
1033 1   50     4 my $T= $db->{Types} || die 'not a database';
1034 1         2 my $D= $T->[11]; # array of data records
1035 1 50       4 return undef if ($idx > $#$D);
1036              
1037 1         2 my $Dx= $D->[$idx]; # data record for the given index
1038 1         2 my $rv;
1039              
1040 1 50       3 unless (defined ($rv= $Dx->{obj}))
1041             { # no record data was previously stored, fetch that
1042 1         1 my $N= $T->[9]; # array of note records
1043 1         3 my $F= $db->{fielddef};
1044 1         2 my $b= $Dx->{data};
1045 1         2 my $APT= $db->{APT};
1046              
1047             # print "FETCH: T=$T D=$D N=$N F=$F b=$b\n";
1048 1         5 my ($ok, $o)= &fetch_data ($b, $F, $N, $APT);
1049             # &dump_data_record ($b, $ok, $o);
1050              
1051 1         3 $Dx->{obj}= $rv= $o;
1052 1         2 $Dx->{ok}= $ok;
1053             }
1054              
1055 1         5 return $rv;
1056             }
1057              
1058             # ----------------------------------------------------------------------------
1059             sub FETCH_data_raw
1060             {
1061 0     0 0 0 my $db= shift;
1062 0         0 my $idx= shift;
1063              
1064 0   0     0 my $T= $db->{Types} || return undef;
1065 0         0 my $D= $T->[11]; # array of data records
1066 0 0       0 return undef if ($idx > $#$D);
1067              
1068 0         0 $D->[$idx]->{data}; # data record for the given index
1069             }
1070              
1071             # ----------------------------------------------------------------------------
1072             sub FETCH_note_raw
1073             {
1074 0     0 0 0 my $db= shift;
1075 0         0 my $idx= shift;
1076              
1077 0   0     0 my $T= $db->{Types} || return undef;
1078 0         0 my $N= $T->[9]; # array of note records
1079 0 0       0 return undef if ($idx > $#$N);
1080              
1081 0         0 $N->[$idx]->{data}; # data record for the given index
1082             }
1083              
1084             # ----------------------------------------------------------------------------
1085             sub STORE
1086             {
1087 0     0   0 my $db= shift;
1088 0         0 my $idx= shift;
1089 0         0 my $val= shift;
1090             # print "STORE: ", join (':', %$val), "\n";
1091              
1092 0   0     0 my $T= $db->{Types} || die;
1093 0         0 my $D= $T->[11]; # array of data records
1094 0         0 my $N= $T->[9]; # array of note records
1095 0         0 my $F= $db->{fielddef};
1096 0         0 my $APT= $db->{APT};
1097              
1098 0         0 my $Dx;
1099              
1100 0 0       0 if ($idx > $#$D)
1101             {
1102             # print "adding records: num=$#$D idx=$idx\n";
1103 0         0 $Dx= { 'data' => '' };
1104             }
1105             else
1106             {
1107 0         0 $Dx= $D->[$idx]; # data record for the given index
1108             }
1109              
1110 0         0 my ($ok, $b)= &store_data ($val, $F, $N, $APT, $db->{rec_size});
1111              
1112 0         0 $Dx->{data}= $b;
1113 0         0 undef ($Dx->{obj});
1114 0         0 undef ($Dx->{ok});
1115 0         0 $D->[$idx]= $Dx;
1116              
1117             # T2D: unfinished
1118             # missing items: refreshing and/or invalidating view points
1119 0         0 $db->{update}++;
1120             }
1121              
1122             # ----------------------------------------------------------------------------
1123             sub STORE_data_raw
1124             {
1125 0     0 0 0 my $db= shift;
1126 0         0 my $idx= shift;
1127 0         0 my $data= shift;
1128              
1129 0   0     0 my $T= $db->{Types} || die;
1130 0         0 my $D= $T->[11]; # array of data records
1131 0         0 $D->[$idx]->{data}= $data;
1132 0         0 $db->{update}++;
1133             }
1134              
1135             # ----------------------------------------------------------------------------
1136             sub STORE_note_raw
1137             {
1138 0     0 0 0 my $db= shift;
1139 0         0 my $idx= shift;
1140 0         0 my $data= shift;
1141              
1142 0   0     0 my $T= $db->{Types} || die;
1143 0         0 my $N= $T->[9]; # array of note records
1144 0         0 $N->[$idx]->{data}= $data;
1145 0         0 $db->{update}++;
1146             }
1147              
1148             # ----------------------------------------------------------------------------
1149             sub FETCHSIZE
1150             {
1151 0     0   0 my $db= shift;
1152 0         0 return 1 + $db->get_last_index();
1153             }
1154              
1155             # ----------------------------------------------------------------------------
1156             sub get_last_index
1157             {
1158 0     0 0 0 my $db= shift;
1159              
1160 0   0     0 my $T= $db->{Types} || die;
1161 0         0 my $D= $T->[11]; # array of data records
1162 0         0 return $#$D;
1163             }
1164              
1165             # ----------------------------------------------------------------------------
1166             sub get_str
1167             {
1168 3     3 0 4 my $b= shift;
1169 3         3 my $off= shift;
1170              
1171 3         6 my $res= substr ($$b, $off);
1172 3         7 my $idx= index ($res, "\000");
1173 3 50       8 $res= substr ($res, 0, $idx) if ($idx >= 0);
1174 3         7 $res;
1175             }
1176              
1177             # ----------------------------------------------------------------------------
1178             sub fmt_date
1179             {
1180 1     1 0 3 my $str= shift;
1181              
1182 1         3 my ($year, $mon, $day)= unpack ('CCC', $str);
1183 1 50 33     13 ($year == $no_year && $mon == $no_mon && $day == $no_day)
1184             ? '' # empty date field
1185             : sprintf ("%d-%02d-%02d", 1900 + $year, $mon+1, $day+1);
1186             }
1187              
1188             # ----------------------------------------------------------------------------
1189             sub pack_date
1190             {
1191 0     0 0 0 my $val= shift;
1192 0         0 my ($year, $mon, $day);
1193              
1194 0         0 $year= $mon= $day= $no_date;
1195 0 0       0 if ($val =~ /(\d+)-(\d+)-(\d+)/)
1196             {
1197 0         0 ($year, $mon, $day)= ($1, $2, $3);
1198             # check for valid dates otherwise set no_date value
1199 0 0 0     0 $year= $mon= $day= $no_date
      0        
      0        
      0        
      0        
1200             if ($year < 1900 || $year > 2155
1201             || $mon < 1 || $mon > 12
1202             || $day < 1 || $day > 31);
1203              
1204 0         0 $year -= 1900;
1205 0         0 $mon--;
1206 0         0 $day--;
1207             }
1208              
1209 0         0 pack ('CCC', $year, $mon, $day);
1210             }
1211              
1212             # ----------------------------------------------------------------------------
1213             sub fmt_time
1214             {
1215 1     1 0 3 my $str= shift;
1216              
1217 1         2 my $val= unpack ('v', $str);
1218 1 50 33     7 return '' if ($val == $no_time || $val == $no_val);
1219              
1220 1         3 my $min= $val % 60;
1221 1         3 my $xval= int ($val / 60);
1222 1         4 sprintf ("%d:%02d", $xval, $min);
1223             }
1224              
1225             # ----------------------------------------------------------------------------
1226             sub fetch_data
1227             {
1228 1     1 0 2 my $b= shift; # raw binary data
1229 1         1 my $Fdef= shift; # Field Definitions
1230 1         1 my $N= shift; # Notes Data
1231 1         2 my $APT= shift; # application type
1232              
1233 1         2 my $ok= 1;
1234 1         1 my %o;
1235             my %RB; # radio button at offset
1236 0         0 my $field;
1237              
1238 1         4 my @Fdef= @$Fdef; # Field Definition List
1239 1         2 my $APT2;
1240              
1241 1 50       4 if ($APT eq 'ADB')
1242             { # For appointment book entries we have to analyze if
1243             # the record describes a to-do item or a date or event
1244              
1245 0         0 my $val= unpack ('C', substr ($b, 0x0E, 1));
1246 0         0 my @TLT= ();
1247              
1248             # if ($val & 0x02) { $APT2= 'Done'; } # checked to-do entry
1249 0 0       0 if ($val & 0x10) { $APT2= 'To-Do'; @TLT= (0, 1, 8..12); }
  0 0       0  
  0 0       0  
1250 0         0 elsif ($val & 0x20) { $APT2= 'Event'; @TLT= (0..7, 12, 14, 15); }
  0         0  
1251 0         0 elsif ($val & 0x80) { $APT2= 'Date'; @TLT= (0..7, 12, 14, 15); }
  0         0  
1252              
1253 0         0 $o{'type'}= $APT2;
1254 0         0 $o{'repeat'}= unpack ('C', substr ($b, 0x1A, 1));
1255              
1256 0         0 @Fdef= map { $Fdef[$_] } @TLT;
  0         0  
1257             }
1258              
1259 1         2 FIELD: foreach $field (@Fdef)
1260             {
1261 13         18 my $type= $field->{ftype};
1262 13         19 my $off= $field->{off};
1263 13         18 my $name= $field->{name};
1264 13         13 my $res;
1265             # printf ("APT= 0x%02X %2d '%s'\n", $off, $type, $name);
1266              
1267 13 50 66     191 if ($type == 0) # BYTE_BOOL
    100 33        
    50 66        
    100 100        
    100 100        
    100 33        
    100 66        
    100 33        
    50 66        
    0 100        
    0 66        
    0 33        
      0        
      33        
      0        
      0        
      0        
      0        
1268             {
1269 0         0 my $val= unpack ('C', substr ($b, $off, 1));
1270 0 0       0 $res= ($val) ? 'X' : '';
1271             }
1272             elsif ($type == 1) # WORD_BOOL
1273             {
1274 2         5 my $val= unpack ('v', substr ($b, $off, 2));
1275 2 100       13 $res= ($val) ? 'X' : '';
1276             }
1277             elsif ($type == 2 && $APT eq 'ADB' && $off eq 0x1B)
1278             { # Beschreibung bei ADB geht ohne Offset!
1279 0         0 $res= &get_str (\$b, $off);
1280             }
1281             elsif ($type == 2 # STRING
1282             || $type == 3 # PHONE
1283             || $type == 4 # NUMBER
1284             || $type == 6 # CATEGORY
1285             )
1286             {
1287 3         7 my $offs= unpack ('v', substr ($b, $off, 2));
1288 3         10 $res= &get_str (\$b, $offs);
1289             }
1290             elsif ($type == 7 # TIME
1291             || ($type == 24 && $APT eq 'ADB') # Vorlauf
1292             )
1293             {
1294             #??? next if ($APT eq 'APT' && $APT2 eq 'To-Do'); # overlapping fields
1295 1         5 $res= &fmt_time (substr ($b, $off, 2));
1296             }
1297             elsif ($type == 8 # DATE
1298             || ($type == 19 && $APT eq 'ADB') # Beginndatum
1299             )
1300             {
1301 1         5 $res= &fmt_date (substr ($b, $off, 3));
1302             }
1303             elsif ($type == 9) # RADIO_BUTTON
1304             {
1305 2         5 my $val= unpack ('C', substr ($b, $off, 1)); # 2 or 1 byte??
1306 2         5 my $cnt= ++$RB{$off};
1307 2 100       6 $res= ($cnt == $val) ? 'X' : '';
1308             }
1309             elsif ($type == 10) # NOTE
1310             {
1311 1         3 my $note_number= unpack ('v', substr ($b, $off, 2));
1312 1         12 $o{"$name&nr"}= $note_number;
1313 1 50       4 unless ($note_number eq $no_note)
1314             {
1315 1         1 my $nr;
1316 1         2 $nr= $N->[$note_number]; # $nr should be a valid reference!
1317 1 50       10 $res= (defined ($nr)) ? $nr->{data} : '';
1318             }
1319             }
1320             elsif ($type == 11 # GROUP
1321             || $type == 12 # STATIC (e.g. Label)
1322             || $type == 14 # LIST
1323             || $type == 15 # COMBO
1324             || ($type == 18 && $APT eq 'ADB') # repeat factor
1325             ) # no action ?!?!?
1326             {
1327 3         6 next FIELD;
1328             }
1329             elsif ($type == 16 && $APT == 'WDB')
1330             {
1331 0         0 $res= unpack ('v', substr ($b, $off, 2));
1332             }
1333             elsif ($APT eq 'ADB'
1334             && ($type == 23 # number of days
1335             || $type == 20 # date due Faelligkeitsdatum
1336             )
1337             )
1338             {
1339 0 0 0     0 next if ($type == 23 && $APT2 eq 'To-Do');
1340 0 0 0     0 next if ($type == 20 && $APT2 ne 'To-Do');
1341              
1342 0         0 $res= unpack ('v', substr ($b, $off, 2)); # 2 byte integer value
1343             }
1344             elsif ($APT eq 'ADB' && $type == 22)
1345             {
1346             # print "\n", $delim, "\n>>> U22: APT2='$APT2'\n";
1347 0 0       0 next unless ($APT2 eq 'To-Do'); # priority code
1348 0         0 $res= substr ($b, $off, 2);
1349 0         0 $res=~ s/\x00//g;
1350             }
1351             else
1352             {
1353 0         0 $res= "unknown type $type";
1354 0         0 &show_field_def (*STDOUT, $field, -1);
1355 0         0 $ok= 0;
1356             }
1357              
1358             # print "fetch: name=$name res=$res\n";
1359 10         31 $o{$name}= $res;
1360             }
1361              
1362 1         5 return ($ok, \%o);
1363             }
1364              
1365             # ----------------------------------------------------------------------------
1366             sub store_data
1367             {
1368 0     0 0 0 my $data= shift; # record data to be stored into the database
1369 0         0 my $Fdef= shift; # Field Definitions
1370 0         0 my $N= shift; # Notes Data; array of references
1371 0         0 my $APT= shift; # application type
1372 0         0 my $rec_size= shift; # standard record size and next string position
1373              
1374 0         0 my $b_off= 0; # offset into binary data
1375 0         0 my @b= # binary data at each offset
1376             my $b; # final binary data
1377 0         0 my $nil_addr; # address of the NIL string record
1378             # this is set up when there are actually strings
1379             # see notes below
1380              
1381 0         0 my $ok= 1;
1382 0         0 my %RB;
1383             my $field;
1384              
1385             # print "rec_size= $rec_size\n";
1386              
1387             # NOTE: ADB records should possibly not be handled here at all!!!
1388              
1389 0         0 FIELD: foreach $field (@$Fdef)
1390             {
1391 0         0 my $type= $field->{ftype};
1392 0         0 my $off= $field->{off};
1393 0         0 my $name= $field->{name};
1394 0 0       0 my $ex= (exists ($data->{$name})) ? 1 : 0; # data value present?
1395 0         0 my $val= $data->{$name}; # actual value
1396 0         0 my $APT2;
1397              
1398 0 0       0 $APT2= $data->{type} if ($APT eq 'ADB');
1399              
1400             # print "offset= $off type=$type name=$name val='$val'\n";
1401              
1402 0 0 0     0 if ($type == 0) # BYTEBOOL
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0          
    0          
1403             {
1404 0 0       0 $b [$off]= pack ('C', ($val) ? 1 : 0);
1405             }
1406             elsif ($type == 1) # WORDBOOL
1407             {
1408 0 0       0 $b [$off]= pack ('v', ($val) ? 1 : 0);
1409             }
1410             elsif ($type == 2 # STRING
1411             || $type == 3 # PHONE
1412             || $type == 4 # NUMBER
1413             || $type == 6 # CATEGORY
1414             )
1415             {
1416 0 0       0 if ($nil_addr eq '')
1417             { # create empty string which is used for all other empty strings
1418             # see note below
1419 0         0 $nil_addr= $rec_size;
1420 0         0 $b [$rec_size++]= "\000";
1421             # print "insert nil at $nil_addr, rec_size=$rec_size\n";
1422             }
1423              
1424 0 0       0 if ($val)
1425             {
1426 0         0 $b [$off] = pack ('v', $rec_size);
1427 0         0 $b [$rec_size]= $val . "\000";
1428 0         0 $rec_size += length ($val) + 1;
1429             }
1430             else
1431             { # store pointer to the empty string record
1432 0         0 $b [$off] = pack ('v', $nil_addr);
1433             }
1434             # &hex_dump ($b[$off]);
1435             }
1436             elsif ($type == 7) # TIME
1437             {
1438 0 0 0     0 next if ($APT eq 'ADB' && $APT2 eq 'To-Do');
1439              
1440 0         0 my ($h, $m, $t);
1441 0         0 $h= $val;
1442 0 0       0 ($h, $m)= ($1, $2) if ($val =~ /(\d+)[:\.](\d+)/);
1443 0         0 $t= $h*60+$m;
1444 0 0 0     0 $t= $no_time if (!$ex || $t < 0 || $t > $no_time);
      0        
1445 0         0 $b [$off]= pack ('v', $t);
1446             }
1447             elsif ($type == 8) # DATE
1448             {
1449 0         0 $b [$off]= &pack_date ($val);
1450             }
1451             elsif ($type == 9) # RADIO_BUTTON
1452             { # several radio buttons point to the same offset
1453             # the value can be the number of the button pointing there
1454             # or 0 when no button is checked
1455              
1456 0         0 my $v; # value to be stored
1457 0 0       0 my $checked= ($val) ? 1 : 0;
1458 0 0       0 $checked= 0 if ($v= $RB{$off}); # only the first button is valid
1459 0 0       0 $RB{$off}= $v= $field->{res} if ($checked);
1460              
1461 0         0 $b [$off]= pack ('v', $v); # Note: should be 'c' ?!?!
1462             }
1463             elsif ($type == 10) # NOTE
1464             { # store note record
1465              
1466             # possible cases:
1467             # stored | new | action
1468             # no | no | no action, $no_note is already stored
1469             # no | yes | store new note number
1470             # yes | no | T2D: delete old note, but how??
1471             # yes | yes | store note number and replace the note
1472              
1473 0         0 my $note_nr= $no_note;
1474 0         0 my $xn= "$name&nr";
1475 0 0       0 $note_nr= $data->{$xn} if (defined ($data->{$xn})); # stored note
1476              
1477 0 0 0     0 if ($note_nr == $no_note && $val ne '')
    0 0        
    0 0        
1478             { # no note before but a valid note: create new note record
1479 0         0 push (@$N, { data => $val });
1480 0         0 $data->{$xn}= $note_nr= $#$N;
1481             }
1482             elsif ($note_nr != $no_note && $val eq '')
1483             { # T2D: delete note!!
1484             # this leaves an empty note record in the database !!!
1485 0         0 undef ($N->[$note_nr]->{data}); # T2D, Test
1486 0         0 $data->{$xn}= $note_nr= $no_note;
1487             }
1488             elsif ($note_nr != $no_note && $val ne '')
1489             { # replace existing note
1490 0         0 $N->[$note_nr]->{data}= $val;
1491             }
1492              
1493 0         0 $b [$off]= pack ('v', $note_nr);
1494             }
1495             elsif ($type == 11 # GROUP
1496             || $type == 12 # STATIC
1497             || $type == 14 # LIST
1498             || $type == 15 # COMBO
1499             ) # no action ?!?!?
1500             {
1501 0         0 next FIELD;
1502             }
1503             else
1504             {
1505 0         0 print "store_data: ERROR! unknown type $type\n";
1506 0         0 &show_field_def (*STDOUT, $field, -1);
1507 0         0 print "value: $val\n";
1508 0         0 $ok= 0;
1509             }
1510             }
1511              
1512 0 0       0 if ($ok)
1513             {
1514 0         0 $b= join ('', @b);
1515              
1516 0 0       0 if (length ($b) != $rec_size)
1517             {
1518 0         0 print "ERROR: resulting record size does not match!\n",
1519             "length=", length ($b), " rec_size=$rec_size\n";
1520 0         0 &hex_dump ($b);
1521 0         0 my ($x, $y);
1522 0         0 for ($x= 0; $x <= $#b; $x++)
1523             {
1524 0 0       0 next unless ($y= $b[$x]);
1525 0         0 printf ("[%02d] %2d '%s'\n", $x, length ($y), $y);
1526             }
1527             }
1528             }
1529              
1530             # T2D: unfinished
1531 0         0 return ($ok, $b);
1532             }
1533              
1534             # NOTES:
1535             # Empty Strings are stored as null character at the beginning of the
1536             # extended data record. All empty strings point to the same address.
1537             # An empty string is stored even when all strings have a value.
1538              
1539             # ----------------------------------------------------------------------------
1540             # read a 6 byte record header
1541             sub get_recheader
1542             {
1543 24     24 0 42 local *F= shift;
1544 24         128 my $b;
1545              
1546 24 50       186 read (F, $b, 6) || return undef;
1547 24         70 my ($type, $status, $length, $idx)= unpack ('CCvv', $b);
1548              
1549 24         101 my $rec=
1550             {
1551             'type' => $type,
1552             'status' => $status,
1553             'length' => $length,
1554             'idx' => $idx,
1555             };
1556              
1557 24         86 $rec;
1558             }
1559              
1560             # ----------------------------------------------------------------------------
1561             # write a 6 byte record header
1562             sub put_recheader
1563             {
1564 0     0 0 0 local *F= shift;
1565 0         0 my $r= shift;
1566              
1567 0         0 my $b= pack ('CCvv', $r->{'type'}, $r->{'status'},
1568             $r->{'length'}, $r->{'idx'});
1569 0         0 print F $b;
1570             }
1571              
1572             # ----------------------------------------------------------------------------
1573             sub fmt_time_stamp
1574             {
1575 0     0 0 0 my $time= shift;
1576 0         0 my $Time= sprintf ("%d-%02d-%02d %2d:%02d",
1577             $time->{'year'}, $time->{'mon'}+1, $time->{'day'}+1,
1578             $time->{'min'} / 60, $time->{'min'} % 60);
1579              
1580 0         0 $Time;
1581             }
1582              
1583             # ----------------------------------------------------------------------------
1584             sub get_carddef
1585             {
1586 1     1 0 3 my $def= shift;
1587 1         2 my @wins;
1588 1         2 my $num= 0;
1589              
1590             # print ">>> processing card definition\n";
1591 1         3 while ($def)
1592             {
1593 13         18 my $pw= substr ($def, 0, 20);
1594 13         17 $def= substr ($def, 20);
1595              
1596 13         31 my ($u, $x, $y, $w, $h, $Lsize, $style, $parent)=
1597             unpack ('VvvvvvVv', $pw);
1598              
1599             # printf ("[%3d] x=%3d y=%3d w=%3d h=%3d L=%3d S=0x%08lX P=0x%04X\n",
1600             # $num, $x, $y, $w, $h, $Lsize, $style, $parent);
1601              
1602 13         16 $num++;
1603              
1604 13         40 my $win=
1605             {
1606             'x' => $x,
1607             'y' => $y,
1608             'w' => $w,
1609             'h' => $h,
1610             'Lsize' => $Lsize,
1611             'Style' => $style,
1612             'Parent' => $parent,
1613             };
1614              
1615 13         32 push (@wins, $win);
1616             }
1617              
1618 1         4 \@wins;
1619             }
1620              
1621             # ----------------------------------------------------------------------------
1622             sub get_fielddef
1623             {
1624 13     13 0 22 my $def= shift;
1625              
1626 13         42 my ($ftype, $fid, $off, $flg, $res)= unpack ('CCvCv', $def);
1627 13         28 my $name= substr ($def, 7, length ($def)-8);
1628 13         21 $name=~ s/\&//g;
1629              
1630 13         80 my $fd=
1631             {
1632             'ftype' => $ftype,
1633             'Ftype' => $FIELD_TYPE [$ftype]->{Desc},
1634             'fid' => $fid,
1635             'off' => $off,
1636             'flg' => $flg,
1637             'res' => $res,
1638             'name' => $name,
1639             };
1640              
1641 13         24 $off += $FIELD_TYPE [$ftype]->{Size};
1642 13         38 ($fd, $off);
1643             }
1644              
1645             # ----------------------------------------------------------------------------
1646             sub get_cardpagedef
1647             {
1648 0     0 0   my $def= shift;
1649              
1650             # print ">>> processing card page definition\n";
1651 0           my @pages;
1652 0           my ($PW, $CP, $PC, @ps, @pc, $i);
1653              
1654 0           ($PW, $CP, $PC,
1655             $ps[1], $ps[2], $ps[3], $ps[4],
1656             $pc[1], $pc[2], $pc[3], $pc[4])= unpack ('vvvvvvvvvv', $def);
1657              
1658             # print ">>>> CP=$CP PC=$PC\n";
1659 0           for ($i= 1; $i <= $PC; $i++)
1660             {
1661 0           push (@pages, { 'num' => $i, 'start' => $ps[$i], 'size' => $pc[$i] });
1662             # print ">>>>> [$i] start=$ps[$i] size=$pc[$i]\n";
1663             }
1664              
1665 0           \@pages;
1666             }
1667              
1668             # ----------------------------------------------------------------------------
1669             sub show_field_def
1670             {
1671 0     0 0   local *FO= shift;
1672 0           my $fdef= shift;
1673 0           my $num= shift;
1674              
1675 0           my $type= $fdef->{'ftype'};
1676 0           my $ftype= $FIELD_TYPE[$type];
1677 0   0       my $ttype= $ftype->{Desc} || "USER$type";
1678 0           my $x_siz= $ftype->{Size};
1679 0           my $x_off= sprintf ('0x%02X', $fdef->{off});
1680 0           my $x_flg= sprintf ('0x%02X', $fdef->{flg});
1681 0           my $x_name= $fdef->{name};
1682 0           $x_name=~ s/[\x80-\xFF]/?/g;
1683              
1684 0           printf FO "[%02d] %2d %-12s %3s %-24s %3d %s 0x%02X %s\n",
1685             $num, $type, $ttype, $x_siz, "'$x_name'",
1686             $fdef->{fid}, $x_off, $fdef->{res}, $x_flg;
1687              
1688             # print FO "'$x_name'\n";
1689             # print FO "[$num] type= $ttype ($type) name='$fdef->{name}'"
1690             # " id=$fdef->{fid} off=$x_off res=$fdef->{res} flg=$x_flg\n";
1691              
1692 0           $x_off;
1693             }
1694              
1695             # ----------------------------------------------------------------------------
1696             sub decode_type14 # analyze application specific field type 14
1697             {
1698 0     0 0   my $obj= shift;
1699 0           local *FO= shift;
1700 0           my $b= shift;
1701              
1702 0           my $AD= $obj->{APT_Data};
1703 0           my $lng= length ($b);
1704              
1705 0           my ($off, $d, $v);
1706 0 0         if (defined ($AD->{View_Table}))
    0          
1707             {
1708 0           print <
1709             Warning: type 14 in data base appears more than twice.
1710             Please send a sample of your database to the author
1711             &hex_dump ($b);
1712             EOX
1713             }
1714             elsif (defined ($AD->{Header}))
1715             {
1716 0           my @View_Table;
1717 0           for ($off= 0; $off+5 <= $lng; $off += 5)
1718             {
1719 0           $d= &fmt_date (substr ($b, $off, 3));
1720 0           $v= unpack ('v', substr ($b, $off+3, 2));
1721 0 0         last if ($v eq $no_val); # end marker
1722 0           push (@View_Table, { 'date' => $d, num => $v } );
1723             # print FO " date=$d num=$v\n";
1724             }
1725 0           $AD->{View_Table}= \@View_Table;
1726             # &hex_dump ($b);
1727             }
1728             else
1729             {
1730 0           $d= &fmt_date (substr ($b, 0, 3));
1731 0           $AD->{Head_Date}= $d;
1732 0           $AD->{Header}= $b;
1733             }
1734             }
1735              
1736             # ----------------------------------------------------------------------------
1737             sub print_recheader
1738             {
1739 0     0 0   local *FH= shift;
1740 0           my $txt= shift;
1741 0           my $r= shift;
1742              
1743 0           my @extra= @_;
1744 0           my $fld;
1745 0           my $type= $r->{'type'};
1746 0   0       my $ttype= $REC_TYPE[$type] || "USER$type";
1747              
1748 0           print "$txt\n";
1749              
1750 0           print " type= $ttype ($type)\n";
1751 0           foreach $fld ('status', 'length', 'idx', @extra)
1752             {
1753 0           print " $fld= $r->{$fld}\n";
1754             }
1755             }
1756              
1757             # ----------------------------------------------------------------------------
1758             sub dump_def
1759             {
1760 0     0 0   my $self= shift;
1761 0           local *FO= shift;
1762 0           my $level= shift;
1763              
1764 0           my $hdr= $self->{Header};
1765 0           my $Time= &fmt_time_stamp ($hdr->{'time'});
1766              
1767 0           my $fld;
1768 0           my $sig= substr ($hdr->{sig}, 0, 3);
1769 0           my $x_ltable= sprintf ("0x%08lX", $hdr->{lookup_table_offset});
1770 0           my $APT= &decode_apt ($hdr->{file_type});
1771              
1772 0           print FO <
1773             Filename: $self->{Filename}
1774             Meta: $self->{Meta}
1775             DB Header:
1776             sig= $sig
1777             time= $Time
1778             lookup_table_offset= $x_ltable
1779             file_type= $hdr->{file_type} $APT
1780             EOX
1781              
1782 0           foreach $fld (sort keys %$hdr)
1783             {
1784 0 0         print FO " $fld= $hdr->{$fld}\n" unless (defined ($XHDR{$fld}));
1785             }
1786              
1787             # &print_recheader (*FO, 'record header:', $hdr->{recheader});
1788             # print FO 'self:: ', join (',', sort keys %$self), "\n";
1789              
1790             # $level= 0 if ($self->{Meta} eq 'Encrypted' && $level < 10);
1791              
1792 0 0         if ($level > 0)
1793             {
1794 0           $self->show_db_def (*FO);
1795             # $self-> show_card_def (*FO);
1796             }
1797              
1798 0 0         if ($level > 1)
1799             {
1800 0           print FO $delim, "\n\n";
1801 0           for ($fld= 0; $fld < 32; $fld++)
1802             {
1803 0           $self->dump_db (*FO, $fld);
1804             }
1805             }
1806             }
1807              
1808             # ----------------------------------------------------------------------------
1809             sub dump_db
1810             {
1811 0     0 0   my $self= shift;
1812 0           local *FO= shift;
1813 0           my $type= shift;
1814 0           my $idx= shift;
1815              
1816 0           my $Types= $self->{Types};
1817 0           my $Data= $Types->[$type];
1818 0           my ($el, $i);
1819              
1820 0 0         if (defined ($idx))
1821             {
1822 0           $el= $Data->[$idx];
1823 0           &dump_db_rec (*FO, $idx, $el);
1824 0           return;
1825             }
1826              
1827 0           $idx= 0;
1828 0           foreach $el (@$Data)
1829             {
1830 0           &dump_db_rec (*FO, $idx, $el);
1831 0           $idx++;
1832             }
1833             }
1834              
1835             # ----------------------------------------------------------------------------
1836             sub dump_db_rec
1837             {
1838 0     0 0   local *FO= shift;
1839 0           my $i= shift;
1840 0           my $el= shift;
1841              
1842 0 0         unless (defined ($el))
1843             {
1844 0           print FO "data record [$i] not defined!\n";
1845 0           return;
1846             }
1847              
1848 0           &print_recheader (*FO, "data record [$i]", $el, 'filters', 'flags');
1849             # print FO "el= ", join (':', keys %$el), "\n";
1850 0           print FO "data=\n";
1851 0           &hex_dump ($el->{data}, *FO);
1852 0           print FO $delim, "\n\n";
1853             }
1854              
1855             # ----------------------------------------------------------------------------
1856             sub dump_data_record
1857             {
1858 0     0 0   my $b= shift;
1859 0           my $ok= shift;
1860 0           my $o= shift;
1861              
1862 0           print "dump_data_record:\n";
1863 0           print join (':', %$o), "\n";
1864             # print "note: $nd\n" if ($nd);
1865              
1866 0 0 0       unless ($ok && 0)
1867             {
1868 0           &hex_dump ($b);
1869             }
1870             }
1871              
1872             # ----------------------------------------------------------------------------
1873             sub hex_dump
1874             {
1875 0     0 0   my $data= shift;
1876 0   0       local *FX= shift || *STDOUT;
1877              
1878 0           my $off= 0;
1879 0           my ($i, $c, $v);
1880              
1881 0           while ($data)
1882             {
1883 0           my $char= '';
1884 0           my $hex= '';
1885 0           my $offx= sprintf ('%08X', $off);
1886 0           $off += 0x10;
1887              
1888 0           for ($i= 0; $i < 16; $i++)
1889             {
1890 0           $c= substr ($data, 0, 1);
1891              
1892 0 0         if ($c ne '')
1893             {
1894 0           $data= substr ($data, 1);
1895 0           $v= unpack ('C', $c);
1896 0 0 0       $c= '.' if ($v < 0x20 || $v >= 0x7F);
1897              
1898 0           $char .= $c;
1899 0           $hex .= sprintf (' %02X', $v);
1900             }
1901             else
1902             {
1903 0           $char .= ' ';
1904 0           $hex .= ' ';
1905             }
1906             }
1907              
1908 0           print FX "$offx $hex |$char|\n";
1909             }
1910             }
1911              
1912             # ----------------------------------------------------------------------------
1913             # Decrypt the password of a HP 200LX Database.
1914             # This function implements the algorithm in Curtis Cameron's dbcheck program.
1915             # Returns a session key and the original password. I'm not quite sure
1916             # if the original password is correct in all cases, this needs more testing.
1917             sub decrypt_password
1918             {
1919 0     0 0   my ($b, $siz)= @_;
1920 0           my ($pass, $key);
1921              
1922 0 0         if ($siz != 17)
1923             {
1924 0           print "WARNING: decrypt_password (siz=$siz): ",
1925             "password block size should be 17 byte!\n";
1926             }
1927              
1928 0           my ($i, $c, $k, $p);
1929 0           for ($i= 0; $i < 17; $i++)
1930             {
1931 0           $c= unpack ('C', substr ($b, $i, 1));
1932 0           $k= $c ^ $i ^ $CODE_A[$i];
1933             # my $diag= sprintf ("%02X ^ %02X ^ A[%3d]=%02X", $c, $i, $i, $CODE_A[$i]);
1934              
1935             # this CODE_B round cancels the effect of the same thing in decrypt_data
1936             # $k ^= $CODE_B[$i];
1937             # $diag .= sprintf (" ^ B[%2d]=%02X", $i, $CODE_B[$i]);
1938              
1939 0           push (@$key, $k);
1940             # push (@DIAG_K, $diag);
1941              
1942 0           $p= $PW_CODE [$i] ^ $c;
1943 0 0         $pass .= pack ('C', $p) if ($p > 0x00);
1944             }
1945              
1946 0           print "database is encrypted\npassword record, encrypted, siz=$siz\n";
1947 0           &hex_dump ($b);
1948 0           print "password record, decryption attempted (1)\n";
1949 0           &hex_dump ($pass);
1950 0           print "password= '$pass'\n";
1951              
1952 0           ($pass, $key);
1953             }
1954              
1955             # ----------------------------------------------------------------------------
1956             # Decrypt the data portion of a HP 200LX Database record.
1957             # This function implements the algorithm in Curtis Cameron's dbcheck program.
1958             sub decrypt_data
1959             {
1960 0     0 0   my ($b, $siz, $code_ref)= @_;
1961              
1962 0           my ($cc, $c0, $bb);
1963 0           my ($c_a, $c_b, $c_k);
1964 0           my ($ii, $i_127, $i_17);
1965 0           for ($ii= 0; $ii < $siz; $ii++)
1966             {
1967 0           $c0= unpack ('C', substr ($b, $ii, 1));
1968              
1969 0           $c_a= $CODE_A [$i_127];
1970 0           $c_k= $code_ref->[$i_17];
1971 0           $cc= $c0 ^ $c_k ^ $c_a;
1972              
1973             # my $diag= sprintf ("[%4d] %02X ^ K[%2d]=(%s)=%02X ^ A[%3d]=%02X",
1974             # $ii, $c0,
1975             # $i_17, $DIAG_K[$i_17], $c_k,
1976             # $i_127, $c_a);
1977              
1978             # this CODE_B round cancels the effect of the same thing in decrypt_password
1979             # $c_b= $CODE_B [$i_17];
1980             # $cc ^= $c_b;
1981             # $diag .= sprintf (" ^ B[%3d]=%02X", $i_17, $c_b);
1982              
1983 0 0         if ($ii > 126)
1984             {
1985 0           my $ti;
1986 0           for ($ti= $ii-127; $ti >= 0; $ti -= 127)
1987             {
1988 0           $c_b= $CODE_B [$ti % 17];
1989 0           $cc ^= $c_b;
1990             # $diag .= sprintf (" ^ B[%3d]=%02X", $ti%17, $c_b);
1991             }
1992             }
1993             # $diag .= sprintf (" =: %02X %c", $cc, $cc); print $diag, "\n";
1994              
1995 0           $bb .= pack ('C', $cc);
1996 0 0         $i_17= 0 if (++$i_17 >= 17);
1997 0 0         $i_127= 0 if (++$i_127 >= 127);
1998             }
1999              
2000 0           $bb;
2001             }
2002              
2003             # ----------------------------------------------------------------------------
2004             sub recover_password
2005             {
2006 0     0 0   my $self= shift;
2007 0           my $note_nr= shift;
2008 0           my $ptx_fnm= shift;
2009 0           my $key_fnm= shift;
2010              
2011             # fetch encrypted note
2012 0   0       my $T= $self->{Types} || die;
2013 0           my $D= $T->[11]; # array of data records
2014 0           my $N= $T->[9]; # array of note records
2015 0           my $enc_txt= $N->[$note_nr]->{data};
2016             # print "encrypted text:\n"; &hex_dump ($enc_txt);
2017              
2018             # fetch plain text
2019 0           my $ptx_txt;
2020 0 0         open (FI, $ptx_fnm) || die;
2021 0           while () { $ptx_txt .= $_; }
  0            
2022 0           close (FI);
2023             # print "plain text:\n"; &hex_dump ($ptx_txt);
2024              
2025             # recover the key
2026 0           my ($pp, $cc, $ee, $ii, $key);
2027 0           my $ll_enc= length ($enc_txt);
2028 0           my $ll_ptx= length ($ptx_txt);
2029 0           print "text size enc=$ll_enc plain=$ll_ptx\n";
2030              
2031 0           for ($ii= 0; $ii < $ll_ptx; $ii++)
2032             {
2033 0           $pp= unpack ('C', substr ($ptx_txt, $ii, 1));
2034 0           $ee= unpack ('C', substr ($enc_txt, $ii, 1));
2035 0           $cc= $pp ^ $ee ^ $ii;
2036 0           $key .= pack ('C', $cc);
2037             }
2038              
2039             # print "the key is\n"; &hex_dump ($key);
2040              
2041 0           print "dumping key to $key_fnm\n";
2042 0 0         open (FO, ">$key_fnm") || die;
2043 0           binmode (FI); # MS-DOS systems need this, T2D: how about Mac?
2044 0           print FO $key;
2045 0           close (FO);
2046             }
2047              
2048             # ----------------------------------------------------------------------------
2049             sub get_field_type
2050             {
2051 0     0 0   my $ty= shift;
2052 0           $FIELD_TYPE[$ty];
2053             }
2054              
2055             # Autoload methods go after =cut, and are processed by the autosplit program.
2056              
2057             1;
2058             __END__