]> www.vanbest.org Git - sasc-ng.git/commitdiff
release 0.8.4 0.8.4
authorleslie <unknown>
Thu, 27 Dec 2007 21:01:12 +0000 (22:01 +0100)
committerleslie <unknown>
Thu, 27 Dec 2007 21:01:12 +0000 (22:01 +0100)
12 files changed:
HISTORY
examples/smartcard.conf.example
po/fi_FI.po
po/ru_RU.po
systems/nagra/cpu.c
systems/nagra/cpu.h
systems/nagra/nagra2-0101.c
systems/nagra/nagra2-4101.c
systems/sc-videoguard2/sc-videoguard2.c [new file with mode: 0644]
systems/sc-videoguard2/sc-videoguard2.mk [new file with mode: 0644]
systems/viaccess/tps.c
version.h

diff --git a/HISTORY b/HISTORY
index e8cdfbc4d9414189dd344c0e9047476e2bffd4fd..f503e82b506045005f95037467175e475ae7d3e4 100644 (file)
--- a/HISTORY
+++ b/HISTORY
@@ -1,6 +1,16 @@
 VDR Plugin 'sc' Revision History
 --------------------------------
 
+30.09.2007: Version 0.8.4
+- Added Nagra2 4101 Bx support.
+- Added smartcard NDS Videoguard2 code (untested). Needs NDS seed and boxid in
+  smartcard.conf (later only for newer card revisions). This is based on code
+  from sasc-ng changeset r155.
+- Fixed Nagra cpu emu SetPc and ReadHandler call.
+- Fixed crash on TPS ECM when no TPS encryption is used and no TPS AU keys are
+  available (null pointer dereference).
+- Updated russian and finnish translations.
+
 15.09.2007: Version 0.8.3
 - Added Nagra2 3101 MECM (same as 0501).
 - Added Nagra2 0901 new 0x40 MECM and rev248 morph (from sasc-ng).
index 7c7bf4c5629ea483f1ec38dc1953dc06f02db51d..c2fb4a2bd9bb90fb5d2b97f37d3082e55dbc0452 100644 (file)
@@ -2,6 +2,20 @@
 ; Comment lines can start with # or ;
 ;
 
+; Videoguard2
+;
+; NDS seed and boxid for camcrypt
+;
+; boxid  - boxid (4 byte = 8 chars)
+; seed1  - 1st seed (64 byte = 128 chars)
+; seed2  - 2nd seed (64 byte = 128 chars)
+;
+; videoguard2: SEED seed1 seed2
+videoguard2: SEED 00112233445566778899001122334455667788990011223344556677889900112233445566778899001122334455667788990011223344556677889900112233 00112233445566778899001122334455667788990011223344556677889900112233445566778899001122334455667788990011223344556677889900112233
+;
+; videoguard2: BOXID boxid
+videoguard2: BOXID 11223344
+
 ; Nagra
 ;
 ; boxkey and modulus for camcrypt
index ff89dd7592ad18a5242edac5773db32cff268c6e..0176172ff7703ea9ffc9b577293343be5415d89c 100644 (file)
@@ -182,7 +182,7 @@ msgstr "Tiedoston nimi"
 
 #: sc.c:1210
 msgid "Filesize limit (KB)"
-msgstr ""
+msgstr "Tiedoston maksimikoko (KB)"
 
 #: sc.c:1211
 msgid "Log to syslog"
index a39a04be2a11f0d26f555582e52d1f3683160de8..a17c015f848050de73cff30928d18907bf94bd7d 100644 (file)
@@ -7,12 +7,13 @@ msgstr ""
 "Project-Id-Version: VDR 1.5.9\n"
 "Report-Msgid-Bugs-To: <noone@nowhere.org>\n"
 "POT-Creation-Date: 2007-09-13 19:11+0200\n"
-"PO-Revision-Date: 2007-08-27 12:45+0200\n"
-"Last-Translator: somebody\n"
-"Language-Team: somebody\n"
+"PO-Revision-Date: 2007-09-17 23:03+0200\n"
+"Last-Translator: <bikalexander@gmail.com>\n"
+"Language-Team: ru\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=ISO-8859-5\n"
 "Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.11.4\n"
 
 #: sc.c:117 sc.c:1198
 msgid "off"
@@ -182,7 +183,7 @@ msgstr "
 
 #: sc.c:1210
 msgid "Filesize limit (KB)"
-msgstr ""
+msgstr "»ØÜØâ äÐÙÛР(Ú±)"
 
 #: sc.c:1211
 msgid "Log to syslog"
@@ -198,44 +199,45 @@ msgstr "
 
 #: systems/cardclient/cc.c:241
 msgid "Cardclient: connect immediately"
-msgstr ""
+msgstr "Cardclient: áÞÕÔØÝØâì ÝÕÜÕÔÛÕÝÝÞ"
 
 #: systems/nagra/nagra1.c:977
 msgid "Nagra: min. ECM processing time"
-msgstr ""
+msgstr "Nagra: ÜØÝ. ÒàÕÜï ÞÑàÐÑÞâÚØ ECM"
 
 #: systems/sc-cryptoworks/sc-cryptoworks.c:81
 msgid "don't touch"
-msgstr ""
+msgstr "ÝÕ Ø×ÜÕÝïâì"
 
 #: systems/sc-cryptoworks/sc-cryptoworks.c:82
 msgid "disable"
-msgstr ""
+msgstr "ÒëÚÛîçØâì"
 
 #: systems/sc-cryptoworks/sc-cryptoworks.c:86
 msgid "SC-Cryptoworks: Parental rating"
-msgstr ""
+msgstr "SC-Cryptoworks: ÒÞ×àÐáâÝÞÕ ÞÓàÐÝØçÕÝØÕ"
 
 #: systems/sc-seca/sc-seca.c:81
 msgid "allow ALL"
-msgstr ""
+msgstr "ÒáÕ àÐ×àÕèØâì"
 
 #: systems/sc-seca/sc-seca.c:82
 msgid "block UNIQUE"
-msgstr ""
+msgstr "ÑÛÞÚØàÞÒÐâì UNIQUE"
 
 #: systems/sc-seca/sc-seca.c:83
 msgid "block SHARED"
-msgstr ""
+msgstr "ÑÛÞÚØàÞÒÐâì SHARED"
 
 #: systems/sc-seca/sc-seca.c:84
 msgid "block ALL"
-msgstr ""
+msgstr "ÒáÕ ÑÛÞÚØàÞÒÐâì"
 
 #: systems/sc-seca/sc-seca.c:100
 msgid "SC-Seca: EMM updates"
-msgstr ""
+msgstr "SC-Seca: ÞÑÝÞÒÛÕÝØï EMM"
 
 #: systems/sc-seca/sc-seca.c:101
 msgid "SC-Seca: activate PPV"
-msgstr ""
+msgstr "SC-Seca: ÐÚâØÒØàÞÒÐâì PPV"
+
index cdb972b22d86ef6e268caab954f18cd0654f75ce..b283b00a40e9ef34146ee3d36894bfe451e12465 100644 (file)
@@ -35,7 +35,7 @@ cMapMem::cMapMem(unsigned short Offset, int Size)
   offset=Offset; size=Size;
   if((mem=(unsigned char *)malloc(size)))
     memset(mem,0,size);
-  PRINTF(L_SYS_EMU,"mapmem: new map off=%04x size=%04x",offset,size);
+//  PRINTF(L_SYS_EMU,"mapmem: new map off=%04x size=%04x",offset,size);
 }
 
 cMapMem::~cMapMem()
@@ -255,9 +255,9 @@ void c6805::SetSp(unsigned short SpHi, unsigned short SpLow)
   spLow   =SpLow;
 }
 
-void c6805::SetPc(unsigned short addr)
+void c6805::SetPc(unsigned short addr, unsigned char seg)
 {
-  pc=addr;
+  pc=addr; cr=seg;
 }
 
 void c6805::PopPc(void)
@@ -562,7 +562,7 @@ int c6805::Run(int max_count)
         case 0x0:                      // bit
         case 0x1:
           op=Get(pr,ea);
-          if(hasReadHandler) ReadHandler(cr,ea,op);
+          if(hasReadHandler) ReadHandler(pr,ea,op);
           CCLOGLBPUT("{%02x} ",op);
           break;
         case 0xA:                      // immediate
index 5e1cfeab7e859330de585c9180d87238b0bff4b0..9cf78523ac82abbf0da14273ff2eb79fb1385181 100644 (file)
@@ -140,7 +140,7 @@ protected:
   void SetMem(unsigned short addr, const unsigned char *data, int len, unsigned char seg=0);
   void ForceSet(unsigned short ea, unsigned char val, bool ro);
   void SetSp(unsigned short SpHi, unsigned short SpLow);
-  void SetPc(unsigned short addr);
+  void SetPc(unsigned short addr, unsigned char seg=0);
   unsigned short GetPc(void) const { return pc; }
   void PopPc(void);
   void PopCr(void);
index e543b320180ba8643872016406cbbb1cb7c16be9..b56fe1a165aea9b7c374d3ba1673bd724660e669 100644 (file)
@@ -286,7 +286,7 @@ void cN2Prov0101::WriteHandler(unsigned char seg, unsigned short ea, unsigned ch
     if(ea==0x05) {
       special05=(op&0x40)!=0;
       }
-    else if(ea==0x16) {
+    else if(ea==0x0a || ea==0x12 || ea==0x16) {
       unsigned char old=Get(ea);
       if(old&2) op=(old&~0x02) | (op&0x02);
       }
index f140b47878d0d8b6faa09d156ebe5516a8c08842..0abe6d68de972ff550479fd31038f2108f990328 100644 (file)
 
 // -- cN2Prov4101 ----------------------------------------------------------------
 
-class cN2Prov4101 : public cN2Prov {
+class cN2Prov4101 : public cN2Prov, public cN2Emu {
 protected:
+  virtual void WriteHandler(unsigned char seg, unsigned short ea, unsigned char &op);
 public:
-  cN2Prov4101(int Id, int Flags):cN2Prov(Id,Flags) {}
+  cN2Prov4101(int Id, int Flags);
   virtual bool PostProcAU(int id, unsigned char *data);
+  virtual int ProcessBx(unsigned char *data, int len, int pos);
   };
 
-static cN2ProvLinkReg<cN2Prov4101,0x4101,N2FLAG_POSTAU> staticPL4101;
+static cN2ProvLinkReg<cN2Prov4101,0x4101,N2FLAG_POSTAU|N2FLAG_Bx> staticPL4101;
+
+cN2Prov4101::cN2Prov4101(int Id, int Flags)
+:cN2Prov(Id,Flags)
+{
+  hasWriteHandler=true;
+}
 
 bool cN2Prov4101::PostProcAU(int id, unsigned char *data)
 {
@@ -47,13 +55,38 @@ bool cN2Prov4101::PostProcAU(int id, unsigned char *data)
   return true;
 }
 
-// -- cN2Prov7101 ----------------------------------------------------------------
+int cN2Prov4101::ProcessBx(unsigned char *data, int len, int pos)
+{
+  if(Init(id,110)) {
+    SetMem(0x80,data,len);
+    SetPc(0x80+pos);
+    SetSp(0x0FFF,0x0FE0);
+    ClearBreakpoints();
+    AddBreakpoint(0xACDD);
+    AddBreakpoint(0x9BDD);
+    while(!Run(5000)) {
+      switch(GetPc()) {
+        case 0xACDD:
+          return -1;
+        case 0x9BDD:
+          GetMem(0x80,data,len);
+          return a;
+        }
+      }
+    }
+  return -1;
+}
 
-/*
-class cN2Prov7101 : public cN2Prov4101 {
-public:
-  cN2Prov7101(int Id, int Flags):cN2Prov4101(Id,Flags) {}
-  };
-*/
+void cN2Prov4101::WriteHandler(unsigned char seg, unsigned short ea, unsigned char &op)
+{
+  if(cr==0x00) {
+    if(ea==0x0a || ea==0x12 || ea==0x16) {
+      unsigned char old=Get(ea);
+      if(old&2) op=(old&~0x02) | (op&0x02);
+      }
+    }
+}
+
+// -- cN2Prov7101 ----------------------------------------------------------------
 
 static cN2ProvLinkReg<cN2Prov4101,0x7101,N2FLAG_POSTAU> staticPL7101;
diff --git a/systems/sc-videoguard2/sc-videoguard2.c b/systems/sc-videoguard2/sc-videoguard2.c
new file mode 100644 (file)
index 0000000..05b4c84
--- /dev/null
@@ -0,0 +1,801 @@
+/*
+ * 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 "system-common.h"
+#include "smartcard.h"
+#include "data.h"
+#include "crypto.h"
+#include "misc.h"
+#include "parse.h"
+#include "helper.h"
+#include "log-sc.h"
+
+#define SYSTEM_VIDEOGUARD2   0x0900
+
+#define SYSTEM_NAME          "SC-VideoGuard2"
+#define SYSTEM_PRI           -5
+
+#define SC_NAME "VideoGuard2"
+#define SC_ID   MAKE_SC_ID('V','i','G','2')
+
+#define L_SC        17
+#define L_SC_ALL    LALL(L_SC_LASTDEF)
+
+static const struct LogModule lm_sc = {
+  (LMOD_ENABLE|L_SC_ALL)&LOPT_MASK,
+  (LMOD_ENABLE|L_SC_DEFDEF)&LOPT_MASK,
+  "sc-videoguard2",
+  { L_SC_DEFNAMES }
+  };
+ADD_MODULE(L_SC,lm_sc)
+
+// -- cSystemScVideoGuard2 ---------------------------------------------------------------
+
+class cSystemScVideoGuard2 : public cSystemScCore {
+public:
+  cSystemScVideoGuard2(void);
+  };
+
+cSystemScVideoGuard2::cSystemScVideoGuard2(void)
+:cSystemScCore(SYSTEM_NAME,SYSTEM_PRI,SC_ID,"SC VideoGuard2")
+{
+  hasLogger=true;
+}
+
+// -- cSystemLinkScVideoGuard2 --------------------------------------------------------
+
+class cSystemLinkScVideoGuard2 : public cSystemLink {
+public:
+  cSystemLinkScVideoGuard2(void);
+  virtual bool CanHandle(unsigned short SysId);
+  virtual cSystem *Create(void) { return new cSystemScVideoGuard2; }
+  };
+
+static cSystemLinkScVideoGuard2 staticInit;
+
+cSystemLinkScVideoGuard2::cSystemLinkScVideoGuard2(void)
+:cSystemLink(SYSTEM_NAME,SYSTEM_PRI)
+{}
+
+bool cSystemLinkScVideoGuard2::CanHandle(unsigned short SysId)
+{
+  bool res=false;
+  cSmartCard *card=smartcards.LockCard(SC_ID);
+  if(card) {
+    res=card->CanHandle(SysId);
+    smartcards.ReleaseCard(card);
+    }
+  return res;
+}
+
+// -- cSmartCardDataVideoGuard2 -----------------------------------------------------
+
+enum eDataType { dtBoxId, dtSeed };
+
+class cSmartCardDataVideoGuard2 : public cSmartCardData {
+public:
+  eDataType type;
+  unsigned char boxid[4];
+  unsigned char seed0[64], seed1[64];
+  //
+  cSmartCardDataVideoGuard2(void);
+  cSmartCardDataVideoGuard2(eDataType Type);
+  virtual bool Parse(const char *line);
+  virtual bool Matches(cSmartCardData *param);
+  };
+
+cSmartCardDataVideoGuard2::cSmartCardDataVideoGuard2(void)
+:cSmartCardData(SC_ID)
+{}
+
+cSmartCardDataVideoGuard2::cSmartCardDataVideoGuard2(eDataType Type)
+:cSmartCardData(SC_ID)
+{
+  type=Type;
+}
+
+bool cSmartCardDataVideoGuard2::Matches(cSmartCardData *param)
+{
+  cSmartCardDataVideoGuard2 *cd=(cSmartCardDataVideoGuard2 *)param;
+  return cd->type==type;
+}
+
+bool cSmartCardDataVideoGuard2::Parse(const char *line)
+{
+  line=skipspace(line);
+  if(!strncasecmp(line,"BOXID",5))     { type=dtBoxId; line+=5; }
+  else if(!strncasecmp(line,"SEED",4)) { type=dtSeed; line+=4; }
+  else {
+    PRINTF(L_CORE_LOAD,"smartcarddatavideoguard2: format error: datatype");
+    return false;
+    }
+  line=skipspace(line);
+  switch(type) {
+    case dtBoxId:
+      if(GetHex(line,boxid,sizeof(boxid))!=sizeof(boxid)) {
+        PRINTF(L_CORE_LOAD,"smartcarddatavideoguard2: format error: boxid");
+        return false;
+        }
+      break;
+    case dtSeed:
+      if(GetHex(line,seed0,sizeof(seed0))!=sizeof(seed0)) {
+        PRINTF(L_CORE_LOAD,"smartcarddatacryptoworks: format error: seed0");
+        return false;
+        }
+      line=skipspace(line);
+      if(GetHex(line,seed1,sizeof(seed1))!=sizeof(seed1)) {
+        PRINTF(L_CORE_LOAD,"smartcarddatacryptoworks: format error: seed1");
+        return false;
+        }
+      break;
+    }
+  return true;
+}
+
+// -- cCamCryptVG2 -------------------------------------------------------------
+
+#define xor16(v1,v2,d) xxor((d),16,(v1),(v2))
+#define val_by2on3(x)  ((0xaaab*(x))>>16) //fixed point *2/3
+
+class cCamCryptVG2 : private cAES {
+private:
+  unsigned short cardkeys[3][32];
+  unsigned char stateD3A[16];
+  //
+  void LongMult(unsigned short *pData, unsigned short *pLen, unsigned int mult, unsigned int carry);
+  void PartialMod(unsigned short val, unsigned int count, unsigned short *outkey, const unsigned short *inkey);
+  void RotateRightAndHash(unsigned char *p);
+  void Reorder16A(unsigned char *dest, const unsigned char *src);
+  void ReorderAndEncrypt(unsigned char *p);
+  //
+  void Process_D0(const unsigned char *ins, unsigned char *data, const unsigned char *status);
+  void Process_D1(const unsigned char *ins, unsigned char *data, const unsigned char *status);
+  void Decrypt_D3(unsigned char *ins, unsigned char *data, const unsigned char *status);
+public:
+  void PostProcess_Decrypt(unsigned char *buff, int len, unsigned char *cw1, unsigned char *cw2);
+  void SetSeed(const unsigned char *Key1, const unsigned char *Key2);
+  void GetCamKey(unsigned char *buff);
+};
+
+void cCamCryptVG2::SetSeed(const unsigned char *Key1, const unsigned char *Key2)
+{
+  memcpy(cardkeys[1],Key1,sizeof(cardkeys[1]));
+  memcpy(cardkeys[2],Key2,sizeof(cardkeys[2]));
+}
+
+void cCamCryptVG2::GetCamKey(unsigned char *buff)
+{
+  unsigned short *tb2=(unsigned short *)buff, c=1;
+  memset(tb2,0,64);
+  tb2[0]=1;
+  for(int i=0; i<32; i++) LongMult(tb2,&c,cardkeys[1][i],0);
+}
+
+void cCamCryptVG2::PostProcess_Decrypt(unsigned char *buff, int len, unsigned char *cw1, unsigned char *cw2)
+{
+  switch(buff[0]) {
+    case 0xD0:
+      Process_D0(buff,buff+5,buff+buff[4]+5);
+      break;
+    case 0xD1:
+      Process_D1(buff,buff+5,buff+buff[4]+5);
+      break;
+    case 0xD3:
+      Decrypt_D3(buff,buff+5,buff+buff[4]+5);
+      if(buff[1]==0x54) {
+        memcpy(cw1,buff+5,8);
+        for(int ind=14; ind<len+5;) {
+          if(buff[ind]==0x25) {
+            memcpy(cw2,buff+5+ind+2,8);
+            break;
+            }
+          if(buff[ind+1]==0) break;
+          ind+=buff[ind+1];
+          }
+        }
+      break;
+    }
+}
+
+void cCamCryptVG2::Process_D0(const unsigned char *ins, unsigned char *data, const unsigned char *status)
+{
+  switch(ins[1]) {
+    case 0xb4:
+      memcpy(cardkeys[0],data,sizeof(cardkeys[0]));
+      break;
+    case 0xbc: 
+      {
+      unsigned short *idata=(unsigned short *)data;
+      const unsigned short *key1=(const unsigned short *)cardkeys[1];
+      unsigned short key2[32];
+      memcpy(key2,cardkeys[2],sizeof(key2));
+      for(int count2=0; count2<32; count2++) {
+        unsigned int rem=0, div=key1[count2];
+        for(int i=31; i>=0; i--) {
+          unsigned int x=idata[i] | (rem<<16);
+          rem=(x%div)&0xffff;
+          }
+        unsigned int carry=1, t=val_by2on3(div) | 1;
+        while(t) {
+          if(t&1) carry=((carry*rem)%div)&0xffff;
+          rem=((rem*rem)%div)&0xffff;
+          t>>=1;
+          }
+        PartialMod(carry,count2,key2,key1);
+        }
+      unsigned short idatacount=0;
+      for(int i=31; i>=0; i--) LongMult(idata,&idatacount,key1[i],key2[i]);
+      unsigned char stateD1[16];
+      Reorder16A(stateD1,data);
+      cAES::SetKey(stateD1);
+      break;
+      }
+  }
+}
+
+void cCamCryptVG2::Process_D1(const unsigned char *ins, unsigned char *data, const unsigned char *status)
+{
+  unsigned char iter[16], tmp[16];
+  memset(iter,0,sizeof(iter));
+  memcpy(iter,ins,5);
+  xor16(iter,stateD3A,iter);
+  memcpy(stateD3A,iter,sizeof(iter));
+
+  int datalen=status-data;
+  int datalen1=datalen;
+  if(datalen<0) datalen1+=15;
+  int blocklen=datalen1>>4;
+  for(int i=0,iblock=0; i<blocklen+2; i++,iblock+=16) {
+    unsigned char in[16];
+    if(blocklen==i) {
+      memset(in,0,sizeof(in));
+      memcpy(in,&data[iblock],datalen-(datalen1&~0xf));
+      }
+    else if(blocklen+1==i) {
+      memset(in,0,sizeof(in));
+      memcpy(&in[5],status,2);
+      }
+    else
+      memcpy(in,&data[iblock],sizeof(in));
+
+    xor16(iter,in,tmp);
+    ReorderAndEncrypt(tmp);
+    xor16(tmp,stateD3A,iter);
+    }
+  memcpy(stateD3A,tmp,16);
+}
+
+void cCamCryptVG2::Decrypt_D3(unsigned char *ins, unsigned char *data, const unsigned char *status)
+{
+  if(ins[4]>16) ins[4]-=16;
+  if(ins[1]==0xbe) memset(stateD3A,0,sizeof(stateD3A));
+
+  unsigned char tmp[16];
+  memset(tmp,0,sizeof(tmp));
+  memcpy(tmp,ins,5);
+  xor16(tmp,stateD3A,stateD3A);
+
+  int len1=ins[4];
+  int blocklen=len1>>4;
+  if(ins[1]!=0xbe) blocklen++;
+
+  unsigned char iter[16], states[16][16];
+  memset(iter,0,sizeof(iter));
+  for(int blockindex=0; blockindex<blocklen; blockindex++) {
+    iter[0]+=blockindex;
+    xor16(iter,stateD3A,iter);
+    ReorderAndEncrypt(iter);
+    xor16(iter,&data[blockindex*16],states[blockindex]);
+    if(blockindex==(len1>>4)) {
+      int c=len1-(blockindex*16);
+      if(c<16) memset(&states[blockindex][c],0,16-c);
+      }
+    xor16(states[blockindex],stateD3A,stateD3A);
+    RotateRightAndHash(stateD3A);
+    }
+  memset(tmp,0,sizeof(tmp));
+  memcpy(tmp+5,status,2);
+  xor16(tmp,stateD3A,stateD3A);
+  ReorderAndEncrypt(stateD3A);
+
+  memcpy(stateD3A,status-16,sizeof(stateD3A));
+  ReorderAndEncrypt(stateD3A);
+
+  memcpy(data,states[0],len1);
+  if(ins[1]==0xbe) {
+    Reorder16A(tmp,states[0]);
+    cAES::SetKey(tmp);
+    }
+}
+
+void cCamCryptVG2::ReorderAndEncrypt(unsigned char *p)
+{
+  unsigned char tmp[16];
+  Reorder16A(tmp,p); cAES::Encrypt(tmp,16,tmp); Reorder16A(p,tmp);
+}
+
+// reorder AAAABBBBCCCCDDDD to ABCDABCDABCDABCD
+
+void cCamCryptVG2::Reorder16A(unsigned char *dest, const unsigned char *src)
+{
+  for(int i=0,k=0; i<4; i++)
+    for(int j=i; j<16; j+=4,k++)
+      dest[k]=src[j];
+}
+
+void cCamCryptVG2::LongMult(unsigned short *pData, unsigned short *pLen, unsigned int mult, unsigned int carry)
+{
+  for(int i=0; i<*pLen; i++) {
+    carry+=pData[i]*mult;
+    pData[i]=(unsigned short)carry;
+    carry>>=16;
+    }
+  if(carry) pData[*pLen++]=carry;
+}
+
+void cCamCryptVG2::PartialMod(unsigned short val, unsigned int count, unsigned short *outkey, const unsigned short *inkey)
+{
+  if(count) {
+    unsigned int mod=inkey[count];
+    unsigned short mult=(inkey[count]-outkey[count-1])&0xffff;
+    for(unsigned int i=0,ib1=count-2; i<count-1; i++,ib1--) {
+      unsigned int t=(inkey[ib1]*mult)%mod;
+      mult=t-outkey[ib1];
+      if(mult>t) mult+=mod;
+      }
+    mult+=val;
+    if((val>mult) || (mod<mult)) mult-=mod;
+    outkey[count]=(outkey[count]*mult)%mod;
+    }
+  else 
+    outkey[0]=val;
+}
+
+static const unsigned char table1[256] = {
+  0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5, 0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
+  0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0, 0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
+  0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc, 0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
+  0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a, 0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
+  0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0, 0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
+  0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b, 0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
+  0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85, 0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
+  0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5, 0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
+  0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17, 0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
+  0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88, 0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
+  0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c, 0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
+  0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9, 0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
+  0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6, 0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
+  0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e, 0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
+  0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94, 0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
+  0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68, 0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16,
+  };
+
+void cCamCryptVG2::RotateRightAndHash(unsigned char *p)
+{
+  for(unsigned int i=0; i<16; i++)
+    p[i]=table1[ (p[i]>>1) | ((p[(i-1)&15]&1)<<7) ];
+}
+
+// -- cCmdTable ----------------------------------------------------------------
+
+struct CmdTabEntry {
+  unsigned char cla;
+  unsigned char cmd;
+  unsigned char len;
+  unsigned char mode;
+};
+
+struct CmdTab {
+  unsigned char index;
+  unsigned char size;
+  unsigned char Nentries; 
+  unsigned char dummy;
+  CmdTabEntry e[0];
+};
+
+class CmdTable {
+private:
+  struct CmdTab *tab;
+public:
+  CmdTable(const unsigned char *mem, int size);
+  ~CmdTable();
+  bool GetInfo(const unsigned char *cmd, unsigned char &rlen, unsigned char &rmode);
+  };
+
+CmdTable::CmdTable(const unsigned char *mem, int size)
+{
+  tab=(struct CmdTab *)new unsigned char[size];
+  memcpy(tab,mem,size);
+}
+
+CmdTable::~CmdTable()
+{
+  delete tab;
+}
+
+bool CmdTable::GetInfo(const unsigned char *cmd, unsigned char &rlen, unsigned char & rmode)
+{
+  struct CmdTabEntry *pcte=tab->e;
+  for(int i=0; i<tab->Nentries; i++,pcte++)
+    if(cmd[1]==pcte->cmd) {
+      rlen=pcte->len;
+      rmode=pcte->mode;
+      return true;
+      }
+  return false;
+}
+
+// -- cSmartCardVideoGuard2 -----------------------------------------------------------
+
+#define BASEYEAR 2000 // for date calculation
+
+class cSmartCardVideoGuard2 : public cSmartCard, private cIdSet {
+private:
+  unsigned char CW1[8], CW2[8];
+  unsigned char cardID[4], groupID[4], boxID[4];
+  unsigned int CAID;
+  cCamCryptVG2 state;
+  CmdTable *cmdList;
+public:
+  void ReadTiers(void);
+  void RevDateCalc(const unsigned char *Date, int &year, int &mon, int &day, int &hh, int &mm, int &ss);
+  void Datecalc(int year, int mon, int day, int hh, int mm, int ss , unsigned char * Date);
+  int DoCmd(const unsigned char *ins, const unsigned char *txbuff=0, unsigned char *rxbuff=0);
+  int ReadCmdLen(const unsigned char *cmd);
+public:
+  cSmartCardVideoGuard2(void);
+  ~cSmartCardVideoGuard2();
+  virtual bool Init(void);
+  virtual bool Decode(const cEcmInfo *ecm, const unsigned char *data, unsigned char *cw);
+  virtual bool Update(int pid, int caid, const unsigned char *data);
+  virtual bool CanHandle(unsigned short SysId);
+  };
+
+static const struct StatusMsg msgs[] = {
+  { { 0x90,0x00 }, "Instruction executed without errors, noupdate, nofilter, IRD# not set", true },
+  { { 0x90,0x01 }, "Instruction executed without errors, update, nofilter, IRD# not set", true },
+  { { 0x90,0x20 }, "Instruction executed without errors, noupdate, nofilter, IRD# set", true },
+  { { 0x90,0x21 }, "Instruction executed without errors, update, nofilter, IRD# set", true },
+  { { 0x90,0x80 }, "Instruction executed without errors, noupdate, filter, IRD# not set", true },
+  { { 0x90,0x81 }, "Instruction executed without errors, update, filter, IRD# not set", true },
+  { { 0x90,0xa0 }, "Instruction executed without errors, noupdate, filter, IRD# set", true },
+  { { 0x90,0xa1 }, "Instruction executed without errors, update, filter, IRD# set", true },
+  { { 0x91,0x00 }, "Instruction executed without errors, noupdate, nofilter, IRD# not set", true },
+  { { 0x91,0x01 }, "Instruction executed without errors, update, nofilter, IRD# not set", true },
+  { { 0x91,0x20 }, "Instruction executed without errors, noupdate, nofilter, IRD# set", true },
+  { { 0x91,0x21 }, "Instruction executed without errors, update, nofilter, IRD# set", true },
+  { { 0x91,0x80 }, "Instruction executed without errors, noupdate, filter, IRD# not set", true },
+  { { 0x91,0x81 }, "Instruction executed without errors, update, filter, IRD# not set", true },
+  { { 0x91,0xa0 }, "Instruction executed without errors, noupdate, filter, IRD# set", true },
+  { { 0x91,0xa1 }, "Instruction executed without errors, update, filter, IRD# set", true },
+  { { 0xFF,0xFF }, 0, false }
+  };
+
+static const struct CardConfig cardCfg = {
+  SM_8O2,2000,1400
+  };
+
+cSmartCardVideoGuard2::cSmartCardVideoGuard2(void)
+:cSmartCard(&cardCfg,msgs)
+{
+  cmdList=0;
+}
+
+cSmartCardVideoGuard2::~cSmartCardVideoGuard2(void)
+{
+  delete cmdList;
+}
+
+bool cSmartCardVideoGuard2::CanHandle(unsigned short SysId)
+{
+  return SysId==CAID;
+}
+
+bool cSmartCardVideoGuard2::Init(void)
+{
+  static const unsigned char vg2Hist[] = { 'i',0xff,'J','P' };
+  if(atr->histLen<4 || memcmp(&atr->hist[3],vg2Hist,4)) {
+    PRINTF(L_SC_INIT,"doesn't look like a VideoGuard2 card");
+    return false;
+    }
+
+  infoStr.Begin();
+  infoStr.Strcat("VideoGuard2 smartcard\n");
+  snprintf(idStr,sizeof(idStr),"%s (%c%c.%d)",SC_NAME,atr->hist[10],atr->hist[11],atr->hist[12]);
+  
+  ResetIdSet();
+  delete cmdList; cmdList=0;
+
+  static unsigned char ins7401[] = { 0xD0,0x74,0x01,0x00,0x00 };
+  int l;
+  if((l=ReadCmdLen(ins7401))<0) return false;
+  ins7401[4]=l;
+  unsigned char buff[256];
+  if(!IsoRead(ins7401,buff) || !Status()) {
+    PRINTF(L_SC_ERROR,"failed to read cmd list");
+    return false;
+    }
+  cmdList=new CmdTable(buff,l);
+
+  static const unsigned char ins7416[5] = { 0xD0,0x74,0x16,0x00,0x00 };
+  if(DoCmd(ins7416)<0 || !Status()) {
+    PRINTF(L_SC_ERROR,"cmd 7416 failed");
+    return false;
+    }
+
+  static const unsigned char ins36[5] = { 0xD0,0x36,0x00,0x00,0x00 };
+  bool boxidOK=false;
+  if((l=DoCmd(ins36,0,buff))>0 && Status())
+    for(int i=0; i<l ;i++) {
+      if(buff[i]==0x00 && buff[i+1]==0xF3) {
+        memcpy(&boxID,&buff[i+2],sizeof(boxID));
+        boxidOK=true;
+        break;
+        }
+      }
+  if(!boxidOK) {
+    cSmartCardDataVideoGuard2 cd(dtBoxId);
+    cSmartCardDataVideoGuard2 *entry=(cSmartCardDataVideoGuard2 *)smartcards.FindCardData(&cd);
+    if(entry) {
+      memcpy(&boxID,entry->boxid,sizeof(boxID));
+      boxidOK=true;
+      }
+    }
+  if(!boxidOK) {
+    PRINTF(L_SC_ERROR,"no boxID available");
+    return false;
+    }
+
+  static const unsigned char ins4C[5] = { 0xD0,0x4C,0x00,0x00,0x00 }; 
+  static unsigned char payload4C[9] = { 0,0,0,0, 3,0,0,2,4 };
+  memcpy(payload4C,boxID,4);
+  if(DoCmd(ins4C,payload4C)<0 || !Status()) {
+    PRINTF(L_SC_ERROR,"sending boxid failed");
+    return false;
+    }
+
+  static const unsigned char ins58[5] = { 0xD0,0x58,0x00,0x00,0x00 };
+  if(DoCmd(ins58,0,buff)<0 || !Status()) {
+    PRINTF(L_SC_ERROR,"failed to read card details");
+    return false;
+    }
+  
+  CAID=WORD(buff,0x1D,0xFFFF);
+  memcpy(&cardID,&buff[8],4);
+  memcpy(&groupID,&buff[8],4); groupID[3]=0;
+  SetCard(new cCardNDS(cardID));
+  AddProv(new cProviderNDS(groupID));
+  char str[20], str2[20];
+  infoStr.Printf("Cardtype: %c%c.%d\n"
+                 "BoxID %s Caid %04x CardID %s\n",
+                 atr->hist[10],atr->hist[11],atr->hist[12],HexStr(str,boxID,4),CAID,HexStr(str2,cardID,4));
+  PRINTF(L_SC_INIT,"cardtype: %c%c.%d boxID %s caid %04x cardID %s",atr->hist[10],atr->hist[11],atr->hist[12],HexStr(str,boxID,4),CAID,HexStr(str2,cardID,4));
+
+  cSmartCardDataVideoGuard2 cd(dtSeed);
+  cSmartCardDataVideoGuard2 *entry=(cSmartCardDataVideoGuard2 *)smartcards.FindCardData(&cd);
+  if(!entry) {
+    PRINTF(L_SC_ERROR,"no NDS seed available");
+    return false;
+    }
+  state.SetSeed(entry->seed0,entry->seed1);
+  unsigned char tbuff[64];
+  state.GetCamKey(tbuff);
+
+  static const unsigned char insB4[5] = { 0xD0,0xB4,0x00,0x00,0x00 };
+  if(DoCmd(insB4,tbuff)<0 || !Status()) {
+    PRINTF(L_SC_ERROR,"cmd D0B4 failed");
+    return false;
+    }
+  
+  static const unsigned char insBC[5] = { 0xD0,0xBC,0x00,0x00,0x00 };
+  if(DoCmd(insBC)<0 || !Status()) {
+    PRINTF(L_SC_ERROR,"cmd D0BC failed");
+    return false;
+    }
+  static const unsigned char insBE[5] = { 0xD3,0xBE,0x00,0x00,0x00 };
+  if(DoCmd(insBE)<0 || !Status()) {
+    PRINTF(L_SC_ERROR,"cmd D3BE failed");
+    return false;
+    }
+
+  static const unsigned char ins58a[5] = { 0xD1,0x58,0x00,0x00,0x00 }; 
+  if(DoCmd(ins58a)<0 || !Status()) {
+    PRINTF(L_SC_ERROR,"cmd D158 failed");
+    return false;
+    }
+  static const unsigned char ins4Ca[5] = { 0xD1,0x4C,0x00,0x00,0x00 }; 
+  if(DoCmd(ins4Ca,payload4C)<0 || !Status()) {
+    PRINTF(L_SC_ERROR,"cmd D14C failed");
+    return false;
+    }
+  ReadTiers();
+  return true;
+}
+
+bool cSmartCardVideoGuard2::Decode(const cEcmInfo *ecm, const unsigned char *data, unsigned char *cw)
+{
+  static unsigned char ins40[5] = { 0xD1,0x40,0x40,0x80,0xFF };
+  static const unsigned char ins54[5] = { 0xD3,0x54,0x00,0x00,0x00 };
+  int posECMpart2=data[6]+7;
+  int lenECMpart2=data[posECMpart2]+1;
+  unsigned char tbuff[264];
+  tbuff[0]=0;
+  memcpy(&tbuff[1],&data[posECMpart2+1],lenECMpart2);
+  ins40[4]=lenECMpart2;
+  if(DoCmd(ins40,tbuff)>0 && Status()) {
+    if(DoCmd(ins54)>0 && Status()) {
+      if(data[0]&1) memcpy(cw+8,CW1,8);
+      else          memcpy(cw+0,CW1,8);
+      return true;
+      }
+    }
+  return false;
+}
+
+bool cSmartCardVideoGuard2::Update(int pid, int caid, const unsigned char *data)
+{
+  static unsigned char ins42[5] = { 0xD1,0x40,0x00,0x00,0xFF }; 
+  if(MatchEMM(data)) {
+    const unsigned char *payloaddata=cParseNDS::PayloadStart(data); //points to 02 xx yy
+    int lenEMM;
+    switch(payloaddata[0]) {
+      case 2:
+        lenEMM=payloaddata[payloaddata[1]+2];
+        payloaddata+=3+payloaddata[1]; // skip len specifier
+        break;
+      default:
+//        di(printf("scvg2: ***ERROR***: EMM: bad payload type byte %02x\n",payloaddata[0]));
+//        DUMP3("VG2EMM",data,32);
+        return false;
+      }
+//    di(printf("scvg2: EMM: pid %d (%x) caid %d (%x) len %d (%x)\n",pid,pid,caid,caid,lenEMM,lenEMM));
+//    DUMP3("VG2EMM",data,32);
+    if(lenEMM<=8 || lenEMM>188) {
+//      di(printf("scvg2: ***ERROR***: EMM: len %d bad\n",lenEMM));
+      return false;
+      }
+    ins42[4]=lenEMM;
+    if(DoCmd(ins42,payloaddata)>0 && Status())
+      return true;
+    }
+  return false;
+}
+
+/*
+bool cSmartCardVideoGuard2::ZKT(void)
+{
+  static unsigned char payload4A[1] = { 0x01 };
+  static unsigned char ins4A[5] = { 0xD0,0x4A,0x15,0x01,0x01 };
+  DoCmd(ins4A,payload4A);
+  static unsigned char ins5A1[5] = { 0xD0,0x5A,0x15,0x01,0x10 };
+  DoCmd(ins5A1);
+  static unsigned char ins5A2[5] = { 0xD0,0x5A,0x11,0x02,0x40 };
+  DoCmd(ins5A2);
+  DoCmd(ins4A,payload4A);
+  DoCmd(ins5A1);
+  static unsigned char ins5A3[5] = { 0xD0,0x5A,0x10,0x02,0x40 };
+  DoCmd(ins5A3);
+  return true;
+}
+*/
+
+void cSmartCardVideoGuard2::ReadTiers(void)
+{
+  static const unsigned char ins2a[5] = { 0xd0,0x2a,0x00,0x00,0x00 };
+  if(DoCmd(ins2a)<0 || !Status()) return;
+  static unsigned char ins76[5] = { 0xd0,0x76,0x00,0x00,0x00 };
+  ins76[3]=0x7f; ins76[4]=2;
+  unsigned char buff[270];
+  if(!IsoRead(ins76,buff) || !Status()) return;
+  ins76[3]=0; ins76[4]=0;
+  int num=buff[1];
+  if(num>0) {
+    infoStr.Strcat("Tier\tExpiry Date\n");
+    infoStr.Strcat("----\t-----------\n");
+    }
+  for(int i=0; i<num; i++) {
+    ins76[2]=i;
+    if(DoCmd(ins76,0,buff)<0 || !Status()) return;
+    if(buff[5+2]==0 && buff[5+3]==0) break;
+    int y,m,d,H,M,S;
+    RevDateCalc(&buff[5+4],y,m,d,H,M,S);
+    char str[100];
+    snprintf(str,sizeof(str),"%02x%02x\t%04d/%02d/%02d-%02d:%02d:%02d\n",buff[5+2],buff[5+3],y,m,d,H,M,S);
+    infoStr.Strcat(str);
+    PRINTF(L_SC_INIT,"Tier %02x%02x expires %04d/%02d/%02d-%02d:%02d:%02d",buff[5+2],buff[5+3],y,m,d,H,M,S);
+    }
+}
+
+void cSmartCardVideoGuard2::RevDateCalc(const unsigned char *Date, int &year, int &mon, int &day, int &hh, int &mm, int &ss)
+{ 
+  year=(Date[0]/12)+BASEYEAR; 
+  mon=(Date[0]%12)+1; 
+  day=Date[1]; 
+  hh=Date[2]/8; 
+  mm=(0x100*(Date[2]-hh*8)+Date[3])/32; 
+  ss=(Date[3]-mm*32)*2; 
+}
+
+void cSmartCardVideoGuard2::Datecalc(int year, int mon, int day, int hh, int mm, int ss , unsigned char *Date)
+{ 
+  Date[0]=((year-BASEYEAR)*12 + (mon-1)); 
+  Date[1]=day; 
+  Date[2]=hh*8+mm/8; 
+  Date[3]=ss/2+mm*32; 
+} 
+
+int cSmartCardVideoGuard2::DoCmd(const unsigned char *ins, const unsigned char *txbuff, unsigned char *rxbuff)
+{
+  unsigned char ins2[5];
+  memcpy(ins2,ins,5);
+  unsigned char len=0, mode=0;
+  if(cmdList->GetInfo(ins2,len,mode)) {
+    if(len==0xFF && mode==2) {
+      if(ins2[4]==0) ins2[4]=len=ReadCmdLen(ins2);
+      }
+    else if(mode!=0) ins2[4]=len;
+    }
+  if(ins2[0]==0xd3) ins2[4]=len+16;
+  len=ins2[4];
+
+  unsigned char b[256], tmp[264];
+  if(!rxbuff) rxbuff=tmp;
+  if(mode>1) {
+    if(!IsoRead(ins2,b) || !Status()) return -1;
+    memcpy(rxbuff,ins2,5);
+    memcpy(rxbuff+5,b,len);
+    memcpy(rxbuff+5+len,sb,2);
+    }
+  else {
+    if(!IsoWrite(ins2,txbuff) || !Status()) return -1;
+    memcpy(rxbuff,ins2,5);
+    memcpy(rxbuff+5,txbuff,len);
+    memcpy(rxbuff+5+len,sb,2);
+    }
+  state.PostProcess_Decrypt(rxbuff,len,CW1,CW2);
+  return len;
+}
+
+int cSmartCardVideoGuard2::ReadCmdLen(const unsigned char *cmd)
+{
+  unsigned char cmd2[5], resp[16];
+  memcpy(cmd2,cmd,5);
+  cmd2[3]=0x80;
+  cmd2[4]=1;
+  if(!IsoRead(cmd2,resp) || !Status()) {
+    PRINTF(L_SC_ERROR,"failed to read %02x%02x cmd length",cmd[1],cmd[2]);
+    return -1;
+    }
+  return resp[0];  
+}
+
+// -- cSmartCardLinkVG2 -------------------------------------------------------
+
+class cSmartCardLinkVG2 : public cSmartCardLink {
+public:
+  cSmartCardLinkVG2(void):cSmartCardLink(SC_NAME,SC_ID) {}
+  virtual cSmartCard *Create(void) { return new cSmartCardVideoGuard2; }
+  virtual cSmartCardData *CreateData(void) { return new cSmartCardDataVideoGuard2; }
+  };
+
+static cSmartCardLinkVG2 staticScInit;
diff --git a/systems/sc-videoguard2/sc-videoguard2.mk b/systems/sc-videoguard2/sc-videoguard2.mk
new file mode 100644 (file)
index 0000000..749aaeb
--- /dev/null
@@ -0,0 +1,5 @@
+#
+# Smartcard VideoGuard2
+#
+TARGET = sc_videoguard2
+OBJS   = sc-videoguard2.o
index 6989e43cda43e0472349d19744bf50a5a0052587..8bc8d018a17dfcee62283125827981687662f7c3 100644 (file)
@@ -1186,10 +1186,10 @@ int cTPS::Decrypt(int cardNum, int Source, int Transponder, unsigned char *data,
       case 0xEA:
         if(doPre) TpsDecrypt(&data[i+2],k->Mode(0),k->Key(0));
         if(doTPS) TpsDecrypt(&data[i+2],(hasDF)?k->Mode(2):1,k->Key(2));
+        if(doPost) { postMode=k->Mode(1); memcpy(postKey,k->Key(1),sizeof(postKey)); }
         break;
       }
     }
-  postMode=k->Mode(1); memcpy(postKey,k->Key(1),sizeof(postKey));
   return ret;
 }
 
index 058a93ad4641d6a140757ed1f5627f9bedb8eb6a..3bde6fe0a87d51926c9e15508f8218e9579fe182 100644 (file)
--- a/version.h
+++ b/version.h
@@ -21,8 +21,8 @@
 #define ___VERSION_H
 
 // all release versions must end with 0xFF !!
-#define SCVERSNUM 0x000803FF
-#define SCVERSION "0.8.3"
+#define SCVERSNUM 0x000804FF
+#define SCVERSION "0.8.4"
 
 extern const char *ScVersion;