/*
* 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
*/

#if defined(NAGRA) && defined(OPENSSL)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
#ifndef TESTER
#include <openssl/bn.h>
#endif

#include <vdr/tools.h>
//#include <vdr/thread.h>

#include "common.h"
#ifndef TESTER
//#include "sc.h"
#include "system.h"
#include "system-common.h"
#include "data.h"
#include "misc.h"
#include "opts.h"
#include "crypto.h"
#include "..\\main.h"
//#include "vdr-compat.h"
#endif

#define SYSTEM_NAGRA         0x1800
#define SYSTEM_NAGRA_BEV     0x1234

#define SYSTEM_NAME          "Nagra"
#define SYSTEM_PRI           -10
#define SYSTEM_CAN_HANDLE(x) ((x)==SYSTEM_NAGRA || (x)==SYSTEM_NAGRA_BEV)

#define FILEMAP_DOMAIN       "nagra"

//#define DEBUG_EMU      // debug CardEmu (very verbose!)#define DEBUG_EMU_0x80 // if the above is enabled, limit output to range x080-0xc0
//#define DEBUG_STAT     // give some statistics on CardEmu
//#define DEBUG_MAP      // debug file mapping code
//#define DEBUG_NAGRA    // debug Nagra crypt code
//#define DEBUG_EMU_0x80

#ifdef DEBUG_EMU
#ifdef DEBUG_EMU_0x80
static bool pc80flag=false;
#define de(x) { if(pc80flag) { (x); } }
#else
#define de(x) { (x); }
#endif
#else
#define de(x) ;
#endif

#ifdef DEBUG_NAGRA
#define dn(a) { a; }
#else
#define dn(a) ;
#endif

// -- cMap ---------------------------------------------------------------------

class cMap {
public:
	cMap(void) {};
	virtual ~cMap() {};
	virtual unsigned char Get(unsigned short ea)=0;
	virtual void Set(unsigned short ea, unsigned char val)=0;
	virtual bool IsFine(void)=0;
};

// -- cMapMem ------------------------------------------------------------------

class cMapMem : public cMap {
private:
	unsigned short offset;
	unsigned char *mem;
	int size;
public:
	cMapMem(unsigned short Offset, int Size);
	virtual ~cMapMem();
	virtual unsigned char Get(unsigned short ea);
	virtual void Set(unsigned short ea, unsigned char val);
	virtual bool IsFine(void);
};

cMapMem::cMapMem(unsigned short Offset, int Size)
{
	offset=Offset; size=Size;
	if((mem=(unsigned char *)malloc(size)))
		memset(mem,0,size);
	dn(m_DLLInstance.Log("mapmem: new map off=%04x size=%04x\n",offset,size))
}

cMapMem::~cMapMem()
{
	free(mem);
}

bool cMapMem::IsFine(void)
{
	return (mem!=0);
}

unsigned char cMapMem::Get(unsigned short ea)
{
	return (ea>=offset && ea<offset+size) ? mem[ea-offset] : 0;
}

void cMapMem::Set(unsigned short ea, unsigned char val)
{
	if(ea>=offset && ea<offset+size)
		mem[ea-offset]=val;
}

// -- cMapRom ------------------------------------------------------------------

class cMapRom : public cMap {
private:
	unsigned short offset;
	cFileMap *fm;
	unsigned char *addr;
	int size;
public:
	cMapRom(unsigned short Offset, const char *Filename);
	virtual ~cMapRom();
	virtual unsigned char Get(unsigned short ea);
	virtual void Set(unsigned short ea, unsigned char val);
	virtual bool IsFine(void);
};

cMapRom::cMapRom(unsigned short Offset, const char *Filename)
{
	offset=Offset; addr=0;
	fm=filemaps.GetFileMap(Filename,FILEMAP_DOMAIN,false);
	if(fm && fm->Map()) {
		addr=fm->Addr();
		size=fm->Size();
		dn(m_DLLInstance.Log("maprom: new map off=%04x size=%04x\n",offset,size))
    }
}

cMapRom::~cMapRom()
{
	if(fm) fm->Unmap();
}

bool cMapRom::IsFine(void)
{
	return (addr!=0);
}

unsigned char cMapRom::Get(unsigned short ea)
{
	return (ea>=offset && ea<offset+size) ? addr[ea-offset] : 0;
}

void cMapRom::Set(unsigned short ea, unsigned char val)
{
	if(ea>=offset && ea<offset+size) de(m_DLLInstance.Log("[ROM] \n"))
		// this is a ROM!
}

// -- cMapEeprom ---------------------------------------------------------------

class cMapEeprom : public cMap {
private:
	unsigned short offset;
	cFileMap *fm;
	unsigned char *addr;
	int size, otpSize;
public:
	cMapEeprom(unsigned short Offset, const char *Filename, int OtpSize);
	virtual ~cMapEeprom();
	virtual unsigned char Get(unsigned short ea);
	virtual void Set(unsigned short ea, unsigned char val);
	virtual bool IsFine(void);
};

cMapEeprom::cMapEeprom(unsigned short Offset, const char *Filename, int OtpSize)
{
	offset=Offset; otpSize=OtpSize; addr=0;
	fm=filemaps.GetFileMap(Filename,FILEMAP_DOMAIN,true);
	if(fm && fm->Map()) {
		addr=fm->Addr();
		size=fm->Size();
		dn(m_DLLInstance.Log("mapeeprom: new map off=%04x size=%04x otp=%04x\n",offset,size,otpSize))
    }
}

cMapEeprom::~cMapEeprom()
{
	if(fm) fm->Unmap();
}

bool cMapEeprom::IsFine(void)
{
	return (addr!=0);
}

unsigned char cMapEeprom::Get(unsigned short ea)
{
	return (ea>=offset && ea<offset+size) ? addr[ea-offset] : 0;
}

void cMapEeprom::Set(unsigned short ea, unsigned char val)
{
	if(ea>=offset && ea<offset+otpSize) {
		if(addr[ea-offset]==0) {
			addr[ea-offset]=val;
			de(m_DLLInstance.Log("[OTP-SET]\n "))
		}
		de(m_DLLInstance.Log("[OTP]\n "))
    }
	if(ea>=offset+otpSize && ea<offset+size) {
		addr[ea-offset]=val;
		de(m_DLLInstance.Log("[EEP] \n"))
    }
}

// -- c6805 --------------------------------------------------------------------

#define MAX_BREAKPOINTS 4
#define MAX_MAPPER      8
#define MEM_SIZE        64*1024

#define bitset(d,bit) (((d)>>(bit))&1)
#define HILO(ea)      ((Get(ea)<<8)+Get(ea+1))

class c6805 {
private:
	unsigned short pc, sp, spHi, spLow;
	unsigned short bp[MAX_BREAKPOINTS], numBp;
	unsigned char mapMap[MEM_SIZE];
	cMap *mapper[MAX_MAPPER];
	int nextMapper;
	bool indirect;
#ifdef DEBUG_STAT
	unsigned int stats[256];
#endif
	//
	void branch(bool branch);
	inline void tst(unsigned char c);
	inline void push(unsigned char c);
	inline unsigned char pop(void);
	void pushpc(void);
	void poppc(void);
	void pushc(void);
	void popc(void);
	unsigned char add(unsigned char op, unsigned char c);
	unsigned char sub(unsigned char op1, unsigned char op2, unsigned char c);
	unsigned char rollR(unsigned char op, unsigned char c);
	unsigned char rollL(unsigned char op, unsigned char c);
protected:
	unsigned char a, x, y;
	struct CC { unsigned char c, z, n, i, h; } cc;
	//
	int Run(int max_count);
	void AddBreakpoint(unsigned short addr);
	void ClearBreakpoints(void);
	bool AddMapper(cMap *map, unsigned short start, int size);
	unsigned char Get(unsigned short ea) const;
	void Set(unsigned short ea, unsigned char val);
	void GetMem(unsigned short addr, unsigned char *data, int len) const;
	void SetMem(unsigned short addr, const unsigned char *data, int len);
	void ForceSet(unsigned short ea, unsigned char val, bool ro);
	void SetSp(unsigned short SpHi, unsigned short SpLow);
	void SetPc(unsigned short addr);
	unsigned short GetPc(void) const { return pc; }
	virtual void Stepper(void)=0;
	public:
		c6805(void);
		virtual ~c6805();
};

c6805::c6805(void) {
	cc.c=0; cc.z=0; cc.n=0; cc.i=0; cc.h=0;
	pc=0; a=0; x=0; y=0; sp=spHi=0x100; spLow=0xC0;
	ClearBreakpoints();
	
	memset(mapMap,0,sizeof(mapMap));
	memset(mapper,0,sizeof(mapper));
	mapper[0]=new cMapMem(0,MEM_SIZE);
	nextMapper=1;
	
#ifdef DEBUG_STAT
	memset(stats,0,sizeof(stats));
#endif
}

c6805::~c6805()
{
#ifdef DEBUG_STAT
	int i, j, sort[256];
	bool done=false;
	for(i=0 ; i<256 ; i++) sort[i]=i;
	for(i=0 ; i<256 && !done ; i++)
		for(j=0 ; j<255 ; j++)
			if(stats[sort[j]]<stats[sort[j+1]]) {
				int x=sort[j];
				sort[j]=sort[j+1];
				sort[j+1]=x;
				done=false;
			}
			m_DLLInstance.Log("6805: opcode statistics\n");
			for(i=0 ; i<256 ; i++)
				if((j=stats[sort[i]])) m_DLLInstance.Log("6805: opcode %02x: %d\n",sort[i],j);
#endif
				for(int i=0; i<MAX_MAPPER; i++) delete mapper[i];
}

bool c6805::AddMapper(cMap *map, unsigned short start, int size)
{
	if(map && map->IsFine()) {
		if(nextMapper<MAX_MAPPER) {
			mapper[nextMapper]=map;
			memset(&mapMap[start],nextMapper,size);
			nextMapper++;
			return true;
		}
		else de(m_DLLInstance.Log("6805: too many mappers\n"))
    }
	else de(m_DLLInstance.Log("6805: mapper not ready\n"))
		delete map;
	return false;
}

unsigned char c6805::Get(unsigned short ea) const
{
	return mapper[mapMap[ea]&0x7f]->Get(ea);
}

void c6805::Set(unsigned short ea, unsigned char val)
{
	unsigned char mapId=mapMap[ea];
	if(!(mapId&0x80)) mapper[mapId&0x7f]->Set(ea,val);
}

void c6805::ForceSet(unsigned short ea, unsigned char val, bool ro)
{
	mapMap[ea]=0;     		// reset to RAM map
	Set(ea,val);      		// set value
	if(ro) mapMap[ea]|=0x80; 	// protect byte
}

void c6805::SetMem(unsigned short addr, const unsigned char *data, int len)
{
	while(len>0) { Set(addr++,*data++); len--; }
}

void c6805::GetMem(unsigned short addr, unsigned char *data, int len) const
{
	while(len>0) { *data++=Get(addr++); len--; }
}

void c6805::SetSp(unsigned short SpHi, unsigned short SpLow)
{
	spHi =sp=SpHi;
	spLow   =SpLow;
}

void c6805::SetPc(unsigned short addr)
{
	pc=addr;
}

void c6805::AddBreakpoint(unsigned short addr)
{
	if(numBp<MAX_BREAKPOINTS) {
		bp[numBp++]=addr;
		de(m_DLLInstance.Log("6805: setting breakpoint at 0x%04x\n",addr))
    }
	else de(m_DLLInstance.Log("6805: too many breakpoints\n"))
}

void c6805::ClearBreakpoints(void)
{
	numBp=0;
	memset(bp,0,sizeof(bp));
}

#ifdef DEBUG_EMU
static const char * const ops[] = {
	//         0x00    0x01    0x02    0x03    0x04    0x05    0x06    0x07    0x08    0x09    0x0a    0x0b    0x0c    0x0d    0x0e    0x0f
	/* 0x00 */ "BRSET","BRCLR","BRSET","BRCLR","BRSET","BRCLR","BRSET","BRCLR","BRSET","BRCLR","BRSET","BRCLR","BRSET","BRCLR","BRSET","BRCLR",
	/* 0x10 */ "BSET", "BCLR", "BSET", "BCLR", "BSET", "BCLR", "BSET", "BCLR", "BSET", "BCLR", "BSET", "BCLR", "BSET", "BCLR", "BSET", "BCLR", 
	/* 0x20 */ "BRA",  "BRN",  "BHI",  "BLS",  "BCC",  "BCS",  "BNE",  "BEQ",  "BHCC", "BHCS", "BPL",  "BMI",  "BMC",  "BMS",  "BIL",  "BIH",
	/* 0x30 */ "NEG",  "op31", "op32", "COM",  "LSR",  "op35", "ROR",  "ASR",  "ASL",  "ROL",  "DEC",  "op3b", "INC",  "TST",  "SWAP", "CLR",  
	/* 0x40 */ "NEG",  "op41", "MUL",  "COM",  "LSR",  "op45", "ROR",  "ASR",  "ASL",  "ROL",  "DEC",  "op4b", "INC",  "TST",  "SWAP", "CLR",  
	/* 0x50 */ "NEG",  "op51", "MUL",  "COM",  "LSR",  "op55", "ROR",  "ASR",  "ASL",  "ROL",  "DEC",  "op5b", "INC",  "TST",  "SWAP", "CLR",  
	/* 0x60 */ "NEG",  "op61", "op62", "COM",  "LSR",  "op65", "ROR",  "ASR",  "ASL",  "ROL",  "DEC",  "op6b", "INC",  "TST",  "SWAP", "CLR",  
	/* 0x70 */ "NEG",  "op71", "op72", "COM",  "LSR",  "op75", "ROR",  "ASR",  "ASL",  "ROL",  "DEC",  "op7b", "INC",  "TST",  "SWAP", "CLR",  
	/* 0x80 */ "RTI",  "RTS",  "op82", "SWI",  "POPA", "POP%c","POPC", "op87", "PUSHA","PUSH%c","PUSHC","op8b","op8c", "op8d", "STOP", "WAIT", 
	/* 0x90 */ "pre90","pre91","pre92","T%2$c%1$c","T%cS","TAS","TS%c","TA%c", "CLC",  "SEC",  "CLI",  "SEI",  "RSP",  "NOP",  "TSA",  "T%cA", 
	/* 0xa0 */ "SUB",  "CMP",  "SBC",  "CP%c", "AND",  "BIT",  "LDA",  "opa7", "EOR",  "ADC",  "ORA",  "ADD",  "opac", "BSR",  "LD%c", "opaf", 
	/* 0xb0 */ "SUB",  "CMP",  "SBC",  "CP%c", "AND",  "BIT",  "LDA",  "STA",  "EOR",  "ADC",  "ORA",  "ADD",  "JMP",  "JSR",  "LD%c", "ST%c", 
	/* 0xc0 */ "SUB",  "CMP",  "SBC",  "CP%c", "AND",  "BIT",  "LDA",  "STA",  "EOR",  "ADC",  "ORA",  "ADD",  "JMP",  "JSR",  "LD%c", "ST%c", 
	/* 0xd0 */ "SUB",  "CMP",  "SBC",  "CP%c", "AND",  "BIT",  "LDA",  "STA",  "EOR",  "ADC",  "ORA",  "ADD",  "JMP",  "JSR",  "LD%c", "ST%c", 
	/* 0xe0 */ "SUB",  "CMP",  "SBC",  "CP%c", "AND",  "BIT",  "LDA",  "STA",  "EOR",  "ADC",  "ORA",  "ADD",  "JMP",  "JSR",  "LD%c", "ST%c", 
	/* 0xf0 */ "SUB",  "CMP",  "SBC",  "CP%c", "AND",  "BIT",  "LDA",  "STA",  "EOR",  "ADC",  "ORA",  "ADD",  "JMP",  "JSR",  "LD%c", "ST%c", 
};
#endif

// Flags:
// 1 - read operant
// 2 - write operant
static const char opFlags[] = {
	//         00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
	/* 0x00 */  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	/* 0x10 */  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
	/* 0x20 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x30 */  3, 0, 0, 3, 3, 0, 3, 3, 3, 3, 3, 0, 3, 1, 3, 2, 
	/* 0x40 */  3, 0, 0, 3, 3, 0, 3, 3, 3, 3, 3, 0, 3, 1, 3, 2, 
	/* 0x50 */  3, 0, 0, 3, 3, 0, 3, 3, 3, 3, 3, 0, 3, 1, 3, 2, 
	/* 0x60 */  3, 0, 0, 3, 3, 0, 3, 3, 3, 3, 3, 0, 3, 1, 3, 2, 
	/* 0x70 */  3, 0, 0, 3, 3, 0, 3, 3, 3, 3, 3, 0, 3, 1, 3, 2, 
	/* 0x80 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x90 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xa0 */  1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0,
	/* 0xb0 */  1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0, 0, 1, 2,
	/* 0xc0 */  1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0, 0, 1, 2,
	/* 0xd0 */  1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0, 0, 1, 2,
	/* 0xe0 */  1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0, 0, 1, 2,
	/* 0xf0 */  1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0, 0, 1, 2,
};

int c6805::Run(int max_count)
{
	// returns:
	// 0 - breakpoint
	// 1 - stack overflow
	// 2 - instruction counter exeeded
	// 3 - unsupported instruction
	
#ifdef DEBUG_EMU_0x80
	pc80flag=false;
#endif
	int count=0;
	de(m_DLLInstance.Log("6805: -pc- aa xx yy -sp- HINZC -mem@pc- -mem@sp-\n"))
		while (1) {
#ifdef DEBUG_EMU_0x80
			{
				bool flag=(pc>=0x80 && pc<0xC0);
				if(pc80flag && !flag) m_DLLInstance.Log("6805: [...]\n");
				pc80flag=flag;
			}
#endif
			de(m_DLLInstance.Log("6805: %04x %02x %02x %02x %04x %c%c%c%c%c %02x%02x%02x%02x %02x%02x%02x%02x \n",
				pc,a,x,y,sp,
				cc.h?'H':'.',cc.i?'I':'.',cc.n?'N':'.',cc.z?'Z':'.',cc.c?'C':'.',
				Get(pc),Get(pc+1),Get(pc+2),Get(pc+3),Get(sp+1),Get(sp+2),Get(sp+3),Get(sp+4)))
				if(sp<spLow) {
					de(m_DLLInstance.Log("stack overflow\n"))
						return 1;
				}
				if(++count>=max_count) {
					de(m_DLLInstance.Log("max. instruction counter exceeded\n"))
						return 2;
				}
				
				Stepper();
				unsigned char *ex=&x;
				indirect=false;
				unsigned char ins=Get(pc++);
#ifdef DEBUG_EMU
				char xs='X';
#endif
				
				// check pre-byte (ST7 extention)
				switch(ins) {
				case 0x91: // use Y register with indirect addr mode
					indirect=true;
					// fall through
				case 0x90: // use Y register
					ex=&y; de(xs='Y')
						ins=Get(pc++);
					break;
				case 0x92: // use indirect addr mode
					indirect=true;
					ins=Get(pc++);
					break;
				}
				
#ifdef DEBUG_EMU
#ifdef DEBUG_EMU_0x80
				if(pc80flag)
#endif
				{
					char str[8];
					snprintf(str,sizeof(str),ops[ins],xs,xs^1);
					m_DLLInstance.Log("%-5s \n",str);
				}
#endif
				
				// address decoding
				unsigned short ea=0;
				switch(ins>>4) {
				case 0x2:			// no or special address mode
				case 0x8:
				case 0x9:
					break;
				case 0xA:			// immediate
					de(m_DLLInstance.Log("#%02x ",Get(pc)))
						ea=pc++; break;
				case 0x3:			// short
				case 0xB:
					ea=Get(pc++);
					if(!indirect) {		// short direct
					}
					else {			// short indirect
						de(m_DLLInstance.Log("[%02x] -> ",ea))
							ea=Get(ea);
					}
					de(m_DLLInstance.Log("%02x ",ea))
						break;
				case 0xC:			// long
					if(!indirect) {		// long direct
						ea=HILO(pc); pc+=2;
					}
					else {			// long indirect
						ea=Get(pc++);
						de(m_DLLInstance.Log("[%02x] -> ",ea))
							ea=HILO(ea);
					}
					de(m_DLLInstance.Log("%04x ",ea))
						break;
				case 0xD:			// long indexed
					if(!indirect) {		// long direct indexed
						ea=HILO(pc); pc+=2;
						de(m_DLLInstance.Log("(%04x",ea))
					}
					else {			// long indirect indexed
						ea=Get(pc++);
						de(m_DLLInstance.Log("([%02x]",ea))
							ea=HILO(ea);
						de(m_DLLInstance.Log(",%c) -> (%04x",xs,ea))
					}
					ea+=*ex;
					de(m_DLLInstance.Log(",%c) -> %04x ",xs,ea))
						break;
				case 0x6:			// short indexed
				case 0xE:
					ea=Get(pc++);
					if(!indirect) {		// short direct indexed
						de(m_DLLInstance.Log("(%02x",ea))
					}
					else {			// short indirect indexed
						de(m_DLLInstance.Log("([%02x]",ea))
							ea=Get(ea);
						de(m_DLLInstance.Log(",%c) -> (%02x",xs,ea))
					}
					ea+=*ex;
					de(m_DLLInstance.Log(",%c) -> %04x ",xs,ea))
						break;
				case 0x7:			// indexed
				case 0xF:
					ea=*ex;
					de(m_DLLInstance.Log("(%c) -> %02x ",xs,ea))
						break;
				case 0x4:			// inherent A
					de(m_DLLInstance.Log("A "))
						break;
				case 0x5:			// inherent X/Y
					de(m_DLLInstance.Log("%c ",xs))
						break;
				case 0x0:			// bit
				case 0x1:
					ea=Get(pc++);
					if(!indirect) {
						de(m_DLLInstance.Log("%02x",ea))
					}
					else {
						de(m_DLLInstance.Log("[%02x]",ea))
							ea=Get(ea);
						indirect=false; // don't use indirect mode in case this is a bit branch
					}
					break;
				}
				
				// read operant
				unsigned char flags=opFlags[ins], op=0;
				if(flags & 1) {
					switch(ins>>4) {
					case 0x2:			// no or special address mode
					case 0x8:
					case 0x9:
						break;
					case 0xA:			// immediate
					case 0x3:			// short
					case 0xB:
					case 0xC:			// long
					case 0xD:			// long indexed
					case 0x6:			// short indexed
					case 0xE:
					case 0x7:			// indexed
					case 0xF:
					case 0x0:			// bit
					case 0x1:
						op=Get(ea); break;
					case 0x4:			// inherent A
						op=a; break;
					case 0x5:			// inherent X/Y
						op=*ex; break;
					}
				}
				
				// command decoding
#ifdef DEBUG_STAT
				stats[ins]++;
#endif
				switch(ins) {
				case 0xA6: // LDA
				case 0xB6:
				case 0xC6:
				case 0xD6:
				case 0xE6:
				case 0xF6:
					a=op; tst(op); break;
				case 0xAE: // LDX
				case 0xBE:
				case 0xCE:
				case 0xDE:
				case 0xEE:
				case 0xFE:
					*ex=op; tst(op); break;
				case 0xB7: // STA
				case 0xC7:
				case 0xD7:
				case 0xE7:
				case 0xF7:
					op=a; tst(op); break;
				case 0xBF: // STX
				case 0xCF:
				case 0xDF:
				case 0xEF:
				case 0xFF:
					op=*ex; tst(op); break;
				case 0x97: // TAX
					*ex=a; break;
				case 0x9F: // TXA
					a=*ex; break;
				case 0x93: // TYX (ST7)
					if(ex==&x) *ex=y; else *ex=x; break;
				case 0x3D: // TST
				case 0x4D:
				case 0x5D:
				case 0x6D:
				case 0x7D:
					tst(op); break;
				case 0x3F: // CLR
				case 0x4F:
				case 0x5F:
				case 0x6F:
				case 0x7F:
					op=0; tst(0); break;
				case 0x3C: // INC
				case 0x4C:
				case 0x5C:
				case 0x6C:
				case 0x7C:
					op++; tst(op); break;
				case 0x3A: // DEC
				case 0x4A:
				case 0x5A:
				case 0x6A:
				case 0x7A:
					op--; tst(op); break;
				case 0x33: // COM
				case 0x43:
				case 0x53:
				case 0x63:
				case 0x73:
					op=~op; cc.c=1; tst(op); break;
				case 0x30: // NEG
				case 0x40:
				case 0x50:
				case 0x60:
				case 0x70:
					op=~op+1; if(!op) cc.c=0; tst(op); break;
				case 0x42: // MUL
				case 0x52:
					{
						unsigned short res=*ex * a;
						*ex=(res>>8); a=res&0xff; cc.c=0; cc.h=0;
						break;
					}
				case 0xA9: // ADC
				case 0xB9:
				case 0xC9:
				case 0xD9:
				case 0xE9:
				case 0xF9:
					a=add(op,cc.c); break;
				case 0xAB: // ADD
				case 0xBB:
				case 0xCB:
				case 0xDB:
				case 0xEB:
				case 0xFB:
					a=add(op,0); break;
				case 0xA2: // SBC
				case 0xB2:
				case 0xC2:
				case 0xD2:
				case 0xE2:
				case 0xF2:
					a=sub(a,op,cc.c); break;
				case 0xA0: // SUB
				case 0xB0:
				case 0xC0:
				case 0xD0:
				case 0xE0:
				case 0xF0:
					a=sub(a,op,0); break;
				case 0xA1: // CMP
				case 0xB1:
				case 0xC1:
				case 0xD1:
				case 0xE1:
				case 0xF1:
					sub(a,op,0); break;
				case 0xA3: // CPX
				case 0xB3:
				case 0xC3:
				case 0xD3:
				case 0xE3:
				case 0xF3:
					sub(*ex,op,0); break;
				case 0xA4: // AND
				case 0xB4:
				case 0xC4:
				case 0xD4:
				case 0xE4:
				case 0xF4:
					a &= op; tst(a); break;
				case 0xAA: // ORA
				case 0xBA:
				case 0xCA:
				case 0xDA:
				case 0xEA:
				case 0xFA:
					a |= op; tst(a); break;
				case 0xA8: // EOR
				case 0xB8:
				case 0xC8:
				case 0xD8:
				case 0xE8:
				case 0xF8:
					a ^= op; tst(a); break;
				case 0xA5: // BIT
				case 0xB5:
				case 0xC5:
				case 0xD5:
				case 0xE5:
				case 0xF5:
					tst(a & op); break;
				case 0x38: // ASL
				case 0x48:
				case 0x58:
				case 0x68:
				case 0x78:
					op=rollL(op,0); break;
				case 0x39: // ROL
				case 0x49:
				case 0x59:
				case 0x69:
				case 0x79:
					op=rollL(op,cc.c); break;
				case 0x37: // ASR
				case 0x47:
				case 0x57:
				case 0x67:
				case 0x77:
					op=rollR(op,bitset(op,7)); break;
				case 0x34: // LSR
				case 0x44:
				case 0x54:
				case 0x64:
				case 0x74:
					op=rollR(op,0); break;
				case 0x36: // ROR
				case 0x46:
				case 0x56:
				case 0x66:
				case 0x76:
					op=rollR(op,cc.c); break;
				case 0x3E: // SWAP (ST7)
				case 0x4E:
				case 0x5E:
				case 0x6E:
				case 0x7E:
					op=(op<<4)|(op>>4); tst(op); break;
				case 0x00 ... 0x0F: // BRSET BRCLR
					{
						int bit=(ins&0x0F)>>1;
						de(m_DLLInstance.Log(",#%x,",bit))
							cc.c=bitset(op,bit);
						branch((ins&0x01) ? !cc.c:cc.c);
						break;
					}
				case 0x10 ... 0x1F: // BSET BCLR
					{
						int bit=(ins&0x0F)>>1;
						de(m_DLLInstance.Log(",#%x",bit))
							if(ins&0x01) op &= ~(1<<bit);
							else         op |=  (1<<bit);
							break;
					}
				case 0x20: // BRA
					branch(true); break;
				case 0x21: // BRN
					branch(false); break;
				case 0x22: // BHI
					branch(!cc.c && !cc.z); break;
				case 0x23: // BLS
					branch( cc.c ||  cc.z); break;
				case 0x24: // BCC BHS
					branch(!cc.c); break;
				case 0x25: // BCS BLO
					branch( cc.c); break;
				case 0x26: // BNE
					branch(!cc.z); break;
				case 0x27: // BEQ
					branch( cc.z); break;
				case 0x28: // BHCC
					branch(!cc.h); break;
				case 0x29: // BHCS
					branch( cc.h); break;
				case 0x2A: // BPL
					branch(!cc.n); break;
				case 0x2B: // BMI
					branch( cc.n); break;
				case 0x2C: // BMC
					branch(!cc.i); break;
				case 0x2D: // BMS
					branch( cc.i); break;
				case 0xBC: // JMP
				case 0xCC:
				case 0xDC:
				case 0xEC:
				case 0xFC:
					pc=ea; break;
				case 0xAD: // BSR
					pushpc(); pc--; branch(true); break;
				case 0xBD: // JSR
				case 0xCD:
				case 0xDD:
				case 0xED:
				case 0xFD:
					pushpc(); pc=ea; break;
				case 0x81: // RTS
					poppc(); break;
				case 0x83: // SWI
					pushpc(); push(x); push(a); pushc();
					cc.i=1; pc=HILO(0x1ffc); break;
				case 0x80: // RTI
					popc(); a=pop(); x=pop(); poppc(); break;
				case 0x9C: // RSP
					sp=spHi; break;
				case 0x96: // TSX
					*ex=sp; break;
				case 0x94: // TXS (ST7)
					sp=*ex; break;
				case 0x9E: // TSA
					a=sp; break;
				case 0x95: // TAS (ST7)
					sp=a; break;
				case 0x84: // POPA (ST7)
					a=pop(); break;
				case 0x85: // POPX (ST7)
					*ex=pop(); break;
				case 0x86: // POPC (ST7)
					popc(); break;
				case 0x88: // PUSHA (ST7)
					push(a); break;
				case 0x89: // PUSHX (ST7)
					push(*ex); break;
				case 0x8A: // PUSHC (ST7)
					pushc(); break;
				case 0x98: // CLC
					cc.c=0; break;
				case 0x99: // SEC
					cc.c=1; break;
				case 0x9A: // CLI
					cc.i=0; break;
				case 0x9B: // SEI
					cc.i=1; break;
				case 0x9D: // NOP
					break;
				case 0x90: // pre-bytes
				case 0x91:
				case 0x92:
					de(m_DLLInstance.Log("pre-byte %02x in command decoding\n",ins))
						return 3;
				default:
					de(m_DLLInstance.Log("unsupported instruction 0x%02x\n",ins))
						return 3;
      }
	  
	  // write operant
	  if(flags & 2) {
		  switch(ins>>4) {
		  case 0x2:			// no or special address mode
		  case 0x8:
		  case 0x9:
			  break;
		  case 0xA:			// immediate
		  case 0x3:			// short
		  case 0xB:
		  case 0xC:			// long
		  case 0xD:			// long indexed
		  case 0x6:			// short indexed
		  case 0xE:
		  case 0x7:			// indexed
		  case 0xF:
		  case 0x0:			// bit
		  case 0x1:
			  Set(ea,op); break;
		  case 0x4:			// inherent A
			  a=op; break;
		  case 0x5:			// inherent X/Y
			  *ex=op; break;
		  }
      }
	  de(m_DLLInstance.Log("\n"))
		  
		  for(int i=numBp-1 ; i>=0 ; i--) {
			  if(bp[i]==pc) {
				  de(m_DLLInstance.Log("6805: breakpoint at %04x\n",pc))
					  return 0;
			  }
		  }
    }
}

void c6805::branch(bool branch)
{
#ifdef DEBUG_EMU
	{
		unsigned char off=Get(pc);
		if(indirect) {
			de(m_DLLInstance.Log("[%02x] -> ",off))
				off=Get(off);
		}
		unsigned short npc=pc+off+1;
		if(off&0x80) npc-=0x100; // gcc fixup. take care of sign
		de(m_DLLInstance.Log("%04x ",npc))
			if(branch) de(m_DLLInstance.Log("(taken) "))
	}
#endif
	pc++;
	if(branch) {
		unsigned char offset=Get(pc-1);
		if(indirect) offset=Get(offset);
		pc+=offset;
		if(offset&0x80) pc-=0x100; // gcc fixup. take care of sign
    }
}

void c6805::push(unsigned char c)
{
	Set(sp--,c);
}

unsigned char c6805::pop(void)
{
	return Get(++sp);
}

void c6805::pushpc(void)
{
	push(pc & 0xff);
	push(pc >> 8);
}

void c6805::poppc(void)
{
	pc=(pop()<<8) | pop();
}

void c6805::pushc(void)
{
	unsigned char c=0xE0+(cc.h?16:0)+(cc.i?8:0)+(cc.n?4:0)+(cc.z?2:0)+(cc.c?1:0);
	push(c);
}

void c6805::popc(void)
{
	unsigned char c=pop();
	cc.h=(c&16) ? 1:0;
	cc.i=(c& 8) ? 1:0;
	cc.n=(c& 4) ? 1:0;
	cc.z=(c& 2) ? 1:0;
	cc.c=(c& 1) ? 1:0;
}

void c6805::tst(unsigned char c)
{
	cc.z=!c;
	cc.n=bitset(c,7);
}

unsigned char c6805::add(unsigned char op, unsigned char c)
{
	unsigned short res_half=(a&0x0f) + (op&0x0f) + c;
	unsigned short res=(unsigned short)a + (unsigned short)op + (unsigned short)c;
	cc.h=res_half > 0x0f;
	cc.c=res > 0xff;
	res&=0xff;
	tst(res);
	return res;
}

unsigned char c6805::sub(unsigned char op1, unsigned char op2, unsigned char c)
{
	short res=(short)op1 - (short)op2 - (short)c;
	cc.c=res < 0;
	res&=0xff;
	tst(res);
	return res;
}

unsigned char c6805::rollR(unsigned char op, unsigned char c)
{
	cc.c=bitset(op,0);
	op >>= 1;
	op |= c << 7;
	tst(op);
	return op;
}

unsigned char c6805::rollL(unsigned char op, unsigned char c)
{
	cc.c=bitset(op,7);
	op <<= 1;
	op |= c;
	tst(op);
	return op;
}

// -- cEmu ---------------------------------------------------------------------

#define MAX_COUNT 1000000

class cEmu : public c6805 {
protected:
	int romNr, id;
	char *romName, *romExtName, *eepromName;
	//
	int InitStart, InitEnd;
	int EmmStart, EmmEnd, EmmKey0, EmmKey1;
	int FindKeyStart, FindKeyEnd;
	int Rc1H, Rc1L;
	int EnsIrdChk, Cmd83Chk;
	int SoftInt, StackHigh;
	//
	bool AddRom(unsigned short addr, unsigned short size, const char *name);
	bool AddEeprom(unsigned short addr, unsigned short size, unsigned short otpSize, const char *name);
	//
	virtual bool InitSetup(void) { return true; }
	virtual bool UpdateSetup(const unsigned char *emm) { return true; }
public:
	cEmu(void);
	virtual ~cEmu();
	bool Init(int RomNr, int Id);
	bool GetOpKeys(const unsigned char *Emm, unsigned char *id, unsigned char *key0, unsigned char *key1);
	bool GetPkKeys(const unsigned char *select, unsigned char *pkset);
	bool Matches(int RomNr, int Id);
};

cEmu::cEmu(void)
{
	romName=romExtName=eepromName=0;
	FindKeyStart=0;
}

cEmu::~cEmu()
{
	free(romName);
	free(romExtName);
	free(eepromName);
}

bool cEmu::AddRom(unsigned short addr, unsigned short size, const char *name)
{
	cMap *map=new cMapRom(addr,name);
	return AddMapper(map,addr,size);
}

bool cEmu::AddEeprom(unsigned short addr, unsigned short size, unsigned short otpSize, const char *name)
{
	cMap *map=new cMapEeprom(addr,name,otpSize);
	return AddMapper(map,addr,size);
}

bool cEmu::Matches(int RomNr, int Id)
{
	return (romNr==RomNr && id==Id);
}

bool cEmu::Init(int RomNr, int Id)
{
	romNr=RomNr; id=Id;
	romName=new char[20];
	romExtName=new char[20];
	eepromName=new char[20];
	sprintf(romName,"ROM%d.bin",romNr);
	sprintf(romExtName,"ROM%dext.bin",romNr);
	sprintf(eepromName,"eep%i_%02x.bin",romNr,(id&0xff00)>>8);
	if(InitSetup()) {
		ForceSet(EnsIrdChk, 0x81,true);
		ForceSet(Cmd83Chk+0,0x98,true);
		ForceSet(Cmd83Chk+1,0x9d,true);
		if(SoftInt) {
			Set(0x1ffc,SoftInt>>8);
			Set(0x1ffd,SoftInt&0xff);
		}
		SetSp(StackHigh,0);
		SetPc(InitStart);
		ClearBreakpoints();
		AddBreakpoint(InitEnd);
		if(!Run(MAX_COUNT)) return true;
    }
	return false;
}

bool cEmu::GetOpKeys(const unsigned char *Emm, unsigned char *id, unsigned char *key0, unsigned char *key1)
{
	int keys=0;
	if(UpdateSetup(Emm)) {
		SetMem(0x0080,&Emm[0],64);
		SetMem(0x00F8,&Emm[1],2);
		SetPc(EmmStart);
		ClearBreakpoints();
		AddBreakpoint(EmmEnd);
		AddBreakpoint(EmmKey0);
		AddBreakpoint(EmmKey1);
		while(!Run(MAX_COUNT)) {
			unsigned short pc=GetPc();
			if(pc==EmmKey0) {
				GetMem(0x82,key0,8);
				keys++;
			}
			if(pc==EmmKey1) {
				GetMem(0x82,key1,8);
				keys++;
			}
			if(pc==EmmEnd) {
				GetMem(0x00F8,id,2);
				break;
			}
		}
    }
	return (keys==2);
}

bool cEmu::GetPkKeys(const unsigned char *select, unsigned char *pkset)
{
	if(FindKeyStart) {
		Set(0x0081,select[2]<<7);
		SetMem(0x00F8,select,3);
		SetPc(FindKeyStart);
		ClearBreakpoints();
		AddBreakpoint(FindKeyEnd);
		while(!Run(MAX_COUNT)) {
			unsigned short pc=GetPc();
			if(pc==FindKeyEnd) {
				unsigned short loc=(Get(Rc1H)<<8)+Get(Rc1L);
				for(int i=0; i<45; i+=15) GetMem(loc+4+i,pkset+i,15);
				return true;
			}
		}
    }
	return false;
}

// -- cEmuRom3Core -------------------------------------------------------------

class cEmuRom3Core : public cEmu {
protected:
	virtual void Stepper(void);
	bool CoreInitSetup(void);
	bool DoMaps(bool hasExt);
};

void cEmuRom3Core::Stepper(void)
{
	srand(time(0));
	int rnd=rand();
	unsigned char mem4=Get(0x04);
	if(!bitset(mem4,2)) {
		Set(0x06,(rnd&0xff00)>>8);
		Set(0x07,rnd&0xff);
    }
}

bool cEmuRom3Core::CoreInitSetup(void)
{
	ForceSet(0x0001,1<<3,true);
	Set(0x0002,0x03);
	Set(0x004e,0x4B);
	return true;
}

bool cEmuRom3Core::DoMaps(bool hasExt)
{
	// Eeprom & ROMext are non-fatal also they are required for some providers
	if(hasExt) AddRom(0x2000,0x2000,romExtName);
	AddEeprom(0xE000,0x1000,0x20,eepromName);
	return AddRom(0x4000,0x4000,romName);
}

// -- cEmuRom3 -----------------------------------------------------------------

class cEmuRom3 : public cEmuRom3Core {
protected:
	virtual bool InitSetup(void);
public:
	cEmuRom3(void);
};

cEmuRom3::cEmuRom3(void)
{
	InitStart=0x4000;
	InitEnd  =0x734b;
	EmmStart =0x5a82;
	EmmEnd   =0x67db;
	EmmKey0  =0x617c;
	EmmKey1  =0x6184;
	FindKeyStart=0x6133;
	FindKeyEnd  =0x6149;
	Rc1H     =0x0024;
	Rc1L     =0x0025;
	EnsIrdChk=0x6437;
	Cmd83Chk =0x6431;
	SoftInt  =0x0000;
	StackHigh=0x7f;
}

bool cEmuRom3::InitSetup(void)
{
	return DoMaps(true) && CoreInitSetup();
}

// -- cEmuRom7 -----------------------------------------------------------------

class cEmuRom7 : public cEmuRom3Core {
protected:
	virtual bool InitSetup(void);
public:
	cEmuRom7(void);
};

cEmuRom7::cEmuRom7(void)
{
	InitStart=0x4000;
	InitEnd  =0x7b68;
	EmmStart =0x482b;
	EmmEnd   =0x6146;
	EmmKey0  =0x4f2a;
	EmmKey1  =0x4f32;
	FindKeyStart=0x4ee4;
	FindKeyEnd  =0x4ef7;
	Rc1H     =0x0028;
	Rc1L     =0x0029;
	EnsIrdChk=0x51df;
	Cmd83Chk =0x51d9;
	SoftInt  =0x4008;
	StackHigh=0x7f;
}

bool cEmuRom7::InitSetup(void)
{
	return DoMaps(false) && CoreInitSetup();
}

// -- cEmuRom10Core ------------------------------------------------------------

class cEmuRom10Core : public cEmu {
protected:
	virtual void Stepper(void);
	bool DoMaps(bool hasExt, int romSize);
	bool CoreInitSetup(void);
	bool CoreUpdateSetup(const unsigned char *emm);
};

void cEmuRom10Core::Stepper(void)
{
	srand(time(0));
	int rnd=rand();
	unsigned char mem7=Get(0x07);
	if(cc.i) mem7&=0xFD; else mem7|=0x02;
	Set(0x07,mem7);
	if(bitset(mem7,1) && bitset(mem7,7)) {
		Set(0x05,(rnd&0xFF00)>>8);
		Set(0x06,rnd&0xFF);
    }
}

bool cEmuRom10Core::DoMaps(bool hasExt, int romSize)
{
	// Eeprom & ROMext are non-fatal also they are required for some providers
	if(hasExt) AddRom(0x2000,0x2000,romExtName);
	AddEeprom(0xC000,0x2000,0x40,eepromName);
	return AddRom(0x4000,romSize,romName);
}

bool cEmuRom10Core::CoreInitSetup(void)
{
	ForceSet(0x01,0x13,true);
	Set(0x02,0x3);
	Set(0x07,0xFF);
	return true;
}

bool cEmuRom10Core::CoreUpdateSetup(const unsigned char *emm)
{
	SetMem(0x01A2,&emm[1],2);
	SetMem(0x0307,&emm[1],2);
	Set(0x0300,emm[5]);
	Set(0x0301,emm[2]);
	return true;
}

// -- cEmuRom10 ----------------------------------------------------------------

class cEmuRom10 : public cEmuRom10Core {
protected:
	virtual bool InitSetup(void);
	virtual bool UpdateSetup(const unsigned char *emm);
public:
	cEmuRom10(void);
};

cEmuRom10::cEmuRom10(void)
{
	InitStart=0x4000;
	InitEnd  =0x81ca;
	EmmStart =0x6a71;
	EmmEnd   =0x81f7;
	EmmKey0  =0x7172;
	EmmKey1  =0x717a;
	FindKeyStart=0x712c;
	FindKeyEnd  =0x713f;
	Rc1H     =0x004a;
	Rc1L     =0x004b;
	EnsIrdChk=0x7427;
	Cmd83Chk =0x7421;
	SoftInt  =0x4004;
	StackHigh=0x3ff;
}

bool cEmuRom10::InitSetup(void)
{
	return DoMaps(false,0x5A00) && CoreInitSetup();
}

bool cEmuRom10::UpdateSetup(const unsigned char *emm)
{
	Set(0x006e,0x4B);
	return CoreUpdateSetup(emm);
}

// -- cEmuRom11 ----------------------------------------------------------------

class cEmuRom11 : public cEmuRom10Core {
protected:
	virtual bool InitSetup(void);
	virtual bool UpdateSetup(const unsigned char *emm);
public:
	cEmuRom11(void);
};

cEmuRom11::cEmuRom11(void)
{
	InitStart=0x4000;
	InitEnd  =0x405b;
	EmmStart =0x5865;
	EmmEnd   =0x9990;
	EmmKey0  =0x5f66;
	EmmKey1  =0x5f6e;
	FindKeyStart=0x5f20;
	FindKeyEnd  =0x5f33;
	Rc1H     =0x004a;
	Rc1L     =0x004b;
	EnsIrdChk=0x621b;
	Cmd83Chk =0x6215;
	SoftInt  =0x4004;
	StackHigh=0x3ff;
}

bool cEmuRom11::InitSetup(void)
{
	return DoMaps(false,0x8000) && CoreInitSetup();
}

bool cEmuRom11::UpdateSetup(const unsigned char *emm)
{
	Set(0x0068,0x4B);
	return CoreUpdateSetup(emm);
}

// -- cNagraDES ----------------------------------------------------------------

class cNagraDES : private cDes {
protected:
	void Decrypt(const unsigned char *data, const unsigned char *key, unsigned char *out, bool desmod=false);
	void Crypt(const unsigned char *data, const unsigned char *key, unsigned char *out);
	bool SigCheck(const unsigned char *block, const unsigned char *sig, const unsigned char *vkey, const int rounds);
};

void cNagraDES::Decrypt(const unsigned char *data, const unsigned char *key, unsigned char *out, bool desmod)
{
	unsigned char cardkey[8];
	memcpy(cardkey,key,8);
	RotateBytes(cardkey,8);
	memcpy(out,data,8);
	if(!desmod) RotateBytes(out,8);
	Des(out,cardkey,NAGRA_DES_DECR);
	if(!desmod) RotateBytes(out,8);
}

void cNagraDES::Crypt(const unsigned char *data, const unsigned char *key, unsigned char *out)
{
	unsigned char cardkey[8];
	memcpy(cardkey,key,8);
	RotateBytes(cardkey,8);
	memcpy(out,data,8);
	RotateBytes(out,8);
	Des(out,cardkey,NAGRA_DES_ENCR);
	RotateBytes(out,8);
}

bool cNagraDES::SigCheck(const unsigned char *block, const unsigned char *sig, const unsigned char *vkey, const int rounds)
{
	unsigned char hash[8];
	memcpy(hash,vkey,8);
	for(int j=0; j<rounds; j++) {
		unsigned char cr[8];
		Crypt(block+j*8,hash,cr);
		for(int i=0; i<8; i++) hash[i]=cr[i]^block[j*8+i];
    }
	return (0==memcmp(hash,sig,8));
}

#ifndef TESTER

// -- cNagraRSA ----------------------------------------------------------------

class cNagraRSA : public cNagraDES {
private:
	void ExpandPK(const unsigned char *pk, BIGNUM *p, BIGNUM *q);
protected:
	cBN e2;
	//
	void ComputeRSAKeys(const unsigned char *pk, const unsigned char *data, BIGNUM *n1, BIGNUM *e1);
	bool DecryptECM(const unsigned char *data, unsigned char *decrypted, const unsigned char *vkey, int pecmlen, BIGNUM *e1, BIGNUM *n1, BIGNUM *n2);
	bool DecryptEMM(const unsigned char *data, unsigned char *decrypted, const unsigned char *vkey, int emmlen, BIGNUM *e1, BIGNUM *n1, BIGNUM *n2);
public:
	cNagraRSA(void);
	virtual ~cNagraRSA() {}
};

cNagraRSA::cNagraRSA(void)
{
	const unsigned char exp=0x03;
	BN_bin2bn(&exp,1,e2);
}

bool cNagraRSA::DecryptECM(const unsigned char *data, unsigned char *decrypted, const unsigned char *vkey, int pmecmlen, BIGNUM *e1, BIGNUM *n1, BIGNUM *n2)
{
	memcpy(decrypted,&data[2],pmecmlen);
	RotateBytes(decrypted,pmecmlen);
	
	cBN ecm;
	BN_bin2bn(decrypted,pmecmlen,ecm);
	
	cBNctx ctx;
	cBN result;
	if(!BN_mod_exp(result,ecm,e2,n2,ctx)) {
		dn(m_DLLInstance.Log("nagra: error decrypting ECM (round 1)\n"))
			return false;;
    }
	
	BN_set_word(ecm,data[0]>>4);
	BN_lshift(ecm,ecm,508);
	BN_add(result,result,ecm);
	
	if(!BN_mod_exp(result,result,e1,n1,ctx)) {
		dn(m_DLLInstance.Log("nagra: error decrypting ECM (round 2)\n"))
			return false;
    }
	if(BN_num_bytes(result)!=64) {
		dn(m_DLLInstance.Log("nagra: error: result of ECM decryption is not 64 bytes\n"))
			return false;
    }
	BN_bn2bin(result,decrypted);
	if(SigCheck(decrypted,&decrypted[56],vkey,7)) {
		dn(m_DLLInstance.Log("nagra: ECM signature OK\n"))
			return true;
    }else
    {
        dn(m_DLLInstance.Log("nagra: ECM signature fail\n"))
    }
	
	dn(m_DLLInstance.Log("nagra: ECM decryption failed\n"))
		m_DLLInstance.Log("[Nagra] ECM decryption failed\n");
	return false;
}

bool cNagraRSA::DecryptEMM(const unsigned char *data, unsigned char *decrypted, const unsigned char *vkey, int emmlen, BIGNUM *e1, BIGNUM *n1, BIGNUM *n2)
{
	memcpy(decrypted,&data[9],emmlen);
	RotateBytes(decrypted,emmlen);  // LE <-> BE
	
	cBN emm;
	BN_bin2bn(decrypted,emmlen,emm);
	
	cBNctx ctx;
	cBN result1;
	if(!BN_mod_exp(result1,emm,e2,n2,ctx)) {
		dn(m_DLLInstance.Log("nagra: error decrypting EMM (round1)\n"))
			return false;
    }
	
	BN_set_word(emm,data[0]>>6);
	BN_lshift(emm,emm,510);
	BN_add(result1,result1,emm);
	
	cBN result2;
	if(!BN_mod_exp(result2,result1,e1,n1,ctx)) {
		dn(m_DLLInstance.Log("nagra: error decrypting EMM (round 2)\n"))
			return false;
    }
	if(BN_bn2bin(result2,decrypted) < 64) {
		dn(m_DLLInstance.Log("nagra: error: result of decryption was shorter than 64 bytes\n"))
			return false;
    }
	if(SigCheck(decrypted,&data[1],vkey,8)) {
		dn(m_DLLInstance.Log("nagra: EMM signature OK\n"))
			return true;
    }
    
	// We need to use signature exchange method (7blocks emm,1 block signature)
	dn(m_DLLInstance.Log("nagra: warning: signature failed,trying signature exchange method\n"))
		unsigned char buf[8];
	memcpy(buf,&data[1],sizeof(buf));
	RotateBytes(buf,sizeof(buf));
	cBN newdata, newsig;
	BN_bin2bn(buf,sizeof(buf),newdata);
	BN_copy(newsig,result1);
	BN_mask_bits(newsig,64);
	BN_bn2bin(newsig,buf);
	RotateBytes(buf,sizeof(buf));
	
	BN_rshift(result1,result1,64); // delete the lower 64 bits,we saved it
	BN_lshift(result1,result1,64); // before as the new 64 bit signature
	BN_add(result1,result1,newdata);
	
	if(!BN_mod_exp(result2,result1,e1,n1,ctx)) {
		dn(m_DLLInstance.Log("nagra: error decrypting EMM (round 3)\n"))
			return false;
    }
	if(BN_bn2bin(result2,decrypted) < 64) {
		dn(m_DLLInstance.Log("nagra: error: result of EMM decryption was shorter than 64 bytes\n"))
			return false;
    }
	if(SigCheck(decrypted,buf,vkey,8)) {
		dn(m_DLLInstance.Log("nagra: EMM signature OK\n"))
			return true;
    }
	dn(m_DLLInstance.Log("nagra: fatal: signature failed in signature exchange method\n"))
		m_DLLInstance.Log("[Nagra]Signature failed in signature exchange method.!!\n");
	m_DLLInstance.Log("[Nagra]Check your keys.\n");
	return false;
}

void cNagraRSA::ExpandPK(const unsigned char *pk, BIGNUM *p, BIGNUM *q)
{
	// Make des_key
	unsigned char des_data[32];
	unsigned char des_key[8], des_tmp[8];
	memcpy(des_data,pk,8);
	memcpy(des_tmp,pk,8);
	RotateBytes(des_tmp,8);
	des_tmp[7]=0x00;
	Decrypt(des_data,des_tmp,des_key,true);
	RotateBytes(des_key,8);
	des_key[7]=0x00;
	
	// Calculate P
	memcpy(&des_data[ 0],&pk[4],8);
	memcpy(&des_data[ 8],&pk[4],8);
	memcpy(&des_data[16],&pk[4],8);
	memcpy(&des_data[24],&pk[4],8);
	des_data[ 0]^=0;
	des_data[ 8]^=1;
	des_data[16]^=2;
	des_data[24]^=3;
	
	for(int i=0; i<4; i++) {
		const int off=i*8;
		Decrypt(&des_data[off],des_key,des_tmp,true);
		memcpy(&des_data[off],des_tmp,8);
    }
	BN_bin2bn(des_data,32,p);
	BN_add_word(p,(pk[12]<<4)|((pk[13]>>4)&0x0f));
	BN_set_bit(p,(BN_num_bytes(p)*8)-1);
	
	// Calculate Q
	memcpy(&des_data[ 0],&pk[4],8);
	memcpy(&des_data[ 8],&pk[4],8);
	memcpy(&des_data[16],&pk[4],8);
	memcpy(&des_data[24],&pk[4],8);
	des_data[ 0]^=4;
	des_data[ 8]^=5;
	des_data[16]^=6;
	des_data[24]^=7;
	
	for(int i=0; i<4; i++) {
		const int off=i*8;
		Decrypt(&des_data[off],des_key,des_tmp,true);
		memcpy(&des_data[off],des_tmp,8);
    }
	BN_bin2bn(des_data,32,q);
	BN_add_word(q,((pk[13]&0x0f)<<8)|pk[14]);
	BN_set_bit(q,(BN_num_bytes(q)*8)-1);
}

void cNagraRSA::ComputeRSAKeys(const unsigned char *pk, const unsigned char *data, BIGNUM *n1, BIGNUM *e1)
{
	// Calculate P and Q from PK
	cBN p,q;
	ExpandPK(pk,p,q);
	// Calculate N=P*Q (modulus)
	cBNctx ctx;
	BN_mul(n1,p,q,ctx);
	if(data) BN_bin2bn(data,64,e1); // set provided data as E1
	else {                          // else calculate the 'official' one
		// E = ( ( ( (P-1) * (Q-1) * 2) + 1) / 3)
		BN_sub_word(p,1);
		BN_sub_word(q,1);
		BN_mul(e1,p,q,ctx);
		BN_mul_word(e1,2);
		BN_add_word(e1,1);
		BN_div_word(e1,3);
    }
}

// -- cPlainKeyNagra -----------------------------------------------------------

#define PLAINLEN_NAGRA_H 8
#define PLAINLEN_NAGRA_B 64

#define DEF_TYPE 0
#define DEF_PK   2
#define DEF_ROM  0

#define TYPE(keynr)         (((keynr)>>16)&1)
#define PK(keynr)           (((keynr)>>17)&3)
#define ROM(keynr)          (((keynr)>>19)&15)
#define KEYSET(rom,pk,type) ((((rom)&15)<<3)|(((pk)&3)<<1)|((type)&1))

class cPlainKeyNagra : public cDualKey {
private:
	int ParseTag(const char *tag,  char * &line);
protected:
	virtual void SetIsBNKey(void);
public:
	cPlainKeyNagra(bool Super);
	virtual bool Parse(char *line);
	virtual bool Save(FILE *f);
};

static cPlainKeyTypeReg<cPlainKeyNagra,'N'> KeyReg;

cPlainKeyNagra::cPlainKeyNagra(bool Super)
:cDualKey(Super,true)
{}

void cPlainKeyNagra::SetIsBNKey(void)
{
	keyIsBN=false;
	switch(keynr & C2MASK) {
    case MBC('E','1'):
    case MBC('N','1'):
    case MBC('N','2'): keyIsBN=true; break;
    }
}

int cPlainKeyNagra::ParseTag(const char *tag, char * &line)
{
	int r=-1;
	const int l=strlen(tag);
	if(!strncasecmp(line,tag,l)) {
		line+=l; r=0;
		while(*line!=0 && isdigit(*line)) {
			r=r*10 + *line-'0';
			line++;
		}
		line=skipspace(line);
    }
	return r;
}

bool cPlainKeyNagra::Parse( char *line)
{
	unsigned char sid[2];
	//#ifdef DEBUG
	const char *sline=line;
	//#endif
	int len;
	if(GetChar(line,&type,1) && (len=GetHex(line,sid,2,false))) {
		type=toupper(type); id=Bin2Int(sid,len); 
		line=skipspace(line);
		bool ok, tags;
		switch(toupper(*line)) {
		case 'E':
		case 'N': ok=GetChar(line,&keynr,2); tags=true; break;
		case 'V': ok=GetChar(line,&keynr,1); tags=true; break;
		default:  {
			unsigned char skeynr;
			ok=GetHex(line,&skeynr,1); keynr=skeynr; tags=false; break;
				  }
		}
		if(ok) {
			int romnr=DEF_ROM, pk=DEF_PK, keytype=DEF_TYPE;
			if(keynr=='V' || keynr==MBC('N','2')) pk=0; // different default
			line=skipspace(line);
			while(tags) {
				int r;
				if((r=ParseTag("ROM",line))>=0) {
					if(r>0 && r<=15) romnr=r;
					else d(m_DLLInstance.Log("nagrakey: ignoring bad ROM number %d\n",r));
				}
				else if((r=ParseTag("TYP",line))>=0) {
					if(r>=0 && r<=1) keytype=r;
					else d(m_DLLInstance.Log("nagrakey: ignoring bad key type %d\n",r));
				}
				else if((r=ParseTag("PK",line))>=0) {
					if(r>=0 && r<=2) pk=r;
					else d(m_DLLInstance.Log("nagrakey: ignoring bad PK key number %d\n",pk));
				}
				else {
					tags=false;
					if(pk!=0 && (keynr=='V' || keynr==MBC('N','2'))) {
						pk=0;
						d(m_DLLInstance.Log("nagrakey: auto-adjusting to PK0 for N2/V key\n"))
					}
					if(romnr!=0 && keytype!=1) {
						keytype=1;
						d(m_DLLInstance.Log("nagrakey: auto-adjusting to TYP1 for ROM key\n"))
					}
				}
			}
			SetIsBNKey();
			if(keyIsBN || keynr=='V') keynr=ADDC3(keynr,KEYSET(romnr,pk,keytype));
			len=keyIsBN ? PLAINLEN_NAGRA_B : PLAINLEN_NAGRA_H;
			unsigned char skey[len];
			ok=GetHex(line,skey,len);
			if(ok) {
				SetKeyFromBin(skey,len);
				return true;
			}
		}
    }
	d(m_DLLInstance.Log("nagrakey: bad key format '%s'\n",sline))
		m_DLLInstance.Log("[Nagra]Key format error.\n Line:%s\n",sline);
	return false;
}

bool cPlainKeyNagra::Save(FILE *f)
{
	char nr[8];
	int k=keynr & C2MASK;
	SetIsBNKey();
	if(keyIsBN || k=='V') {
		int i=(keyIsBN ? 2:1);
		nr[i--]=0; while(i>=0) { nr[i]=k&0xff; k>>=8; i--; }
    }
	else snprintf(nr,sizeof(nr),"%.2X",keynr);
	
	char rom[8], pk[8], keytype[8];
	rom[0]=0; pk[0]=0; keytype[0]=0;
	if(keyIsBN || C3(keynr)) {
		if(ROM(keynr) !=DEF_ROM ) snprintf(rom,sizeof(rom),"ROM%d ",ROM(keynr));
		if(PK(keynr)  !=DEF_PK  ) snprintf(pk,sizeof(pk),"PK%d ",PK(keynr));
		if(TYPE(keynr)!=DEF_TYPE) snprintf(keytype,sizeof(keytype),"TYP%d ",TYPE(keynr));
    }
	
	char *str=Print();
	fprintf(f,"%c %.4X %s %s%s%s%s\n",type,id,nr,rom,pk,keytype,str);
	free(str);
	return ferror(f)==0;
}

// -- cSystemNagra ---------------------------------------------------------------

static int minEcmTime=400; // ms

class cSystemNagra : public cSystem, private cNagraRSA {
private:
	unsigned char mecmTable[256];
	cEmu *emu;
	//
	void WriteTable(unsigned char *from, int off);
public:
	cSystemNagra(void);
	virtual ~cSystemNagra();
	virtual bool ProcessECM(const cEcmInfo *ecm, unsigned char *data);
	virtual void ProcessEMM(int pid, const cEcmInfo *ecm, unsigned char *buffer, int n);
};

cSystemNagra::cSystemNagra(void)
:cSystem(SYSTEM_NAME,SYSTEM_PRI)
{
	emu=0;
	memset(mecmTable,0,sizeof(mecmTable));
	hasLogger=true;
}

cSystemNagra::~cSystemNagra()
{
	delete emu;
}

void cSystemNagra::WriteTable(unsigned char *from, int off)
{
	off&=0xFF;
	if(off+16<256) memcpy(mecmTable+off,from,16);
	else {
		int l=256-off;
		memcpy(mecmTable+off,from,l);
		memcpy(mecmTable,from+l,16-l);
    }
}
#include <windows.h>
#define SLEEPMS Sleep
bool cSystemNagra::ProcessECM(const cEcmInfo *ecm, unsigned char *data)
{
	if(data[3]!=0x03) {
		dn(m_DLLInstance.Log("system-nagra: invalid ECM\n"))
			return false;
    }
	int minTime=time_ms()+minEcmTime;
	const int cmdLen=data[4];
	const int id=(data[5]*256)+data[6];
	data+=7;
	if(data[0]!=0x10) {
		dn(m_DLLInstance.Log("system-nagra: no ECM data available\n"))
			return false;
    }
	const int ecmLen=data[1];
	const int verType=(data[2]>>3)&1;
	const int keynr=(data[2]>>4)&1;
	const int ecmParm=data[2]&7;
	bool decrypt;
	if(ecmParm==7) decrypt=false;
	else if(ecmParm==5) decrypt=true;
	else {
		dn(m_DLLInstance.Log("system-nagra: unknown ecmParm, ignoring ECM\n"))
			return false;
    }
	
	cKeySnoop ks(this,'N',id,keynr);
	unsigned char sessionKey[8], verifyKey[8];
	cPlainKey *pk;
	if(!(pk=FastKey('N',id,ADDC3('V',KEYSET(0,0,verType))))) {
		if(doLog) d(m_DLLInstance.Log("system-nagra: missing %04x V TYP%d key\n",id,verType))
			m_DLLInstance.Log("[Nagra] missing %04x V TYP%d key\n",id,verType);
		return false;
    }
	pk->Get(verifyKey);
	if(!(pk=FastKey('N',id,keynr))) return false;
	pk->Get(sessionKey);
	
	const int desLen=(ecmLen-9) & ~7; // datalen - signature - ks byte
	unsigned char decrypted[desLen];
	for(int i=(desLen/8)-1; decrypt && i>=0; i--) {
		const int off=i*8;
		Decrypt(data+11+off,sessionKey,decrypted+off);
    }
	if(decrypt && !SigCheck(decrypted,data+3,verifyKey,desLen/8)) {
		dn(m_DLLInstance.Log("system-nagra: signature check failed in DES decrypt\n"))
			m_DLLInstance.Log("[Nagra]Signature check failed in DES decrypt\n");
		return false;
    }
	int cwEvenMecmIndex=-1, cwOddMecmIndex=-1;
	switch(decrypted[0]) {
    case 0x10:  // Whole CW
		cwOddMecmIndex=decrypted[1];
		memcpy(cw+8,&decrypted[2],8);
		cwEvenMecmIndex=decrypted[10];
		memcpy(cw,&decrypted[11],8);
		break;
    case 0x11: // Odd CW
		cwOddMecmIndex=decrypted[1];
		memcpy(cw+8, &decrypted[2],8);
		break;
    case 0x12: // Even CW
		cwEvenMecmIndex=decrypted[1];
		memcpy(cw,&decrypted[2],8);
		break;
    default: dn(m_DLLInstance.Log("system-nagra: failed to get CW\n")) return false;
    }
	
	const unsigned char * const mecmData=data+(ecmLen+2);
	const int mecmRSALen=mecmData[1]-4;
	if((cmdLen-(ecmLen+2))>64 && (*mecmData==0x20 || *mecmData==0x39)) {
		if(mecmRSALen!=64) {
			if(mecmRSALen>64 && doLog)
				d(m_DLLInstance.Log("system-nagra: ECM too big (len: %d)\n",mecmRSALen))
				return false;
		}
		
		const int mecmProvId=mecmData[2]*256+mecmData[3];
		const int keyType=(mecmData[4]>>3)&1;
		const int pkey=mecmData[4]&3;
		
		cBN e1, n1, n2;
		cPlainKey *pk;
		if(!(pk=FastKey('N',mecmProvId,MBC3('E','1',KEYSET(0,pkey,keyType))))) {
			if(doLog) d(m_DLLInstance.Log("system-nagra: missing %04x E1 PK%d TYP%d key\n",mecmProvId,pkey,keyType))
				m_DLLInstance.Log("[Nagra]Missing %04x E1 PK%d TYP%d key\n",mecmProvId,pkey,keyType);
			return false;
		}
		pk->Get(e1);
		if(!(pk=FastKey('N',mecmProvId,MBC3('N','1',KEYSET(0,pkey,keyType)))))  {
			if(doLog) d(m_DLLInstance.Log("system-nagra: missing %04x N1 PK%d TYP%d key\n",mecmProvId,pkey,keyType))
				m_DLLInstance.Log("[Nagra]Missing %04x N1 PK%d TYP%d key\n",mecmProvId,pkey,keyType);
			return false;
		}
		pk->Get(n1);
		if(!(pk=FastKey('N',mecmProvId,MBC3('N','2',KEYSET(0,0,keyType)))))  {
			if(doLog) d(m_DLLInstance.Log("system-nagra: missing %04x N2 TYP%d key\n",mecmProvId,keyType))
				m_DLLInstance.Log("[Nagra]Missing %04x N2 TYP%d key\n",mecmProvId,keyType);
			return false;
		}
		pk->Get(n2);
		if(!(pk=FastKey('N',mecmProvId,ADDC3('V',KEYSET(0,0,keyType)))))  {
			if(doLog) d(m_DLLInstance.Log("system-nagra: missing %04x V TYP%d key\n",mecmProvId,keyType))
				m_DLLInstance.Log("[Nagra]Missing %04x V TYP%d key\n",mecmProvId,keyType);
			return false;
		}
		pk->Get(verifyKey);
		unsigned char decrMecmData[mecmRSALen];
		if(!DecryptECM(&mecmData[4],decrMecmData,verifyKey,mecmRSALen,e1,n1,n2))
			return false;
		
		if(*mecmData==0x39 || (*mecmData==0x20 && (mecmProvId&0xFE00)==0x4800)) {
			unsigned char xor_table[64];
			for(int i=sizeof(xor_table)-1; i>=0; i--) xor_table[i]=63-i;
			ComputeRSAKeys(&decrMecmData[24],xor_table,n1,e1);
			
			// new XOR table for MECM data
			cBNctx ctx;
			cBN x;
			BN_mod_exp(x,e1,e2,n1,ctx);
			int l=sizeof(xor_table)-BN_num_bytes(x);
			memset(xor_table,0,l);
			BN_bn2bin(x,xor_table+l);
			RotateBytes(xor_table,sizeof(xor_table));
			
			// And finally, new MECM table
			for(int i=39; i<mecmRSALen; i++) decrMecmData[i]^=xor_table[i-39];
			memcpy(&decrMecmData[3],&decrMecmData[39],mecmRSALen-39);
		}
		
		if(decrMecmData[0]==0x2F && decrMecmData[1]==mecmData[2] && decrMecmData[2]==mecmData[3])
			WriteTable(decrMecmData+4,decrMecmData[3]*2);
    }
	
	if(cwOddMecmIndex>=0 && cwOddMecmIndex<0x80) {
		const int d=cwOddMecmIndex*2;
		for(int i=0 ; i<8 ; i++) cw[i+8]^=mecmTable[(d+i)&0xFF]; // odd
    }
	if(cwEvenMecmIndex>=0 && cwEvenMecmIndex<0x80) {
		const int d=cwEvenMecmIndex*2;
		for(int i=0 ; i<8 ; i++) cw[i]^=mecmTable[(d+i)&0xFF]; // even
    }
	
	ks.OK(pk);
	minTime-=time_ms(); if(minTime>0 && minEcmTime>0) SLEEPMS(minTime);
	return true;
}

void cSystemNagra::ProcessEMM(int pid, const cEcmInfo *ecm, unsigned char *buffer, int n)
{
	static const unsigned char tester[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x4B };
	if(memcmp(&buffer[3],tester,sizeof(tester)) || n<(12+9+64)) {
		// dl(m_DLLInstance.Log("logger-nagra %d: not a valid Nagra EMM structure\n",CardNum()))
		return;
	}else{
		//dl(m_DLLInstance.Log("logger-nagra %d:Valid EMM structure\n",CardNum()))
		//m_DLLInstance.to_binlog("NagraEMM.bin",buffer,184);
	}
	const int id=buffer[10]*256+buffer[11];
	const int pkey=(buffer[12]&3);
	cPlainKey *pk;
	cBN e1, n1, n2;
	
	if(!(pk=keys.FindKey('N',id,MBC3('E','1',KEYSET(0,pkey,0)))))
	{ 
		m_DLLInstance.Log("[Nagra]N %X E1 PK%d key error!.\n",id,pkey);
		return;
	}
	
	pk->Get(e1);
	if(!(pk=keys.FindKey('N',id,MBC3('N','1',KEYSET(0,pkey,0)))))
	{ m_DLLInstance.Log("[Nagra]N %X N1 PK%d key error!.\n",id,pkey);return;}
	
	pk->Get(n1);
	unsigned char v[8];
	if(!(pk=keys.FindKey('N',id,ADDC3('V',KEYSET(0,0,0))))) {m_DLLInstance.Log("[Nagra]N %X V key error!.\n",id,pkey);return;}
	
	pk->Get(v);
	int mode=(buffer[12]&8) ? 0:2;
	unsigned char emmdata[64];
	bool r;
	do {
		switch(mode) {
		case 0: // get the ROM10 specific management key
			if(!(pk=keys.FindKey('N',id,MBC3('N','2',KEYSET(10,0,1))))) {m_DLLInstance.Log("[Nagra]N %X N2 ROM10 key error!.\n",id,0);return;}
			
			pk->Get(n2);
			mode=1; break;
		case 1: // get the ROM11 specific management keys
			if(!(pk=keys.FindKey('N',id,MBC3('E','1',KEYSET(11,pkey,1))))) {m_DLLInstance.Log("[Nagra]N %X E1 ROM11 PK%d key error!.\n",id,pkey);return;}
			
			pk->Get(e1);
			if(!(pk=keys.FindKey('N',id,MBC3('N','1',KEYSET(11,pkey,1))))) {m_DLLInstance.Log("[Nagra]N %X N1 ROM11 PK%d key error!.\n",id,pkey);return;}
			
			pk->Get(n1);
			if(!(pk=keys.FindKey('N',id,MBC3('N','2',KEYSET(11,0,1))))) {m_DLLInstance.Log("[Nagra]N %X N2 ROM11 PK%d key error!.\n",id,pkey);return;}
			
			pk->Get(n2);
			if(!(pk=keys.FindKey('N',id,ADDC3('V',KEYSET(11,0,1))))) {m_DLLInstance.Log("[Nagra]N %X V ROM11 key error!.\n",id,pkey);return;}
			pk->Get(v);
			mode=-1; break;
		case 2: // get the universal management key
			
			if(!(pk=keys.FindKey('N',id,MBC3('N','2',KEYSET(0,0,0)))))  {m_DLLInstance.Log("[Nagra]N %X N2 key error!.\n",id,pkey);return;}
			pk->Get(n2);
			mode=-1; break;
		}
		r=DecryptEMM(&buffer[12],emmdata,v,64,e1,n1,n2);
    } while(!r && mode>=0);
	if(r) {
		unsigned int i=0;
		bool gotKeys=false;
		unsigned char key0[8], key1[8];
		int keyId=(emmdata[i+1]<<8)+emmdata[i+2];
		switch(emmdata[i]) { // Check filter type
		case 0x00: // One card
			i+=7; break;
		case 0x01 ... 0x20: // Group of cards
			i+=emmdata[i]+0x7; break;
		case 0x3e:
			i+=6; break;
		case 0x3d: // All cards with same system ID
		case 0x3f:
			i+=3; break;
		}
        bool PolsatPatch=false;
		if((emmdata[42]) == 0x42) // dirty patch for 7001
		{ int keys=0;
    		if(emmdata[43]==0x05)
	       	{ memcpy(key0,&emmdata[44],sizeof(key0));
    	   	memcpy(key1,&emmdata[54],sizeof(key1));
	       	}
    		else
	       	{ memcpy(key1,&emmdata[44],sizeof(key1));
        		memcpy(key0,&emmdata[54],sizeof(key0));
		     }
		
		keys++;keys++;//gotKeys=true;
		if (keyId==0x7001) keyId=0x7101;
        PolsatPatch=true;
		}
		int nrKeys=0;
		while(i<sizeof(emmdata)) {
           // m_DLLInstance.Log("emmdata[i]=0x%x i=%d\n",emmdata[i],i);
			if((emmdata[i]&0xF0)==0xF0) { // Update with CPU code
				const int romNr=emmdata[i]&0x0F;
				if(!emu || !emu->Matches(romNr,id)) {
					delete emu; emu=0;
					//          dl(m_DLLInstance.Log("logger-nagra %d: setting defaults for ROM%d\n",CardNum(),romNr))
					switch(romNr) {
					case 3:  emu=new cEmuRom3;  break;
					case 7:  emu=new cEmuRom7;  break;
					case 10: emu=new cEmuRom10; break;
					case 11: emu=new cEmuRom11; break;
					default:  m_DLLInstance.Log("[Nagra]Unsupported ROM!.\n",id,pkey);//dl(m_DLLInstance.Log("logger-nagra %d: unsupported ROM\n",CardNum())) return;
					}
					if(!emu || !emu->Init(romNr,id)) {
						delete emu; emu=0;
						//            dl(m_DLLInstance.Log("logger-nagra %d: initialization failed for ROM %d\n",CardNum(),romNr))
						return;
					}
				}
				unsigned char ki[2];
                if(!PolsatPatch)
				if((gotKeys=emu->GetOpKeys(emmdata,ki,key0,key1))) {

					keyId=(ki[0]<<8)+ki[1];
					m_DLLInstance.Log("N %04X 00 %.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X\n",keyId,key0[0],key0[1],key0[2],key0[3],key0[4],key0[5],key0[6],key0[7]);
					m_DLLInstance.Log("N %04X 01 %.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X\n",keyId,key1[0],key1[1],key1[2],key1[3],key1[4],key1[5],key1[6],key1[7]);
					
					cPlainKey* pk;
					unsigned char oldkey[8];
					
					if(!(pk=FastKey('N',keyId,0)))
						m_DLLInstance.SaveKey('N',keyId,0,key0,8,NULL);
					else
					{
						pk->Get(oldkey);
						m_DLLInstance.SaveKey('N',keyId,0,key0,8,oldkey);
					}
					if(!(pk=FastKey('N',keyId,1)))
						m_DLLInstance.SaveKey('N',keyId,1,key1,8,NULL);
					else
					{
						pk->Get(oldkey);
						m_DLLInstance.SaveKey('N',keyId,1,key1,8,oldkey);
					}
					
					keys.NewKey('N',keyId,00,key0,8);
					keys.NewKey('N',keyId,01,key1,8);
					//m_DLLInstance.SaveKey('N',keyId,0,key0,8);
					//m_DLLInstance.SaveKey('N',keyId,1,key1,8);
					m_DLLInstance.FoundKey();

					
				}
				unsigned char select[3], pkset[3][15];
				select[0]=(keyId>>8)|0x01; // always high id for ECM RSA keys
				select[1]=keyId&0xFF;
				select[2]=0; // type 0
				
				if(emu->GetPkKeys(&select[0],&pkset[0][0])) {
					int pkKeyId=((select[0]<<8)+select[1]);
					dn(m_DLLInstance.Log("[Nagra]PK keys for %04X (ROM %d) catched.\n",pkKeyId,romNr));
					char str[128+1];
					for(int i=0; i<3; i++) {
						ComputeRSAKeys(pkset[i],0,n1,e1);
						dn(HexStr(str,(unsigned char *)(void*)n1,64))
							dn(m_DLLInstance.Log("N %04X N1 PK%d ROM%d %s\n",pkKeyId,i,romNr,str));
						dn(HexStr(str,(unsigned char *)(void*)e1,64))
							dn(m_DLLInstance.Log("N %04X E1 PK%d ROM%d %s\n",pkKeyId,i,romNr,str));
						//            cSoftCAM::FoundKey();
						//            if(keys.NewKey('N',pkKeyId,ADDC3(MBC('N','1'),KEYSET(0,i,0)),(void *)n1,64)) cSoftCAM::NewKey();
						//            cSoftCAM::FoundKey();
						//            if(keys.NewKey('N',pkKeyId,ADDC3(MBC('E','1'),KEYSET(0,i,0)),(void *)e1,64)) cSoftCAM::NewKey();
					}
					//           cSoftCAM::Save();
				}
				break; // don't process other nanos
			}
			else if(emmdata[i]==0x60) { // NULL nano
				i+=2;
			}
			else if(emmdata[i]==0x00) {
				i++;
			}
			else if(emmdata[i]==0x81) {
				i++;
			}
			else if(emmdata[i]==0x83) {
				keyId=(emmdata[i+1]<<8)+emmdata[i+2];
				i+=3;
			}
			else if(emmdata[i]==0x42) { // plain Key
				m_DLLInstance.Log("Plain Key Processing 0x%x\n",emmdata[i+1]);
				if(emmdata[i+1]==0x05) memcpy(key0,&emmdata[i+2],sizeof(key0));
				else                   memcpy(key1,&emmdata[i+2],sizeof(key1));
				i+=10;
				
				if(++nrKeys==2) {
					gotKeys=true;
					m_DLLInstance.Log("[Nagra]Keys for %04X (plain) getted.\n",keyId);

					m_DLLInstance.Log("N %04X 00 %.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X\n",keyId,key0[0],key0[1],key0[2],key0[3],key0[4],key0[5],key0[6],key0[7]);
					m_DLLInstance.Log("N %04X 01 %.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X\n",keyId,key1[0],key1[1],key1[2],key1[3],key1[4],key1[5],key1[6],key1[7]);
					cPlainKey* pk;
					unsigned char oldkey[8];
					
					if(!(pk=FastKey('N',keyId,0)))
						m_DLLInstance.SaveKey('N',keyId,0,key0,8,NULL);
					else
					{
						pk->Get(oldkey);
						m_DLLInstance.SaveKey('N',keyId,0,key0,8,oldkey);
					}
					if(!(pk=FastKey('N',keyId,1)))
						m_DLLInstance.SaveKey('N',keyId,1,key1,8,NULL);
					else
					{
						pk->Get(oldkey);
						m_DLLInstance.SaveKey('N',keyId,1,key1,8,oldkey);
					}
					
					keys.NewKey('N',keyId,00,key0,8);
					keys.NewKey('N',keyId,01,key1,8);
					//m_DLLInstance.SaveKey('N',keyId,0,key0,8);
					//m_DLLInstance.SaveKey('N',keyId,1,key1,8);
					m_DLLInstance.FoundKey();
					
					break;
				}
			}
			else {
				//dl(m_DLLInstance.Log("logger-nagra %d: ignored nano %02x\n",CardNum(),emmdata[i]))
				break;
			}
      }
	  if(PolsatPatch) {
		  //      cSoftCAM::FoundKey();
		  //      if(keys.NewKey('N',keyId,00,key0,8)) cSoftCAM::NewKey();
		  //      cSoftCAM::FoundKey();
		  //      if(keys.NewKey('N',keyId,01,key1,8)) cSoftCAM::NewKey();
		  //      cSoftCAM::Save();
//					keyId=(ki[0]<<8)+ki[1];
					m_DLLInstance.Log("N %04X 00 %.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X\n",keyId,key0[0],key0[1],key0[2],key0[3],key0[4],key0[5],key0[6],key0[7]);
					m_DLLInstance.Log("N %04X 01 %.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X\n",keyId,key1[0],key1[1],key1[2],key1[3],key1[4],key1[5],key1[6],key1[7]);
					
					cPlainKey* pk;
					unsigned char oldkey[8];
					
					if(!(pk=FastKey('N',keyId,0)))
						m_DLLInstance.SaveKey('N',keyId,0,key0,8,NULL);
					else
					{
						pk->Get(oldkey);
						m_DLLInstance.SaveKey('N',keyId,0,key0,8,oldkey);
					}
					if(!(pk=FastKey('N',keyId,1)))
						m_DLLInstance.SaveKey('N',keyId,1,key1,8,NULL);
					else
					{
						pk->Get(oldkey);
						m_DLLInstance.SaveKey('N',keyId,1,key1,8,oldkey);
					}
					
					keys.NewKey('N',keyId,00,key0,8);
					keys.NewKey('N',keyId,01,key1,8);
					//m_DLLInstance.SaveKey('N',keyId,0,key0,8);
					//m_DLLInstance.SaveKey('N',keyId,1,key1,8);
					m_DLLInstance.FoundKey();		  
      }
    }
}

// -- cSystemLinkNagra ---------------------------------------------------------

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

static cSystemLinkNagra staticInit;

cSystemLinkNagra::cSystemLinkNagra(void)
:cSystemLink(SYSTEM_NAME,SYSTEM_PRI)
{
	opts=new cOpts(SYSTEM_NAME,1);
	opts->Add(new cOptInt("MinEcmTime","Nagra: min. ECM processing time",&minEcmTime,0,5000));
}

bool cSystemLinkNagra::CanHandle(unsigned short SysId)
{
	return SYSTEM_CAN_HANDLE(SysId);
}

#endif //TESTER

#else //#if defined(NAGRA) && defined(OPENSSL)

// add dummy loader for Nagra keys
#include "system-common.h"
static cPlainKeyTypeReg<cPlainKeyDummy,'N'> KeyReg;

#endif //#if defined(NAGRA) && defined(OPENSSL)
