/*
 * Softcam plugin to VDR (C++)
 *
 * This code 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; either version 2
 * of the License, or (at your option) any later version.
 *
 * This code 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <unistd.h>
#include <ctype.h>
#include "stdafx.h"
//#include <vdr/tools.h>

#include "common.h"
#include "sc.h"
#include "system.h"
#include "misc.h"
#include "opts.h"
#include "network.h"
#include "crypto.h"
//#include "md5crypt/md5crypt.h"
#include "helper.h"
/////////

#include "network.h"
#include "xnprintf.h"

#include <openssl/bn.h>
#include <openssl/des.h>
#include <openssl/sha.h>
#include "openssl-compat.h"

#include "debug.h"
#include "nagra.h"
#include "cpu.h"

//********************************************  Modification - 12/26/06  **
#include "main.h"
extern class DllClass m_DLLInstance;

extern bool g_BeepOnNewKey;
extern bool g_LogE3;

//#include "morph.h"
//extern class B1morph B1_morph;
//********************************************  Modification - 12/26/06  **

#define SYSTEM_NAME          "Nagra2"
#define SYSTEM_PRI           -10

// -- cN2Emu -------------------------------------------------------------------

class cN2Emu : protected c6805 {
private:
  bool initDone;
protected:
  bool Init(int id, int romv);
  virtual void Stepper(void) {}
public:
  cN2Emu(void);
  virtual ~cN2Emu() {}
  };

cN2Emu::cN2Emu(void)
{
  initDone=false;
}

bool cN2Emu::Init(int id, int romv)
{
  if(!initDone) {
    ResetMapper();
    char buff[256];
    _snprintf(buff,sizeof(buff),"ROM%d.bin",romv);
    // UROM  0x00:0x4000-0x7fff
    if(!AddMapper(new cMapRom(0x4000,buff,0x00000),0x4000,0x4000,0x00)) return false;
    // ROM00 0x00:0x8000-0xffff
    if(!AddMapper(new cMapRom(0x8000,buff,0x04000),0x8000,0x8000,0x00)) return false;
    // ROM01 0x01:0x8000-0xffff
    if(!AddMapper(new cMapRom(0x8000,buff,0x0C000),0x8000,0x8000,0x01)) return false;
    // ROM02 0x02:0x8000-0xbfff
    if(!AddMapper(new cMapRom(0x8000,buff,0x14000),0x8000,0x4000,0x02)) return false;

    _snprintf(buff,sizeof(buff),"EEP%02X_%d.bin",(id>>8)&0xFF,romv);
    // Eeprom00 0x00:0x3000-0x37ff OTP 0x80
    if(!AddMapper(new cMapRom(0x3000,buff,0x0000),0x3000,0x0800,0x00)) return false;
    //XXX if(!AddMapper(new cMapEeprom(0x3000,buff,128,0x0000),0x3000,0x0800,0x00)) return false;
    // Eeprom80 0x80:0x8000-0xbfff
    if(!AddMapper(new cMapRom(0x8000,buff,0x0800),0x8000,0x4000,0x80)) return false;
    //XXX if(!AddMapper(new cMapEeprom(0x8000,buff,  0,0x0800),0x8000,0x4000,0x80)) return false;
    initDone=true;
    }
  return true;
}

// -- cN2Prov ------------------------------------------------------------------

class cN2Prov {
private:
  unsigned seed[5], cwkey[8];
  bool keyValid;
  cIDEA idea;
protected:
  int id, flags;
  //
  virtual bool Algo(int algo, const unsigned char *hd, unsigned char *hw) { return false; }
  virtual bool NeedsCwSwap(void) { return false; }
  void ExpandInput(unsigned char *hw);
public:
  cN2Prov(int Id, int Flags);
  virtual ~cN2Prov() {}
  bool MECM(unsigned char in15, int algo, unsigned char *cws);
  void SwapCW(unsigned char *cw);
  virtual int ProcessB1(unsigned char *data, int len, int pos) { return -1; }
  bool CanHandle(int Id) { return ((Id^id)&~0x107)==0; }
  bool HasFlags(int Flags) { return (flags&Flags)==Flags; }
  };

cN2Prov::cN2Prov(int Id, int Flags)
{
  keyValid=false; id=Id; flags=Flags;
}

void cN2Prov::ExpandInput(unsigned char *hw)
{
  hw[0]^=(0xDE +(0xDE<<1)) & 0xFF;
  hw[1]^=(hw[0]+(0xDE<<1)) & 0xFF;
  for(int i=2; i<128; i++) hw[i]^=hw[i-2]+hw[i-1];
  IdeaKS ks;
  idea.SetEncKey((unsigned char *)"NagraVision S.A.",&ks);
  unsigned char buf[8];
  memset(buf,0,8);
  for(int i=0; i<128; i+=8) {
    xxor(buf,8,buf,&hw[i]);
    idea.Encrypt(buf,8,buf,&ks,0);
    xxor(buf,8,buf,&hw[i]);
    memcpy(&hw[i],buf,8);
    }
}

bool cN2Prov::MECM(unsigned char in15, int algo, unsigned char *cw)
{
  unsigned char hd[5], hw[128+64], buf[20];
  hd[0]=in15&0x7F;
  hd[1]=cw[14];
  hd[2]=cw[15];
  hd[3]=cw[6];
  hd[4]=cw[7];

  if(keyValid && !memcmp(seed,hd,5)) {	// key cached
    memcpy(buf,cwkey,8);
    }
  else {				// key not cached
    memset(hw,0,sizeof(hw));
    if (!Algo(algo,hd,hw)) return false;
    memcpy(&hw[128],hw,64);
    RotateBytes(&hw[64],128);
    SHA1(&hw[64],128,buf);
    RotateBytes(buf,20);

    memcpy(seed,hd,5);
    memcpy(cwkey,buf,8);
    keyValid=true;
    }  

  memcpy(&buf[8],buf,8);
  IdeaKS ks;
  idea.SetEncKey(buf,&ks);
  memcpy(&buf[0],&cw[8],6);
  memcpy(&buf[6],&cw[0],6);
  idea.Encrypt(&buf[4],8,&buf[4],&ks,0);
  idea.Encrypt(buf,8,buf,&ks,0);

  memcpy(&cw[ 0],&buf[6],3);
  memcpy(&cw[ 4],&buf[9],3);
  memcpy(&cw[ 8],&buf[0],3);
  memcpy(&cw[12],&buf[3],3);
  for(int i=0; i<16; i+=4) cw[i+3]=cw[i]+cw[i+1]+cw[i+2];
  return true;
}

void cN2Prov::SwapCW(unsigned char *cw)
{
  if(NeedsCwSwap()) {
    unsigned char tt[8];
    memcpy(&tt[0],&cw[0],8);
    memcpy(&cw[0],&cw[8],8);
    memcpy(&cw[8],&tt[0],8);
    }
}

// -- cN2ProvLink & cN2Providers -----------------------------------------------

#define N2FLAG_NONE 0
#define N2FLAG_MECM 1
#define N2FLAG_B1   2
#define N2FLAG_INV  128

class cN2Providers;

class cN2ProvLink {
friend class cN2Providers;
private:
  cN2ProvLink *next;
protected:
  int id, flags;
  //
  virtual cN2Prov *Create(void)=0;
  bool CanHandle(int Id) { return ((Id^id)&~0x107)==0; }
  bool HasFlags(int Flags) { return (flags&Flags)==Flags; }
public:
  cN2ProvLink(int Id, int Flags);
  virtual ~cN2ProvLink() {}
  };

class cN2Providers {
friend class cN2ProvLink;
private:
  static cN2ProvLink *first;
  //
  static void Register(cN2ProvLink *plink);
public:
  static cN2Prov *GetProv(int Id, int Flags);
  };

template<class PROV, int ID, int FLAGS> class cN2ProvLinkReg : public cN2ProvLink {
public:
  cN2ProvLinkReg(void):cN2ProvLink(ID,FLAGS) {}
  virtual cN2Prov *Create(void) { return new PROV(id,flags); }
  };

cN2ProvLink *cN2Providers::first=0;

void cN2Providers::Register(cN2ProvLink *plink)
{
  dyn(printf("n2providers: registering prov %04X with flags %d\n",plink->id,plink->flags))
  plink->next=first;
  first=plink;
}

cN2Prov *cN2Providers::GetProv(int Id, int Flags)
{
  cN2ProvLink *pl=first;
  while(pl) {
    if(pl->CanHandle(Id) && pl->HasFlags(Flags)) return pl->Create();
    pl=pl->next;
    }
  return 0;
}

cN2ProvLink::cN2ProvLink(int Id, int Flags)
{
  id=Id; flags=Flags;
  cN2Providers::Register(this);
}

#include "nagra2-prov.cpp"
//#include "nagra2-0101.cpp"

#ifndef TESTER

// -- cNagra2 ------------------------------------------------------------------

class cNagra2 : public cNagra {
private:
  bool Signature(const unsigned char *vkey, const unsigned char *sig, const unsigned char *msg, int len);
protected:
  cIDEA idea;
  //
  virtual void CreatePQ(const unsigned char *key, BIGNUM *p, BIGNUM *q);
  bool DecryptECM(const unsigned char *in, unsigned char *out, const unsigned char *key, int len, const unsigned char *vkey, BIGNUM *m);
  bool DecryptEMM(const unsigned char *in, unsigned char *out, const unsigned char *key, int len, const unsigned char *vkey, BIGNUM *m);
  };

void cNagra2::CreatePQ(const unsigned char *key, BIGNUM *p, BIGNUM *q)
{
  // Calculate P and Q from PK
  IdeaKS ks;
  idea.SetEncKey(key,&ks);
  // expand IDEA-G key
  unsigned char idata[96];
  for(int i=11; i>=0; i--) {
    const int off=i*8;
    memcpy(&idata[off],&key[13],8);
    idata[off]^=i;
    idea.Decrypt(&idata[off],8,&ks,0);
    for(int j=7; j>=0; j--) idata[off+j] ^= key[13+j];
    idata[off]^=i;
    }
  // Calculate P
  idata[0] |= 0x80;
  idata[47] |= 1;
  BN_bin2bn(idata,48,p);
  BN_add_word(p,(key[21] << 5 ) | ((key[22] & 0xf0) >> 3));
  // Calculate Q
  idata[48] |= 0x80;
  idata[95] |= 1;
  BN_bin2bn(idata+48,48,q);
  BN_add_word(q,(key[22] &0xf << 9 ) | (key[23]<<1));
}

bool cNagra2::Signature(const unsigned char *vkey, const unsigned char *sig, const unsigned char *msg, int len)
{
  unsigned char buff[16];
  memcpy(buff,vkey,sizeof(buff));
  for(int i=0; i<len; i+=8) {
    IdeaKS ks;
    idea.SetEncKey(buff,&ks);
    memcpy(buff,buff+8,8);
    idea.Encrypt(msg+i,8,buff+8,&ks,0);
    for(int j=7; j>=0; j--) buff[j+8]^=msg[i+j];
    }
  buff[8]&=0x7F;
  return (memcmp(sig,buff+8,8)==0);
}

bool cNagra2::DecryptECM(const unsigned char *in, unsigned char *out, const unsigned char *key, int len, const unsigned char *vkey, BIGNUM *m)
{
  int sign=in[0] & 0x80;
  if(rsa.RSA(out,in+1,64,pubExp,m)<=0) {
    d(printf("nagra2: first RSA failed (ECM)\n"))
    return false;
    }
  out[63]|=sign; // sign adjustment
  if(len>64) memcpy(out+64,in+65,len-64);

  if(in[0]&0x04) {
    unsigned char tmp[8];
    DES_key_schedule ks1, ks2; 
    RotateBytes(tmp,&key[0],8);
    DES_KEY_SCHED((DES_cblock *)tmp,ks1);
    RotateBytes(tmp,&key[8],8);
    DES_KEY_SCHED((DES_cblock *)tmp,ks2);
    memset(tmp,0,sizeof(tmp));
    for(int i=7; i>=0; i--) RotateBytes(out+8*i,8);
    DES_EDE2_CBC_ENCRYPT(out,out,len,ks1,ks2,(DES_cblock *)tmp,DES_DECRYPT);
    for(int i=7; i>=0; i--) RotateBytes(out+8*i,8);
    } 
  else idea.Decrypt(out,len,key,0); 

  RotateBytes(out,64);
  if(rsa.RSA(out,out,64,pubExp,m,false)<=0) {
    //d(printf("nagra2: second RSA failed (ECM)\n"))
    return false;
    }
  if(vkey && !Signature(vkey,out,out+8,len-8)) {
    //d(printf("nagra2: signature failed (ECM)\n"))
    return false;
    }
  return true;
}

bool cNagra2::DecryptEMM(const unsigned char *in, unsigned char *out, const unsigned char *key, int len, const unsigned char *vkey, BIGNUM *m)
{
  int sign=in[0]&0x80;
  if(rsa.RSA(out,in+1,96,pubExp,m)<=0) {
    dl(printf("nagra2: first RSA failed (EMM)\n"))
    return false;
    }
  out[95]|=sign; // sign adjustment
  cBN exp;
  if(in[0]&0x08) {
    // standard IDEA decrypt
    if(len>96) memcpy(out+96,in+97,len-96);
    idea.Decrypt(out,len,key,0);
    BN_set_word(exp,3);
    }
  else {
    // private RSA key expansion
    CreateRSAPair(key,0,exp,m);
    }
  RotateBytes(out,96);
  if(rsa.RSA(out,out,96,exp,m,false)<=0) {
    dl(printf("nagra2: second RSA failed (EMM)\n"))
    return false;
    }
  if(vkey && !Signature(vkey,out,out+8,len-8)) {
    dl(printf("nagra2: signature failed (EMM)\n"))
    return false;
    }
  return true;
}

// -- cSystemNagra2 ------------------------------------------------------------

class cSystemNagra2 : public cSystem, protected cNagra2 {
private:
  int lastEcmId, lastEmmId;
  cN2Prov *ecmP, *emmP;
public:
  cSystemNagra2(void);
  ~cSystemNagra2();
  virtual bool ProcessECM(const cEcmInfo *ecm, unsigned char *data);
  virtual void ProcessEMM(int pid, int caid, unsigned char *buffer);
  };

cSystemNagra2::cSystemNagra2(void)
:cSystem(SYSTEM_NAME,SYSTEM_PRI)
{
  hasLogger=true;
  lastEcmId=lastEmmId=0; ecmP=emmP=0;
}

cSystemNagra2::~cSystemNagra2()
{
  delete ecmP;
  delete emmP;
}

bool cSystemNagra2::ProcessECM(const cEcmInfo *ecm, unsigned char *data)
{
  int cmdLen=data[4]-5;
  int id=(data[5]*256)+data[6];
  cTimeMs minTime;

  int provider=id;
  if (id >= 0x0101 && id <= 0x010F) {
	provider = 0x0101;
  } else if (id >= 0x0901 && id <= 0x090F) {
	provider = 0x0901;
  } else {
	provider = id;
  }

  if(cmdLen<64 || SCT_LEN(data)<cmdLen+10) {
    if(doLog) d(printf("system-nagra2: bad ECM message msgLen=%d sctLen=%d\n",cmdLen,SCT_LEN(data)))
    return false;
    }

  int keyNr=(data[7]&0x10)>>4;
//  cKeySnoop ks(this,'N',id,keyNr);
  cKeySnoop ks(this,'N',provider,keyNr);
  cPlainKey *pk;
  cBN m1;
  unsigned char ideaKey[16], vKey[16];
  bool hasVerifyKey=false;
//  if(!(pk=keys.FindKey('N',id,MBC('M','1'),-1)))  {
  if(!(pk=keys.FindKey('N',provider,MBC('M','1'),-1,0)))  {
    if(doLog) d(printf("system-nagra2: missing %04x M1 key\n",id))
    return false;
    }
  pk->Get(m1);
//  if((pk=keys.FindKey('N',id,'V',sizeof(vKey)))) {
  if((pk=keys.FindKey('N',provider,'V',sizeof(vKey),0))) {
    pk->Get(vKey);
    hasVerifyKey=true;
    }
//  else if(doLog && id!=lastEcmId) d(printf("system-nagra2: missing %04x V key (non-fatal)\n",id))
  else if(doLog && provider!=lastEcmId) d(printf("system-nagra2: missing %04x V key (non-fatal)\n",id))
  if(!(pk=keys.FindKey('N',id,keyNr,sizeof(ideaKey)))) return false;
  pk->Get(ideaKey);

  unsigned char buff[256];
  if(!DecryptECM(data+9,buff,ideaKey,cmdLen,hasVerifyKey?vKey:0,m1)) {
    //if(doLog) d(printf("system-nagra2: decrypt of ECM failed (%04x)\n",id))
    return false;
    }

  if((!ecmP && id!=lastEcmId) || (ecmP && !ecmP->CanHandle(provider))) {
    delete ecmP;
    ecmP=cN2Providers::GetProv(provider,N2FLAG_NONE);
    if(ecmP) d(printf("\nsystem-nagra2: provider %04x capabilities%s%s%s\n",id,ecmP->HasFlags(N2FLAG_MECM)?" MECM":"",ecmP->HasFlags(N2FLAG_B1)  ?" B1":"",ecmP->HasFlags(N2FLAG_INV) ?" INVCW":""))
    }
  lastEcmId=id;
  lastEcmId=provider;

#ifdef DEBUG
  bool contFail=false;
#endif
  int l=0, mecmAlgo=0;
  for(int i=16; i<cmdLen-10 && l!=3; ) {
    switch(buff[i]) {
      case 0x10:
      case 0x11:
        if(buff[i+1]==0x09) {
          int s=(~buff[i])&1;
//		  if(provider==0x0501) s^=1; //PW
//		  if(provider==0x1101) s^=1; //KD 23,5 Kabel
//		  if(provider==0x1102) s^=1; //KD ISH
          mecmAlgo=buff[i+2]&0x60;
          memcpy(cw+(s<<3),&buff[i+3],8);
          i+=11; l|=(s+1);
          }
        else {
          d(printf("system-nagra2: bad length %d in CW nano %02x\n",buff[i+1],buff[i]))
          i++;
          }
        break;
      case 0x00:
        i+=2; break;
	  case 0x13: case 0x14: case 0x15: case 0x16:
        i+=4; break;
      case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36:
      case 0xB0:
        i+=buff[i+1]+2;
        break;
      default:
#ifdef DEBUG
        if(!contFail) printf("system-nagra2: unknown ECM nano");
        printf(" %02x",buff[i]);
        contFail=true;
#endif
        i++;
        continue;
      }
#ifdef DEBUG
    if(contFail) { printf("\n"); contFail=false; }
#endif
    }
#ifdef DEBUG
  if(contFail) printf("\n");
#endif
  if(l!=3) return false;
  if(mecmAlgo>0) {
    if(ecmP && ecmP->HasFlags(N2FLAG_MECM)) {
      if(!ecmP->MECM(buff[15],mecmAlgo,cw)) return false;
      }
    else { d(printf("system-nagra2: MECM for provider %04x not supported\n",id)) return false; }
    }
  if(ecmP) ecmP->SwapCW(cw);
  ks.OK(pk);

  int i=minEcmTime-minTime.Elapsed();
  if(i>0) cCondWait::SleepMs(i);
  return true;
}

void cSystemNagra2::ProcessEMM(int pid, int caid, unsigned char *buffer)
{
	int cmdLen=buffer[9]-5;
	int id=buffer[10]*256+buffer[11];
	char txt[150];

	if(cmdLen<96 || SCT_LEN(buffer)<cmdLen+15) {
		sprintf(txt,"Bad EMM-G Packet (bad EMM message msgLen=%d sctLen=%d)\n",cmdLen,SCT_LEN(buffer));
		m_DLLInstance.PushMessage(MSG_EMM_DATA,txt);
		dl(printf("logger-nagra2: bad EMM message msgLen=%d sctLen=%d\n",cmdLen,SCT_LEN(buffer)))
		return;
    }

	int checkid = 0;
	int keyset=(buffer[12]&0x03);
	int sel=(buffer[12]&0x10)<<2;
	int sigsel=(buffer[13]&0x80)>>1;
	char time_str[20];
	_strtime(time_str);
	char text[100];
	char filename[_MAX_PATH];
	char EMM_filename[_MAX_PATH];
	int CamID = 0;
	int LogType = 0;

	int provider=id;
	if (id >= 0x0101 && id <= 0x010F) {
		provider = 0x0101;
	} else if (id >= 0x0901 && id <= 0x090F) {
		provider = 0x0901;
	} else {
		provider = id;
	}

	int keyPTR = N2_MAGIC;
	if (buffer[0] == 0x83) {
  		keyPTR = 0;
		for (int it=5; it>2; it--) {
			CamID = MBC(CamID,buffer[it]);
		}
  		keyPTR=CID(N2_EMM_S,buffer[5],buffer[4],buffer[3]);

	  LogType = m_DLLInstance.m_log_emm_by;
		switch(LogType) {
			case 0:			//0=no logging
				sprintf(EMM_filename,"");
				break;
			case 1:			//1=single file
				sprintf(EMM_filename,"EMM-S.log");
				break;
			case 2:			//2= two files (group / cam spec.)
				if (buffer[6] == 0x00) {
					sprintf(EMM_filename,"EMM-S_By_Group.log");
				} else {
					sprintf(EMM_filename,"EMM-S_By_Target.log");
				}
				break;
			case 3:			//3=by camid
			default:
				sprintf(EMM_filename,"CamID_%02X%02X%02X%02X.log",buffer[5],buffer[4],buffer[3],buffer[6]);
				break;
		}
	}

	cPlainKey *pk;
	cBN n;
	unsigned char ideaKey[24], vKey[16];
	bool hasVerifyKey=false;
	if(!(pk=keys.FindKey('N',provider,MBC(keyPTR,keyset+0x10+sel),96,0)))  {
		if (buffer[0] == 0x83) {
			sprintf(text,"[%s] EMM-S {%06X%02X} Missing: %04x S %.06X %.02X  RSA key",time_str,CamID,buffer[6],id,CamID,keyset+0x10+sel);
			m_DLLInstance.PushMessage(MSG_MESSAGE,text);
			sprintf(txt,"Your missing: %04x S %.06X %.02X  RSA key (96 bytes)",id,CamID,keyset+0x10+sel);
		} else {
			sprintf(txt,"Your missing: %04x NN %.02X  RSA key (96 bytes)",id,keyset+0x10+sel);
		}
		m_DLLInstance.PushMessage(MSG_EMM_DATA,txt);
		dl(printf("logger-nagra2: missing %04x NN %.02X RSA key (96 bytes)\n",id,keyset+0x10+sel))
		return;
    }
	pk->Get(n);

	if (buffer[0] != 0x83) {
	    if((pk=keys.FindKey('N',provider,MBC(keyPTR,0x03+sigsel),sizeof(vKey)))) {
			pk->Get(vKey);
			hasVerifyKey=true;
		}
		else if(id!=lastEmmId) {
			sprintf(txt,"Your missing: %04x NN %.02X signature key (non-fatal)\n",id,0x03+sigsel);
			m_DLLInstance.PushMessage(MSG_EMM_DATA,txt);
			dl(printf("system-nagra2: missing %04x NN %.02X signature key (non-fatal)\n",id,0x03+sigsel));
		}
	}

	if(!(pk=keys.FindKey('N',provider,MBC(keyPTR,keyset),24,0))) {
	    if(!(pk=keys.FindKey('N',provider,MBC(keyPTR,keyset+sel),16))) {
			if (buffer[0] == 0x83) {
				sprintf(text,"[%s] EMM-S {%06X%02X} Missing: %04x S %.06X %.02X  IDEA key",time_str,CamID,buffer[6],id,CamID,keyset);
				m_DLLInstance.PushMessage(MSG_MESSAGE,text);
  				sprintf(txt,"Your missing: %04x S %.06X %.02X  IDEA key (24 bytes)",id,CamID,keyset);
			} else {
 				sprintf(txt,"Your missing: %04x NN %.02x IDEA key (24 or 16 bytes)\n",id,keyset);
			}
			m_DLLInstance.PushMessage(MSG_EMM_DATA,txt);
			dl(printf("logger-nagra2: missing %04x NN %.02x IDEA key (24 or 16 bytes)\n",id,keyset));
			return;
		}
		memset(ideaKey+16,0,8);
	}
	pk->Get(ideaKey);

	unsigned char emmdata[256];
	if(!DecryptEMM(buffer+14,emmdata,ideaKey,cmdLen,hasVerifyKey?vKey:0,n)) {
		sprintf(txt,"[%s] nagra2: decrypt of EMM    failed (%04x)", time_str, id);
		dl(printf("logger-nagra2: decrypt of EMM failed (%04x)\n",id))
		if (buffer[0] == 0x82 && m_DLLInstance.m_hide_bad_data == false) {
			m_DLLInstance.PushMessage(MSG_EMM_STATUS,txt);
			m_DLLInstance.PushMessage(MSG_EMM_DATA,"Bad EMM-G Packet (Idea Hash failed! -> RSA decryption failed!)");
		} else if (buffer[0] == 0x83 && m_DLLInstance.m_hide_bad_data == false) {
			m_DLLInstance.PushMessage(MSG_EMM_STATUS,txt);
			m_DLLInstance.PushMessage(MSG_EMM_DATA,"Bad EMM-S Packet (Idea Hash failed! -> RSA decryption failed!)");
			sprintf(text,"[%s] EMM-S {%06X%02X} (Idea Hash failed! -> RSA decryption failed!)",time_str,CamID,buffer[6]);
			m_DLLInstance.PushMessage(MSG_MESSAGE,text);
		} 
		return;
	} else {
		if (buffer[0] == 0x82) {
			sprintf(txt,"[%s] nagra2: decrypt of EMM Succeeded (%04x)", time_str, id);
			dl(printf("%s\n", txt));
			m_DLLInstance.PushMessage(MSG_EMM_STATUS,txt);
			m_DLLInstance.WriteEMM(emmdata);
			char *str2 = new char [51];	// 4AFFB6B0C6C52F7C0801144F8C9F18717787E3
			sprintf(&str2[0],"%02X ",emmdata[18]);
			for(int it=1; it<20; it++) sprintf(&str2[(it*2)+1],"%02X",emmdata[it+18]);
			sprintf(txt,"[%s] EMM-G %.39s (%04X)\n",time_str,str2,id);
			m_DLLInstance.PushMessage(MSG_MESSAGE,txt);
			if (m_DLLInstance.m_log_emm_g == true) {
				sprintf(text," EMM-G ");
				for(int it=0 ; it<15 ; it++) sprintf(&text[(it*2)+7],"%02X",buffer[it]);
				sprintf(filename,"EMM-G.log");
				m_DLLInstance.logCamID(filename,emmdata,96,false,text);
			}
		} else if (buffer[0] == 0x83 && 
				emmdata[19] == buffer[5] && 
				emmdata[20] == buffer[4] &&
				emmdata[21] == buffer[3]) {
			sprintf(txt,"[%s] nagra2: decrypt of EMM Succeeded (%04x)", time_str, id);
			dl(printf("%s\n", txt));
			m_DLLInstance.PushMessage(MSG_EMM_STATUS,txt);
			m_DLLInstance.WriteEMM(emmdata);
			sprintf(txt,"[%s] EMM-S {%06X%02X} %02X   # # # Decrypted # # #   (%04X)\n",time_str,CamID,buffer[6],emmdata[18],id);
			m_DLLInstance.PushMessage(MSG_MESSAGE,txt);
			if (LogType > 0) {		//0=no logging
				sprintf(text," Decrypted: ");
				m_DLLInstance.logCamID(EMM_filename,emmdata,96,false,text);
			}
		} else if (buffer[0] == 0x83 && m_DLLInstance.m_hide_bad_data == false) {
			m_DLLInstance.WriteEMM(emmdata);
			sprintf(txt,"[%s] EMM-S {%06X%02X}     ** Bad Decrypted Data ** (%04X)\n",time_str,CamID,buffer[6],id);
			m_DLLInstance.PushMessage(MSG_MESSAGE,txt);
			return;
		} else if (buffer[0] == 0x83 && m_DLLInstance.m_hide_bad_data == true) {
			return;
		}

    }
	lastEmmId=id;

	if (buffer[0] == 0x82) {
		id=(emmdata[8]<<8)+emmdata[9];
	}
#ifdef DEBUG_LOG
	bool contFail=false;
#endif
	for(int i=8+2+4+4; i<cmdLen-22; ) {
#ifdef DEBUG_LOG
		printf("{%02X}",emmdata[i]);
#endif
		switch(emmdata[i]) {
		//End of processing
	      case 0x00: 
		    i=cmdLen;
			break;

		//Write bb at dd
		  case 0x01:
	        i+=3;	//0x03
		    break;

		//spending limit tier create
		  case 0x10:
	        i+=18;	//0x12
		    break;

		//?????????????
		  case 0x12:	//tier update/create
		  case 0x13:
		  case 0x14:
	      case 0x15:
//Skip these nano's for now
	        i=cmdLen;
		    break;

		//date & time update
		  case 0x20:
	        i+=34;	//0x22
		    break;

		//Plain Key update
		  case 0x42:
			if(emmdata[i+2]==0x10 && (emmdata[i+3]&0xBF)==0x06 &&
	           (emmdata[i+4]&0xF8)==0x08 && emmdata[i+5]==0x00 && emmdata[i+6]==0x10) {
		      cSoftCAM::FoundKey();
			  if(keys.NewKey('N',id,(emmdata[i+3]&0x40)>>6,&emmdata[i+7],16)) {
			  if(g_BeepOnNewKey)
			  {
				Beep(300,500);
				Sleep(50);
				Beep(300,500);
			  }
				cSoftCAM::NewKey();
				if (m_DLLInstance.m_use_ca_cache) {
				  cSoftCAM::Save();
				} else {
				  m_DLLInstance.SaveKey('N',id,(emmdata[i+3]&0x40)>>6,&emmdata[i+7],32);
				}
			  } else {
			    for(int it=7 ; it<23 ; it++) sprintf(&text[(it-7)*2],"%02X",emmdata[i+it]);
				sprintf(txt,"[%s] Key %02X: %s  (%04X)\n",time_str,(emmdata[i+3]&0x40)>>6,text,id);
			    m_DLLInstance.PushMessage(MSG_MESSAGE,txt);
			  }
			  if (m_DLLInstance.m_StopEmm_OnChange){
				m_DLLInstance.StopEMMFilter();
				m_DLLInstance.ClearAllData();
			  }
			}
	        i+=(emmdata[i+6]+7);
		    break;

		//Change data prov. id
		  case 0x83:
	        id=(emmdata[i+1]<<8)|emmdata[i+2];
		    i+=3;
			break;

		//?????????????
		  case 0x85:
//Skip this nano for now
	        i=cmdLen;
		    break;

		//CamID target update
		  case 0xA0:
	        i+=5;
		    break;

		//?????????????
		  case 0xA2:
//Skip this nano for now
	        i=cmdLen;
		    break;

		//Cam group target update
		  case 0xA3:
	        i+=4;
		    break;

		//conditional (always no match assumed for now)
		  case 0xA4: 
//Skip this nano for now
	        i=cmdLen;
		    break;

		//generally updates tier/blackout
		  case 0xA6:
			if (emmdata[i+4] == 0xFD && emmdata[i+5] == 0x98) {
			  i+=(emmdata[i+1]+emmdata[i+3]+4);
			} else {
			  i=cmdLen;
			}
		    break;

		//?????????????
		  case 0xAA:
		  case 0xAE:
//Skip these nano's for now
	        i=cmdLen;
		    break;

		//Update with ROM101 CPU code
		  case 0xB0: 
//Skip this nano for now
	        i=cmdLen;
		    break;

		//ROM102 Morph Coding
		  case 0xB1:
		    {
	        if(!emmP || !emmP->CanHandle(id)) {
		      delete emmP;
			  emmP=cN2Providers::GetProv(id,2);
	          if(!emmP) {
		        dl(printf("system-nagra2: B1 for provider %04x not supported\n",id))
			    i=cmdLen;
	            break;
			  }
			}
		  //if (buffer[0] == 0x83) return;
		  int B1Start = i;
		  int dataLen2 = 103;
		  char *str2 = new char [dataLen2*2+17];
		  for(int it=0 ; it<15 ; it++) sprintf(&str2[it*2],"%02X",buffer[it]);
		  d(printf("\n[%s] decrypted: %s", time_str, str2));
		  for(int it=0 ; it<dataLen2 ; it++) sprintf(&str2[it*2],"%02X",emmdata[it]);
		  d(printf(" %s\n\n", str2));

		  int rValue;
	      rValue = emmP->ProcessB1(emmdata,cmdLen,i+1);
		  if (rValue > 3) {	// 0 ??
		    i += rValue;
		  } else {
		    dl(printf("system-nagra2: B1 executing failed for %04x\n",id))
			i=cmdLen;
          }
          break; 
        } 

		//DN key update
		  case 0xE0: 
	        if(emmdata[i+1]==0x25) {
				cSoftCAM::FoundKey();
				if(keys.NewKey('N',id,(emmdata[i+16]&0x40)>>6,&emmdata[i+23],16)) cSoftCAM::NewKey();
				cSoftCAM::Save();
				i+=39;
			} else {
		        i=cmdLen;
			}
	        break;

		//Eeprom update
		  case 0xE3: 
#if 0
	  	    if(g_LogE3)
		    {
			  FILE *fh;
			  if(fopen_s(&fh, "e3.log", "a") == 0) {
				  printf("system-nagra2: Saving E3\n");
				  HexDump(emmdata, cmdLen);
	          
				  for(int ii = 0; ii < cmdLen; ii++) {
				    if(ii % 16 == 0) {
					  fprintf(fh, "\n%04x:", ii);
				    }
				    fprintf(fh, " %02x", emmdata[ii]);
				  }
				  fprintf(fh,"\n");
				  fclose(fh);
			  } else {
				  printf("system-nagra2: Unable to save E3\n");
			  }
		    }
#endif
	        i=cmdLen;
		    break; 

		//Unknown EMM cmd's
		  default:
#ifdef DEBUG_LOG
	        if(!contFail) printf("logger-nagra2: unknown EMM nano");
		    printf(" %02x",emmdata[i]);
			contFail=true;
#endif
//Skip all other nano's for now
	        i=cmdLen;
			continue;
      }
#ifdef DEBUG_LOG
    if(contFail) { printf("\n"); contFail=false; }
#endif
    }
#ifdef DEBUG_LOG
  if(contFail) printf("\n");
#endif
}

// -- cSystemLinkNagra2 --------------------------------------------------------
/*
static const tI18nPhrase Phrases2[] = {
  { "Nagra2: AUXserver hostname",
    "Nagra2: AUXserver Hostname",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
  },
  { "Nagra2: AUXserver port",
    "Nagra2: AUXserver Port",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
  },
  { "Nagra2: AUXserver password",
    "Nagra2: AUXserver Passwort",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
  },
  { NULL }
  };
*/

class cSystemLinkNagra2 : public cSystemLink {
public:
  cSystemLinkNagra2(void);
  virtual bool CanHandle(unsigned short SysId);
  virtual cSystem *Create(void) { return new cSystemNagra2; }
  };

static cSystemLinkNagra2 staticInitN2;

cSystemLinkNagra2::cSystemLinkNagra2(void)
:cSystemLink(SYSTEM_NAME,SYSTEM_PRI)
{
#ifdef HAS_AUXSRV
  static const char allowed_chars[] = "0123456789abcdefghijklmnopqrstuvwxyz-.";
  opts=new cOpts(SYSTEM_NAME,3);
  opts->Add(new cOptStr("AuxServerAddr","Nagra2: AUXserver hostname",auxAddr,sizeof(auxAddr),allowed_chars));
  opts->Add(new cOptInt("AuxServerPort","Nagra2: AUXserver port",&auxPort,0,65535));
  opts->Add(new cOptStr("AuxServerPass","Nagra2: AUXserver password",auxPassword,sizeof(auxPassword),allowed_chars));
//  Feature.AddPhrases(Phrases2);
#endif
  Feature.NeedsKeyFile();
}

bool cSystemLinkNagra2::CanHandle(unsigned short SysId)
{
  return ((SysId&SYSTEM_MASK)==SYSTEM_NAGRA && (SysId&0xFF)>0) ||
          SysId==SYSTEM_NAGRA_BEV;
}

#endif //TESTER
