// ------------------------------- //
// -------- Start of File -------- //
// ------------------------------- //
// ----------------------------------------------------------- // 
// C++ Source Code File Name: vb_debug.cpp 
// Compiler Used: MSVC40, DJGPP 2.7.2.1, GCC 2.7.2.1, HP CPP 10.24
// Produced By: Doug Gaer 
// File Creation Date: 02/04/1997  
// Date Last Modified: 03/17/1999
// Copyright (c) 1997 Douglas M. Gaer
// ----------------------------------------------------------- // 
// ------------- Program Description and Details ------------- // 
// ----------------------------------------------------------- // 
/*
The VBD C++ classes are copyright (c) 1997, by Douglas M. Gaer.
All those who put this code or its derivatives in a commercial
product MUST mention this copyright in their documentation for
users of the products in which this code or its derivative
classes are used. Otherwise, you have the freedom to redistribute
verbatim copies of this source code, adapt it to your specific
needs, or improve the code and release your improvements to the
public provided that the modified files carry prominent notices
stating that you changed the files and the date of any change.

THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
THE ENTIRE RISK OF THE QUALITY AND PERFORMANCE OF THIS SOFTWARE
IS WITH YOU. SHOULD ANY ELEMENT OF THIS SOFTWARE PROVE DEFECTIVE,
YOU WILL ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR
CORRECTION.

This program is used to display a detailed analysis of every
variable data block allocated in a VBD file.

Changes:
================================================================
01/20/1998 - Rebuilds the VBD file static area data if the VBD
header is still intact.
Changed by: Doug Gaer

03/09/1998 - Corrected "Checking the FreeSpace value" routine in
AnalyzeHeader() function. In previous version the error value was
incremented outside of the if(vb.CkWord != CheckWord) statement.
Changed by: Doug Gaer

06/10/1998 - Changed the Rebuild() function to write only normal
variable blocks to the new file: if(vb.Status == NormalVB) 
Changed by: Doug Gaer
================================================================
*/
// ----------------------------------------------------------- // 
#include <iostream.h>
#include <iomanip.h>
#include <string.h>
#include "vbdfile.h"
#include "vbdstats.h"

// Program name and version number
const char *VersionNumber = "1031.101";
const char *ProgramName = "vb_debug";

// Function prototypes for display menu
void SkipToEol(istream &s);
int Quit();
void Menu(const VBDFilePtr &f);
void pause();
void Version();

// Function prototypes for VBD utilities 
void FindVB(const VBDFilePtr &f, int verbose = 0, int walk = 0);
void AnalyzeHeader(const VBDFilePtr &f);
void DisplayStats(const VBDFilePtr &f);
void Rebuild(const VBDFilePtr &f);

void DisplayStats(const VBDFilePtr &f)
{
  const char *FName = f->VBDFileName();
  FileHeader fh;

  f->Read(&fh, sizeof(FileHeader), 0);
  if(memcmp(fh.Signature, VBDFile::VBDSignature, 7)) { // Test file type
    cout << endl;
    cout << "VBD File Header is damaged or does not exist" << endl;
    cout << endl;
    return;
  }

  cout << endl;
  __SBYTE__ FileStatus = f->GetFileStatus(); // Get the current file status
  cout << "----- VBD file statistics -----" << endl;
  cout << endl;
  VBDStats(f);
}

void AnalyzeHeader(const VBDFilePtr &f)
{
  FileHeader fh;
  VBHeader vb;
  int errors = 0;
  
  f->Read(&fh, sizeof(FileHeader), 0);
  cout << endl;
  cout << "Analyzing VBD file header..." << endl;
  cout << endl;

  cout << "Checking VBD signature..." << endl;
  if (memcmp(fh.Signature, VBDFile::VBDSignature, 7)) { // Test file type
    cout << endl;
    cout << "VBD File Header is damaged or does not exist" <<endl;
    cout << endl;
    FindVB(f, 0);
    return;
  }
  else if (memcmp(fh.Signature, VBDFile::VBDSignature, 8)) {
    // Check revision letters
    char revision[8];
    revision[8] = 0; // Ensure null termination
    char rev_letter;
    cout << endl;
    cout << "NOTE: The VBD file revision letters do not match." << endl;
    memmove(revision, VBDFile::VBDSignature, 8);
    rev_letter = revision[7];
    if(rev_letter == 0)
      cout << "Current VBD File Revision = NONE" << endl;
    else
      cout << "Current VBD File Revision = " << rev_letter << endl;
    memmove(revision, fh.Signature, 8);
    rev_letter = revision[7];
    if(rev_letter == 0)
      cout << "Current VBD File Revision = NONE" << endl;
    else
      cout << "The file reads: " << rev_letter << endl;
    cout << "Backward compatibility rules will be enforced." << endl;
    cout << endl;
  }

  cout << "Checking for End of File errors..." << endl;
  FAU StaticEOF = f->FileSize(f->VBDFileName());
  if(StaticEOF > fh.EndOfFile) {
    cout << endl;
    cout << "End of file error in file: " << f->VBDFileName() << endl;
    cout << "The actual length is longer then the allocated length!" << endl;
    cout << endl;
    errors++;
  }

  cout << "Checking the HeapStart value..." << endl;
  f->Read(&vb, sizeof(VBHeader), fh.HeapStart);
  if (vb.CkWord != CheckWord) {
    cout << endl;
    cout << "Bad HeapStart value in file: " << f->VBDFileName() << endl;
    cout << "No variable block found at file address: "
	 << fh.HeapStart << endl;
    cout << endl;
    errors++;
  }

  cout << "Checking the FreeSpace value..." << endl;
  if(fh.FreeSpace != 0) {
    f->Read(&vb, sizeof(VBHeader), fh.FreeSpace);
    if(vb.CkWord != CheckWord) {
      cout << endl;
      cout << "Bad FreeSpace value in file: " << f->VBDFileName() << endl;
      cout << "No variable block found at file address: "
	   << fh.FreeSpace << endl;
      cout << endl;
      errors++;
    }
  }

  cout << "Checking the HighestVB value..." << endl;
  f->Read(&vb, sizeof(VBHeader), fh.HighestVB);
  if (vb.CkWord != CheckWord) {
    cout << endl;
    cout << "Bad HeapStart value in file: " << f->VBDFileName() << endl;
    cout << "No variable block found at file address: "
	 << fh.HighestVB << endl;
    cout << endl;
    errors++;
  }
  
  cout << "Checking the VBD version number..." << endl;
  if(fh.Version != VBDFile::VBDVersion) {
    cout << endl;
    cout << "NOTE: The version numbers do not match." << endl;
    cout << "Current VBD File Version = " << VBDFile::VBDVersion << endl;
    cout << "The file reads: " << fh.Version << endl;
    cout << "Backward compatibility rules will be enforced." << endl;
    cout << endl;
  }

  if(errors) {
    cout << endl;
    cout << "VBD file header has errors!" << endl;
  }
  else {
    cout << endl;
    cout << "VBD file header checks good." << endl;
  }
  cout << endl;
}

void FindVB(const VBDFilePtr &f, int verbose, int walk)
// Serach the file for all variable block.
{
  VBHeader vb;
  int count = 0;
  int badvb = 0;
  
  cout << endl;
  cout << "Searching file for all variable blocks..." << endl;
  
  FAU StaticEOF = f->FileSize(f->VBDFileName());
  FAU addr = f->VBSearch(0); // Search the entire file
  
  if(addr == 0) {
    cout << endl;
    cout << "No variable blocks found in file: "
	 << f->VBDFileName() << endl;
    cout << endl;
    return;
  }
  
  while(1) { 
    if(addr >= StaticEOF) break;
    f->Read(&vb, sizeof(VBHeader), addr);

    if (vb.CkWord == CheckWord) {
      count++; // Increment the variable block count
      if(verbose) {
	VBStats(f, (addr+sizeof(VBHeader)));
	if(walk) {
	  char c;
	  cout << endl;
	  cout << "Press enter to continue or enter 'X' to exit >";
	  cin.clear();
	  cin.get(c);
	  switch(c) {
	    case 'x' : case 'X' :
	      cout << endl;
	      return;
	    default:
	      break;
	  }
	}
      }
      addr = addr + vb.Length; // Goto the next variable block
    }
    else {
      badvb++;
      cout << endl;
      cout << "Found bad variable block at address " << addr << endl;
      cout << "Searching for next good variable block..." << endl;
      addr = f->VBSearch(addr); // Search for the next good block
      if(!addr) {
	cout << "None found!" << endl;
	cout << endl;
	return;
      }
    }
  }
  cout << endl;
  cout << "Found " << count << " good varible blocks." << endl;
  if(badvb) cout << "Found " << badvb << " bad variable blocks." << endl;
  cout << endl;
}

void Rebuild(const VBDFilePtr &f)
{
  cout << endl;
  cout << "Rebuilding damaged VBD file..." << endl;
  cout << endl;

  char buf[255];
  int retry = 3;
  
  while(1) { // Loop until a good file name is found
    cout << "Enter new name of file to build >";
    cin.getline(buf, sizeof(buf));
    cout << endl;
    if(!*buf) { // Return if nothing is entered
      Menu(f);
      return;
    }
    if(VBDFile::Exists(buf)) {
      cout << "File already exists!" << endl << endl;
       retry--;
      if(!retry) {
	Menu(f);
	return;
      }
    }
    else
      break;
  }

  FileHeader fh;
  VBHeader vb;
  int errors = 0;
  
  // Analyze the VBD file header to determine if the file has
  // a pre-allocated static area
  __LWORD__ static_area;
  f->Read(&fh, sizeof(FileHeader), 0);

  // Check the VBD FileHeader's signature
  if (memcmp(fh.Signature, VBDFile::VBDSignature, 8))
    errors++; // Header is damaged and cannot be read

  if(!errors) { // Check the HeapStart value 
    f->Read(&vb, sizeof(VBHeader), fh.HeapStart);
    if (vb.CkWord != CheckWord)
      errors++;
  }

  // If no errors, calculate the the size of the static area
  if(!errors) static_area = fh.HeapStart - sizeof(FileHeader);

  FAU StaticEOF = f->FileSize(f->VBDFileName());
  FAU addr = f->VBSearch(0); // Search the entire file
  
  if(addr == 0) {
    cout << endl;
    cout << "No variable blocks found in file: "
	 << f->VBDFileName() << endl;
    pause();
    return;
  }

  // Create the new file
  VBDFilePtr NewFile(new VBDFile);
  NewFile->Create(buf, static_area);

  int count = 0;
  int badvb = 0;

  char *v;

  if(static_area) { // Write the static area data
    v = new char[static_area];
    f->Read(v, static_area, sizeof(FileHeader));
    NewFile->Write(v, static_area, sizeof(FileHeader));
    delete v;
  }
  
  FAU ObjectLength;
  UINT32 checksum;
  char rev_letter = f->GetRevLetter();
  
  while(1) { 
    if(addr >= StaticEOF) break;

    f->Read(&vb, sizeof(VBHeader), addr);
    if(vb.CkWord == CheckWord) {
      ObjectLength = f->ObjectLength(addr + sizeof(VBHeader));
      v = new char[ObjectLength];
      f->Read(v, ObjectLength);
      if((__SBYTE__)vb.Status == NormalVB) {
	count++; // Increment the variable block count
	NewFile->Alloc(ObjectLength);
	NewFile->Write(v, ObjectLength);
	switch(rev_letter) {
	  case 'A':
	    checksum = calcCRC32(v, ObjectLength);
	    NewFile->Write(&checksum, sizeof(checksum));
	    break;

	  default: // Default to versions prior to 1027 rev A
	    break;
	}
      }
      delete v;
      addr = addr + vb.Length; // Goto the next variable block
    }
    else {
      badvb++;
      addr = f->VBSearch(addr); // Search for the next good block
      if(!addr) break; 
    }
  }
  cout << endl;

  cout << "Wrote " << count << " good variable blocks to file: "
       << NewFile->VBDFileName() << endl;

  if(static_area)
    cout << "Wrote " << static_area << " bytes of Static area data."
	 << endl;
 
  if(badvb) 
    cout << "Did not write " << badvb << " bad variables found in file: "
	 << f->VBDFileName() << endl;

  if(errors) {
    cout << f->VBDFileName() << " file header is damaged!" << endl;
    cout << "No header information was copied to "
	 << NewFile->VBDFileName() << endl;
  }

  NewFile->Close();
  cout << endl;
}

void Menu(const VBDFilePtr &f)
{
  cout << endl;
  cout << "Analyzing file: " << f->VBDFileName() << endl;
  cout << "Enter the letter of your selection at the prompt." << endl;
  cout << endl;
  cout << "(A) - Analyze the VBD file header" << endl;
  cout << "(D) - Dump every variable block" << endl;
  cout << "(F) - Find every variable block in the file" << endl;
  cout << "(H) - Displays this menu" << endl;
  cout << "(Q) - Quit this program" << endl;
  cout << "(R) - Rebuild a damaged VBD file" << endl;
  cout << "(S) - Display VBD file statistics" << endl;
  cout << "(W) - Walk through every variable block" << endl;
  cout << endl;
}

void SkipToEol(istream &s)
// Used to clear istream
{
  char c;
  s.clear();
  while(s.get(c) && c != '\n') { ; }
}

void pause()
{
  cout << endl;
  cout << "Press enter to continue..." << endl;
  cin.get();
}

int Quit()
{
  cout << "Exiting..." << endl;
  return 0;
}

void Version()
{
  cout << endl;
  cout << ProgramName << " version number: " << VersionNumber << endl;
  cout << endl;
}

int main(int argc, char **argv)
{
  // Display the program version information and exit the program
  if(argc >= 2) {
    if(strcmp(argv[1], "version") == 0) {
      Version();
      return 0;
    }
  }

  if(argc < 2) {
    cerr << endl;
    cerr << "VBD file debug utility version " << VersionNumber << endl;
    cerr << "Usage: " << ProgramName << " infile.vbd" << endl;
    cerr << "Usage: " << ProgramName << " infile.vbd (command)" << endl;
    cerr << endl;
    return 1;
   }

  VBDFilePtr f(new VBDFile);   
  const char *FName = argv[1];
  if(!VBDFile::Exists(FName)) 
    Error->SignalException(EHandler::NoFileExists);
  else
    f->BlindOpen(FName, VBDFile::READONLY);

  char key;
  if(argc <= 2) Menu(f); // Not processing a command
  int rv = 1;

  while(rv) {
    if(argc > 2) { // Process a single command and exit the loop
      key = *(argv[2]);
      rv = 0;
    }
    else {
      if (!cin) { // Input is in fail state
	SkipToEol(cin); // Go to end of line
	if (!cin) {  // Can't fix
          cout << "Input stream is broken" << endl;
          return 0;
	}
      }
      cout << '>';
      cin >> key;
      if (!cin) continue; // Fix at top of loop
    }
    switch(key) {
      case 'a' : case 'A' :
	if(argc <= 2) SkipToEol(cin);
	AnalyzeHeader(f);
	break;
      case 'f' : case 'F' :
	if(argc <= 2) SkipToEol(cin);
	FindVB(f);
	break;
      case 'd' : case 'D' :
	if(argc <= 2) SkipToEol(cin);
	FindVB(f, 1);
	break;
      case 'h' : case 'H' :
	Menu(f);
	break;
      case '?' :
	Menu(f);
	break; 
      case 'q' : case 'Q' :
	rv = Quit();
	break;
      case 'r' : case 'R' :
	if(argc <= 2) SkipToEol(cin);
	Rebuild(f);
	break;
      case 's' : case 'S' :
	if(argc <= 2) SkipToEol(cin);
	DisplayStats(f);
	break;
      case 'w' : case 'W' :
	if(argc <= 2) SkipToEol(cin);
	FindVB(f, 1, 1);
	break;
      default:
        cout << "Unrecognized command" << endl;
    }
  }

  return 0;
}
// ----------------------------------------------------------- //
// ------------------------------- //
// --------- End of File --------- //
// ------------------------------- //

