]> git.zerfleddert.de Git - proxmark3-svn/blobdiff - winsrc/command.cpp
Add checks in em4x50read to avoid segfaults in case loops go over the GraphTraceLen.
[proxmark3-svn] / winsrc / command.cpp
index 1c434ebbd7d2ba317d6344e6b222285b47739e8b..454fbab2417b1ddff306eb647fee98ce602ce862 100644 (file)
@@ -12,6 +12,7 @@
 \r
 #include "prox.h"\r
 #include "../common/iso14443_crc.c"\r
+#include "../common/crc16.c"\r
 \r
 #define arraylen(x) (sizeof(x)/sizeof((x)[0]))\r
 #define BIT(x) GraphBuffer[x * clock]\r
@@ -52,6 +53,13 @@ static void CmdReset(char *str)
        SendCommand(&c, FALSE);\r
 }\r
 \r
+static void CmdBuffClear(char *str)\r
+{\r
+       UsbCommand c;\r
+       c.cmd = CMD_BUFF_CLEAR;\r
+       SendCommand(&c, FALSE);\r
+       CmdClearGraph(TRUE);\r
+}\r
 \r
 static void CmdQuit(char *str)\r
 {\r
@@ -211,10 +219,10 @@ int CmdClearGraph(int redraw)
 {\r
        int gtl = GraphTraceLen;\r
        GraphTraceLen = 0;\r
-       \r
+\r
        if (redraw)\r
                RepaintGraphWindow();\r
-       \r
+\r
        return gtl;\r
 }\r
 \r
@@ -225,8 +233,8 @@ static void CmdAppendGraph(int redraw, int clock, int bit)
 \r
        for (i = 0; i < (int)(clock/2); i++)\r
                GraphBuffer[GraphTraceLen++] = bit ^ 1;\r
-       \r
-       for (i = (int)(clock/2); i < clock; i++)        \r
+\r
+       for (i = (int)(clock/2); i < clock; i++)\r
                GraphBuffer[GraphTraceLen++] = bit;\r
 \r
        if (redraw)\r
@@ -240,7 +248,7 @@ static void CmdEM410xwatch(char *str)
        char *zero = "";\r
        char *twok = "2000";\r
        go = 1;\r
-       \r
+\r
        do\r
        {\r
                CmdLoread(zero);\r
@@ -249,6 +257,150 @@ static void CmdEM410xwatch(char *str)
        } while (go);\r
 }\r
 \r
+/* Read the transmitted data of an EM4x50 tag\r
+ * Format:\r
+ *\r
+ *  XXXXXXXX [row parity bit (even)] <- 8 bits plus parity\r
+ *  XXXXXXXX [row parity bit (even)] <- 8 bits plus parity\r
+ *  XXXXXXXX [row parity bit (even)] <- 8 bits plus parity\r
+ *  XXXXXXXX [row parity bit (even)] <- 8 bits plus parity\r
+ *  CCCCCCCC                         <- column parity bits\r
+ *  0                                <- stop bit\r
+ *  LW                               <- Listen Window\r
+ *\r
+ * This pattern repeats for every block of data being transmitted.\r
+ * Transmission starts with two Listen Windows (LW - a modulated\r
+ * pattern of 320 cycles each (32/32/128/64/64)).\r
+ *\r
+ * Note that this data may or may not be the UID. It is whatever data\r
+ * is stored in the blocks defined in the control word First and Last\r
+ * Word Read values. UID is stored in block 32.\r
+ */\r
+static void CmdEM4x50read(char *str)\r
+{\r
+       int i, j, startblock, clock, skip, block, start, end, low, high;\r
+       BOOL complete= FALSE;\r
+       int tmpbuff[MAX_GRAPH_TRACE_LEN / 64];\r
+       char tmp[6];\r
+\r
+       high= low= 0;\r
+       clock= 64;\r
+\r
+       /* first get high and low values */\r
+       for (i = 0; i < GraphTraceLen; i++)\r
+       {\r
+               if (GraphBuffer[i] > high)\r
+                       high = GraphBuffer[i];\r
+               else if (GraphBuffer[i] < low)\r
+                       low = GraphBuffer[i];\r
+       }\r
+\r
+       /* populate a buffer with pulse lengths */\r
+       i= 0;\r
+       j= 0;\r
+       while(i < GraphTraceLen)\r
+               {\r
+               // measure from low to low\r
+               while((GraphBuffer[i] > low) && (i<GraphTraceLen))\r
+                       ++i;\r
+               start= i;\r
+               while((GraphBuffer[i] < high) && (i<GraphTraceLen))\r
+                       ++i;\r
+               while((GraphBuffer[i] > low) && (i<GraphTraceLen))\r
+                       ++i;\r
+               if (j>(MAX_GRAPH_TRACE_LEN/64)) {\r
+                       break;\r
+               }\r
+               tmpbuff[j++]= i - start;\r
+               }\r
+\r
+       /* look for data start - should be 2 pairs of LW (pulses of 192,128) */\r
+       start= -1;\r
+       skip= 0;\r
+       for (i= 0; i < j - 4 ; ++i)\r
+               {\r
+               skip += tmpbuff[i];\r
+               if (tmpbuff[i] >= 190 && tmpbuff[i] <= 194)\r
+                       if (tmpbuff[i+1] >= 126 && tmpbuff[i+1] <= 130)\r
+                               if (tmpbuff[i+2] >= 190 && tmpbuff[i+2] <= 194)\r
+                                       if (tmpbuff[i+3] >= 126 && tmpbuff[i+3] <= 130)\r
+                                               {\r
+                                               start= i + 3;\r
+                                               break;\r
+                                               }\r
+               }\r
+       startblock= i + 3;\r
+\r
+       /* skip over the remainder of the LW */\r
+       skip += tmpbuff[i+1]+tmpbuff[i+2];\r
+       while(skip < MAX_GRAPH_TRACE_LEN && GraphBuffer[skip] > low)\r
+               ++skip;\r
+       skip += 8;\r
+\r
+       /* now do it again to find the end */\r
+       end= start;\r
+       for (i += 3; i < j - 4 ; ++i)\r
+               {\r
+               end += tmpbuff[i];\r
+               if (tmpbuff[i] >= 190 && tmpbuff[i] <= 194)\r
+                       if (tmpbuff[i+1] >= 126 && tmpbuff[i+1] <= 130)\r
+                               if (tmpbuff[i+2] >= 190 && tmpbuff[i+2] <= 194)\r
+                                       if (tmpbuff[i+3] >= 126 && tmpbuff[i+3] <= 130)\r
+                                               {\r
+                                               complete= TRUE;\r
+                                               break;\r
+                                               }\r
+               }\r
+\r
+       if (start >= 0)\r
+               PrintToScrollback("Found data at sample: %i",skip);\r
+       else\r
+               {\r
+               PrintToScrollback("No data found!");\r
+               PrintToScrollback("Try again with more samples.");\r
+               return;\r
+               }\r
+\r
+       if (!complete)\r
+               {\r
+               PrintToScrollback("*** Warning!");\r
+               PrintToScrollback("Partial data - no end found!");\r
+               PrintToScrollback("Try again with more samples.");\r
+               }\r
+\r
+       /* get rid of leading crap */\r
+       sprintf(tmp,"%i",skip);\r
+       CmdLtrim(tmp);\r
+\r
+       /* now work through remaining buffer printing out data blocks */\r
+       block= 0;\r
+       i= startblock;\r
+       while(block < 6)\r
+               {\r
+               PrintToScrollback("Block %i:", block);\r
+               // mandemod routine needs to be split so we can call it for data\r
+               // just print for now for debugging\r
+               Cmdmanchesterdemod("i 64");\r
+               skip= 0;\r
+               /* look for LW before start of next block */\r
+               for ( ; i < j - 4 ; ++i)\r
+                       {\r
+                       skip += tmpbuff[i];\r
+                       if (tmpbuff[i] >= 190 && tmpbuff[i] <= 194)\r
+                               if (tmpbuff[i+1] >= 126 && tmpbuff[i+1] <= 130)\r
+                                       break;\r
+                       }\r
+               while(GraphBuffer[skip] > low)\r
+                       ++skip;\r
+               skip += 8;\r
+               sprintf(tmp,"%i",skip);\r
+               CmdLtrim(tmp);\r
+               start += skip;\r
+               block++;\r
+               }\r
+}\r
+\r
+\r
 /* Read the ID of an EM410x tag.\r
  * Format:\r
  *   1111 1111 1           <-- standard non-repeatable header\r
@@ -262,9 +414,10 @@ static void CmdEM410xread(char *str)
        int i, j, clock, header, rows, bit, hithigh, hitlow, first, bit2idx, high, low;\r
        int parity[4];\r
        char id[11];\r
+       int retested = 0;\r
        int BitStream[MAX_GRAPH_TRACE_LEN];\r
        high = low = 0;\r
-       \r
+\r
        /* Detect high and lows and clock */\r
        for (i = 0; i < GraphTraceLen; i++)\r
        {\r
@@ -272,15 +425,15 @@ static void CmdEM410xread(char *str)
                        high = GraphBuffer[i];\r
                else if (GraphBuffer[i] < low)\r
                        low = GraphBuffer[i];\r
-       }       \r
-       \r
+       }\r
+\r
        /* get clock */\r
        clock = GetClock(str, high);\r
-       \r
+\r
        /* parity for our 4 columns */\r
        parity[0] = parity[1] = parity[2] = parity[3] = 0;\r
        header = rows = 0;\r
-       \r
+\r
        /* manchester demodulate */\r
        bit = bit2idx = 0;\r
        for (i = 0; i < (int)(GraphTraceLen / clock); i++)\r
@@ -288,7 +441,7 @@ static void CmdEM410xread(char *str)
                hithigh = 0;\r
                hitlow = 0;\r
                first = 1;\r
-               \r
+\r
                /* Find out if we hit both high and low peaks */\r
                for (j = 0; j < clock; j++)\r
                {\r
@@ -296,25 +449,26 @@ static void CmdEM410xread(char *str)
                                hithigh = 1;\r
                        else if (GraphBuffer[(i * clock) + j] == low)\r
                                hitlow = 1;\r
-                       \r
+\r
                        /* it doesn't count if it's the first part of our read\r
                         because it's really just trailing from the last sequence */\r
                        if (first && (hithigh || hitlow))\r
                                hithigh = hitlow = 0;\r
                        else\r
                                first = 0;\r
-                       \r
+\r
                        if (hithigh && hitlow)\r
                                break;\r
                }\r
-               \r
+\r
                /* If we didn't hit both high and low peaks, we had a bit transition */\r
                if (!hithigh || !hitlow)\r
                        bit ^= 1;\r
-               \r
+\r
                BitStream[bit2idx++] = bit;\r
        }\r
-       \r
+\r
+retest:\r
        /* We go till 5 before the graph ends because we'll get that far below */\r
        for (i = 1; i < bit2idx - 5; i++)\r
        {\r
@@ -327,29 +481,29 @@ static void CmdEM410xread(char *str)
                                /* Read another byte! */\r
                                sprintf(id+rows, "%x", (8 * BitStream[i]) + (4 * BitStream[i+1]) + (2 * BitStream[i+2]) + (1 * BitStream[i+3]));\r
                                rows++;\r
-                               \r
+\r
                                /* Keep parity info */\r
                                parity[0] ^= BitStream[i];\r
                                parity[1] ^= BitStream[i+1];\r
                                parity[2] ^= BitStream[i+2];\r
                                parity[3] ^= BitStream[i+3];\r
-                               \r
+\r
                                /* Move 4 bits ahead */\r
                                i += 4;\r
                        }\r
-                       \r
+\r
                        /* Damn, something wrong! reset */\r
                        else\r
                        {\r
                                PrintToScrollback("Thought we had a valid tag but failed at word %d (i=%d)", rows + 1, i);\r
-                               \r
+\r
                                /* Start back rows * 5 + 9 header bits, -1 to not start at same place */\r
                                i -= 9 + (5 * rows) - 5;\r
 \r
                                rows = header = 0;\r
                        }\r
                }\r
-               \r
+\r
                /* Step 3: Got our 40 bits! confirm column parity */\r
                else if (rows == 10)\r
                {\r
@@ -360,34 +514,44 @@ static void CmdEM410xread(char *str)
                        {\r
                                /* Sweet! */\r
                                PrintToScrollback("EM410x Tag ID: %s", id);\r
-                               \r
+\r
                                /* Stop any loops */\r
                                go = 0;\r
-                               break;\r
+                               return;\r
                        }\r
-                       \r
+\r
                        /* Crap! Incorrect parity or no stop bit, start all over */\r
                        else\r
                        {\r
                                rows = header = 0;\r
-                               \r
+\r
                                /* Go back 59 bits (9 header bits + 10 rows at 4+1 parity) */\r
                                i -= 59;\r
                        }\r
                }\r
-                               \r
+\r
                /* Step 1: get our header */\r
                else if (header < 9)\r
                {\r
                        /* Need 9 consecutive 1's */\r
                        if (BitStream[i] == 1)\r
                                header++;\r
-                       \r
+\r
                        /* We don't have a header, not enough consecutive 1 bits */\r
                        else\r
                                header = 0;\r
                }\r
        }\r
+\r
+       /* if we've already retested after flipping bits, return */\r
+       if (retested++)\r
+               return;\r
+\r
+       /* if this didn't work, try flipping bits */\r
+       for (i = 0; i < bit2idx; i++)\r
+               BitStream[i] ^= 1;\r
+\r
+       goto retest;\r
 }\r
 \r
 /* emulate an EM410X tag\r
@@ -402,20 +566,20 @@ static void CmdEM410xsim(char *str)
 {\r
        int i, n, j, h, binary[4], parity[4];\r
        char *s = "0";\r
-       \r
+\r
        /* clock is 64 in EM410x tags */\r
        int clock = 64;\r
-       \r
+\r
        /* clear our graph */\r
        CmdClearGraph(0);\r
-       \r
+\r
        /* write it out a few times */\r
        for (h = 0; h < 4; h++)\r
        {\r
                /* write 9 start bits */\r
                for (i = 0; i < 9; i++)\r
                        CmdAppendGraph(0, clock, 1);\r
-               \r
+\r
                /* for each hex char */\r
                parity[0] = parity[1] = parity[2] = parity[3] = 0;\r
                for (i = 0; i < 10; i++)\r
@@ -424,36 +588,36 @@ static void CmdEM410xsim(char *str)
                        sscanf(&str[i], "%1x", &n);\r
                        for (j = 3; j >= 0; j--, n/= 2)\r
                                binary[j] = n % 2;\r
-                       \r
+\r
                        /* append each bit */\r
                        CmdAppendGraph(0, clock, binary[0]);\r
                        CmdAppendGraph(0, clock, binary[1]);\r
                        CmdAppendGraph(0, clock, binary[2]);\r
                        CmdAppendGraph(0, clock, binary[3]);\r
-                       \r
+\r
                        /* append parity bit */\r
                        CmdAppendGraph(0, clock, binary[0] ^ binary[1] ^ binary[2] ^ binary[3]);\r
-                       \r
+\r
                        /* keep track of column parity */\r
                        parity[0] ^= binary[0];\r
                        parity[1] ^= binary[1];\r
                        parity[2] ^= binary[2];\r
                        parity[3] ^= binary[3];\r
                }\r
-               \r
+\r
                /* parity columns */\r
                CmdAppendGraph(0, clock, parity[0]);\r
                CmdAppendGraph(0, clock, parity[1]);\r
                CmdAppendGraph(0, clock, parity[2]);\r
                CmdAppendGraph(0, clock, parity[3]);\r
-               \r
+\r
                /* stop bit */\r
                CmdAppendGraph(0, clock, 0);\r
        }\r
-       \r
+\r
        /* modulate that biatch */\r
        Cmdmanchestermod(s);\r
-       \r
+\r
        /* booyah! */\r
        RepaintGraphWindow();\r
 \r
@@ -463,7 +627,7 @@ static void CmdEM410xsim(char *str)
 static void ChkBitstream(char *str)\r
 {\r
        int i;\r
-       \r
+\r
        /* convert to bitstream if necessary */\r
        for (i = 0; i < (int)(GraphTraceLen / 2); i++)\r
        {\r
@@ -478,11 +642,10 @@ static void ChkBitstream(char *str)
 static void CmdLosim(char *str)\r
 {\r
        int i;\r
-       char *zero = "0";\r
-       \r
+\r
        /* convert to bitstream if necessary */\r
        ChkBitstream(str);\r
-       \r
+\r
        for (i = 0; i < GraphTraceLen; i += 48) {\r
                UsbCommand c;\r
                int j;\r
@@ -516,6 +679,37 @@ static void CmdLoread(char *str)
        SendCommand(&c, FALSE);\r
 }\r
 \r
+static void CmdDetectReader(char *str)\r
+{\r
+       UsbCommand c;\r
+       // 'l' means LF - 125/134 kHz\r
+       if(*str == 'l') {\r
+               c.ext1 = 1;\r
+       } else if (*str == 'h') {\r
+               c.ext1 = 2;\r
+       } else if (*str != '\0') {\r
+               PrintToScrollback("use 'detectreader' or 'detectreader l' or 'detectreader h'");\r
+               return;\r
+       }\r
+       c.cmd = CMD_LISTEN_READER_FIELD;\r
+        SendCommand(&c, FALSE);\r
+}\r
+\r
+/* send a command before reading */\r
+static void CmdLoCommandRead(char *str)\r
+{\r
+       static char dummy[3];\r
+\r
+       dummy[0]= ' ';\r
+\r
+       UsbCommand c;\r
+       c.cmd = CMD_MOD_THEN_ACQUIRE_RAW_ADC_SAMPLES_125K;\r
+       sscanf(str, "%i %i %i %s %s", &c.ext1, &c.ext2, &c.ext3, (char *) &c.d.asBytes,(char *) &dummy+1);\r
+       // in case they specified 'h'\r
+       strcpy((char *)&c.d.asBytes + strlen((char *)c.d.asBytes), dummy);\r
+       SendCommand(&c, FALSE);\r
+}\r
+\r
 static void CmdLosamples(char *str)\r
 {\r
        int cnt = 0;\r
@@ -604,7 +798,6 @@ static void CmdHisamples(char *str)
        RepaintGraphWindow();\r
 }\r
 \r
-\r
 static int CmdHisamplest(char *str, int nrlow)\r
 {\r
        int cnt = 0;\r
@@ -1204,18 +1397,19 @@ static void CmdHi15demod(char *str)
        PrintToScrollback("CRC=%04x", Iso15693Crc(outBuf, k-2));\r
 }\r
 \r
-static void CmdTiread(char *str)\r
+static void CmdTIReadRaw(char *str)\r
 {\r
        UsbCommand c;\r
        c.cmd = CMD_ACQUIRE_RAW_BITS_TI_TYPE;\r
        SendCommand(&c, FALSE);\r
 }\r
 \r
-static void CmdTibits(char *str)\r
+static void CmdTIBits(char *str)\r
 {\r
        int cnt = 0;\r
        int i;\r
-       for(i = 0; i < 1536; i += 12) {\r
+//     for(i = 0; i < 1536; i += 12) {\r
+       for(i = 0; i < 4000; i += 12) {\r
                UsbCommand c;\r
                c.cmd = CMD_DOWNLOAD_RAW_BITS_TI_TYPE;\r
                c.ext1 = i;\r
@@ -1237,11 +1431,155 @@ static void CmdTibits(char *str)
                        }\r
                }\r
        }\r
-       GraphTraceLen = 1536*32;\r
+//     GraphTraceLen = 1536*32;\r
+       GraphTraceLen = 4000*32;\r
+       RepaintGraphWindow();\r
+}\r
+\r
+static void CmdFSKdemod(char *cmdline)\r
+{\r
+       static const int LowTone[]  = {\r
+               1,  1,  1,  1,  1, -1, -1, -1, -1, -1,\r
+               1,  1,  1,  1,  1, -1, -1, -1, -1, -1,\r
+               1,  1,  1,  1,  1, -1, -1, -1, -1, -1,\r
+               1,  1,  1,  1,  1, -1, -1, -1, -1, -1,\r
+               1,  1,  1,  1,  1, -1, -1, -1, -1, -1\r
+       };\r
+       static const int HighTone[] = {\r
+               1,  1,  1,  1,  1,     -1, -1, -1, -1,\r
+               1,  1,  1,  1,         -1, -1, -1, -1,\r
+               1,  1,  1,  1,         -1, -1, -1, -1,\r
+               1,  1,  1,  1,         -1, -1, -1, -1,\r
+               1,  1,  1,  1,         -1, -1, -1, -1,\r
+               1,  1,  1,  1,     -1, -1, -1, -1, -1,\r
+       };\r
+\r
+       int convLen = max(arraylen(HighTone), arraylen(LowTone));\r
+       DWORD hi = 0, lo = 0;\r
+\r
+       int i, j;\r
+       int minMark=0, maxMark=0;\r
+       int lowLen = arraylen(LowTone);\r
+       int highLen = arraylen(HighTone);\r
+\r
+       for(i = 0; i < GraphTraceLen - convLen; i++) {\r
+               int lowSum = 0, highSum = 0;\r
+\r
+               for(j = 0; j < lowLen; j++) {\r
+                       lowSum += LowTone[j]*GraphBuffer[i+j];\r
+               }\r
+               for(j = 0; j < highLen; j++) {\r
+                       highSum += HighTone[j]*GraphBuffer[i+j];\r
+               }\r
+               lowSum = abs((100*lowSum) / lowLen);\r
+               highSum = abs((100*highSum) / highLen);\r
+               GraphBuffer[i] = (highSum << 16) | lowSum;\r
+       }\r
+\r
+       for(i = 0; i < GraphTraceLen - convLen - 16; i++) {\r
+               int j;\r
+               int lowTot = 0, highTot = 0;\r
+               // 10 and 8 are f_s divided by f_l and f_h, rounded\r
+               for(j = 0; j < 10; j++) {\r
+                       lowTot += (GraphBuffer[i+j] & 0xffff);\r
+               }\r
+               for(j = 0; j < 8; j++) {\r
+                       highTot += (GraphBuffer[i+j] >> 16);\r
+               }\r
+               GraphBuffer[i] = lowTot - highTot;\r
+               if (GraphBuffer[i]>maxMark) maxMark=GraphBuffer[i];\r
+               if (GraphBuffer[i]<minMark) minMark=GraphBuffer[i];\r
+       }\r
+\r
+       GraphTraceLen -= (convLen + 16);\r
+\r
        RepaintGraphWindow();\r
+\r
+       // Find bit-sync (3 lo followed by 3 high)\r
+       int max = 0, maxPos = 0;\r
+       for(i = 0; i < 6000; i++) {\r
+               int dec = 0;\r
+               for(j = 0; j < 3*arraylen(LowTone); j++) {\r
+                       dec -= GraphBuffer[i+j];\r
+               }\r
+               for(; j < 3*(arraylen(LowTone) + arraylen(HighTone) ); j++) {\r
+                       dec += GraphBuffer[i+j];\r
+               }\r
+               if(dec > max) {\r
+                       max = dec;\r
+                       maxPos = i;\r
+               }\r
+       }\r
+\r
+       // place start of bit sync marker in graph\r
+       GraphBuffer[maxPos] = maxMark;\r
+       GraphBuffer[maxPos+1] = minMark;\r
+\r
+       maxPos += j;\r
+\r
+       // place end of bit sync marker in graph\r
+       GraphBuffer[maxPos] = maxMark;\r
+       GraphBuffer[maxPos+1] = minMark;\r
+\r
+       PrintToScrollback("actual data bits start at sample %d", maxPos);\r
+       PrintToScrollback("length %d/%d", arraylen(HighTone), arraylen(LowTone));\r
+\r
+       BYTE bits[46];\r
+       bits[sizeof(bits)-1] = '\0';\r
+\r
+       // find bit pairs and manchester decode them\r
+       for(i = 0; i < arraylen(bits)-1; i++) {\r
+               int dec = 0;\r
+               for(j = 0; j < arraylen(LowTone); j++) {\r
+                       dec -= GraphBuffer[maxPos+j];\r
+               }\r
+               for(; j < arraylen(LowTone) + arraylen(HighTone); j++) {\r
+                       dec += GraphBuffer[maxPos+j];\r
+               }\r
+               maxPos += j;\r
+               // place inter bit marker in graph\r
+               GraphBuffer[maxPos] = maxMark;\r
+               GraphBuffer[maxPos+1] = minMark;\r
+\r
+               // hi and lo form a 64 bit pair\r
+               hi = (hi<<1)|(lo>>31);\r
+               lo = (lo<<1);\r
+               // store decoded bit as binary (in hi/lo) and text (in bits[])\r
+               if(dec<0) {\r
+                       bits[i] = '1';\r
+                       lo|=1;\r
+               } else {\r
+                       bits[i] = '0';\r
+               }\r
+       }\r
+       PrintToScrollback("bits: '%s'", bits);\r
+       PrintToScrollback("hex: %08x %08x", hi, lo);\r
+}\r
+\r
+// read a TI tag and return its ID\r
+static void CmdTIRead(char *str)\r
+{\r
+       UsbCommand c;\r
+       c.cmd = CMD_READ_TI_TYPE;\r
+       SendCommand(&c, FALSE);\r
+}\r
+\r
+// write new data to a r/w TI tag\r
+static void CmdTIWrite(char *str)\r
+{\r
+       UsbCommand c;\r
+       int res=0;\r
+\r
+       c.cmd = CMD_WRITE_TI_TYPE;\r
+       res = sscanf(str, "0x%x 0x%x 0x%x ", &c.ext1, &c.ext2, &c.ext3);\r
+       if (res == 2) c.ext3=0;\r
+       if (res<2)\r
+               PrintToScrollback("Please specify 2 or three hex strings, eg 0x1234 0x5678");\r
+       else\r
+               SendCommand(&c, FALSE);\r
 }\r
 \r
-static void CmdTidemod(char *cmdline)\r
+static void CmdTIDemod(char *cmdline)\r
 {\r
        /* MATLAB as follows:\r
 f_s = 2000000;  % sampling frequency\r
@@ -1257,41 +1595,47 @@ h = 2*pi*ones(1, floor(f_s*T_h))*(f_h/f_s);
 l = sign(sin(cumsum(l)));\r
 h = sign(sin(cumsum(h)));\r
        */\r
-       static const int LowTone[] = {\r
-               1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1,\r
-               1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1,\r
-               1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1,\r
-               -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1,\r
-               -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1,\r
-               -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1,\r
-               1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1,\r
-               1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1,\r
-               1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1,\r
-               -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1,\r
-               -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1,\r
-               -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1,\r
-               1, 1, 1, 1, 1, 1, 1, -1, -1, -1,\r
-       };\r
-       static const int HighTone[] = {\r
-               1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1,\r
-               1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1,\r
-               -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1,\r
-               -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1,\r
-               1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1,\r
-               1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1,\r
-               -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1,\r
-               -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,\r
-               1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1,\r
-               1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1,\r
-               -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1,\r
-               -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1,\r
-               1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1,\r
-               1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1,\r
-       };\r
+ static const int LowTone[] = {\r
+  1,  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1,  1, -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1,\r
+ };\r
+ static const int HighTone[] = {\r
+  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,  1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,     -1, -1, -1, -1, -1, -1, -1, -1,\r
+  1,  1,  1,  1,  1,  1,  1,\r
+ };\r
 \r
        int convLen = max(arraylen(HighTone), arraylen(LowTone));\r
-\r
-       int i;\r
+       WORD crc;\r
+       int i, TagType;\r
        for(i = 0; i < GraphTraceLen - convLen; i++) {\r
                int j;\r
                int lowSum = 0, highSum = 0;;\r
@@ -1326,17 +1670,29 @@ h = sign(sin(cumsum(h)));
 \r
        RepaintGraphWindow();\r
 \r
-       // Okay, so now we have unsliced soft decisions; find bit-sync, and then\r
-       // get some bits.\r
+       // TI tag data format is 16 prebits, 8 start bits, 64 data bits,\r
+       // 16 crc CCITT bits, 8 stop bits, 15 end bits\r
 \r
+       // the 16 prebits are always low\r
+       // the 8 start and stop bits of a tag must match\r
+       // the start/stop prebits of a ro tag are 01111110\r
+       // the start/stop prebits of a rw tag are 11111110\r
+  // the 15 end bits of a ro tag are all low\r
+  // the 15 end bits of a rw tag match bits 15-1 of the data bits\r
+\r
+       // Okay, so now we have unsliced soft decisions;\r
+       // find bit-sync, and then get some bits.\r
+       // look for 17 low bits followed by 6 highs (common pattern for ro and rw tags)\r
        int max = 0, maxPos = 0;\r
        for(i = 0; i < 6000; i++) {\r
                int j;\r
                int dec = 0;\r
-               for(j = 0; j < 8*arraylen(LowTone); j++) {\r
+               // searching 17 consecutive lows\r
+               for(j = 0; j < 17*arraylen(LowTone); j++) {\r
                        dec -= GraphBuffer[i+j];\r
                }\r
-               for(; j < 8*arraylen(LowTone) + 8*arraylen(HighTone); j++) {\r
+               // searching 7 consecutive highs\r
+               for(; j < 17*arraylen(LowTone) + 6*arraylen(HighTone); j++) {\r
                        dec += GraphBuffer[i+j];\r
                }\r
                if(dec > max) {\r
@@ -1344,14 +1700,18 @@ h = sign(sin(cumsum(h)));
                        maxPos = i;\r
                }\r
        }\r
-       GraphBuffer[maxPos] = 800;\r
-       GraphBuffer[maxPos+1] = -800;\r
 \r
-       maxPos += 8*arraylen(LowTone);\r
+       // place a marker in the buffer to visually aid location\r
+       // of the start of sync\r
        GraphBuffer[maxPos] = 800;\r
        GraphBuffer[maxPos+1] = -800;\r
-       maxPos += 8*arraylen(HighTone);\r
 \r
+       // advance pointer to start of actual data stream (after 16 pre and 8 start bits)\r
+       maxPos += 17*arraylen(LowTone);\r
+       maxPos += 6*arraylen(HighTone);\r
+\r
+       // place a marker in the buffer to visually aid location\r
+       // of the end of sync\r
        GraphBuffer[maxPos] = 800;\r
        GraphBuffer[maxPos+1] = -800;\r
 \r
@@ -1359,13 +1719,12 @@ h = sign(sin(cumsum(h)));
 \r
        PrintToScrollback("length %d/%d", arraylen(HighTone), arraylen(LowTone));\r
 \r
-       GraphBuffer[maxPos] = 800;\r
-       GraphBuffer[maxPos+1] = -800;\r
-\r
-       BYTE bits[64+16+8+1];\r
+       BYTE bits[1+64+16+8+16];\r
        bits[sizeof(bits)-1] = '\0';\r
 \r
-       for(i = 0; i < arraylen(bits); i++) {\r
+       DWORD shift3 = 0x7e000000, shift2 = 0, shift1 = 0, shift0 = 0;\r
+\r
+       for(i = 0; i < arraylen(bits)-1; i++) {\r
                int high = 0;\r
                int low = 0;\r
                int j;\r
@@ -1375,30 +1734,79 @@ h = sign(sin(cumsum(h)));
                for(j = 0; j < arraylen(HighTone); j++) {\r
                        high += GraphBuffer[maxPos+j];\r
                }\r
+\r
                if(high > low) {\r
                        bits[i] = '1';\r
                        maxPos += arraylen(HighTone);\r
+                       // bitstream arrives lsb first so shift right\r
+                       shift3 |= (1<<31);\r
                } else {\r
                        bits[i] = '.';\r
                        maxPos += arraylen(LowTone);\r
                }\r
+\r
+               // 128 bit right shift register\r
+         shift0 = (shift0>>1) | (shift1 << 31);\r
+         shift1 = (shift1>>1) | (shift2 << 31);\r
+         shift2 = (shift2>>1) | (shift3 << 31);\r
+         shift3 >>= 1;\r
+\r
+               // place a marker in the buffer between bits to visually aid location\r
                GraphBuffer[maxPos] = 800;\r
                GraphBuffer[maxPos+1] = -800;\r
        }\r
-       PrintToScrollback("bits: '%s'", bits);\r
+       PrintToScrollback("Info: raw tag bits = %s", bits);\r
 \r
-       DWORD h = 0, l = 0;\r
-       for(i = 0; i < 32; i++) {\r
-               if(bits[i] == '1') {\r
-                       l |= (1<<i);\r
-               }\r
+       TagType = (shift3>>8)&0xff;\r
+       if ( TagType != ((shift0>>16)&0xff) ) {\r
+               PrintToScrollback("Error: start and stop bits do not match!");\r
+               return;\r
+       }\r
+       else if (TagType == 0x7e) {\r
+               PrintToScrollback("Info: Readonly TI tag detected.");\r
+               return;\r
        }\r
-       for(i = 32; i < 64; i++) {\r
-               if(bits[i] == '1') {\r
-                       h |= (1<<(i-32));\r
+       else if (TagType == 0xfe) {\r
+               PrintToScrollback("Info: Rewriteable TI tag detected.");\r
+\r
+         // put 64 bit data into shift1 and shift0\r
+         shift0 = (shift0>>24) | (shift1 << 8);\r
+         shift1 = (shift1>>24) | (shift2 << 8);\r
+\r
+               // align 16 bit crc into lower half of shift2\r
+         shift2 = ((shift2>>24) | (shift3 << 8)) & 0x0ffff;\r
+\r
+               // align 16 bit "end bits" or "ident" into lower half of shift3\r
+         shift3 >>= 16;\r
+\r
+               // only 15 bits compare, last bit of ident is not valid\r
+               if ( (shift3^shift0)&0x7fff ) {\r
+                       PrintToScrollback("Error: Ident mismatch!");\r
+               }\r
+               // WARNING the order of the bytes in which we calc crc below needs checking\r
+               // i'm 99% sure the crc algorithm is correct, but it may need to eat the\r
+               // bytes in reverse or something\r
+               // calculate CRC\r
+               crc=0;\r
+               crc = update_crc16(crc, (shift0)&0xff);\r
+               crc = update_crc16(crc, (shift0>>8)&0xff);\r
+               crc = update_crc16(crc, (shift0>>16)&0xff);\r
+               crc = update_crc16(crc, (shift0>>24)&0xff);\r
+               crc = update_crc16(crc, (shift1)&0xff);\r
+               crc = update_crc16(crc, (shift1>>8)&0xff);\r
+               crc = update_crc16(crc, (shift1>>16)&0xff);\r
+               crc = update_crc16(crc, (shift1>>24)&0xff);\r
+               PrintToScrollback("Info: Tag data = %08X%08X", shift1, shift0);\r
+               if (crc != (shift2&0xffff)) {\r
+                       PrintToScrollback("Error: CRC mismatch, calculated %04X, got ^04X", crc, shift2&0xffff);\r
+               } else {\r
+                       PrintToScrollback("Info: CRC %04X is good", crc);\r
                }\r
        }\r
-       PrintToScrollback("hex: %08x %08x", h, l);\r
+       else {\r
+               PrintToScrollback("Unknown tag type.");\r
+               return;\r
+       }\r
 }\r
 \r
 static void CmdNorm(char *str)\r
@@ -1476,6 +1884,20 @@ static void CmdZerocrossings(char *str)
        RepaintGraphWindow();\r
 }\r
 \r
+static void CmdThreshold(char *str)\r
+{\r
+       int i;\r
+       int threshold = atoi(str);\r
+\r
+       for(i = 0; i < GraphTraceLen; i++) {\r
+               if(GraphBuffer[i]>= threshold)\r
+                       GraphBuffer[i]=1;\r
+               else\r
+                       GraphBuffer[i]=-1;\r
+       }\r
+       RepaintGraphWindow();\r
+}\r
+\r
 static void CmdLtrim(char *str)\r
 {\r
        int i;\r
@@ -1822,60 +2244,53 @@ static void CmdFlexdemod(char *str)
 }\r
 \r
 /*\r
- * Generic command to demodulate ASK. bit length in argument.\r
- * Giving the bit length helps discriminate ripple effects\r
- * upon zero crossing for noisy traces.\r
+ * Generic command to demodulate ASK.\r
  *\r
- * Second is convention: positive or negative (High mod means zero\r
+ * Argument is convention: positive or negative (High mod means zero\r
  * or high mod means one)\r
  *\r
  * Updates the Graph trace with 0/1 values\r
  *\r
  * Arguments:\r
- * sl : bit length in terms of number of samples per bit\r
- *      (use yellow/purple markers to compute).\r
  * c : 0 or 1\r
  */\r
 \r
 static void Cmdaskdemod(char *str) {\r
        int i;\r
-       int sign = 1;\r
-       int n = 0;\r
-       int c = 0;\r
-       int t1 = 0;\r
+       int c, high = 0, low = 0;\r
 \r
        // TODO: complain if we do not give 2 arguments here !\r
-       sscanf(str, "%i %i", &n, &c);\r
-       if (c == 0) {\r
-               c = 1 ;\r
-       } else {\r
-               c = -1;\r
+       sscanf(str, "%i", &c);\r
+\r
+       /* Detect high and lows and clock */\r
+       for (i = 0; i < GraphTraceLen; i++)\r
+       {\r
+               if (GraphBuffer[i] > high)\r
+                       high = GraphBuffer[i];\r
+               else if (GraphBuffer[i] < low)\r
+                       low = GraphBuffer[i];\r
        }\r
 \r
-       if (GraphBuffer[0]*c > 0) {\r
-               GraphBuffer[0] = 1;\r
+       if (GraphBuffer[0] > 0) {\r
+               GraphBuffer[0] = 1-c;\r
        } else {\r
-               GraphBuffer[0] = 0;\r
+               GraphBuffer[0] = c;\r
        }\r
        for(i=1;i<GraphTraceLen;i++) {\r
-               /* Analyse signal within the symbol length */\r
-               /* Decide if we crossed a zero */\r
-               if (GraphBuffer[i]*sign < 0) {\r
-                        /* Crossed a zero, check if this is a ripple or not */\r
-                       if ( (i-t1) > n/4 ) {\r
-                               sign = -sign;\r
-                               t1=i;\r
-                               if (GraphBuffer[i]*c > 0){\r
-                                       GraphBuffer[i]=1;\r
-                               } else {\r
-                                       GraphBuffer[i]=0;\r
-                               }\r
-                       } else {\r
-                       /* This is a ripple, set the current sample value\r
-                          to the same as previous */\r
-                               GraphBuffer[i] = GraphBuffer[i-1];\r
-                       }\r
+               /* Transitions are detected at each peak\r
+                * Transitions are either:\r
+                * - we're low: transition if we hit a high\r
+                * - we're high: transition if we hit a low\r
+                * (we need to do it this way because some tags keep high or\r
+                * low for long periods, others just reach the peak and go\r
+                * down)\r
+                */\r
+               if ((GraphBuffer[i]==high) && (GraphBuffer[i-1] == c)) {\r
+                                       GraphBuffer[i]=1-c;\r
+               } else if ((GraphBuffer[i]==low) && (GraphBuffer[i-1] == (1-c))){\r
+                       GraphBuffer[i] = c;\r
                } else {\r
+                       /* No transition */\r
                        GraphBuffer[i] = GraphBuffer[i-1];\r
                }\r
        }\r
@@ -1917,7 +2332,7 @@ int detectclock(int peak)
                        lastpeak = i;\r
                }\r
        }\r
-       \r
+\r
        return clock;\r
 }\r
 \r
@@ -1925,7 +2340,7 @@ int detectclock(int peak)
 int GetClock(char *str, int peak)\r
 {\r
        int clock;\r
-       \r
+\r
        sscanf(str, "%i", &clock);\r
        if (!strcmp(str, ""))\r
                clock = 0;\r
@@ -1934,12 +2349,12 @@ int GetClock(char *str, int peak)
        if (!clock)\r
        {\r
                clock = detectclock(peak);\r
-               \r
+\r
                /* Only print this message if we're not looping something */\r
                if (!go)\r
                        PrintToScrollback("Auto-detected clock rate: %d", clock);\r
        }\r
-       \r
+\r
        return clock;\r
 }\r
 \r
@@ -1966,16 +2381,16 @@ static void Cmdbitstream(char *str) {
 \r
        /* Get our clock */\r
        clock = GetClock(str, high);\r
-       \r
+\r
        gtl = CmdClearGraph(0);\r
-       \r
+\r
        bit = 0;\r
        for (i = 0; i < (int)(gtl / clock); i++)\r
        {\r
                hithigh = 0;\r
                hitlow = 0;\r
                first = 1;\r
-               \r
+\r
                /* Find out if we hit both high and low peaks */\r
                for (j = 0; j < clock; j++)\r
                {\r
@@ -1983,18 +2398,18 @@ static void Cmdbitstream(char *str) {
                                hithigh = 1;\r
                        else if (GraphBuffer[(i * clock) + j] == low)\r
                                hitlow = 1;\r
-                       \r
+\r
                        /* it doesn't count if it's the first part of our read\r
                         because it's really just trailing from the last sequence */\r
                        if (first && (hithigh || hitlow))\r
                                hithigh = hitlow = 0;\r
                        else\r
                                first = 0;\r
-                       \r
+\r
                        if (hithigh && hitlow)\r
                                break;\r
                }\r
-               \r
+\r
                /* If we didn't hit both high and low peaks, we had a bit transition */\r
                if (!hithigh || !hitlow)\r
                        bit ^= 1;\r
@@ -2015,7 +2430,7 @@ static void Cmdmanchestermod(char *str)
        int i, j;\r
        int clock;\r
        int bit, lastbit, wave;\r
-       \r
+\r
        /* Get our clock */\r
        clock = GetClock(str, 0);\r
 \r
@@ -2024,17 +2439,17 @@ static void Cmdmanchestermod(char *str)
        for (i = 0; i < (int)(GraphTraceLen / clock); i++)\r
        {\r
                bit = GraphBuffer[i * clock] ^ 1;\r
-               \r
+\r
                for (j = 0; j < (int)(clock/2); j++)\r
                        GraphBuffer[(i * clock) + j] = bit ^ lastbit ^ wave;\r
                for (j = (int)(clock/2); j < clock; j++)\r
                        GraphBuffer[(i * clock) + j] = bit ^ lastbit ^ wave ^ 1;\r
-               \r
+\r
                /* Keep track of how we start our wave and if we changed or not this time */\r
                wave ^= bit ^ lastbit;\r
                lastbit = bit;\r
        }\r
-       \r
+\r
        RepaintGraphWindow();\r
 }\r
 \r
@@ -2052,7 +2467,7 @@ static void Cmdmanchestermod(char *str)
  *               Typical values can be 64, 32, 128...\r
  */\r
 static void Cmdmanchesterdemod(char *str) {\r
-       int i, j;\r
+       int i, j, invert= 0;\r
        int bit;\r
        int clock;\r
        int lastval;\r
@@ -2064,6 +2479,16 @@ static void Cmdmanchesterdemod(char *str) {
        int bit2idx = 0;\r
        int warnings = 0;\r
 \r
+       /* check if we're inverting output */\r
+       if(*str == 'i')\r
+       {\r
+               PrintToScrollback("Inverting output");\r
+               invert= 1;\r
+               do\r
+                       ++str;\r
+               while(*str == ' '); // in case a 2nd argument was given\r
+       }\r
+\r
        /* Holds the decoded bitstream: each clock period contains 2 bits       */\r
        /* later simplified to 1 bit after manchester decoding.                 */\r
        /* Add 10 bits to allow for noisy / uncertain traces without aborting   */\r
@@ -2084,9 +2509,9 @@ static void Cmdmanchesterdemod(char *str) {
 \r
        /* Get our clock */\r
        clock = GetClock(str, high);\r
-       \r
+\r
        int tolerance = clock/4;\r
-       \r
+\r
        /* Detect first transition */\r
        /* Lo-Hi (arbitrary)       */\r
        for (i = 0; i < GraphTraceLen; i++)\r
@@ -2101,8 +2526,11 @@ static void Cmdmanchesterdemod(char *str) {
        /* If we're not working with 1/0s, demod based off clock */\r
        if (high != 1)\r
        {\r
-               bit = 0;\r
-               for (i = 0; i < (int)(GraphTraceLen / clock); i++)\r
+               bit = 0; /* We assume the 1st bit is zero, it may not be\r
+                         * the case: this routine (I think) has an init problem.\r
+                         * Ed.\r
+                         */\r
+               for (; i < (int)(GraphTraceLen / clock); i++)\r
                {\r
                        hithigh = 0;\r
                        hitlow = 0;\r
@@ -2131,7 +2559,7 @@ static void Cmdmanchesterdemod(char *str) {
                        if (!hithigh || !hitlow)\r
                                bit ^= 1;\r
 \r
-                       BitStream[bit2idx++] = bit;\r
+                       BitStream[bit2idx++] = bit ^ invert;\r
                }\r
        }\r
 \r
@@ -2139,7 +2567,7 @@ static void Cmdmanchesterdemod(char *str) {
        else\r
        {\r
 \r
-       /* Then detect duration between 2 successive transitions */\r
+               /* Then detect duration between 2 successive transitions */\r
                for (bitidx = 1; i < GraphTraceLen; i++)\r
                {\r
                        if (GraphBuffer[i-1] != GraphBuffer[i])\r
@@ -2174,18 +2602,18 @@ static void Cmdmanchesterdemod(char *str) {
                                                PrintToScrollback("Error: too many detection errors, aborting.");\r
                                                return;\r
                                        }\r
+                               }\r
                        }\r
                }\r
-       }\r
 \r
-       // At this stage, we now have a bitstream of "01" ("1") or "10" ("0"), parse it into final decoded bitstream\r
-       // Actually, we overwrite BitStream with the new decoded bitstream, we just need to be careful\r
-       // to stop output at the final bitidx2 value, not bitidx\r
-       for (i = 0; i < bitidx; i += 2) {\r
-               if ((BitStream[i] == 0) && (BitStream[i+1] == 1)) {\r
-                       BitStream[bit2idx++] = 1;\r
+               // At this stage, we now have a bitstream of "01" ("1") or "10" ("0"), parse it into final decoded bitstream\r
+               // Actually, we overwrite BitStream with the new decoded bitstream, we just need to be careful\r
+               // to stop output at the final bitidx2 value, not bitidx\r
+               for (i = 0; i < bitidx; i += 2) {\r
+                       if ((BitStream[i] == 0) && (BitStream[i+1] == 1)) {\r
+                               BitStream[bit2idx++] = 1 ^ invert;\r
                } else if ((BitStream[i] == 1) && (BitStream[i+1] == 0)) {\r
-                       BitStream[bit2idx++] = 0;\r
+                       BitStream[bit2idx++] = 0 ^ invert;\r
                } else {\r
                        // We cannot end up in this state, this means we are unsynchronized,\r
                        // move up 1 bit:\r
@@ -2199,9 +2627,9 @@ static void Cmdmanchesterdemod(char *str) {
                                        PrintToScrollback("Error: too many decode errors, aborting.");\r
                                        return;\r
                                }\r
+                       }\r
                }\r
        }\r
-       }\r
 \r
        PrintToScrollback("Manchester decoded bitstream");\r
        // Now output the bitstream to the scrollback by line of 16 bits\r
@@ -2226,8 +2654,6 @@ static void Cmdmanchesterdemod(char *str) {
        }\r
 }\r
 \r
-\r
-\r
 /*\r
  * Usage ???\r
  */\r
@@ -2255,6 +2681,12 @@ static void CmdPlot(char *str)
        ShowGraphWindow();\r
 }\r
 \r
+static void CmdGrid(char *str)\r
+{\r
+       sscanf(str, "%i %i", &PlotGridX, &PlotGridY);\r
+       RepaintGraphWindow();\r
+}\r
+\r
 static void CmdHide(char *str)\r
 {\r
        HideGraphWindow();\r
@@ -2323,6 +2755,14 @@ static void CmdHIDsimTAG(char *str)
        SendCommand(&c, FALSE);\r
 }\r
 \r
+static void CmdReadmem(char *str)\r
+{\r
+       UsbCommand c;\r
+       c.cmd = CMD_READ_MEM;\r
+       c.ext1 = atoi(str);\r
+       SendCommand(&c, FALSE);\r
+}\r
+\r
 static void CmdLcdReset(char *str)\r
 {\r
        UsbCommand c;\r
@@ -2343,12 +2783,6 @@ static void CmdLcd(char *str)
        }\r
 }\r
 \r
-\r
-\r
-static void CmdTest(char *str)\r
-{\r
-}\r
-\r
 /*\r
  * Sets the divisor for LF frequency clock: lets the user choose any LF frequency below\r
  * 600kHz.\r
@@ -2366,84 +2800,96 @@ static void CmdSetDivisor(char *str)
        }\r
 }\r
 \r
-static void CmdSweepLF(char *str)\r
-{\r
-       UsbCommand c;\r
-       c.cmd = CMD_SWEEP_LF;\r
-       SendCommand(&c, FALSE);\r
-}\r
-\r
-\r
 typedef void HandlerFunction(char *cmdline);\r
 \r
+/* in alphabetic order */\r
 static struct {\r
        char            *name;\r
        HandlerFunction *handler;\r
        int             offline;  // 1 if the command can be used when in offline mode\r
        char            *docString;\r
 } CommandTable[] = {\r
-       "tune",                         CmdTune,0,              "measure antenna tuning",\r
-       "tiread",                       CmdTiread,0,            "read a TI-type 134 kHz tag",\r
-       "tibits",                       CmdTibits,0,            "get raw bits for TI-type LF tag",\r
-       "tidemod",                      CmdTidemod,0,           "demod raw bits for TI-type LF tag",\r
-       "vchdemod",                     CmdVchdemod,0,          "demod samples for VeriChip",\r
-       "plot",                         CmdPlot,1,              "show graph window",\r
-       "hide",                         CmdHide,1,              "hide graph window",\r
-       "losim",                        CmdLosim,0,             "simulate LF tag",\r
-       "em410xsim",            CmdEM410xsim,1,         "simulate EM410x tag",\r
-       "em410xread",           CmdEM410xread,1,        "extract ID from EM410x tag",\r
-       "em410xwatch",          CmdEM410xwatch,0,       "watches for EM410x tags",\r
-       "loread",                       CmdLoread,0,            "read (125/134 kHz) LF ID-only tag",\r
-       "losamples",            CmdLosamples,0,         "get raw samples for LF tag",\r
-       "hisamples",            CmdHisamples,0,         "get raw samples for HF tag",\r
-       "hisampless",           CmdHisampless,0,        "get signed raw samples, HF tag",\r
-       "hisamplest",           CmdHi14readt,0,         "get samples HF, for testing",\r
-       "higet",                        CmdHi14read_sim,0,      "get samples HF, 'analog'",\r
-       "bitsamples",           CmdBitsamples,0,        "get raw samples as bitstring",\r
-       "hexsamples",           CmdHexsamples,0,        "dump big buffer as hex bytes",\r
-       "hi15read",                     CmdHi15read,0,          "read HF tag (ISO 15693)",\r
-       "hi15reader",           CmdHi15reader,0,        "act like an ISO15693 reader", // new command greg\r
-       "hi15sim",                      CmdHi15tag,0,           "fake an ISO15693 tag", // new command greg\r
-       "hi14read",                     CmdHi14read,0,          "read HF tag (ISO 14443)",\r
-       "sri512read",           CmdSri512read,0,        "Read contents of a SRI512 tag",\r
-       "hi14areader",          CmdHi14areader,0,       "act like an ISO14443 Type A reader",   // ## New reader command\r
-       "hi15demod",            CmdHi15demod,1,         "demod ISO15693 from tag",\r
-       "hi14bdemod",           CmdHi14bdemod,1,        "demod ISO14443 Type B from tag",\r
-       "autocorr",                     CmdAutoCorr,1,          "autocorrelation over window",\r
-       "norm",                         CmdNorm,1,              "normalize max/min to +/-500",\r
-       "dec",                          CmdDec,1,               "decimate",\r
-       "hpf",                          CmdHpf,1,               "remove DC offset from trace",\r
-       "zerocrossings",        CmdZerocrossings,1,     "count time between zero-crossings",\r
-       "ltrim",                        CmdLtrim,1,             "trim from left of trace",\r
-       "scale",                        CmdScale,1,             "set cursor display scale",\r
-       "flexdemod",            CmdFlexdemod,1,         "demod samples for FlexPass",\r
-       "save",                         CmdSave,1,              "save trace (from graph window)",\r
-       "load",                         CmdLoad,1,              "load trace (to graph window",\r
-       "hisimlisten",          CmdHisimlisten,0,       "get HF samples as fake tag",\r
-       "hi14sim",                      CmdHi14sim,0,           "fake ISO 14443 tag",\r
-       "hi14asim",                     CmdHi14asim,0,          "fake ISO 14443a tag",                                  // ## Simulate 14443a tag\r
-       "hi14snoop",            CmdHi14snoop,0,         "eavesdrop ISO 14443",\r
-       "hi14asnoop",           CmdHi14asnoop,0,        "eavesdrop ISO 14443 Type A",                   // ## New snoop command\r
-       "hi14list",                     CmdHi14list,0,          "list ISO 14443 history",\r
-       "hi14alist",            CmdHi14alist,0,         "list ISO 14443a history",                              // ## New list command\r
-       "hiddemod",                     CmdHiddemod,1,          "HID Prox Card II (not optimal)",\r
-       "hidfskdemod",          CmdHIDdemodFSK,0,       "HID FSK demodulator",\r
-    "indalademod",             CmdIndalademod,0,         "demod samples for Indala",\r
-       "askdemod",                     Cmdaskdemod,1,          "Attempt to demodulate simple ASK tags",\r
-       "bitstream",            Cmdbitstream,1,         "Convert waveform into a bitstream",\r
-       "hidsimtag",            CmdHIDsimTAG,0,         "HID tag simulator",\r
-       "mandemod",                     Cmdmanchesterdemod,1,   "Try a Manchester demodulation on a binary stream",\r
-       "manmod",                       Cmdmanchestermod,1,     "Manchester modulate a binary stream",\r
-       "detectclock",          Cmddetectclockrate,1, "Detect clock rate",\r
-       "fpgaoff",                      CmdFPGAOff,0,           "set FPGA off",                                                 // ## FPGA Control\r
-       "lcdreset",                     CmdLcdReset,0,          "Hardware reset LCD",\r
-       "lcd",                          CmdLcd,0,                       "Send command/data to LCD",\r
-       "setlfdivisor",         CmdSetDivisor,0,        "Drive LF antenna at 12Mhz/(divisor+1)",\r
-       "sweeplf",                      CmdSweepLF,0,           "Sweep through LF freq range and store results in buffer",\r
-       "reset",                        CmdReset,0,                     "Reset the Proxmark3",\r
-       "quit",                         CmdQuit,1,                      "quit program"\r
+       {"askdemod",                    Cmdaskdemod,                            1, "<samples per bit> <0|1> -- Attempt to demodulate simple ASK tags"},\r
+       {"autocorr",                    CmdAutoCorr,                            1, "<window length> -- Autocorrelation over window"},\r
+       {"bitsamples",          CmdBitsamples,                  0, "Get raw samples as bitstring"},\r
+       {"bitstream",                   Cmdbitstream,                           1, "[clock rate] -- Convert waveform into a bitstream"},\r
+       {"buffclear",                   CmdBuffClear,                           1, "Clear sample buffer and graph window"},\r
+       {"dec",                                         CmdDec,                                                 1, "Decimate samples"},\r
+       {"detectclock",         Cmddetectclockrate,     1, "Detect clock rate"},\r
+       {"detectreader",        CmdDetectReader,                0, "['l'|'h'] -- Detect external reader field (option 'l' or 'h' to limit to LF or HF)"},\r
+       {"em410xsim",                   CmdEM410xsim,                           1, "<UID> -- Simulate EM410x tag"},\r
+       {"em410xread",          CmdEM410xread,                  1, "[clock rate] -- Extract ID from EM410x tag"},\r
+       {"em410xwatch",         CmdEM410xwatch,                 0, "Watches for EM410x tags"},\r
+       {"em4x50read",          CmdEM4x50read,                  1, "Extract data from EM4x50 tag"},\r
+       {"exit",                                        CmdQuit,                                                1, "Exit program"},\r
+       {"flexdemod",                   CmdFlexdemod,                           1, "Demodulate samples for FlexPass"},\r
+       {"fpgaoff",                             CmdFPGAOff,                                     0, "Set FPGA off"},\r
+       {"fskdemod",                    CmdFSKdemod,                            1, "Demodulate graph window as a HID FSK"},\r
+       {"grid",                                        CmdGrid,                                                1, "<x> <y> -- overlay grid on graph window, use zero value to turn off either"},\r
+       {"hexsamples",          CmdHexsamples,                  0, "<blocks> -- Dump big buffer as hex bytes"},\r
+       {"hi14alist",                   CmdHi14alist,                           0, "List ISO 14443a history"},\r
+       {"hi14areader",         CmdHi14areader,                 0, "Act like an ISO14443 Type A reader"},\r
+       {"hi14asim",                    CmdHi14asim,                            0, "<UID> -- Fake ISO 14443a tag"},\r
+       {"hi14asnoop",          CmdHi14asnoop,                  0, "Eavesdrop ISO 14443 Type A"},\r
+       {"hi14bdemod",          CmdHi14bdemod,                  1, "Demodulate ISO14443 Type B from tag"},\r
+       {"hi14list",                    CmdHi14list,                            0, "List ISO 14443 history"},\r
+       {"hi14read",                    CmdHi14read,                            0, "Read HF tag (ISO 14443)"},\r
+       {"hi14sim",                             CmdHi14sim,                                     0, "Fake ISO 14443 tag"},\r
+       {"hi14snoop",                   CmdHi14snoop,                           0, "Eavesdrop ISO 14443"},\r
+       {"hi15demod",                   CmdHi15demod,                           1, "Demodulate ISO15693 from tag"},\r
+       {"hi15read",                    CmdHi15read,                            0, "Read HF tag (ISO 15693)"},\r
+       {"hi15reader",          CmdHi15reader,                  0, "Act like an ISO15693 reader"},\r
+       {"hi15sim",                             CmdHi15tag,                                     0, "Fake an ISO15693 tag"},\r
+       {"hiddemod",                    CmdHiddemod,                            1, "Demodulate HID Prox Card II (not optimal)"},\r
+       {"hide",                                        CmdHide,                                                1, "Hide graph window"},\r
+       {"hidfskdemod",         CmdHIDdemodFSK,                 0, "Realtime HID FSK demodulator"},\r
+       {"hidsimtag",                   CmdHIDsimTAG,                           0, "<ID> -- HID tag simulator"},\r
+       {"higet",                                       CmdHi14read_sim,                0, "<samples> -- Get samples HF, 'analog'"},\r
+       {"hisamples",                   CmdHisamples,                           0, "Get raw samples for HF tag"},\r
+       {"hisampless",          CmdHisampless,                  0, "<samples> -- Get signed raw samples, HF tag"},\r
+       {"hisamplest",          CmdHi14readt,                           0, "Get samples HF, for testing"},\r
+       {"hisimlisten",         CmdHisimlisten,                 0, "Get HF samples as fake tag"},\r
+       {"hpf",                                         CmdHpf,                                                 1, "Remove DC offset from trace"},\r
+       {"indalademod",         CmdIndalademod,                 0, "['224'] -- Demodulate samples for Indala 64 bit UID (option '224' for 224 bit)"},\r
+       {"lcd",                                         CmdLcd,                                                 0, "<HEX command> <count> -- Send command/data to LCD"},\r
+       {"lcdreset",                    CmdLcdReset,                            0, "Hardware reset LCD"},\r
+       {"load",                                        CmdLoad,                                                1, "<filename> -- Load trace (to graph window"},\r
+       {"locomread",                   CmdLoCommandRead,               0, "<off period> <'0' period> <'1' period> <command> ['h'] -- Modulate LF reader field to send command before read (all periods in microseconds) (option 'h' for 134)"},\r
+       {"loread",                              CmdLoread,                                      0, "['h'] -- Read 125/134 kHz LF ID-only tag (option 'h' for 134)"},\r
+       {"losamples",                   CmdLosamples,                           0, "[128 - 16000] -- Get raw samples for LF tag"},\r
+       {"losim",                                       CmdLosim,                                               0, "Simulate LF tag"},\r
+       {"ltrim",                                       CmdLtrim,                                               1, "<samples> -- Trim samples from left of trace"},\r
+       {"mandemod",                    Cmdmanchesterdemod,     1, "[i] [clock rate] -- Manchester demodulate binary stream (option 'i' to invert output)"},\r
+       {"manmod",                              Cmdmanchestermod,               1, "[clock rate] -- Manchester modulate a binary stream"},\r
+       {"norm",                                        CmdNorm,                                                1, "Normalize max/min to +/-500"},\r
+       {"plot",                                        CmdPlot,                                                1, "Show graph window"},\r
+       {"quit",                                        CmdQuit,                                                1, "Quit program"},\r
+       {"readmem",                             CmdReadmem,                                     0, "[address] -- Read memory at decimal address from flash"},\r
+       {"reset",                                       CmdReset,                                               0, "Reset the Proxmark3"},\r
+       {"save",                                        CmdSave,                                                1, "<filename> -- Save trace (from graph window)"},\r
+       {"scale",                                       CmdScale,                                               1, "<int> -- Set cursor display scale"},\r
+       {"setlfdivisor",        CmdSetDivisor,                  0, "<19 - 255> -- Drive LF antenna at 12Mhz/(divisor+1)"},\r
+       {"sri512read",          CmdSri512read,                  0, "<int> -- Read contents of a SRI512 tag"},\r
+       {"tibits",                              CmdTIBits,                                      0, "Get raw bits for TI-type LF tag"},\r
+       {"tidemod",                             CmdTIDemod,                                     1, "Demodulate raw bits for TI-type LF tag"},\r
+       {"tireadraw",                   CmdTIReadRaw,                           0, "Read a TI-type 134 kHz tag in raw mode"},\r
+       {"tiread",                              CmdTIRead,                                      0, "Read and decode a TI 134 kHz tag"},\r
+       {"tiwrite",                             CmdTIWrite,                                     0, "Write new data to a r/w TI 134 kHz tag"},\r
+       {"threshold",                   CmdThreshold,                           1, "Maximize/minimize every value in the graph window depending on threshold"},\r
+       {"tune",                                        CmdTune,                                                0, "Measure antenna tuning"},\r
+       {"vchdemod",                    CmdVchdemod,                            0, "['clone'] -- Demodulate samples for VeriChip"},\r
+       {"zerocrossings",       CmdZerocrossings,               1, "Count time between zero-crossings"},\r
 };\r
 \r
+static struct {\r
+       char *name;\r
+       char *args;\r
+       char *argshelp;\r
+       char *description;\r
+       }       CommandExtendedHelp[]= {\r
+               {"detectreader","'l'|'h'","'l' specifies LF antenna scan only, 'h' specifies HF antenna scan only.","Monitor antenna for changes in voltage. Output is in three fields: CHANGED, CURRENT, PERIOD,\nwhere CHANGED is the value just changed from, CURRENT is the current value and PERIOD is the\nnumber of program loops since the last change.\n\nThe RED LED indicates LF field detected, and the GREEN LED indicates HF field detected."},\r
+               {"tune","","","Drive LF antenna at all divisor range values (19 - 255) and store the results in the output\nbuffer. Issuing 'losamples' and then 'plot' commands will display the resulting peak. 12MHz\ndivided by the peak's position plus one gives the antenna's resonant frequency. For convenience,\nthis value is also printed out by the command."},\r
+               };\r
 \r
 //-----------------------------------------------------------------------------\r
 // Entry point into our code: called whenever the user types a command and\r
@@ -2452,15 +2898,30 @@ static struct {
 void CommandReceived(char *cmd)\r
 {\r
        int i;\r
+       char line[256];\r
 \r
        PrintToScrollback("> %s", cmd);\r
 \r
-       if(strcmp(cmd, "help")==0) {\r
+       if(strcmp(cmd, "help") == 0 || strncmp(cmd,"help ",strlen("help ")) == 0) {\r
+               // check if we're doing extended help\r
+               if(strlen(cmd) > strlen("help ")) {\r
+                       cmd += strlen("help ");\r
+                       for(i = 0; i < sizeof(CommandExtendedHelp) / sizeof(CommandExtendedHelp[0]); i++) {\r
+                               if(strcmp(CommandExtendedHelp[i].name,cmd) == 0) {\r
+                                       PrintToScrollback("\nExtended help for '%s':\n", cmd);\r
+                                       PrintToScrollback("Args: %s\t- %s\n",CommandExtendedHelp[i].args,CommandExtendedHelp[i].argshelp);\r
+                                       PrintToScrollback(CommandExtendedHelp[i].description);\r
+                                       PrintToScrollback("");\r
+                                       return;\r
+                               }\r
+                       }\r
+               PrintToScrollback("No extended help available for '%s'", cmd);\r
+               return;\r
+               }\r
                if (offline) PrintToScrollback("Operating in OFFLINE mode (no device connected)");\r
                PrintToScrollback("\r\nAvailable commands:");\r
                for(i = 0; i < sizeof(CommandTable) / sizeof(CommandTable[0]); i++) {\r
                        if (offline && (CommandTable[i].offline==0)) continue;\r
-                       char line[256];\r
                        memset(line, ' ', sizeof(line));\r
                        strcpy(line+2, CommandTable[i].name);\r
                        line[strlen(line)] = ' ';\r
@@ -2468,7 +2929,7 @@ void CommandReceived(char *cmd)
                        PrintToScrollback("%s", line);\r
                }\r
                PrintToScrollback("");\r
-               PrintToScrollback("and also: help, cls");\r
+               PrintToScrollback("'help <command>' for extended help on that command\n");\r
                return;\r
        }\r
 \r
@@ -2515,19 +2976,27 @@ void UsbCommandReceived(UsbCommand *c)
                        break;\r
 \r
                case CMD_MEASURED_ANTENNA_TUNING: {\r
-                       int zLf, zHf;\r
+                       int peakv, peakf;\r
                        int vLf125, vLf134, vHf;\r
                        vLf125 = c->ext1 & 0xffff;\r
                        vLf134 = c->ext1 >> 16;\r
-                       vHf = c->ext2;\r
-                       zLf = c->ext3 & 0xffff;\r
-                       zHf = c->ext3 >> 16;\r
-                       PrintToScrollback("# LF antenna @ %3d mA / %5d mV [%d ohms] 125Khz",\r
-                               vLf125/zLf, vLf125, zLf);\r
-                       PrintToScrollback("# LF antenna @ %3d mA / %5d mV [%d ohms] 134Khz",\r
-                               vLf134/((zLf*125)/134), vLf134, (zLf*125)/134);\r
-                       PrintToScrollback("# HF antenna @ %3d mA / %5d mV [%d ohms] 13.56Mhz",\r
-                               vHf/zHf, vHf, zHf);\r
+                       vHf = c->ext2 & 0xffff;;\r
+                       peakf = c->ext3 & 0xffff;\r
+                       peakv = c->ext3 >> 16;\r
+                       PrintToScrollback("");\r
+                       PrintToScrollback("");\r
+                       PrintToScrollback("# LF antenna: %5.2f V @   125.00 kHz", vLf125/1000.0);\r
+                       PrintToScrollback("# LF antenna: %5.2f V @   134.00 kHz", vLf134/1000.0);\r
+                       PrintToScrollback("# LF optimal: %5.2f V @%9.2f kHz", peakv/1000.0, 12000.0/(peakf+1));\r
+                       PrintToScrollback("# HF antenna: %5.2f V @    13.56 MHz", vHf/1000.0);\r
+                       if (peakv<2000)\r
+                               PrintToScrollback("# Your LF antenna is unusable.");\r
+                       else if (peakv<10000)\r
+                               PrintToScrollback("# Your LF antenna is marginal.");\r
+                       if (vHf<2000)\r
+                               PrintToScrollback("# Your HF antenna is unusable.");\r
+                       else if (vHf<5000)\r
+                               PrintToScrollback("# Your HF antenna is marginal.");\r
                        break;\r
                }\r
                default:\r
Impressum, Datenschutz