/*
 * Season7 -- Videocrypt BSkyB smard card emulator for DOS.
 *
 * Compiles with Borland C++ 2.0, small model.
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
// #include <conio.h>
#include <dos.h>
#include <clib/dos_protos.h>
#include <devices/serial.h>
#include <string.h>
#include <time.h>
#include "decrypt.h"
#include "async.h"

#define VERSION "1.2"         /* last change: 1994-04-27 */

/*
 * information in answer to reset: (as defined in ISO 7816-3)
 * inverse convention (3f), protocol type T=0, F=372, D=1, I=50mA, P=5V, N=5.
 * historic characters (final 10 bytes): ???
 */
#define RESET_ANSWER_LEN 16
unsigned char reset_answer[RESET_ANSWER_LEN] = {
  0x3f, 0xfa, 0x11, 0x25, 0x05, 0x00, 0x01, 0xb0,
  0x02, 0x3b, 0x36, 0x4d, 0x59, 0x02, 0x81, 0x80
};

/* we show this shortly after a reset on the screen */
unsigned char display[25] ="\x80"
  "SEASON7 V" VERSION
  "   --:--    ";

/* channel id (based on byte 7 in 74 messages) for TV screen display */
char *channel_id[16] = {
  "    ????    ", " SKY MOVIES ", "MOVIECHANNEL", "MOVIES  GOLD",
  "    ????    ", "    ????    ", " SKY SPORTS ", "    ????    ",
  "  TV ASIA   ", "    ????    ", "    ????    ", "    ????    ",
  "MULTICHANNEL", "    ????    ", "    ????    ", "    ????    "
};

/* channel name for text screen display */
char *channel_name[16] = {
  "???", "Sky Movies", "The Moviechannel", "Sky Movies Gold",
  "???", "???", "Sky Sports", "???",
  "TV Asia", "???", "???", "???",
  "Sky Multichannels", "???", "???", "???"
};

/* message directions */
#define IN   0   /* to card */
#define OUT  1   /* to decoder */


/*
 * These are some Delay values (milliseconds) used in the program.
 * Use command line option w to modify them.
 */
#define DELAYS 5
unsigned delms[DELAYS] = {
  30,            /* wait between reset and answer to reset */
  0,             /* wait after each outgoing byte */
  0,             /* wait between header and first procedure byte */
  0,             /* wait between procedure byte and data */
  0              /* wait between final data byte and procedure bytes */
};

/*
 * Reverse and invert all bits in each byte of a string.
 * E.g. 10010111b (0x97) becomes 00010110b (0x16).
 */
void inverse(char *data, int len) 
{
  int i, j;
  unsigned char c;

  for (i = 0; i < len; i++) {
    c = 0;
    for (j = 0; j < 8; j++)
      c |= ((data[i] & (1 << j)) != 0) << (7 - j);
    data[i] = ~c;
  }

  return;
}


/* Bogus Routines That I Could Not Replace! */
void clrscr( void );
void clrscr( void )
{
    
}

int kbhit( void );
int kbhit( void )
{
    return(FALSE);
}

void activate_com(int com)
{
 
  AsyncInit(0);
  
  AsyncHand(DTR | RTS);
//   ODD_PARITY : EVEN_PARITY;
  AsyncSet(9600, BITS_8 | STOP_2 | EVEN_PARITY);

  return;
}


void deactivate_com(void)
{
  AsyncStop();
}

/*
 * Send a string of bytes to serial port and wait for each single echo.
 * (More efficient send methods are too fast for decoder, it seems to
 * need more than 2 stop bits.)
 */
int send(char *data, int len)
{
  int i;
  char c;

  AsyncClear();
  for (i = 0; i < len; i++) {
    c = data[i];
    if (reset_answer[0] == 0x3f) inverse(&c, 1);
    AsyncOut(c);
    while (AsyncInStat() == 0)
      if ((AsyncStat()) || kbhit()) return 1;
    AsyncIn();
    if (delms[1]) Delay(delms[1]);
  }

  return 0;
}


/*
 * Get all bytes available from serial port FIFO and return how many
 * bytes were available.
 */
int receive(char *data, int max)
{
  int i = 0;

  while (AsyncInStat() > 0 && i < max)
    data[i++] = AsyncIn();
  if (reset_answer[0] == 0x3f) inverse(data, i);

  return i;
}




main(int argc, char **argv)
{
  unsigned char header[5];
  int hc = 0, received = -1, length, dir;
  unsigned char buffer[65];
  unsigned char msg[32];
  unsigned char *data;
  time_t now;
  char *current_name = "???";
  int station = -1;
  int com = 2;
  int debug = 0, quit = 0, message = 1;
  int i;
#define LOGMAX 800
  time_t logtime[LOGMAX];
  unsigned char logmsg[LOGMAX][32];
  int loglength = 0, lognext = 0;
  

  printf("\nSeason7 -- Version " VERSION " -- Sky smart card emulator\n\n");
  printf(
    "Important: This software must not be used in countries were a\n"
    "           regular subscription of the Sky Broadcasting Network\n"
    "           channels is possible (i.e., Great Britain and Northern\n"
    "           Ireland) unless you have already such a subscription\n"
    "           and want to use this software only for educational\n"
    "           purposes. This software is in the public domain and\n"
    "           may be redistributed and used under the above conditions\n"
    "           without any restrictions from the author.\n\n");

  

  printf("Press q for exit.\n\n");
  printf("Using serial port COM%d, byte Delay %d ms.\n", com, delms[1]);
  activate_com(com);
  
  printf("\nWaiting for reset ...");

  while (!quit) {
    /* keyboard commands */
  
    while (AsyncStat()) {
      /*
       *  we have a reset
       */
      AsyncClear();
      printf("\r\t\t\t\t\t\rRESET\a");
      
      if (delms[0]) Delay(delms[0]);    /* let VCC become stable */
      printf("Delay of %d\n",delms[0]);
      send(reset_answer, RESET_ANSWER_LEN);
      printf("Just Sent Reset Answer\n");
      
      if (message) {
        now = time(NULL);
        strftime((char *) display + 13, 13, "   %H:%M    ", localtime(&now));
        display[0] = '\xd8';
      }
      station = -1;
      hc = 0;
      received = -1;
    }
    if (hc < 5) {
      /* waiting for more header bytes */
      hc += receive((char *) header + hc, 5 - hc);
    } else {
      if (received < 0) {
        /* new 5 byte command header has arrived */
        if (delms[2]) Delay(delms[2]);
        if (header[0] != 0x53) {
          send("\x6e\x00", 2);  /* unsupported class */
          hc = 0;
          printf("\rUnsupported class code %02x!\t\t\n", header[0]);
        } else {
          data = buffer;
          memset(buffer, 0, 64); /* default answer: 00 00 00 ... */
          switch (header[1]) { /* the different instruction types */
            case 0x70: length =  6; dir = OUT; break;
            case 0x72: length = 16; dir = IN;  break;
            case 0x74: length = 32; dir = IN;  break;
            case 0x76: length =  1; dir = IN;  break;
            case 0x78: length =  8; dir = OUT; data = msg; break;
            case 0x7a: length = 25; dir = OUT; data = display; break;
            case 0x7c: length = 16; dir = OUT; break;
            case 0x7e: length = 64; dir = OUT; break;
            case 0x80: length =  1; dir = IN;  break;
            case 0x82: length = 64; dir = OUT; break;
            default:
              send("\x6d\x00", 2);  /* unsupported instruction */
              hc = 0;
              printf("\rUnsupported instruction code %02x!\t\t\n", header[1]);
          }
          if (length != header[4]) {
            send("\x67\x00", 2);  /* incorrect length */
            hc = 0;
            printf("\rIncorrect length %d in instruction %02x!\t\t\n",
                   header[4], header[1]);
          }
          if (hc == 5) {
            /* procedure byte: no Vpp, send all data in one piece */
            if (send((char *) header + 1, 1)) continue;
            if (dir == IN)
              received = 0;
            else {
              if (delms[3]) Delay(delms[3]);
              /* here comes the answer */
              if (send((char *) data, length)) continue;
              if (delms[4]) Delay(delms[4]);
              if (send("\x90\x00", 2)) continue;  /* ack: everything ok */
              
              hc = 0;
              received = -1;
            }
          }
        }
      } else if (received < header[4]) {
        /* waiting for more data to receive */
        received += receive((char *) data + received, header[4] - received);
      } else {
        /* here, all data has been received */
        if (delms[4]) Delay(delms[4]);
        if (send("\x90\x00", 2)) continue;       /* ack: everything ok */
       
        /* reaction on received data */
        switch (header[1]) {
          case 0x74:
            if (data[0] & 8) {
              /* calculate random number seed from crypto message */
              if (decode(buffer, msg))
                printf("\rChecksum wrong !!!\t\t\t\t\t\n");
              /* following fix necessary since 1994-04-27 */
              if ((buffer[6] & 0xf0) == 0xa0)
                for (i = 0; i < 8; i++) msg[i] = 0;
              msg[7] &= 0x0f;   /* only 60-bit are PRNG seed */
              /* keep history of last LOGMAX crypto messages */
              time(&logtime[lognext]);
              memcpy(logmsg[lognext++], buffer, 32);
              if (loglength < LOGMAX) loglength++;
              if (lognext >= LOGMAX) lognext = 0;
              /* manage on screen display */
              if (station == -1 && data[6] != 0xd1) {
                station = data[6];
                strcpy((char *) display + 13, channel_id[data[6] & 0x0f]);
              } else if (station != -1 && station != -2) {
                display[0] = 0x80;
                station = -2;
              }
              /* PC display message */
              if (data[6] != 0xd1)
                current_name = channel_name[data[6] & 0x0f];
              if (!debug)
                printf("\r\t\t\t\t\t\rDecrypting %s (%02x %02x %02x)",
                       current_name, data[0], data[1], data[6]);
            }
            break;
          case 0x76:
            printf("\rAuthorize button pressed.\t\t\t\t\n");
            break;
          default: break;
        }
        hc = 0;
        received = -1;
      }
    }
  }

  deactivate_com();
  printf("\n");

  return 0;
}
