/****************************************************************************
 * cli2nop.c                                                                *
 * Application: CLI2NOP.EXE - Win32 console mode program                    *
 * Purpose:     Find CLI in a [binary] file and replace it with NOP         *
 *                                                                          *
 * Runtime:     Win32                                                       *
 * Build:       Microsoft Visual C++ 6.0                                    *
 *__________________________________________________________________________*
 *                                                                          *
 *    RELEASED UNDER THE GNU GENERAL PUBLIC LICENSE - http://www.gnu.org    *
 *__________________________________________________________________________*
 *                                                                          *
 * This program is free software; you can redistribute it and/or modify it  *
 * under the terms of the GNU General Public License as published by the    *
 * Free Software Foundation (version 2).                                    *
 *                                                                          *
 * This program is distributed in the hope that it will be useful, but      *
 * WITHOUT ANY WARRANTY; without even the implied warranty of               *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU        *
 * General Public License for more details.                                 *
 *                                                                          *
 * http://www.gnu.org/copyleft/gpl.html                                     *
 *__________________________________________________________________________*
 *                                                                          *
 * Revision History:                                                        *
 *                                                                          *
 * No.   Date     By   Reason                                               *
 *--------------------------------------------------------------------------*
 * 100 30 Oct 98  lvr  Original Created by Lawrence Rust                    *
 * 200 10 Jul 02  chs  Solely CLI2NOP, no longer search & replace; GNU'd    *
 *__________________________________________________________________________*/
#define VERSION 200
#define kszInfo "CLI2NOP - replace CLI calls with NOP calls\n\n"
#define kszCopyright "Released under the GNU General Public License. Original Copyright 1998 by Lawrence Rust."

/* Compilation options */


/* Exports */


/* Imports */
/* ANSI */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>


/*
 * Macro definitions
 */
#ifndef lengthof
 #define lengthof( array) (sizeof(array) / sizeof(array[0]))
#endif

#ifndef offsetof
 #define offsetof( type,field) ((size_t)&(((type*)0)->field))
#endif

#ifndef CONTAINING_RECORD
 #define CONTAINING_RECORD( ptr, type, item) \
   (type*)( (char*)(ptr) - (char*)(&((type*)0)->item) )
#endif


/***************************************************************************
 * Data types
 ***************************************************************************/


/***************************************************************************
 * Local function prototypes
 ***************************************************************************/

/* Decode an escaped string*/
static size_t DecodeString(             // Returns size of output
  unsigned char* pszDst,                // OUT:
  const char* pszSrc                    // IN:
);

/* Find and replace byte sequence */
static int Patch(
  FILE* pfiDst,
  FILE* pfiSrc,
  unsigned char aucFind[],
  size_t sizeFind,
  unsigned char aucReplace[],
  int iVerbosity
);


/***************************************************************************
 * Module data
 ***************************************************************************/


/***************************************************************************
 * Exported functions
 ***************************************************************************/

int main(
  int iArgc,
  const char* apszArgv[]
) {
//  unsigned long ulBegin = 0, ulEnd = 0;
  int iVerbose = 0;
  unsigned char aucFind[64], aucReplace[64];
  size_t sizeFind = 0, sizeReplace = 0;
  FILE *pfiSrc, *pfiDst;
  int iOccurrences;

  // Parse switch options
  for ( ; iArgc > 1; --iArgc, ++apszArgv)
    {
//    char* pszEnd;
    const char* pszArg;

    pszArg = apszArgv[1];
    if ( '-' != *pszArg && '/' != *pszArg)
      break;

    ++pszArg;      
    switch ( *(pszArg++))
      {
    case 'h':
    case '?':
      printf(
        kszInfo "\n"
        "patch [-h] [-pP] [-2] source [destination]\n"
        "-h This information\n"
        "-v Verbose output\n"
		"-p pushfd, pop eax, cli -> pushfd, pop eax, nop\n"
        "-P pushfd, cli -> pushfd, nop\n"
		"-2 pushfd, pop eax, cli -> pushfd, pop eax, nop *AND* pushfd, cli -> pushfd, nop (\"aggressive mode\")\n"
/*#if 0 // TODO: begin & end
        "-b[=]dd start file offset\n"
        "-e[=]dd end file offset\n"
#endif
        "-f find string\n"
        "-r replace string\n"*/
      );
      return EXIT_FAILURE;
      break;

    case 'v':
      iVerbose = 1;
      break;

#if 0 // TODO: begin & end
    case 'b':
      ulBegin = strtoul( pszArg, &pszEnd, 10);
      break;

    case 'e':
      ulEnd = strtoul( pszArg, &pszEnd, 10);
      break;
#endif

/*
	case 'f':
      sizeFind = DecodeString( aucFind, pszArg);
      break;

    case 'r':
      sizeReplace = DecodeString( aucReplace, pszArg);
      break;
*/

	case 'p': 
      sizeReplace = DecodeString( aucReplace, "\\x9c\\x58\\x90" ); 
      sizeFind    = DecodeString( aucFind,    "\\x9c\\x58\\xfa" ); 
      break; 

    case 'P': 
      sizeReplace = DecodeString( aucReplace, "\\x9c\\x90" ); 
      sizeFind    = DecodeString( aucFind,    "\\x9c\\xfa" ); 
      break;  

	case '2':
      sizeReplace = DecodeString( aucReplace, "\\x9c\\x58\\x90" ); 
      sizeFind    = DecodeString( aucFind,    "\\x9c\\x58\\xfa" ); 
      sizeReplace = DecodeString( aucReplace, "\\x9c\\x90" ); 
      sizeFind    = DecodeString( aucFind,    "\\x9c\\xfa" ); 
	  break;

    default:
      printf( "Invalid option '%s'\n", apszArgv[1]);
      return EXIT_FAILURE;
      }
    }

  // verify required options
  if ( 0 == sizeFind)
    {
    printf( "Missing find string\n");
    return EXIT_FAILURE;
    }
  
  if ( sizeFind != sizeReplace)
    {
    printf( "Missing or incorrect replace string\n");
    return EXIT_FAILURE;
    }

  // Verify required arguments
  if ( iArgc <= 1)
    {
    printf( "Missing filename\n");
    return EXIT_FAILURE;
    }

  if ( iArgc > 3)
    {
    printf( "Excess arguments\n");
    return EXIT_FAILURE;
    }

  // Open input & output
  pfiSrc = fopen( apszArgv[1], iArgc < 3 ? "rb+" : "rb");
  if ( NULL == pfiSrc)
    {
    perror( "Unable to open input file");
    return EXIT_FAILURE;
    }

  if ( iArgc != 3)
    pfiDst = NULL;
  else
    {
    pfiDst = fopen( apszArgv[2], "wb");
    if ( NULL == pfiDst)
      {
      perror( "Unable to create output file");
      fclose( pfiSrc);
      return EXIT_FAILURE;
      }
    }

  // Patch the file
  iOccurrences = Patch( pfiDst, pfiSrc, aucFind, sizeFind, aucReplace, iVerbose);

  fclose( pfiSrc);
  if ( NULL != pfiDst)
    {
    fclose( pfiDst);

    // Remove the output on error
    if ( iOccurrences < 0)
      remove( apszArgv[2]);
    }

  if ( iOccurrences < 0)
    {
    perror( "Failed");
    return EXIT_FAILURE;
    }

  printf( "%d occurences\n", iOccurrences);
  return EXIT_SUCCESS;
  }


/*
 * Patch a file
 */
static int Patch(                     // Returns no. occurrences, -ve on error
  FILE* pfiDst,
  FILE* pfiSrc,
  unsigned char aucFind[],
  size_t sizeToFind,
  unsigned char aucReplace[],
  int iVerbosity
) {
  int iInput;
  size_t sizeFound = 0;
  int iReplacements = 0;

  while ( EOF != (iInput = fgetc( pfiSrc)))
    {
    if ( iInput != aucFind[ sizeFound])
      {
      // Mismatch
      if ( NULL != pfiDst)
        {
        // Copy input to output
        if ( sizeFound)
          {
          // Matched so far
          if ( sizeFound != fwrite( aucFind, 1, sizeFound, pfiDst))
            return -1;
          }
        fputc( iInput, pfiDst);
        }
      sizeFound = 0;
      }
    else if ( ++sizeFound == sizeToFind)
      {
      // Matched
      ++iReplacements;
      if ( iVerbosity)
        printf( "Found at 0x%08lX\n", ftell( pfiSrc));

      // replace
      if ( NULL == pfiDst)
        fseek( pfiSrc, -((long)sizeToFind), SEEK_CUR);
      if ( sizeToFind != fwrite( aucReplace, 1, sizeToFind, NULL == pfiDst ? pfiSrc : pfiDst))
        return -1;
      if ( NULL == pfiDst)
        fflush( pfiSrc);

      sizeFound = 0;
      }
    }

  if ( NULL != pfiDst && sizeFound)
    {
    // Copy remaining input to output
    if ( sizeFound != fwrite( aucFind, 1, sizeFound, pfiDst))
      return -1;
    }

  return iReplacements;
  }


/*
 * Decode an escaped string
 */
static size_t DecodeString(
  unsigned char* pszDst,                // OUT:
  const char* pszSrc                    // IN:
) {
  // State m/c
  enum { kNormal, kEscape, kOctal, kOctalLast, kHex, kHexLast } eState = kNormal;
  unsigned char *pszStart, ucData;
  char c;

  pszStart = pszDst;
  ucData = 0;
  do
    {
    c = *(pszSrc++);
    switch ( eState)
      {
    default:
      if ( '\\' == c)
        eState = kEscape;
      else
        *(pszDst++) = c;
      break;
      
    case kEscape:
      eState = kNormal;
      switch ( c)
        {
      case '0':
      case '1':
      case '2':
      case '3':
        eState = kOctal;
        ucData = (unsigned char)(c - '0');
        break;
      case '4':
      case '5':
      case '6':
      case '7':
        eState = kOctalLast;
        ucData = (unsigned char)(c - '0');
        break;
        
      case 'x':
        eState = kHex;
        ucData = 0;
        break;

      case 'r':
        *(pszDst++) = 0x0d;
        break;
      case 'n':
        *(pszDst++) = 0x0a;
        break;
      case 'a':
        *(pszDst++) = 0x07;
        break;
      case 't':
        *(pszDst++) = 0x09;
        break;
      case 'f':
        *(pszDst++) = 0x0c;
        break;

      default:
        *(pszDst++) = c;
        break;
        }
      break;
      
    case kOctal:
    case kOctalLast:
      switch ( c)
        {
      default:
        *(pszDst++) = ucData;
        *(pszDst++) = c;
        eState = kNormal;
        break;

      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
        ucData *= 8;
        ucData = (unsigned char)(ucData + (c - '0'));
        if ( kOctal == eState)
          eState = kOctalLast;
        else
          {
          *(pszDst++) = ucData;
          eState = kNormal;
          }
        break;
        }
      break;
      
    case kHex:
    case kHexLast:
      if ( isxdigit( c))
        {
        ucData *= 16;
        if ( isdigit( c))
          ucData = (unsigned char)(ucData + (c - '0'));
        else
          ucData = (unsigned char)(ucData + toupper( c) - 'A' + 10);
   
        if ( kHex == eState)
          eState = kHexLast;
        else
          {
          eState = kNormal;
          *(pszDst++) = ucData;
          }
        }
      else
        {
        *(pszDst++) = ucData;
        *(pszDst++) = c;
        eState = kNormal;
        }
      break;
      }
    }
  while ( '\0' != c);

  return (size_t)(pszDst - pszStart -1);
  }
  
/* End of file */
