//---------------------------------------------------------------------------
// JTHZ steganographic example for Win32
// Borland C++Builder 4 PRO
//---------------------------------------------------------------------------
// embeds a file in a 24 bit Windows DIB bitmap (*.bmp)
// example e bitmap.bmp inputfile
//---------------------------------------------------------------------------
// extracts a file from a 24 bit Windows DIB bitmap (*.bmp)
// example x bitmap.bmp outputfile
//---------------------------------------------------------------------------
// the least significant bit of all 3 bytes (RGB) of every pixel can be used
// first we embed the size, then the entire file
// [FILESIZE][FILEDATA][n BYTES OF ORIGINAL UNUSED DATA] where n can be 0
//---------------------------------------------------------------------------

#include <condefs.h>
#pragma hdrstop
#include <iostream.h>

//---------------------------------------------------------------------------

#define FOREVER for(;;)

//---------------------------------------------------------------------------

int usage();
bool isRgbDib(Graphics::TBitmap *);
int embed(const String, const String);
int extract(const String, const String);

//---------------------------------------------------------------------------

const int   EX_RETURN_ERROR    = -1;
const int   EX_RETURN_OK       = 0;
const int   BAD_FILEHANDLE     = -1;
const ULONG BITS_IN_ONE_BYTE   = 8L;
const ULONG BYTES_IN_ONE_PIXEL = 3L;

//---------------------------------------------------------------------------

int main(int argc, char* argv[]){
  if(argc != 4)
    return usage();
  String mode = LowerCase(argv[1]);
  if(mode == "e")
    return embed(argv[3], argv[2]);
  else if(mode == "x")
    return extract(argv[3], argv[2]);
  return usage();
}

//---------------------------------------------------------------------------

int usage(){
  // display manual of this program
  cout << "usage:" << endl;
  cout << "example e bmpfile inputfile" << endl;
  cout << "example x bmpfile outputfile" << endl;
  return EX_RETURN_ERROR;
}

//---------------------------------------------------------------------------

bool isRgbDib(Graphics::TBitmap *bmp){
  // returns true if bmp is a 24 bit RGB DIB bitmap
  return bmp->PixelFormat == pf24bit && bmp->HandleType == bmDIB;
}

//---------------------------------------------------------------------------

int embed(const String inputFile, const String bitmapFile){
  // try to embed inputFile in bitmapFile
  if(!FileExists(bitmapFile)){
    cout << bitmapFile.c_str() << " does not exists" << endl;
    return EX_RETURN_ERROR;
  }
  if(!FileExists(inputFile)){
    cout << inputFile.c_str() << " does not exists" << endl;
    return EX_RETURN_ERROR;
  }
  TSearchRec searchRec;
  if(FindFirst(inputFile, faAnyFile, searchRec)){
    cout << "error getting filesize of " << inputFile.c_str() << endl;
    return EX_RETURN_ERROR;
  }
  ULONG fileSize = searchRec.Size;
  FindClose(searchRec);
  Graphics::TBitmap *bitmap = new Graphics::TBitmap();
  try{
    bitmap->LoadFromFile(bitmapFile);
  }
  catch(...){
    delete bitmap;
    cout << "error reading " << bitmapFile.c_str() << " as DIB" << endl;
    return EX_RETURN_ERROR;
  }
  if(!isRgbDib(bitmap)){
    delete bitmap;
    cout << bitmapFile.c_str() << ": not a 24 bit RGB DIB bitmap" << endl;
    return EX_RETURN_ERROR;
  }
  ULONG usefulBits = bitmap->Height * bitmap->Width * BYTES_IN_ONE_PIXEL;
  ULONG neededBits = BITS_IN_ONE_BYTE * (sizeof(ULONG) + fileSize);
  if(neededBits > usefulBits){
    delete bitmap;
    cout << "inputfile too large to embed in bitmap" << endl;
    return EX_RETURN_ERROR;
  }
  // create a buffer that contains all the data we will embed
  // i.e. the size of the file and the filedata
  ULONG embedDataSize = sizeof(ULONG) + fileSize;
  Byte *embedData = new Byte[embedDataSize];
  // copy fileSize to first 4 bytes
  CopyMemory(embedData, &fileSize, sizeof(ULONG));
  // read entire file into the next part of embedData
  int fileHandle = FileOpen(inputFile, fmOpenRead);
  if(fileHandle == BAD_FILEHANDLE){
    delete bitmap;
    delete [] embedData;
    cout << "error opening inputfile" << endl;
    return EX_RETURN_ERROR;
  }
  ULONG bytesRead = FileRead(
    fileHandle,
    &embedData[sizeof(ULONG)],
    fileSize
  );
  FileClose(fileHandle);
  if(bytesRead != fileSize){
    delete bitmap;
    delete [] embedData;
    cout << "error reading " << inputFile.c_str() << endl;
    return EX_RETURN_ERROR;
  }
  // embed data into bitmap
  ULONG line = 0L;
  ULONG byteOffset = 0L;
  ULONG bytesInOneLine = BYTES_IN_ONE_PIXEL * bitmap->Width; // RGB
  for(ULONG idx = 0L; idx < embedDataSize; ++idx){
    for(Byte bitIdx = 0; bitIdx < BITS_IN_ONE_BYTE; ++bitIdx){
      Byte *scanlinePtr = reinterpret_cast <Byte*> (bitmap->ScanLine[line]);
      // clear least significant bit
      scanlinePtr[byteOffset] &= 0xfe;
      // 'OR' in the next bit from our data
      Byte nextBit = static_cast <Byte> ((embedData[idx] >> bitIdx) & 0x01);
      scanlinePtr[byteOffset] |= nextBit;
      // advance to next byte in our bitmap
      if(++byteOffset >= bytesInOneLine){
        byteOffset = 0L;
        ++line;
      }
    }
  }
  delete [] embedData;
  // save data
  try{
    bitmap->SaveToFile(bitmapFile);
  }
  catch(...){
    delete bitmap;
    cout << "error saving to " << bitmapFile.c_str() << endl;
    return EX_RETURN_ERROR;
  }
  delete bitmap;
  return EX_RETURN_OK;
}

//---------------------------------------------------------------------------

int extract(const String outputFile, const String bitmapFile){
  // try to extract embedded data from bitmapFile to outputFile
  if(!FileExists(bitmapFile)){
    cout << bitmapFile.c_str() << " does not exists" << endl;
    return EX_RETURN_ERROR;
  }
  Graphics::TBitmap *bitmap = new Graphics::TBitmap();
  try{
    bitmap->LoadFromFile(bitmapFile);
  }
  catch(...){
    delete bitmap;
    cout << "error reading " << bitmapFile.c_str() << " as DIB" << endl;
    return EX_RETURN_ERROR;
  }
  if(!isRgbDib(bitmap)){
    delete bitmap;
    cout << bitmapFile.c_str() << ": not a 24 bit RGB DIB bitmap" << endl;
    return EX_RETURN_ERROR;
  }
  ULONG usefulBits = bitmap->Height * bitmap->Width * BYTES_IN_ONE_PIXEL;
  if(usefulBits < (sizeof(ULONG) * BITS_IN_ONE_BYTE)){
    delete bitmap;
    cout << "No data embedded in " << bitmapFile.c_str() << endl;
    return EX_RETURN_ERROR;
  }
  // extract embedded fileSize from bitmap
  ULONG line = 0L;
  ULONG byteOffset = 0L;
  ULONG bytesInOneLine = BYTES_IN_ONE_PIXEL * bitmap->Width;
  ULONG fileSize = 0L;
  ULONG idx = 0L;
  const ULONG embeddedFileSizeBits = sizeof(ULONG) * BITS_IN_ONE_BYTE;
  FOREVER{
    Byte *scanlinePtr = reinterpret_cast <Byte*> (bitmap->ScanLine[line]);
    fileSize += ((scanlinePtr[byteOffset] & 1) << 31);
    if(++byteOffset >= bytesInOneLine){
      byteOffset = 0L;
      ++line;
    }
    if(++idx == embeddedFileSizeBits)
      break;
    fileSize >>= 1;
  }
  // we're gonna keep the values of byteOffset and line
  ULONG maxDataBits = usefulBits - (sizeof(ULONG) * BITS_IN_ONE_BYTE);
  if((fileSize * BITS_IN_ONE_BYTE) > maxDataBits){
    delete bitmap;
    cout << "No data embedded in " << bitmapFile.c_str() << endl;
    return EX_RETURN_ERROR;
  }
  // extract data from bitmap
  Byte *extractData = new Byte[fileSize];
  ZeroMemory(extractData, fileSize);
  for(ULONG byteIdx = 0L; byteIdx < fileSize; ++byteIdx){
    for(Byte bitIdx = 0; bitIdx < BITS_IN_ONE_BYTE; ++bitIdx){
      Byte *scanlinePtr = reinterpret_cast <Byte*> (bitmap->ScanLine[line]);
      extractData[byteIdx] |= static_cast <Byte> ((scanlinePtr[byteOffset] & 1) << bitIdx);
      if(++byteOffset >= bytesInOneLine){
        byteOffset = 0L;
        ++line;
      }
    }
  }
  delete bitmap;
  // save to file
  int fileHandle = FileCreate(outputFile);
  if(fileHandle == BAD_FILEHANDLE){
    delete extractData;
    cout << "error (re)creating " << outputFile.c_str() << endl;
    return EX_RETURN_ERROR;
  }
  ULONG bytesWritten = FileWrite(
    fileHandle,
    extractData,
    fileSize
  );
  FileClose(fileHandle);
  delete extractData;
  if(bytesWritten != fileSize){
    cout << "error writing to " << outputFile.c_str() << endl;
    return EX_RETURN_ERROR;
  }
  delete extractData;
  return EX_RETURN_OK;
}

//---------------------------------------------------------------------------

