#!/usr/bin/perl

# deipd version 0.1

# Copyright (C) 2008, ashley willis <barry@venamous.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.
#
# See the GNU General Public License in the COPYING file at the
# root directory of this project for more details.

# deconstructs an <BASENAME>.ipd file into individual database files of
# format <BASENAME>/<DB>.deipd, with file <BASENAME>/<BASENAME>.info
# containing a list of the databases.

# NOTES:
# dbID == 0 if first db in IPD, 1 if second, ...
# dbRecordHandler: sequential in overall file, but sometimes skips

$BINARY  = 0;		# must be 0
$SCREEN  = 0;		# optional
$OPENING = 0;		# optional

$VERBOSE         = 0;	# optional
$DBID            = 1;	# must be 1
$DBRECORDLENGTH  = 1;	# must be 1
$DBRECORDHANDLER = 1;	# must be 1
$FIELDLENGTH     = 0;	# optional

if ($VERBOSE) {
  $DBID            = 1;
  $DBRECORDLENGTH  = 1;
  $DBRECORDHANDLER = 1;
  $FIELDLENGTH     = 1;
}

foreach $file (@ARGV) {
  $file =~ /\.ipd2?$/ or die;
  ($OPENING) and print "\nAttempting to open $file...\n";
  open(IN, "$file");
  $basename = $file;
  $basename =~ s/\.ipd2?$// or die;
  mkdir("$basename") or die;

  # overall header = 42 bytes
  read(IN, $header, 38);
  unless ($header =~ /^Inter\@ctive Pager Backup\/Restore File\n$/) {
    print STDERR "Not a valid IPD file: $file\n";
    next;
  }
  read(IN, $ipdVersion, 1);
  unless ($ipdVersion =~ //) {
    print STDERR"Unknown IPD version: " . Bin2dec($ipdVersion) . "\n";
    next;
  }
  read(IN, $numberOfDBs, 2);
  read(IN, $separator, 1);	# ==  
  unless ($separator =~ / /) {
    print STDERR "Unknown error 1: Invalid separator\n";
    next;
  }
  $ipdVersionTXT = Bin2dec($ipdVersion);
  $numberOfDBsTXT = bin2dec($numberOfDBs);

  ($VERBOSE and $SCREEN) and print "header          = \"$header\"\n";
  ($VERBOSE and $SCREEN) and print "ipdVersion      = \'$ipdVersionTXT\'" . ($BINARY ? "\t\"$ipdVersion\"" : "") . "\n";
  ($VERBOSE and $SCREEN) and print "number of DBs   = $numberOfDBsTXT" . ($BINARY ? "\t\"$numberOfDBs\"" : "") . "\n";

  # get database names
  undef @dbnames;
  for ($db = 1; $db <= $numberOfDBsTXT; $db++) {
    read(IN, $dbNameLength, 2);
    $dbNameLengthTXT = Bin2dec($dbNameLength);
    ($VERBOSE and $SCREEN) and print "dbNameLength    = $dbNameLengthTXT" . ($BINARY ? "\t\"$dbNameLength\"" : "") . "\n";
    read(IN, $dbName, $dbNameLengthTXT);
    unless ($dbName =~ s/ $//) {
      print STDERR "Unknown error 2: Invalid dbName end\n";
      next;
    }
    ($SCREEN) and print "dbName          = \"$dbName\"\n";
    $dbNames[$db-1] = $dbName;
    if ($file =~ /2$/) { open(OUT,">$basename/$dbName.deipd2"); }
    else { open(OUT,">$basename/$dbName.deipd"); }
    print OUT "dbName          = \"$dbName\"\n";
    close(OUT);
  }
  open(OUT,">$basename/$basename.info");
  foreach $name (@dbNames) {
    print OUT "$name\n";
  }

  # parse the database entries
  while(read(IN, $dbID, 2)) {
    undef $dbIDTXT, $dbRecordLength, $dbRecordLengthTXT, $dbVersion,
          $dbRecordHandler, $recordID, $fieldLength, $fieldLengthTXT,
          $fieldType, $fieldData;
    ($SCREEN) and print "\n";
    #read(IN, $dbID, 2) || die "out of data";
    $dbIDTXT = Bin2dec(reverse($dbID));
    ($SCREEN) and print "dbID            = $dbIDTXT" . ($BINARY ? "\t\"$dbID\"" : "") . "\n";
    read(IN, $dbRecordLength, 4);
    $dbRecordLengthTXT = Bin2dec($dbRecordLength);
    ($VERBOSE and $SCREEN) and print "dbRecordLength  = $dbRecordLengthTXT" . ($BINARY ? "\t\"$dbRecordLength\"" : "") . "\n";
    read(IN, $dbVersion, 1);
    ($SCREEN) and print "dbVersion       = " . Bin2dec($dbVersion) . "\n";
    read(IN, $dbRecordHandler, 2);
    ($SCREEN) and print "dbRecordHandler = " . Bin2dec($dbRecordHandler) . ($BINARY ? "\t\"$dbRecordHandler\"" : "") . "\n";
    read(IN, $recordID, 4);
    ($SCREEN) and print "recordID        = " . reverse(unpack ("h*", $recordID)) . ($BINARY ? "\t\"$recordID\"" : "") . "\n";

    #open(OUT,">>$dbNames[$dbIDTXT].deipd");
    if ($file =~ /2$/) { open(OUT,">>$basename/$dbNames[$dbIDTXT].deipd2"); }
    else { open(OUT,">>$basename/$dbNames[$dbIDTXT].deipd"); }
    print OUT "\n";
    ($DBID) and print OUT "dbID            = $dbIDTXT" . ($BINARY ? "\t\"$dbID\"" : "") . "\n";
    ($DBRECORDLENGTH) and print OUT "dbRecordLength  = $dbRecordLengthTXT" . ($BINARY ? "\t\"$dbRecordLength\"" : "") . "\n";
    print OUT "dbVersion       = " . Bin2dec($dbVersion) . "\n";
    ($DBRECORDHANDLER) and print OUT "dbRecordHandler = " . Bin2dec($dbRecordHandler) . ($BINARY ? "\t\"$dbRecordHandler\"" : "") . "\n";
    $recordIDTXT = unpack ("H*", reverse($recordID));
    print OUT "recordID        = " . $recordIDTXT . ($BINARY ? "\t\"$recordID\"" : "") . "\n";

    $c = 7;
    while($c < $dbRecordLengthTXT) {
      read(IN, $fieldLength, 2);
      $fieldLengthTXT = Bin2dec($fieldLength);
      ($VERBOSE and $SCREEN) and print "field length    = $fieldLengthTXT" . ($BINARY ? "\t\"$fieldLength\"" : "") . "\n";
      read(IN, $fieldType, 1);
      if ($fieldType eq "\n") {
        $fieldType = "\\n";
      }
      read(IN, $fieldData, $fieldLengthTXT);
      #unless ($fieldType =~ /M/) {
      #  unless ($fieldData =~ s/ $//) {
      #    #die("field data error");
      #  }
      #}
      #print "field type      = \'$fieldType\'\n";
      #print "field data      = \"$fieldData\"\n";
      ($SCREEN) and print "field           = \'$fieldType\' -> \"$fieldData\"\n";
      $c += 3 + $fieldLengthTXT;

      ($FIELDLENGTH) and print OUT "field length    = $fieldLengthTXT" . ($BINARY ? "\t\"$fieldLength\"" : "") . "\n";
      #print OUT "field type      = \'$fieldType\'\n";
      #print OUT "field data      = \"$fieldData\"\n";
      print OUT "field           = \'$fieldType\' -> \"$fieldData\"\n";
    }
#    print OUT "\n";
#    close(OUT);
  }
  print OUT "\n";
  close(OUT);
  ($OPENING) and print "out of data for $file\n";

}

sub bin2dec() {
  return hex(unpack ("H*", $_[0]));
}

sub Bin2dec() {
  return hex(unpack ("H*", reverse($_[0])));
}
