This file is indexed.

/usr/bin/pdbdump is in libpalm-perl 1:1.013-1.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
#!/usr/bin/perl

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell
#
# Dump a Palm PDB or PRC database.
#
# $Id: pdbdump,v 3.5 2002/04/10 00:58:28 arensb Exp $

use strict;
use Palm::PDB;
use Palm::Raw;

# Load handlers for the built-in apps by default
use Palm::Memo;
use Palm::Address;
use Palm::Datebook;
use Palm::Mail;
use Palm::ToDo;

use vars qw( $VERSION %PDBHandlers %PRCHandlers $hexdump );

$VERSION = sprintf "%d.%03d_%03d_%03d",
	'$Revision: 3.5 $ ' =~ m{(\d+)(?:\.(\d+))};

*PDBHandlers = *Palm::PDB::PDBHandlers;
*PRCHandlers = *Palm::PDB::PRCHandlers;

&Palm::PDB::RegisterPRCHandlers("Palm::Raw",
	[ "", "" ]
	);

$hexdump = 1;			# By default, print hex dumps of everything

# Parse command-line arguments
my $arg;
while (($arg = $ARGV[0]) =~ /^-/)
{
	$arg = shift;

	if (($arg eq "-h") || ($arg eq "-help") || ($arg eq "--help"))
	{
		print <<EOT;
Usage: $0 [options] pdb-file
Options:
	-h, -help, --help	This message.
	-nohex			Don't print hex dumps
	-M<module>		Load <module> (e.g., -MPalm::Address)
EOT
#'
		exit 0;
	} elsif ($arg =~ /^-M/)
	{
		eval "use $';";
	} elsif ($arg eq "-nohex")
	{
		$hexdump = 0;
	} else {
		die "Unrecognized option: $arg\n";
	}
}

my $fname = shift;

die "No such file: $fname\n" if ! -f $fname;

my $EPOCH_1904 = 2082844800;		# Difference between Palm's
					# epoch (Jan. 1, 1904) and
					# Unix's epoch (Jan. 1, 1970),
					# in seconds.
my $HeaderLen = 32+2+2+(9*4);		# Size of database header
my $RecIndexHeaderLen = 6;		# Size of record index header
my $IndexRecLen = 8;			# Length of record index entry
my $IndexRsrcLen = 10;			# Length of resource index entry

#%PDBHandlers = ();			# Record handler map
#%PRCHandlers = ();			# Resource handler map


#&Palm::PDB::rawread($fname);
&rawread($fname);

#package Palm::PDB;		# XXX - Gross hack!
sub rawread
{
	my $self = new Palm::Raw;
	my $fname = shift;		# Filename to read from
	my $buf;			# Buffer into which to read stuff

	# Open database file
	open PDB, "< $fname" or die "Can't open \"$fname\": $!\n";
	binmode PDB;			# Parse as binary file under MS-DOS

	# Get the size of the file. It'll be useful later
	seek PDB, 0, 2;		# 2 == SEEK_END. Seek to the end.
	$self->{_size} = tell PDB;
	print "File size: $self->{_size}\n";
	seek PDB, 0, 0;		# 0 == SEEK_START. Rewind to the beginning.

	# Read header
	my $name;
	my $attributes;
	my $version;
	my $ctime;
	my $mtime;
	my $baktime;
	my $modnum;
	my $appinfo_offset;
	my $sort_offset;
	my $type;
	my $creator;
	my $uniqueIDseed;

	read PDB, $buf, $HeaderLen;	# Read the PDB header
	print "Database header:\n";
	if ($hexdump)
	{
		&hexdump("   ", $buf);
		print "\n";
	}

	# Split header into its component fields
	($name, $attributes, $version, $ctime, $mtime, $baktime,
	$modnum, $appinfo_offset, $sort_offset, $type, $creator,
	$uniqueIDseed) =
		unpack "a32 n n N N N N N N a4 a4 N", $buf;

	($self->{name} = $name) =~ s/\0*$//;
	$self->{attributes}{resource} = 1 if $attributes & 0x0001;
	$self->{attributes}{"read-only"} = 1 if $attributes & 0x0002;
	$self->{attributes}{"AppInfo dirty"} = 1 if $attributes & 0x0004;
	$self->{attributes}{backup} = 1 if $attributes & 0x0008;
	$self->{attributes}{"OK newer"} = 1 if $attributes & 0x0010;
	$self->{attributes}{reset} = 1 if $attributes & 0x0020;
	$self->{attributes}{open} = 1 if $attributes & 0x0040;
	$self->{attributes}{launchable} = 1 if $attributes & 0x0200;
	$self->{version} = $version;
	$self->{ctime} = $ctime - $EPOCH_1904;
	$self->{mtime} = $mtime - $EPOCH_1904;
	$self->{baktime} = $baktime - $EPOCH_1904;
	$self->{modnum} = $modnum;
	# _appinfo_offset and _sort_offset are private fields
	$self->{_appinfo_offset} = $appinfo_offset;
	$self->{_sort_offset} = $sort_offset;
	$self->{type} = $type;
	$self->{creator} = $creator;
	$self->{uniqueIDseed} = $uniqueIDseed;

	print <<EOT;
    Name:           $name
    Attributes:     @{[sprintf("0x%02x", $attributes)]}
EOT
	print " "x19;
	print " LAUNCHABLE"	if $self->{attributes}{launchable};
	print " OPEN"		if $self->{attributes}{open};
	print " RESET"		if $self->{attributes}{reset};
	print " OKNEWER"	if $self->{attributes}{"OK newer"};
	print " BACKUP"		if $self->{attributes}{backup};
	print " APPINFO-DIRTY"	if $self->{attributes}{"AppInfo dirty"};
	print " READ-ONLY"	if $self->{attributes}{"read-only"};
	print " RESOURCE"	if $self->{attributes}{resource};
	print "\n";
	print <<EOT;
    Version:        $version
    Ctime:          $ctime	@{[scalar(localtime($ctime-$EPOCH_1904))]}
    Mtime:          $mtime	@{[scalar(localtime($mtime-$EPOCH_1904))]}
    Backup time:    $baktime	@{[scalar(localtime($baktime-$EPOCH_1904))]}
    Mod number:     $modnum
    AppInfo offset: $appinfo_offset
    Sort offset:    $sort_offset
    Type:           $type
    Creator:        $creator
    Unique ID seed: $uniqueIDseed

EOT

	# Rebless this PDB object, depending on its type and/or
	# creator. This allows us to magically invoke the proper
	# &Parse*() function on the various parts of the database.

	# Look for most specific handlers first, least specific ones
	# last. That is, first look for a handler that deals
	# specifically with this database's creator and type, then for
	# one that deals with this database's creator and any type,
	# and finally for one that deals with anything.

	my $handler;
	if ($self->{attributes}{resource})
	{
		# Look among resource handlers
		$handler = $PRCHandlers{$self->{creator}}{$self->{type}} ||
			$PRCHandlers{undef}{$self->{type}} ||
			$PRCHandlers{$self->{creator}}{""} ||
			$PRCHandlers{""}{""};
	} else {
		# Look among record handlers
		$handler = $PDBHandlers{$self->{creator}}{$self->{type}} ||
			$PDBHandlers{""}{$self->{type}} ||
			$PDBHandlers{$self->{creator}}{""} ||
			$PDBHandlers{""}{""};
	}

	if (defined($handler))
	{
		bless $self, $handler;
	} else {
		# XXX - This should probably return 'undef' or something,
		# rather than die.
		die "No handler defined for creator \"$creator\", type \"$type\"\n";
	}

	## Read record/resource index
	# Read index header
	read PDB, $buf, $RecIndexHeaderLen;
	print "Record/resource index header:\n";
	if ($hexdump)
	{
		&hexdump("   ", $buf);
		print "\n";
	}

	my $next_index;
	my $numrecs;

	($next_index, $numrecs) = unpack "N n", $buf;
	$self->{_numrecs} = $numrecs;

	print <<EOT;
    Next index:     $next_index
    # records:      $numrecs

EOT

	# Read the index itself
	if ($self->{attributes}{resource})
	{
		&_load_rsrc_index($self, \*PDB);
	} else {
		&_load_rec_index($self, \*PDB);
	}

	# Ignore the two NUL bytes that are usually here. We'll seek()
	# around them later.

	# Read AppInfo block, if it exists
	if ($self->{_appinfo_offset} != 0)
	{
		&_load_appinfo_block($self, \*PDB);
	}

	# Read sort block, if it exists
	if ($self->{_sort_offset} != 0)
	{
		&_load_sort_block($self, \*PDB);
	}

	# Read record/resource list
	if ($self->{attributes}{resource})
	{
		&_load_resources($self, \*PDB);
	} else {
		&_load_records($self, \*PDB);
	}

	# These keys were needed for parsing the file, but are not
	# needed any longer. Delete them.
	delete $self->{_index};
	delete $self->{_numrecs};
	delete $self->{_appinfo_offset};
	delete $self->{_sort_offset};
	delete $self->{_size};

	close PDB;
}


# _load_rec_index
# Private function. Read the record index, for a record database
sub _load_rec_index
{
	my $pdb = shift;
	my $fh = shift;		# Input file handle
	my $i;
my $lastoffset = 0;

	print "Record index:\n";

	# Read each record index entry in turn
	for ($i = 0; $i < $pdb->{_numrecs}; $i++)
	{
		my $buf;		# Input buffer

		print "  Record index entry $i\n";

		# Read the next record index entry
		my $offset;
		my $attributes;
		my @id;			# Raw ID
		my $id;			# Numerical ID
		my $entry = {};		# Parsed index entry

		read $fh, $buf, $IndexRecLen;
		if ($hexdump)
		{
			&hexdump("   ", $buf);
			print "\n";
		}

		# The ID field is a bit weird: it's represented as 3
		# bytes, but it's really a double word (long) value.

		($offset, $attributes, @id) = unpack "N C C3", $buf;
if ($offset == $lastoffset)
{
print STDERR "Record $i has same offset as previous one: $offset\n";
}
$lastoffset = $offset;

		$entry->{offset} = $offset;
		$entry->{attributes}{expunged} = 1 if $attributes & 0x80;
		$entry->{attributes}{dirty} = 1 if $attributes & 0x40;
		$entry->{attributes}{deleted} = 1 if $attributes & 0x20;
		$entry->{attributes}{private} = 1 if $attributes & 0x10;
		$entry->{id} = ($id[0] << 16) |
				($id[1] << 8) |
				$id[2];

		# The lower 4 bits of the attributes field are
		# overloaded: If the record has been deleted and/or
		# expunged, then bit 0x08 indicates whether the record
		# should be archived. Otherwise (if it's an ordinary,
		# non-deleted record), the lower 4 bits specify the
		# category that the record belongs in.
		if (($attributes & 0xa0) == 0)
		{
			$entry->{category} = $attributes & 0x0f;
		} else {
			$entry->{attributes}{archive} = 1
				if $attributes & 0x08;
		}

		print <<EOT;
    Offset:         $offset
    Attributes:     @{[sprintf("0x%02x", $attributes), keys %{$entry->{attributes}}]}
    Category:       $entry->{category}
    ID:             @{[sprintf("0x%02x%02x%02x", @id)]}

EOT

		# Put this information on a temporary array
		push @{$pdb->{_index}}, $entry;
	}
}

# XXX - Make this print out debugging information
# _load_rsrc_index
# Private function. Read the resource index, for a resource database
sub _load_rsrc_index
{
	my $pdb = shift;
	my $fh = shift;		# Input file handle
	my $i;

	print "Resource index:\n";

	# Read each resource index entry in turn
	for ($i = 0; $i < $pdb->{_numrecs}; $i++)
	{
		my $buf;		# Input buffer

		print "  Resource index entry $i\n";

		# Read the next resource index entry
		my $type;
		my $id;
		my $offset;
		my $entry = {};		# Parsed index entry

		read $fh, $buf, $IndexRsrcLen;
		if ($hexdump)
		{
			&hexdump("   ", $buf);
			print "\n";
		}

		($type, $id, $offset) = unpack "a4 n N", $buf;

		$entry->{type} = $type;
		$entry->{id} = $id;
		$entry->{offset} = $offset;

		print <<EOT;
    Offset:         $offset
    ID:             $id
    Type:           $type

EOT

		push @{$pdb->{_index}}, $entry;
	}
}

# _load_appinfo_block
# Private function. Read the AppInfo block
sub _load_appinfo_block
{
	my $pdb = shift;
	my $fh = shift;		# Input file handle
	my $len;		# Length of AppInfo block
	my $buf;		# Input buffer

	print "AppInfo block:\n";

	# Sanity check: make sure we're positioned at the beginning of
	# the AppInfo block
	if (tell($fh) > $pdb->{_appinfo_offset})
	{
		die "Bad AppInfo offset: expected ",
			sprintf("0x%08x", $pdb->{_appinfo_offset}),
			", but I'm at ",
			tell($fh), "\n";
	}

	# Seek to the right place, if necessary
	if (tell($fh) != $pdb->{_appinfo_offset})
	{
		seek PDB, $pdb->{_appinfo_offset}, 0;
	}

	# There's nothing that explicitly gives the size of the
	# AppInfo block. Rather, it has to be inferred from the offset
	# of the AppInfo block (previously recorded in
	# $pdb->{_appinfo_offset}) and whatever's next in the file.
	# That's either the sort block, the first data record, or the
	# end of the file.

	if ($pdb->{_sort_offset})
	{
		# The next thing in the file is the sort block
		$len = $pdb->{_sort_offset} - $pdb->{_appinfo_offset};
	} elsif ((defined $pdb->{_index}) && @{$pdb->{_index}})
	{
		# There's no sort block; the next thing in the file is
		# the first data record
		$len = $pdb->{_index}[0]{offset} -
			$pdb->{_appinfo_offset};
	} else {
		# There's no sort block and there are no records. The
		# AppInfo block goes to the end of the file.
		$len = $pdb->{_size} - $pdb->{_appinfo_offset};
	}

	# Read the AppInfo block
	read $fh, $buf, $len;
	if ($hexdump)
	{
		&hexdump("   ", $buf);
		print "\n";
	}

	# Tell the real class to parse the AppInfo block
	$pdb->{appinfo} = $pdb->ParseAppInfoBlock($buf);

	# Print out the parsed values
	if (ref($pdb->{appinfo}) ne "")
	{
		&dumphash($pdb->{appinfo}, "\t");
		print "\n";
	}
}

# _load_sort_block
# Private function. Read the sort block.
sub _load_sort_block
{
	my $pdb = shift;
	my $fh = shift;		# Input file handle
	my $len;		# Length of sort block
	my $buf;		# Input buffer

	print "Sort block:\n";

	# Sanity check: make sure we're positioned at the beginning of
	# the sort block
	if (tell($fh) > $pdb->{_sort_offset})
	{
		die "Bad sort block offset: expected ",
			sprintf("0x%08x", $pdb->{_sort_offset}),
			", but I'm at ",
			tell($fh), "\n";
	}

	# Seek to the right place, if necessary
	if (tell($fh) != $pdb->{_sort_offset})
	{
		seek PDB, $pdb->{_sort_offset}, 0;
	}

	# There's nothing that explicitly gives the size of the sort
	# block. Rather, it has to be inferred from the offset of the
	# sort block (previously recorded in $pdb->{_sort_offset})
	# and whatever's next in the file. That's either the first
	# data record, or the end of the file.

	if (defined($pdb->{_index}))
	{
		# The next thing in the file is the first data record
		$len = $pdb->{_index}[0]{offset} -
			$pdb->{_sort_offset};
	} else {
		# There are no records. The sort block goes to the end
		# of the file.
		$len = $pdb->{_size} - $pdb->{_sort_offset};
	}

	# Read the AppInfo block
	read $fh, $buf, $len;
	if ($hexdump)
	{
		&hexdump("   ", $buf);
		print "\n";
	}

	# XXX - Check to see if the sort block has some predefined
	# structure. If so, it might be a good idea to parse the sort
	# block here.

	# Tell the real class to parse the sort block
	$pdb->{sort} = $pdb->ParseSortBlock($buf);
}

# _load_records
# Private function. Load the actual data records, for a record database
# (PDB)
sub _load_records
{
	my $pdb = shift;
	my $fh = shift;		# Input file handle
	my $i;

	print "Records:\n";
	# Read each record in turn
	for ($i = 0; $i < $pdb->{_numrecs}; $i++)
	{
		my $len;	# Length of record
		my $buf;	# Input buffer

		print "  Record $i\n";

		# Sanity check: make sure we're where we think we
		# should be.
		if (tell($fh) > $pdb->{_index}[$i]{offset})
		{
# XXX - The two NULs are really optional.
#			die "Bad offset for record $i: expected ",
#				sprintf("0x%08x",
#					$pdb->{_index}[$i]{offset}),
#				" but it's at ",
#				sprintf("[0x%08x]", tell($fh)), "\n";
		}

		# Seek to the right place, if necessary
		if (tell($fh) != $pdb->{_index}[$i]{offset})
		{
			seek PDB, $pdb->{_index}[$i]{offset}, 0;
		}

		# Compute the length of the record: the last record
		# extends to the end of the file. The others extend to
		# the beginning of the next record.
		if ($i == $pdb->{_numrecs} - 1)
		{
			# This is the last record
			$len = $pdb->{_size} -
				$pdb->{_index}[$i]{offset};
		} else {
			# This is not the last record
			$len = $pdb->{_index}[$i+1]{offset} -
				$pdb->{_index}[$i]{offset};
		}

		# Read the record
		read $fh, $buf, $len;
		if ($hexdump)
		{
			&hexdump("   ", $buf);
			print "\n";
		}

		# Tell the real class to parse the record data. Pass
		# &ParseRecord all of the information from the index,
		# plus a "data" field with the raw record data.
		my $record;

		$record = $pdb->ParseRecord(
			%{$pdb->{_index}[$i]},
			"data"	=> $buf,
			);
		push @{$pdb->{records}}, $record;

		# Print out the parsed values
		&dumphash($record, "\t");
		print "\n";
	}
}

# _load_resources
# Private function. Load the actual data resources, for a resource database
# (PRC)
sub _load_resources
{
	my $pdb = shift;
	my $fh = shift;		# Input file handle
	my $i;

	print "Resources:\n";
	# Read each resource in turn
	for ($i = 0; $i < $pdb->{_numrecs}; $i++)
	{
		my $len;	# Length of record
		my $buf;	# Input buffer

		print "  Resource $i\n";

		# Sanity check: make sure we're where we think we
		# should be.
		if (tell($fh) > $pdb->{_index}[$i]{offset})
		{
			die "Bad offset for resource $i: expected ",
				sprintf("0x%08x",
					$pdb->{_index}[$i]{offset}),
				" but it's at ",
				sprintf("0x%08x", tell($fh)), "\n";
		}

		# Seek to the right place, if necessary
		if (tell($fh) != $pdb->{_index}[$i]{offset})
		{
			seek PDB, $pdb->{_index}[$i]{offset}, 0;
		}

		# Compute the length of the resource: the last
		# resource extends to the end of the file. The others
		# extend to the beginning of the next resource.
		if ($i == $pdb->{_numrecs} - 1)
		{
			# This is the last resource
			$len = $pdb->{_size} -
				$pdb->{_index}[$i]{offset};
		} else {
			# This is not the last resource
			$len = $pdb->{_index}[$i+1]{offset} -
				$pdb->{_index}[$i]{offset};
		}

		# Read the resource
		read $fh, $buf, $len;
		if ($hexdump)
		{
			&hexdump("   ", $buf);
			print "\n";
		}

		# Tell the real class to parse the resource data. Pass
		# &ParseResource all of the information from the
		# index, plus a "data" field with the raw resource
		# data.
		my $resource;

		$resource = $pdb->ParseResource(
			%{$pdb->{_index}[$i]},
			"data"	=> $buf,
			);
		push @{$pdb->{resources}}, $resource;

		# Print out the parsed values
		&dumphash($resource, "\t");
		print "\n";
	}
}

sub hexdump
{
	my $prefix = shift;	# What to print in front of each line
	my $data = shift;	# The data to dump
	my $maxlines = shift;	# Max # of lines to dump
	my $offset;		# Offset of current chunk

	for ($offset = 0; $offset < length($data); $offset += 16)
	{
		my $hex;		# Hex values of the data
		my $ascii;		# ASCII values of the data
		my $chunk;		# Current chunk of data

		last if defined($maxlines) && ($offset >= ($maxlines * 16));

		$chunk = substr($data, $offset, 16);

		($hex = $chunk) =~ s/./sprintf "%02x ", ord($&)/ges;

		($ascii = $chunk) =~ y/\040-\176/./c;

		printf "%s %-48s|%-16s|\n", $prefix, $hex, $ascii;
	}
}

# XXX - Ought to have a &dumparray as well. The two can call each other
# recursively.

sub dumphash
{
	my $hash = shift;
	my $indent = shift;
	my $key;
	my $value;

	while (($key, $value) = each %{$hash})
	{
		if (ref($value) eq "HASH")
		{
			print $indent, $key, ":\n";
			&dumphash($value, $indent . "\t");
		} elsif (ref($value) eq "ARRAY")
		{
			my($i,$j);

			print $indent, $key, ":\n";
			for ($i = 0; $i <= $#{$value}; $i++)
			{
				if (ref($value->[$i]) eq "HASH")
				{
					print $indent, "    $i:\n";
					&dumphash($value->[$i],
						$indent . "\t");
				} elsif (ref($value->[$i]) eq "ARRAY")
				{
					my @v2 = @{$value->[$i]};
					for ($j = 0; $j <= $#v2; $j++)
					{
						print $indent,
							"\t$i-$j: [$v2[$j]]\n";
					}
				}else {
					print $indent,
						"\t$i: [$value->[$i]]\n";
				}
			}
		} else {
			print $indent, $key, " -> [", $value, "]\n";
		}
	}
}

__END__

=head1 NAME

pdbdump - Print the contents of a Palm PDB file

=head1 SYNOPSIS

C<pdbdump> I<[options]> F<filename>

=head1 DESCRIPTION

C<pdbdump> reads a PalmOS F<.pdb> file, parses it, and prints its
contents. This includes both a hex dump of the raw data of each piece,
and a human-readable list of the various values, insofar as possible.
The aim of C<pdbdump> is to allow one to verify whether a particular
file is a well-formed PalmOS database file and if not, where the error
lies.

If the database is of a known type, C<pdbdump> parses the AppInfo
block and records. Otherwise, it simply prints out a hex dump of their
contents. C<pdbdump> includes, by default, support for most of the
built-in applications. Other helper modules may be loaded with the
C<-M> option.

=head1 OPTIONS

=over 4

=item -h -help --help

Print a usage message and exit.

=item -nohex

Don't print the hex dump of the various parts.

=item -MI<module>

C<use> the named module. This can be useful for loading additional
helper modules.

=back

=head1 BUGS

C<pdbdump> only recognizes record databases (C<.pdb> files), not
resource databases (C<.prc> files).

=head1 SEE ALSO

Palm::PDB(3)

=head1 AUTHOR

Andrew Arensburger <arensb@ooblick.com>