// stegg: console app version of Stegmeister.
//
// Written by John A deLaubenfels, https://www.adaptune.com/steg.htm .
// Permission granted to use freely in any non-fraudulent way.
//
// modification history:
//    11-28-23 (jdl): new
//
//
//



#ifdef NEED_PCH
#include "pch.h"
#endif


#pragma warning( disable : 4996 )


#define IS_MAIN

#include "globs.h"
#include "wild.h"
#include "jarq.h"
#include "wild.h"
#include "stufpic.h"
#include "bufin.h"
#include "mt64.h"
#include "ssprint.h"
#include "jgrlil.h"
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>



#ifdef _WIN32
#include <conio.h>
#include <direct.h>
#else
#define _chdir chdir
#define _getcwd getcwd
#define _getch  getch
#endif // !_WIN32


static int iFormat = 1;   // compress by default


// qxhelp_screen()

void help_screen(void) {
   fprintf(stdout, "\nBASIC OPERATION:\n\n");

   fprintf(stdout, "   stegg -i IMG_1467.jpg -si *.txt *.dat\n");
   fprintf(stdout, "      embeds selected files into the image file; stuffed output file is written to a new folder.\n");
   fprintf(stdout, "\n");

   fprintf(stdout, "   stegg -i stuffed.bmp -so\n");
   fprintf(stdout, "      extracts from the stuffed file into a new folder.\n");
   fprintf(stdout, "\n");

   fprintf(stdout, "\nEMBED OPTIONS:\n\n");

   fprintf(stdout, "   -o thisFile.bmp\n");
   fprintf(stdout, "      specifies the name of the stuffed output file (must be .bmp).\n");
   fprintf(stdout, "\n");

   fprintf(stdout, "   -w 1430\n");
   fprintf(stdout, "      specifies the width of the output file (pixels).\n");
   fprintf(stdout, "\n");

   fprintf(stdout, "   -w x 980\n");
   fprintf(stdout, "      specifies the height of the output file (pixels).\n");
   fprintf(stdout, "\n");

   fprintf(stdout, "   -d 1.0\n");
   fprintf(stdout, "      specifies the target detection difficulty (aka safety).\n");
   fprintf(stdout, "\n");

   fprintf(stdout, "   -b 3000000\n");
   fprintf(stdout, "      specifies the target output file size in bytes.\n");
   fprintf(stdout, "\n");

   fprintf(stdout, "   -m\n");
   fprintf(stdout, "      you will type a message to the recipient when prompted.\n");
   fprintf(stdout, "\n");

   fprintf(stdout, "   -sh\n");
   fprintf(stdout, "      don't actually do the output, but show a few options for output sizes.\n");
   fprintf(stdout, "\n");


   return;
} // end of help_screen()...



//
//
//
//
// qxgetTypedInput()

int getTypedInput(std::vector<std::string> &vcSsOut, FILE *fp_echo) {
   int done = 0;
   std::string ssLin;

   while(!done) {
      char c = _getch();
      // qprintf(fp_dbg, "c = %d (%c).\n", c, c);  // qxtemp!
      switch(c) {
         case 8:
         case 127:
            if(ssLin.size() > 0) {
               ssLin.resize(ssLin.size()-1);
               if(fp_echo != NULL) {
                  putc(8,   fp_echo);
                  putc(' ', fp_echo);
                  putc(8,   fp_echo); 
                  fflush(fp_echo);
               } // if(fp_echo != NULL)
            } // if(ssLin.size() > 0)
            break;
         case 10:
         case 13:
            vcSsOut.push_back(ssLin);
            ssLin.clear();
            if(fp_echo != NULL) {
               putc(13,  fp_echo);
               putc(10,  fp_echo); 
               fflush(fp_echo);
            } // if(fp_echo != NULL)
            break;
         case 27:
            done = 1;
            if(ssLin.size() > 0) {
               vcSsOut.push_back(ssLin);
               ssLin.clear();

               if(fp_echo != NULL) {
                  putc(13,  fp_echo);
                  putc(10,  fp_echo); 
                  fflush(fp_echo);
               } // if(fp_echo != NULL)
            } // if(ssLin.size() > 0)
            break;
         default:
            ssLin += c;
            if(fp_echo != NULL) {
               putc(c, fp_echo); 
               fflush(fp_echo);
            } // if(fp_echo != NULL)
            break;
      } // switch(c)
   } // while(!done) 

   return 0;
} // int getTypedInput(std::vector<std::string> &vcSsOut)




//
//
//
//
// qxpasswordWrapper()

int passwordWrapper(std::vector<unsigned long long> &passwordSeedVector) {
   passwordSeedVector.clear();

   int iRet = 0;

   while(1) {   // till he likes the sanity check word.
      qprintf(fp_std, "\nYour password, please!  "); qflush(fp_std);
      MT64::getSeedFromPwd(passwordSeedVector);
      qprintf(fp_std, "\n");

      MT64 anMT, anM2;
      anMT.init_by_array64(&(passwordSeedVector[0]), passwordSeedVector.size());
      anM2.init_by_otherMT64(anMT);

      int maxLen = 36;             // everybody will pass
      std::string ssSilly;
      for(int i=0; i<256; i++) {
         ssSilly = makeSillyWord(anM2, 2);
         if(ssSilly.size() <= maxLen)
            break;
      } // for(int i=0; i<256; i++)

      qprintf(fp_dbg, "Sanity check: %s\n", ssSilly.c_str());

      qprintf(fp_std, "Sanity check: %s :  Ok?  [Y/n] ", ssSilly.c_str()); qflush(fp_std);
      int c = _getch();

      qprintf(fp_std, "\n");

      if((c == 'q') || (c == 'Q') || (c == ESC))
         iRet = NADA;

      if((c != 'n') && (c != 'N'))
         break;

   } // while(1) {   // till he likes the sanity check word.

   return iRet;
} // int passwordWrapper()...



//
//
//
//
//
// qxmainWork()
//

int mainWork(int argc, const char **argv) {
   int    which_arg=0;
   int    iRet   = NADA;
   int    iSiz   = 0;
   int    truu   = 1;
   int    widPix = NADA;
   int    hgtPix = NADA;
   int    outSiz = NADA;
   double safety = -1.0;
   bool  bWildsIn=  false;
   bool  bEmbed  =  false;
   bool  bExtract=  false;
   bool  bDoMsg  =  false;
   bool  bShow   =  false;
   const char *cp;
   std::string ssFileIn, ssFileOut;
   std::vector<std::string> vcSsHFilesIn;
   std::vector<std::string> vcSsHFilesOut;
   std::vector<std::string> vcExclude;
   std::vector<unsigned char> vcToStuff;
   std::vector<unsigned long long> passwordSeedVector;
   std::vector<unsigned long long> passwordSeedVec2;      // after 100,000 scrambles.
   jgrLil jgIn;
   jgrLil jgOut;

   fp_std = stdout;

   
   if(file_t("dbg.txt") > 0) {
      arcMeDotty("blah.dbg");
      fp_dbg = fopen("blah.dbg", "w");
   } // if(file_t("dbg.txt") > 0)

   int iRep = ((fp_dbg == NULL) ? (0) : (1));             // 1: report closing to fp_dbg; nothing to stdout.
   fileCloser afc(fp_dbg, iRep, "blah.dbg as fp_dbg");    // 3: report file closing both to fp_dbg and stdout.

   showArgs(argc, argv, fp_dbg);

   qprintf(fp_std, "Stegmeister version 1.02 .  https://www.adaptune.com/steg.htm\n");
   qprintf(fp_std, "This software uses the FreeImage open source image library.    See https://freeimage.sourceforge.io for details. \n");
   qprintf(fp_std, "This software applies compression to input files using zlib.   See https://zlib.net for details. \n");


   qprintf(fp_dbg, "Size of pointer:     %d.\n", (int)sizeof(void *));
   qprintf(fp_dbg, "Size of int:         %d.\n", (int)sizeof(int));
   qprintf(fp_dbg, "Size of long:        %d.\n", (int)sizeof(long));
   qprintf(fp_dbg, "Size of long long:   %d.\n", (int)sizeof(long long));
   qprintf(fp_dbg, "Size of double:      %d.\n", (int)sizeof(double));
   qprintf(fp_dbg, "Size of long double: %d.\n", (int)sizeof(long double));
   qprintf(fp_dbg, "Size of size_t:      %d.\n", (int)sizeof(size_t));
   qprintf(fp_dbg, "Size of time_t:      %d.\n", (int)sizeof(time_t));
#ifdef _WIN32
   qprintf(fp_dbg, "Size of DWORD:       %d.\n", (int)sizeof(DWORD));
   qprintf(fp_dbg, "Size of FILETIME:    %d.\n", (int)sizeof(FILETIME));
#endif // _WIN32


   // make a random guy anyone can use:
   std::vector<unsigned long long> aLongLongSeed;
   MT64::getAutoSeed(aLongLongSeed);
   MT64 anMT;
   anMT.init_by_array64(&(aLongLongSeed[0]), aLongLongSeed.size());

   std::vector<onePair> vcBackRatEmb;               // ratios to use when embeding
   std::vector<onePair> vcBackRatExt;               // ratios to use when extracting

   // standard backward stuffing ratios:
   vcBackRatExt.push_back(onePair(1,   16));
   vcBackRatExt.push_back(onePair(1,   45));
   vcBackRatExt.push_back(onePair(1,  128));
   vcBackRatExt.push_back(onePair(1,  362));
   vcBackRatExt.push_back(onePair(1, 1024));

   // user-specified backward stuffing ratios:
   Bufin aBuf("backs.txt");
   if(!aBuf.is_active()) {
      // file not found; embed with standard ratios:
      vcBackRatEmb = vcBackRatExt;
   } else {
      // we'll embed only using user-selected ratios; we'll extract using those and the standard ones.
      std::vector<std::string> vcTokens;
      std::set<onePair> stPairz;
      for(int i=0; i<aBuf.nlines(); i++) {
         const char *cpc = aBuf[i];
         int nTok = cutLineIntoTokens(cpc, vcTokens);
         if(nTok >= 2) {
            onePair apair(atoi(vcTokens[0].c_str()), atoi(vcTokens[1].c_str()));
            if((apair.numer > 0) && (apair.numer < 65536) && (apair.denom > 0) && (apair.denom < 65536) && (apair.numer <= apair.denom)) {
               // vcBackRatEmb.push_back(apair);
               stPairz.insert(apair);                 // sort first; vector later.
               // vcBackRatExt.push_back(apair);       // order not important.
            } // if((apair.numer > 0) && (apair.numer < 65536) && (apair.denom > 0) && (apair.denom < 65536) && (apair.numer <= apair.denom))
         } // if(nTok >= 2)
      } // for(int i=0; i<aBuf.nlines(); i++)

      // get sorted guys into the embed vector, large to small (small denominator to large denominator):
      std::set<onePair>::const_reverse_iterator itc;
      for(itc=stPairz.rbegin(); itc!=stPairz.rend(); itc++) {
         vcBackRatEmb.push_back(*itc);
         vcBackRatExt.push_back(*itc);

         qprintf(fp_dbg, "We just pushed back %d/%d.\n", (*itc).numer, (*itc).denom);
      } // for(itc=stPairz.begin(); itc!=stPairz.end(); itc++)

      // sanity check:
      if(vcBackRatEmb.size() == 0)
         vcBackRatEmb = vcBackRatExt;
   } // !if(!aBuf.is_active())




   if(argc < 2) {
      // qprintf(fp_std, "About to call help_screen().\n");
      help_screen();
      return 0;
   } // if(argc < 2)

   // take in and process "-[f]" flags:
   while((cp = arg_extract(argc, argv, &which_arg)) != NULL) {

      // break if not a flag:
      if(cp[0] != '-') break;

      switch(cp[1]) {
         case '\0':
            qprintf(fp_std, "- passed by itself.\n");
            break;
         case 'b':  // -b: bytes of output file size.
            while(truu) {
               if(((cp = arg_extract(argc, argv, &which_arg)) == NULL) || (is_a_flag(cp))) break;
               if(is_a_num(cp)) outSiz = jatoi(cp);

               break;
            } // while(truu)

            if(is_a_flag(cp)) which_arg--;
            break;
         case 'd':  // -d: detection difficulty (safety)
            while(truu) {
               if(((cp = arg_extract(argc, argv, &which_arg)) == NULL) || (is_a_flag(cp))) break;
               if(is_a_num(cp)) safety = atof(cp);

               // range and slightly pad:
               safety = MAX(0.0, safety) + (anMT.realcc() * .001);   // to get random denominators.

               break;
            } // while(truu)

            if(is_a_flag(cp)) which_arg--;
            break;
         case 'h':  // -h: help
            help_screen();
            return 0;
            break;
         case 'i':  // -i: input image file.
            while(truu) {
               if(((cp = arg_extract(argc, argv, &which_arg)) == NULL) || (is_a_flag(cp))) break;
               ssFileIn = cp;

               break;
            } // while(truu)

            if(is_a_flag(cp)) which_arg--;
            break;
         case 'm':  // -m: will type a message.
            bDoMsg = true;
            bEmbed = true;
            break;
         case 'o':  // -o: output image file.
            while(truu) {
               if(((cp = arg_extract(argc, argv, &which_arg)) == NULL) || (is_a_flag(cp))) break;
               ssFileOut = cp;

               break;
            } // while(truu)

            if(is_a_flag(cp)) which_arg--;
            break;
         case 's':  // -s: something steg related.
            switch(cp[2]) {
               case '\0':
                  break;
               case 'h':  // -sh: show sample embed options (don't actually embed)
                  bShow = true;
                  break;
               case 'i':  // -si: steg input (embed files into image).
                  bEmbed = true;
                  while(truu) {      // not really...
                     if(((cp = arg_extract(argc, argv, &which_arg)) == NULL) || (is_a_flag(cp))) break;

                     vcSsHFilesIn.push_back(cp);
                     if(has_wilds((const unsigned char *)cp))
                        bWildsIn = true;
                  }
                  if(is_a_flag(cp)) which_arg--;

                  break;
               case 'o':  // -so: steg output (extract files to disk).
                  bExtract = true;
                  while(truu) {
                     if(((cp = arg_extract(argc, argv, &which_arg)) == NULL) || (is_a_flag(cp))) break;

                     vcSsHFilesOut.push_back(cp);
                  }
                  if(is_a_flag(cp)) which_arg--;

                  break;
               default:
                  break;
            } // switch(cp[2])
            break;
         case 'w':  // -w: output file width & height.
            widPix = NADA;
            hgtPix = NADA;

            while(truu) {
               if(((cp = arg_extract(argc, argv, &which_arg)) == NULL) || (is_a_flag(cp))) break;
               if(is_a_num(cp)) widPix = jatoi(cp);

               if(((cp = arg_extract(argc, argv, &which_arg)) == NULL) || (is_a_flag(cp))) break;
               if(is_a_num(cp)) hgtPix = jatoi(cp);

               break;
            } // while(truu)

            if(is_a_flag(cp)) which_arg--;

            break;
         default:
            break;
      } // switch(cp[1])
   } // while((cp = arg_extract(argc, argv, &which_arg)) != NULL)


   if(ssFileIn.size() == 0) {
      qprintf(fp_std, "\nSpecify an input file with -i.\n");
      return NADA;
   } // if(ssFileIn.size() == 0)

   if(file_t(ssFileIn.c_str()) < 0) {
      qprintf(fp_std, "\nInput file \"%s\" not found.\n", ssFileIn.c_str());
      return NADA;
   } // if(file_t(ssFileIn.c_str()) < 0)

   if(bEmbed) {
      // qprintf(fp_std, "bEmbed.\n");

      qprintf(fp_std, "\n");

      jgIn.readFile(ssFileIn.c_str());
      if(jgIn.nHoriz() == 0) {
         qprintf(fp_std, "Not able to read file \"%s\".\n", ssFileIn.c_str());
         return NADA;
      } // if(jgIn.nHoriz() == 0)

      int bytesAvailable = (int)((bytesCanTakeScattered(jgIn, 1, 1) - 384.0) / 1.015);  // rough, conservative guess.
      const int iComma = 32;
      char cBytesAvail[iComma];
      comma_me(bytesAvailable, cBytesAvail, iComma);

      if(bWildsIn) {
         // change wild-card spec(s) into actual file names:
         std::vector<std::string> vcSpec = vcSsHFilesIn;
         vcSsHFilesIn.clear();
         int j = fillSpecdFiles("./", vcSpec, vcExclude, vcSsHFilesIn, true);   // true: bJustThis
         // qprintf(fp_std, "We come back from fillSpecdFiles() with %d guys.\n", (int)vcSsHFilesIn.size());  // qxtemp!
      } // if(bWildsIn)
      // qprintf(fp_std, "wilds fed.\n");


      JarqAnch jaAnch;
      jaAnch.setSize(1);                     // the dummy message guy, with blank file name.

      jaAnch.addFiles(vcSsHFilesIn, 1);      // 1: compress w/ zlib.

      // preliminary check: don't make him type his message if we already know the stuff won't fit the pic.
      std::string ssTypedMessage;
      vcToStuff.clear();

      int nSkip = 0;
      if(ssTypedMessage.size() == 0)
         nSkip = 1;                          // don't need to archive the first, dummy guy, for blank message.

      if((jaAnch.size() - nSkip) > 0) {
         spitObj mySpit(NULL, &vcToStuff);
         jaAnch.archiveJarq(NADA, mySpit, 12, nSkip);  // 12 extra bytes at the beginning.
         iSiz = (int)vcToStuff.size() - 12;     // don't include the extra 12 bytes.
      } else {
         iSiz = 0;
      } // !if((m_myJrq.size() - nSkip) > 0)

      int bytesRequired = (int)vcToStuff.size();
      char cBytesReq[iComma];
      comma_me(bytesRequired, cBytesReq, iComma);

      if(bytesRequired > bytesAvailable) {
         qprintf(fp_std, "Selected files require %s bytes; wrapper file will only support %s.\n", cBytesReq, cBytesAvail);
         return NADA;
      } // if(bytesRequired > bytesAvailable)

      std::vector<std::string> vcSsMsg;
      if(bDoMsg) {
         qprintf(fp_std, "\nType your message.  Multi-line with Enter; Esc when done.\n");
         int ijk = getTypedInput(vcSsMsg, stdout);

         bool bDump = false;
         if(bDump) {
            qprintf(fp_std, "\n\nThis is what we got:\n");
            for(int j=0; j<(int)vcSsMsg.size(); j++) {
               qprintf(fp_std, "%s\n", vcSsMsg[j].c_str());
            } // for(int j=0; j<(int)vcSsMsg.size(); j++)
         } // if(bDump)

         ssTypedMessage = ssVcToSs(vcSsMsg, false);  // false: no CR's.
         JarqOne *pOne = jaAnch.getIthOne(0);    // the dummy guy
         pOne->fillOne(ssTypedMessage, iFormat);

         // recalc stuff vector:
         vcToStuff.clear();

         nSkip = 0;
         if(ssTypedMessage.size() == 0)
            nSkip = 1;                          // don't need to archive the first, dummy guy, for blank message.

         if((jaAnch.size() - nSkip) > 0) {
            spitObj mySpit(NULL, &vcToStuff);
            jaAnch.archiveJarq(NADA, mySpit, 12, nSkip);  // 12 extra bytes at the beginning.
            iSiz = (int)vcToStuff.size() - 12;     // don't include the extra 12 bytes.
         } else {
            iSiz = 0;
         } // !if((m_myJrq.size() - nSkip) > 0)

         bytesRequired = (int)vcToStuff.size();
         comma_me(bytesRequired, cBytesReq, iComma);

         if(bytesRequired > bytesAvailable) {
            qprintf(fp_std, "Selected files require %s bytes; wrapper file will only support %s.\n", cBytesReq, cBytesAvail);
            return NADA;
         } // if(bytesRequired > bytesAvailable)
      } // if(bDoMsg)

      // qprintf(fp_std, "\n");


      // qprintf(fp_std, "jaAnch has %d entries.\n", (int)jaAnch.size());  // qxtemp

      if(iSiz == 0) {
         qprintf(fp_std, "We didn't find anything to embed.\n");
         return NADA;
      } // if(iSiz == 0)


      if(bShow) {
         // don't embed; show some embed ratio options:
         int outSmax(NADA), widPmax(jgIn.nHoriz()), hgtPmax(NADA);
         double safmax = -1.0;  // 12.0;  // way higher than is likely
         twoPairs twoGmax;
         int j1 = updateShrink(jgIn, (int)vcToStuff.size(), outSmax, widPmax, hgtPmax, safmax, vcBackRatEmb, twoGmax, anMT);

         qprintf(fp_dbg, "j1 = %d; safmax = %6.4f\n", j1, safmax);  // qxtemp

         int outSdef(NADA), widPdef(NADA), hgtPdef(NADA);
         double safdef = -1.0;
         twoPairs twoGdef;
         updateShrink(jgIn, (int)vcToStuff.size(), outSdef, widPdef, hgtPdef, safdef, vcBackRatEmb, twoGdef, anMT);

         qprintf(fp_std, "\n");
         qprintf(fp_std, "safety (-d)   file size   width   height\n");
         qprintf(fp_std, "-----------   ---------   -----   ------\n");

         bool bDidDefault = false;

         double safDelta = 1.0;
         double safWalk = -safDelta;
         while(safWalk < safmax) {
            safWalk = MIN(safmax, (safWalk+safDelta));

            // qprintf(fp_std, "safWalk = %6.2f\n", safWalk);  // qxtemp

            if(safWalk > 10.0)
               break;

//          double safone = MAX(0.0, safWalk + ((anMT.realcc() - 0.5) * .001));   // to get random denominators.
            double safone = safWalk + (anMT.realcc() * .001);   // to get random denominators.
            int outSone(NADA), widPone(NADA), hgtPone(NADA);
            twoPairs twoGone;

            if(safWalk < safmax) {
               // qprintf(fp_std, "About to call updateShrink().\n");  // qxtemp
               int ish = updateShrink(jgIn, (int)vcToStuff.size(), outSone, widPone, hgtPone, safone, vcBackRatEmb, twoGone, anMT);
               // qprintf(fp_std, "Back from updateShrink().\n");  // qxtemp

               // if(!bDidDefault && ((widPone >= widPdef) || (hgtPone >= hgtPdef))) {
               if(!bDidDefault && ((safWalk + .01) > safdef)) {
                  // qprintf(fp_std, "ish %d, safone %12.9f, one %dx%d, def %dx%d.\n", ish, safone, widPone, hgtPone, widPdef, hgtPdef);  // qxtemp!

                  const int iL = 12;
                  char cbuf[iL];
                  comma_me(outSdef, cbuf, iL);

                  qprintf(fp_std, "%11.2f  %10s %7d  %7d    <=== default\n", safdef, cbuf, widPdef, hgtPdef);

                  bDidDefault = true;

                  // avoid duplicate line:
                  if((safWalk - .01) < safdef)
                     continue;
               } // if(!bDidDefault && ((widPone >= widPdef) || (hgtPone >= hgtPdef)))
#ifdef CHANGED_LOGIC
               if((widPone == widPdef) && (hgtPone == hgtPdef))
                  continue;
#endif // CHANGED_LOGIC
            } else {
               // last pass; use values above.
               outSone = outSmax;
               widPone = widPmax;
               hgtPone = hgtPmax;
               safone  = safmax;
            } // !if(safWalk < safmax)

            const int iL = 12;
            char cbuf[iL];
            comma_me(outSone, cbuf, iL);

//          qprintf(fp_std, "%12s %5d %5d %4.2f\n", cbuf, widPone, hgtPone, safone);
            qprintf(fp_std, "%11.2f  %10s %7d  %7d\n", safone, cbuf, widPone, hgtPone);
         } // while(safWalk < safmax)
         
         // qprintf(fp_std, "After that safWalk/safmax while loop.\n");  // qxtemp!
      } else {
         // !bShow: normal processing:

         // get password:
         int ip = passwordWrapper(passwordSeedVector);
         if(ip < 0)
            return NADA;

         passwordSeedVec2 = passwordSeedVector;

         int nCycle = 100000;
         MT64::rescrambleVector(passwordSeedVec2, nCycle);

         twoPairs twoGuys;

         int k = updateShrink(jgIn, (int)vcToStuff.size(), outSiz, widPix, hgtPix, safety, vcBackRatEmb, twoGuys, anMT);
         
         char cOutSz[iComma];
         comma_me(outSiz, cOutSz, iComma);

         if(k != 0) {
            qprintf(fp_std, "updateShrink() barfed; sorry.\n");
            return NADA;
         } // if(k != 0)

         qprintf(fp_std, "\nOutput file %s bytes, %d x %d pixels, safety %5.2f\n", cOutSz, widPix, hgtPix, safety);
         qprintf(fp_std, "Ratios %d/%d, %d/%d.\n", twoGuys.main.numer, twoGuys.main.denom, twoGuys.back.numer, twoGuys.back.denom);

         int iCyc(0), ij(NADA);
         for(iCyc=0; iCyc<16; iCyc++) {
            ij = doEmbed(jgIn, jgOut, vcToStuff, passwordSeedVec2, widPix, hgtPix, twoGuys, anMT);
            
            qprintf(fp_dbg, "iCyc %d: doEmbed() returns %d.\n", iCyc, ij);

            if(ij == 0)
               break;

            // failed; try again.  Must re-load vcToStuff, which is scrambled by the function.
            vcToStuff.clear();

            nSkip = 0;
            if(ssTypedMessage.size() == 0)
               nSkip = 1;                          // don't need to archive the first, dummy guy, for blank message.

            if((jaAnch.size() - nSkip) > 0) {
               spitObj mySpit(NULL, &vcToStuff);
               jaAnch.archiveJarq(NADA, mySpit, 12, nSkip);  // 12 extra bytes at the beginning.
               iSiz = (int)vcToStuff.size() - 12;     // don't include the extra 12 bytes.
            } else {
               iSiz = 0;
            } // !if((m_myJrq.size() - nSkip) > 0)
         } // for(iCyc=0; iCyc<16; iCyc++)

         if(ij != 0) {
            qprintf(fp_dbg, "doEmbed() barfed; sorry.\n");
            qprintf(fp_std, "doEmbed() barfed; sorry.\n");
            return NADA;
         } // if(ij != 0)

         qprintf(fp_dbg, "doEmbed() returns 0 in iCyc %d.\n", iCyc);
         // qprintf(fp_std, "doEmbed() returns 0 in iCyc %d.\n", iCyc);  // qxtemp

         int kk = jgOut.writeBmp(ssFileOut.c_str());
         if(kk == 0) {
            qprintf(fp_std, "\nWe wrote file %s\n", ssFileOut.c_str());
         } else {
            // almost expected: he didn't specify an output file name.

            // make a random name folder:
#ifdef OLDDUE
            SMARTA(pcDir, char, (ssFileIn.size()+16), smaDir);
            SMARTA(pcFil, char, (ssFileIn.size()+16), smaFil);
            SMARTA(pcNuu, char, (ssFileIn.size()+32), smaNuu);

            cut_file_name(ssFileIn.c_str(), pcDir, pcFil);
#endif // OLDDUE
            std::string ssDir, ssFil, ssNuu;
            cut_file_name(ssFileIn.c_str(), ssDir, ssFil);

            while(1) {
               std::string ssSilly = makeSillyWord(anMT, 2);

               if(strcmp(ssDir.c_str(), ".") == 0) {
                  // in '.' directory.
                  ssprint(ssNuu, "%s", ssSilly.c_str());
               } else {
                  // not in '.' directory.
                  ssprint(ssNuu, "%s%c%s", ssDir.c_str(), SLASH, ssSilly.c_str());
               } // !if(strcmp(ssDir.c_str(), ".") == 0)

               if(!is_a_dir(ssNuu.c_str()))
                  break;

            } // while(1)

            mkdirWrap(ssNuu.c_str());


            // SMARTA(pcFull, char, (m_ssPathName.size()+32), smaFull);
#ifdef OLDDUE
            int someLen = strlen(ssFil.c_str() + 32);
            SMARTA(pcBody, char, someLen, smaBody);
            SMARTA(pcDott, char, someLen, smaDott);
            SMARTA(pcExtt, char, someLen, smaExtt);

            break_file_name(ssFil.c_str(), pcBody, pcDott, pcExtt);
#endif // OLDDUE
            std::string ssBody, ssDott, ssExtt;
            break_file_name(ssFil.c_str(), ssBody, ssDott, ssExtt);

            ssAppend(ssNuu, "%c%s%s", SLASH, ssBody.c_str(), ".bmp");

            int kj = jgOut.writeBmp(ssNuu.c_str());
            if(kj == 0) {
               qprintf(fp_std, "\nWe wrote file %s\n", ssNuu.c_str());
               qprintf(fp_dbg, "\nWe wrote file %s\n", ssNuu.c_str()); qflush(fp_dbg);
            } else {
               qprintf(fp_std, "Unable to write file %s\n", ssNuu.c_str());
               qprintf(fp_dbg, "Unable to write file %s\n", ssNuu.c_str()); qflush(fp_dbg);
            } // !if(kj == 0)

         } // if(kk < 0)

         qprintf(fp_dbg, "Out of kk block.\n"); qflush(fp_dbg);  // qxtemp!
      } // !if(bShow)
      
      qprintf(fp_dbg, "Out of that bShow block.\n"); qflush(fp_dbg);  // qxtemp!

   } else if(bExtract) {
      // qprintf(fp_std, "Extract.\n");
      JarqAnch jaAnch;

      jgIn.readFile(ssFileIn.c_str());
      if(jgIn.nHoriz() == 0) {
         qprintf(fp_std, "Not able to read file \"%s\".\n", ssFileIn.c_str());
         return NADA;
      } // if(jgIn.nHoriz() == 0)

      while(1) {   // till right password input or quits.
         int ip = passwordWrapper(passwordSeedVector);
         if(ip < 0)
            return NADA;

         passwordSeedVec2 = passwordSeedVector;


         int ii(NADA);

         for(int i=0; i<2; i++) {

            ii = doExtract(jgIn, passwordSeedVec2, vcBackRatExt, jaAnch);

            if(ii == 0)
               break;     // we found it!

            // another try with crazified vector:
            int nCycle = 100000;

            MT64::rescrambleVector(passwordSeedVec2, nCycle);
         } // for(int i=0; i<2; i++)

         if(ii == 0)
            break;     // we found it!

         qprintf(fp_std, "Could not make that password work; try again? [Y/n] "); qflush(fp_std);
         int c = _getch();

         if((c == 'n') || (c == 'N'))
            break;
 
         qprintf(fp_std, "\n");
      } // while(1)   // till right password input or quits.

      qprintf(fp_std, "\n");


      if(jaAnch.size() > 0) {
#ifdef OLD_WAY
         char cbuf[256];
#endif // OLD_WAY

         // make a random name folder:
         SMARTA(pcDir, char, (ssFileIn.size()+16), smaDir);
         SMARTA(pcFil, char, (ssFileIn.size()+16), smaFil);
         SMARTA(pcNuu, char, (ssFileIn.size()+16), smaNuu);

         cut_file_name(ssFileIn.c_str(), pcDir, pcFil);

         while(1) {
            std::string ssSilly = makeSillyWord(anMT, 2);

            if(strcmp(pcDir, ".") == 0) {
               // in '.' directory.
               sprintf(pcNuu, "%s", ssSilly.c_str());
            } else {
               // not in '.' directory.
               sprintf(pcNuu, "%s%c%s", pcDir, SLASH, ssSilly.c_str());
            } // !if(strcmp(pcDir, ".") == 0)

            if(!is_a_dir(pcNuu))
               break;

         } // while(1)

         int nFiles = jaAnch.nSubstantial();    // size() minus any blank file name (vcContents).

#ifdef OLD_WAY
         _getcwd(cbuf, 256);
#endif // OLD_WAY

         if(nFiles > 0) {
            mkdirWrap(pcNuu);
#ifdef OLD_WAY
            _chdir(pcNuu);
#else
            // modify the name(s) to reflect the new folder.
            std::string ssNuu = pcNuu;
            for(int i=0; i<jaAnch.size(); i++) {
               JarqOne *pOne = jaAnch.getIthOne(i);
               std::string ssNow, ssDir, ssFil;
               ssNow = pOne->getFileNams();

               // if blank file name, don't modify.
               if(ssNow.size() == 0)
                  continue;


               // strip off any directories, if any, then prepend our new one.
               cut_file_name(ssNow.c_str(), ssDir, ssFil);


               std::string ssAssemble = ssNuu;
               ssAssemble += SLASH;
               ssAssemble += ssFil;

               // qprintf(fp_std, "ssNow \"%s\", ssDir \"%s\", ssFil \"%s\", ssAssemble \"%s\".\n", ssNow.c_str(), ssDir.c_str(), ssFil.c_str(), ssAssemble.c_str());   // qxtemp!

               pOne->setFileName(ssAssemble.c_str());
            } // for(int i=0; i<jaAnch.size(); i++)
#endif // ! OLD_WAY
         } // if(nFiles > 0)




         std::vector<unsigned char> vcContentsOut;
         std::vector<FILE *>        vcFileWrites;
         vcFileWrites.push_back(fp_std);
         vcFileWrites.push_back(fp_dbg);

         int nWrit = jaAnch.spitFiles(&vcContentsOut, &vcFileWrites);

#ifdef OLD_WAY
         if(nFiles > 0)
            _chdir(cbuf);
#endif // OLD_WAY

         if(vcContentsOut.size() > 0) {
            std::string ssContents;
            vcUcToSs(vcContentsOut, ssContents);
            ssContents = adCr(ssContents);
            qprintf(fp_std, "\nMessage received:\n");
            qprintf(fp_std, "%s", ssContents.c_str());
         } // !if(vcContentsOut.size() > 0)

         if((nFiles > 0) && (nWrit == 0)) {
            rmdir(pcNuu);
         } // if(nWrit == 0)
      } // if(jaAnch.size() > 0)

   } else {
      qprintf(fp_std, "\nuse -si for steg input (embed), and -so for steg output (extract).\n");
   } // neither embed nor extract

#ifdef DONT_NEED
   if(fp_dbg != NULL)
      {fclose(fp_dbg); fp_dbg=NULL;}
#endif // DONT_NEED

   return iRet;
} // int mainWork(int argc, const char **argv)





#ifdef NEED_PCH
#else

//
// a wrapper for the functional main so we can always announce we made it back home.
//
//
//
// qxmain()

int main(int argc, const char **argv) {

   int iRet = mainWork(argc, argv);

   return iRet;
} // int main(int argc, char **argv)

#endif



