//  FOPS2.C

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <io.h>
#include <errno.h>
#include <time.h>

#include "stdafx.h"

#include "me5.h"

#define MAX_BLOCK_SIZE (10232)
#define MAX_BLOCK_EXPANSION_ERRORS  (9)
#define DEMO_MARKER ((unsigned int)0x7FDE)

unsigned char rnd0;
unsigned char pm0;
int block_expansion_errors;     //  number of block expansion errors

int open_input_file(void);
int open_output_file(void);
int delete_output_file(void);

//  Function in auxlib.dll

double _stdcall file_randomness_auxlib(char *filepath, int min_size);

//  On entry the 64-byte (extended) initial key is at vb_key.
//  This is called before attempting to encrypt or decrypt each file.
/*---------------------------------------------------------*/
void _stdcall operation_initialization(unsigned char *vb_key, 
    int vb_encrypt, unsigned int filesize, int vb_demo)
{
unsigned int i, j;

//  Flags should be passed here.
encrypt = vb_encrypt;
//  Seems this is not used.

demo = vb_demo;

memcpy(key,vb_key,64);

seed_rngs();                        //  RAND.C

//  Seed the C rand() function for use in hiding the 2-byte block header
//  (and in demo mode).
srand((((unsigned int)rnd()|1)*(pm()|1)) + filesize*3 + 12420);
j = (unsigned int)(rnd()&0x3F) + (pm()&0x3F);
for ( i=0; i<j; i++ )
    rand();
}

//   Returns 0 if no error, otherwise:
//   -3 = input file not found
//   -4 = unable to open input file
//   -5 = unable to open output file
//   -6 = read error (output file deleted)
//   -7 = write (or flush) error, invalid handle or file not open (output file deleted)
//   -9 = write error, disk full (output file deleted)
/*------------------------------------------------------------------------*/
int _stdcall encrypt_file(char *vb_input_filepath, char *vb_output_filepath)
{
unsigned int i, j;
unsigned char rnd0, rnd1, rnd2, pm0, pm1, pm2, pml;

strcpy(input_file_path,vb_input_filepath);
strcpy(output_file_path,vb_output_filepath);

//  Open input and output files;
//  ifh and ofh are global variables.
if ( ( ifh = open_input_file() ) < 0 )
    return ( ifh );     //  If error, returns error code -3 or -4.

//  In DOS version we get the timestamp of the input file here
//  (so the output file has the same timestamp).
//  Not clear how to do this in the Windows version.

if ( ( ofh = open_output_file() ) < 0 )
    {
    close(ifh);
    return ( ofh );        //  -5 if open_output_file() returns error code (-1)
    }

block = (unsigned int)-1;

while ( !eof(ifh) )
    {
    block++;
    segment = 0;
    define_priming_key();               //  KEY.C.

    block_size = 8192;
    for ( i=0; i<8; i++ )
        block_size -= rnd();
    for ( i=0; i<8; i++ )
        block_size += pm();
    
    //  block_size >= 8192 - 8*255 and <= 8192 + 8*255
    //  thus 6152 <= block_size <= 10232
    //  but may be decreased if (remainder of) file is smaller.

    block_size = read(ifh,buffer,block_size);
    if ( (int)block_size == -1 )
        {
        close(ifh);
        close(ofh);
        remove(output_file_path);
        return ( -6 );
        }   

    #if DEVEL
    assert( block_size > 0 && block_size < 10240 );
    #endif

    first_half_size = ( (unsigned int)rnd()*256 + pm() ) % block_size;
    comp_fact = 10+(rnd()%3);

    //  Generate seven random numbers for later use.
    pml = pm();
    rnd0 = rnd();
    pm0 = pm();
    rnd1 = rnd();
    pm1 = pm();
    rnd2 = rnd();
    pm2 = pm();
    
    encrypt_block();     //  FOPS3.C
    //  This consists of:
    //  (i)   Russian copulation
    //  (ii)  compression (if possible)
    //  (iii) XOR operation
    //  (iv)  inner encryption process
    //  It sets me5_expand_block.

    //  Add block_size and me5_expand_block flag to 2-byte block_header
    //  This is the block size after compression (if any), 
    //  not the size of the plaintext block which is read in.

    /*
    #if DEMO
    j = block_size;
    block_size = DEMO_MARKER;
    //  If an attempt is made to decrypt the output file
    //  then ME6 Demo will detect this (also ME6 itself).
    #endif
    */

    if ( demo )
        {
        j = block_size;
        block_size = DEMO_MARKER;
        //  If an attempt is made to decrypt the output file
        //  then ME6 Demo will detect this (also ME6 itself).
        }

    block_header[0] = block_size&0xFF;
    block_header[1] = ((block_size&0xFF00)>>8);
    block_header[1] ^= (me5_expand_block<<7);
    block_header[0] ^= rnd0;
    block_header[1] ^= pm0;

    /*
    #if DEMO
    block_size = j;
    #endif
    */

    if ( demo )
        block_size = j;

    //  Generate 256 to 383 bytes using rand().
    j = (unsigned int)256 + (pml&0x7F);
    for ( i=0; i<j; i++ )
        buffer1[i] = (rand()>>3)&0xFF;

    //  Now hide the 2-byte block header within them.
    buffer1[rnd1&0x7F] = block_header[0] ^ pm1;
    buffer1[(rnd2&0x7F)+128] = block_header[1] ^ pm2;

    //  Write buffer1.
    if ( _write(ofh,buffer1,j) == - 1 )
        {
        close(ifh);
        close(ofh);
        remove(output_file_path);
        return ( errno == ENOSPC ? -9 : -7 );
        }

    //  Write block.
    if ( _write(ofh,buffer,block_size) == - 1 )
        {
        close(ifh);
        close(ofh);
        remove(output_file_path);
        return ( errno == ENOSPC ? -9 : -7 );
        }

    //  Flush to disk.
    if ( _commit(ofh) == - 1 )
        {
        close(ifh);
        close(ofh);
        remove(output_file_path);
        return ( -7 );
        }
    }

close(ifh);

//  set_fh_date_time(ofh,&file_date,&file_time);

close(ofh);

return ( 0 );
}

//   Returns 0 if no error, otherwise:
//   -3 = input file not found
//   -4 = unable to open input file
//   -5 = unable to open output file
//   -6 = read error
//   -7 = write (or flush) error
//   -8 = invalid block size

//  If the output file is not deleted (since it may contain partial plaintext)
//  then -10 is added to the errors -6 through -8.

//  -21 = one block expansion error
//  -22 ... -29 = 2 ... 9 block expansion errors
//  With block expansion errors the output file is not deleted even if decryption aborted.

//  -100 = file encrypted using ME6 in demo mode (or by an earlier demo version).
/*------------------------------------------------------------------------*/
int _stdcall decrypt_file(char *vb_input_filepath, char *vb_output_filepath)
{
unsigned int i, j;
unsigned char rnd0, pm0;

strcpy(input_file_path,vb_input_filepath);
strcpy(output_file_path,vb_output_filepath);

//  Open input and output files;
//  ifh and ofh are global variables.
if ( ( ifh = open_input_file() ) < 0 )
    return ( ifh );     //  If error, returns error code -3 or -4.

//  In DOS version we get the timestamp of the input file here
//  (so the output file has the same timestamp).
//  Not clear how to do this in the Windows version.

if ( ( ofh = open_output_file() ) < 0 )
    {
    close(ifh);
    return ( ofh );
    }

block = (unsigned int)-1;      
block_expansion_errors = 0;

while ( !eof(ifh) )
    {
    block++;
    segment = 0; 
    define_priming_key();               //  KEY.C.

    //  Run RNGs as in encrypt().
    for ( i=0; i<8; i++ )
        rnd();
    for ( i=0; i<8; i++ )
        pm();

    first_half_size = ( (unsigned int)rnd()*256 + pm() );
    //  Preliminary; changed below.

    comp_fact = 10+(rnd()%3);

    j = (unsigned int)256 + (pm()&0x7F);        //  pml

    //  Read j bytes into buffer1.
    if ( (int)read(ifh,buffer1,j) == -1 )
        {
        close(ifh);
        return ( delete_output_file() ? -6 : -16 );
        } 

    //  Generate two random numbers for use below.
    rnd0 = rnd();
    pm0 = pm();

    //  Recover 2-byte block header.
    block_header[0] = buffer1[rnd()&0x7F] ^ pm();          //  rnd1 and pm1
    block_header[1] = buffer1[(rnd()&0x7F)+128] ^ pm();    //  rnd2 and pm2

    //  Get block_size and me5_expand_block.
    block_header[0] ^= rnd0;     
    block_header[1] ^= pm0;      
    me5_expand_block = (block_header[1]>>7);
    block_header[1] &= 0x7F;
    block_size = block_header[0] + (((unsigned int)block_header[1])<<8);
    //  block_size may be changed due to expansion.

    //  Can't do first_half_size %= block_size here
    //  because must get size of expanded block (in decrypt_block()).

    if ( block_size == DEMO_MARKER )
        {
        close(ifh);
        delete_output_file();
        return ( -100 );
        }

    if ( block_size > MAX_BLOCK_SIZE )
        {
        close(ifh);
        return ( delete_output_file() ? -8 : -18 );
        }
    
    //  Read block
    if ( (int)read(ifh,buffer,block_size) == -1 )
        {
        close(ifh);
        return ( delete_output_file() ? -6 : -16 );
        } 

    //  Now call decrypt_block() in FOPS3.C.
    //  This consists of:
    //  (i)   inner decryption process
    //  (ii)  XOR operation
    //  (iii) expansion (when needed)
    //  (iv)  decopulation

    if ( decrypt_block() )
        {
        if ( _write(ofh,buffer,block_size) == -1 )
            {
            close(ifh);
            return ( delete_output_file() ? -7 : -17 );
            }
        //  Flush to disk.
        if ( _commit(ofh) == -1 )
            {
            close(ifh);
            return ( delete_output_file() ? -7 : -17 );
            }
        }
    else
        {
        block_expansion_errors++;
        sprintf(buffer1,"\n\n!!! START OF BAD BLOCK #%d !!!\n\n",block_expansion_errors);
        _write(ofh,buffer1,strlen(buffer1));
        _write(ofh,buffer,block_size);
        sprintf(buffer1,"\n\n!!! END OF BAD BLOCK #%d !!!\n\n",block_expansion_errors);
        _write(ofh,buffer1,strlen(buffer1));
        _commit(ofh);
        if ( block_expansion_errors == MAX_BLOCK_EXPANSION_ERRORS )
            break;
        }

    //  erase buffers
    memset(buffer,0,block_size);
    memset(buffer1,0,block_size);
    memset(block_header,0,2);
    }

close(ifh);

//  set_fh_date_time(ofh,&file_date,&file_time);

close(ofh);

if ( block_expansion_errors > 0 )
    return ( -20 - block_expansion_errors );
else
    return ( 0 );
}

/*---------------------*/
int open_input_file(void)
{
int input_file_handle;

if ( ( input_file_handle = open(input_file_path,O_RDONLY|O_BINARY) ) == -1 )
    {
    if ( errno == ENOENT )
        return ( -3 );
    else
        return ( -4 );
    }

return ( input_file_handle );
}

/*----------------------*/
int open_output_file(void)
{
int output_file_handle;

if ( ( output_file_handle = open(output_file_path, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,S_IREAD|S_IWRITE) ) == -1 )
    return ( -5 );

return ( output_file_handle );
}

//  Decide whether to delete output file.
/*------------------------*/
int delete_output_file(void)
{
int delete_file;
double randomness;

if ( _filelength(ofh) == 0 )
    {
    delete_file = true;
    close(ofh);
    }
else
    {
    close(ofh);
    //  Check to see if output file looks like garbage.
    randomness = file_randomness_auxlib(output_file_path, 256);
    delete_file = ( randomness <= 0 || randomness >= 0.8 );
    //  Don't delete file if randomness is > 0 and < 0.8.
    }

if ( delete_file )
    remove(output_file_path);

return ( delete_file );
}

typedef double (CALLBACK* LPFNDLLFUNC2)(char *, int);

//  This works by calling file_randomness() in the AUXLIB dll.
/*----------------------------------------------------------------*/
double _stdcall file_randomness_auxlib(char *filepath, int min_size)
{
HINSTANCE hDLL;               // Handle to DLL
LPFNDLLFUNC2 lpfnDllFunc2;    // Function pointer

hDLL = LoadLibrary(AUXLIB_DLL);     //  #defined in ME5.H

if ( hDLL == NULL )
    return ( -21 );
else
    {
    lpfnDllFunc2 = (LPFNDLLFUNC2)GetProcAddress(hDLL,"_file_randomness@8");
    if ( !lpfnDllFunc2 )
        {
        // handle the error
        FreeLibrary(hDLL);       
        return ( -22 );
        }
    else
        {
        // call the function
        return ( lpfnDllFunc2(filepath,min_size) );
        }
    }
}