/*****************************************************

    DESC : Program steganografi  sederhana, hanya dapat menggunakan bitmap 24bit
    sebagai data  penyamar. Error-checking cukup ekstensif, namun fungsi pembaca
    header kurang meyakinkan karena hanya bergantung pada  analisa (tebakan) le-
    wat hex-editor. Menggunakan  sekaligus fungsi-fungsi C  dan STL C++.  Linear
    demi keterbacaan oleh pemula (mengorbankan  extendability - ubah strukturnya
    bila Anda ingin mengembangkan).
    
    VER : 0.8b
    LAST MODIFIED : 26/02/2006 10:39:05
    LICENSE TYPE : Domain publik

 *****************************************************
 *
 *	(c) copyright 2001, 2006 Cadmus Softworks.
 *
 ****************************************************/
#define DEBUG

#include <iostream> // STL C++. Untuk I/O
#include <fstream>  // STL C++. Spesialisasi I/O file.
#include <sstream>  // STL C++. Spesialisasi I/O string; untuk bmpdst_str.
#include <string>   // STL C++. string::npos.
#include <string.h> // C. memcpy() dan fungsi manipulasi string lainnya.
#include <ctype.h>  // C. Dipakai untuk tolower()
#include <bitset>   // STL C++. Dipakai untuk manipulasi bit-by-bit
#include <math>     // C. min()
typedef unsigned int UINT;
#define BMPHDRCNT 54
#define BMPBUFSIZE 32768
#define DATBUFSIZE (BMPBUFSIZE/8)
#define FN(f)    (subslc(f,'\\',1))
#define ERROR(m) {std::cout << std::endl << "ERROR: " << m; exit(1);}
#define INFO(m)  {std::cout << std::endl << "INFO : " << m;}
// Kalau DEBUG, pernyataan dalam D() dicetak, ekspresi dalam ND() tidak berjalan
#ifdef DEBUG
    std::ofstream cout = std::ofstream("log.txt",std::ios::binary);
    # define D(m)   {cout << std::endl << "DEBUG: " << m;}
    # define ND(m)  {}
#else
    using std::cout;
    using std::cin;
    # define D(m)   {}
    # define ND(m)  {m}
#endif // DEBUG

const char * subslc(const char * s,const char look_for, UINT before_or_after=0);
void ensteg(const char * fn_bmp, const char * fn_data);
void desteg(const char * fn_steg);

int main(const int argc, char** argv)
{
    if (argc == 3)
        ensteg(argv[1],argv[2]);
    else if (argc == 2)
        desteg(argv[1]);
    else
    {
        std::cout << "Penggunaan: " << FN(argv[0])
            << " [namabitmap] [namadata]" << std::endl;
    }
}

void ensteg(const char * fn_bmp, const char * fn_data)
{
    std::ifstream bmpsrc = std::ifstream(fn_bmp,std::ios::binary);
    std::ifstream datsrc = std::ifstream(fn_data,std::ios::binary);
    if (!bmpsrc.is_open()) ERROR("Tidak dapat membuka bitmap " << fn_bmp);
    if (!datsrc.is_open()) ERROR("Tidak dapat membuka data " << fn_data);
    std::ostringstream bmpdst_str;
    bmpdst_str << subslc(fn_bmp,'.') << ".stg.bmp";
    std::ofstream bmpdst = std::ofstream(   bmpdst_str.str().c_str(),
                                            std::ios::binary);
    if (!bmpdst.is_open()) ERROR("Tidak dapat membuka file bitmap hasil.");

    try {
        char bmpbuf[BMPBUFSIZE], datbuf[(BMPBUFSIZE/8)];
        bmpsrc.read(bmpbuf, BMPHDRCNT);
        if(bmpsrc.tellg()!=BMPHDRCNT) ERROR("File bitmap rusak.");
        if(memcmp(bmpbuf,"BM",2) != 0) // Menebak...
            ERROR("File bitmap harus dalam format bitmap Windows.");
        if(memcmp(bmpbuf+10,"6",1) != 0) // Menebak lagi...
            ERROR("Kedalaman warna bitmap harus 24bit.");

        long bmpwdt_long; memcpy(&bmpwdt_long,bmpbuf+18,4); // TEBAK
        long bmphgt_long; memcpy(&bmphgt_long,bmpbuf+22,4); // TERUSS!!
        const long maxdat_long =                    // Berapa data yang bisa
            ((bmpwdt_long * bmphgt_long * 3)/8);    // ditampung tuple byte?
        datsrc.seekg(0,std::ios::end);              // Pointer get ke ujung,
        const long datlen_long = datsrc.tellg();    // untuk cari tahu pjg.
        datsrc.seekg(0,std::ios::beg);              // Kembalikan pointer.
        INFO("Bitmap " << fn_bmp << " berdimensi "
                << bmpwdt_long << "x" << bmphgt_long << ",\n\t\t"
                << "yang berarti dapat menyamarkan " << maxdat_long
                << " byte data.\n\t"
                << "Panjang data dalam " << fn_data
                << " adalah " << datlen_long << " byte.\n\t"
                << "Semua byte berlebih akan diabaikan. Lanjutkan?[Y/T] ");
        ND(if (tolower(cin.get()) != 'y') exit(0);); // Kl debug g nunggu.

        memcpy(bmpbuf+50,&datlen_long,4);  // Di ujung header, info ukuran
        bmpdst.write(bmpbuf,BMPHDRCNT);  // Tulis header.
        bool bmpio = true,          roofing = true;
        long bmprdsz, datrdsz; UINT m_octet;
        unsigned char curbit;
        while(bmpio)
        {
            bmpsrc.read(bmpbuf,BMPBUFSIZE); bmprdsz = bmpsrc.gcount();
            if (bmprdsz != BMPBUFSIZE) bmpio = false;//Kl g ambil ful,habis.
            datsrc.read(datbuf,DATBUFSIZE); datrdsz = datsrc.gcount();

            if(roofing)
            {
                m_octet = min((bmprdsz/8),datrdsz);
                for(UINT octet = 0; octet < m_octet; ++octet)
                    for(UINT bit = 0; bit < 8; ++bit)
                    {
                        curbit = ((datbuf[octet])&(1 << bit));
                        if (bmpbuf[(octet*8)+bit] & 1)
                            bmpbuf[(octet*8)+bit] &= (bmpbuf[(octet*8)+bit] - 1);
                        bmpbuf[(octet*8)+bit] |= (curbit >> bit);
                    }
            }

            // Berhenti menyamarkan kalau bitmap atau data habis terbaca.
            if (!bmpio || datrdsz != DATBUFSIZE) roofing = false;
            bmpdst.write(bmpbuf,bmprdsz);
        }
        bmpsrc.close(); datsrc.close(); bmpdst.close();
    }
    catch (...) // Lazy catch.
    {
        bmpsrc.close(); datsrc.close(); bmpdst.close();
        ERROR("Ada kesalahan yang tidak diketahui. Sori banget gitu...");
    }
}

void desteg(const char * fn_steg)
{
    std::ifstream bmpsrc = std::ifstream(fn_steg,std::ios::binary);
    if (!bmpsrc.is_open()) ERROR("Tidak dapat membuka bitmap " << fn_steg);
    std::ostringstream datdst_str;
    datdst_str << subslc(fn_steg,'.') << ".txt";
    std::ofstream datdst = std::ofstream(   datdst_str.str().c_str(),
                                            std::ios::binary);
    if (!datdst.is_open()) ERROR("Tidak dapat membuka file bitmap hasil.");

    try {
        char srcbuf[BMPBUFSIZE], dstbuf[DATBUFSIZE];
        memset(dstbuf,0,DATBUFSIZE);
        bmpsrc.read(srcbuf, BMPHDRCNT);
        if(bmpsrc.tellg()!=BMPHDRCNT) ERROR("File bitmap penyamar rusak.");
        if(memcmp(srcbuf,"BM",2) != 0) // Menebak...
            ERROR("File bitmap harus dalam format bitmap Windows.");
        if(memcmp(srcbuf+10,"6",1) != 0) // Menebak lagi...
            ERROR("Kedalaman warna bitmap harus 24bit.");
        long bmpwdt_long;memcpy(&bmpwdt_long,srcbuf+18,4);
        long bmphgt_long;memcpy(&bmphgt_long,srcbuf+22,4);
        long spsdat_long;memcpy(&spsdat_long,srcbuf+50,4);memset(srcbuf+50,0,4);
        const long maxdat_long =                    // Berapa data yang bisa
            ((bmpwdt_long * bmphgt_long * 3)/8);    // ditampung tuple byte?
        if (spsdat_long > maxdat_long)
            INFO("Mengambil " << maxdat_long << " byte data tersembunyi, "
                << (spsdat_long - maxdat_long) << " byte selebihnya hilang.")
        else
            INFO("Mengambil " << spsdat_long << " byte data tersembunyi.")

        const long datlen_long = min(spsdat_long, maxdat_long);
        long bmprdsz;
        UINT m_octet;
        unsigned char curbit;
        for(long dst_idx = 0; dst_idx < datlen_long; dst_idx += DATBUFSIZE)
        {
            bmpsrc.read(srcbuf,BMPBUFSIZE); bmprdsz = bmpsrc.gcount();
            m_octet = min(bmprdsz/8,datlen_long-dst_idx);
            // Untuk setiap byte data...
            for(UINT octet = 0; octet < m_octet; ++octet)
            {
                for(UINT bit = 0; bit < 8; ++bit)
                {
                    curbit = srcbuf[(octet*8) + bit] & 1;
                    dstbuf[octet] |= (curbit << bit);
                }
                cout << dstbuf[octet];
            }
            datdst.write(dstbuf,m_octet);
            memset(dstbuf,0,DATBUFSIZE);
        }
        bmpsrc.close(); datdst.close();
    }
    catch (...) // Lazy catch.
    {
        bmpsrc.close(); datdst.close();
        ERROR("Ada kesalahan yang tidak diketahui. Sori banget gitu...");
    }
}

// Substring before or after last occurence of character.
const char * subslc(const char * s,const char c,UINT b)
{
    std::string ss = std::string(s);
    size_t last;
    if ((last=ss.rfind(c)) == std::string::npos) return s;
    if (b <= 0) // before occurence - default action
        return ss.substr(0,last).c_str();
    else
        return ss.substr(last+b).c_str();
}