3 (example) Legic-Prime Layout with 'Kaba Group Header'
4 +----+----+----+----+----+----+----+----+
5 0x00|MCD |MSN0|MSN1|MSN2|MCC | 60 | ea | 9f |
6 +----+----+----+----+----+----+----+----+
7 0x08| ff | 00 | 00 | 00 | 11 |Bck0|Bck1|Bck2|
8 +----+----+----+----+----+----+----+----+
9 0x10|Bck3|Bck4|Bck5|BCC | 00 | 00 |Seg0|Seg1|
10 +----+----+----+----+----+----+----+----+
11 0x18|Seg2|Seg3|SegC|Stp0|Stp1|Stp2|Stp3|UID0|
12 +----+----+----+----+----+----+----+----+
16 MSN = Manufacturer SerialNumber
17 60 ea = DCF Low + DCF high
18 9f = raw byte which holds the bits for OLE,WRP,WRC,RD
19 ff = unknown but important
21 11 = unknown but important
22 Bck = header-backup-area
23 00 00 = Year (00 = 2000) & Week (not important)
25 SegC = Crc8 over the Segment Header
26 Stp = Stamp (could be more as 4 - up to 7)
27 UID = dec User-ID for online-Mapping
28 kghC = crc8 over MCD + MSN0..MSN2 + UID
31 (example) Legic-Cash on MIM256/1024 tag' (37 bytes)
32 +----+----+----+----+----+----+----+----+
33 0x00|Seg0|Seg1|Seg2|Seg3|SegC|STP0|STP1|STP2|
34 +----+----+----+----+----+----+----+----+
35 0x08|STP3|STP4|STP5|STP6| 01 |CURh|CURl|LIMh|
36 +----+----+----+----+----+----+----+----+
37 0x10|LIMm|LIMl|CHKh|CHKl|BALh|BALm|BALl|LRBh|
38 +----+----+----+----+----+----+----+----+
39 0x18|LRBm|LRBl|CHKh|CHKl|SHDh|SHDm|SHDl|LRSh|
40 +----+----+----+----+----+----+----+----+
41 0x20|LRSm|LRSl| CV |CHKh|CHKl|
42 +----+----+----+----+----+
43 STP = Stamp (seems to be always 7 bytes)
44 01 = unknown but important
45 CUR = currency in HEX (ISO 4217)
47 CHK = crc16 over byte-addr 0x05..0x12
49 LRB = ID of the reader that changed the balance
50 CHK = crc16 over BAL + LRB
52 LRS = ID of the reader that changed the shadow balance (?? should be always the same as LRB)
53 CV = Counter value for transactions
54 CHK = crc16 over SHD + LRS + CV
57 example = "script run legic"
64 This script helps you to read, create and modify Legic Prime Tags (MIM22, MIM256, MIM1024)
65 it's kinda interactive with following commands in three categories:
67 Data I/O Segment Manipulation Token-Data
68 ----------------- -------------------- -----------------
69 rt => read Tag as => add Segment mt => make Token
70 wt => write Tag es => edit Segment Header et => edit Token data
71 ed => edit Segment Data tk => toggle KGH-Flag
72 File I/O rs => remove Segment
73 ----------------- cc => check Segment-CRC
74 lf => load File ck => check KGH
75 sf => save File ds => dump Segments
79 (partially) known Segments Virtual Tags Script Output
80 --------------------------- ------------------------------- ------------------------
81 dlc => dump Legic-Cash ct => copy mainTag to backupTag tac => toggle ansicolors
82 elc => edit Legic-Cash tc => copy backupTag to mainTag
83 d3p => dump 3rd-Party-Cash tt => switch mainTag & backupTag
84 e3p => edit 3rd-Party-Cash di => dump mainTag
89 rt: 'read tag' - reads a tag placed near to the PM3
90 wt: 'write tag' - writes the content of the 'virtual inTag' to a tag placed near to th PM3
91 without the need of changing anything - MCD,MSN,MCC will be read from the tag
92 before and applied to the output.
94 lf: 'load file' - load a (xored) file from the local Filesystem into the 'virtual inTag'
95 sf: 'save file' - saves the 'virtual inTag' to the local Filesystem (xored with Tag-MCC)
96 xf: 'xor file' - saves the 'virtual inTag' to the local Filesystem (xored with choosen MCC - use '00' for plain values)
98 ct: 'copy tag' - copy the 'virtual Tag' to a second 'virtual TAG' - not usefull yet, but inernally needed
99 tc: 'copy tag' - copy the 'second virtual Tag' to 'virtual TAG' - not usefull yet, but inernally needed
100 tt: 'toggle tag' - copy mainTag to BackupTag and backupTag to mainTag
102 di: 'dump mainTag' - shows the current content of the 'virtual Tag'
103 do: 'dump backupTag' - shows the current content of the 'virtual outTag'
104 ds: 'dump Segments' - will show the content of a selected Segment
105 as: 'add Segment' - will add a 'empty' Segment to the inTag
106 es: 'edit Segment' - edit the Segment-Header of a selected Segment (len, WRP, WRC, RD, valid)
107 all other Segment-Header-Values are either calculated or not needed to edit (yet)
108 ed: 'edit data' - edit the Data of a Segment (ADF-Aera / Stamp & Payload specific Data)
109 et: 'edit Token' - edit Data of a Token (CDF-Area / SAM, SAM64, SAM63, IAM, GAM specific Data)
110 mt: 'make Token' - create a Token 'from scratch' (guided)
111 rs: 'remove segment' - removes a Segment (except Segment 00, but this can be set to valid=0 for Master-Token)
112 cc: 'check Segment-CRC'- checks & calculates (if check failed) the Segment-CRC of all Segments
113 ck: 'check KGH-CRC' - checks the and calculates a 'Kaba Group Header' if one was detected
114 'Kaba Group Header CRC calculation'
115 tk: 'toggle KGH' - toglle the (script-internal) flag for kgh-calculation for a segment
116 xc: 'etra c' - show string that was used to calculate the kgh-crc of a segment
118 dlc: 'dump Legic-Cash' - show balance and checksums of a Legic-Cash Segment
119 elc: 'edit Legic-Cash' - edit values of a Legic-Cash Segment
121 d3p: 'dump 3rd Party' - show balance, history and checksums of a (yet) unknown 3rd-Party Cash Segment
122 e3p: 'edit 3rd Party' - edit Data in 3rd-Party Cash Segment
124 tac: 'toggle ansicolors'- switch on and off the colored text-output of this script
125 default can be changed by setting the variable 'colored_output' to false
131 local utils = require('utils')
132 local getopt = require('getopt')
133 local ansicolors = require('ansicolors')
136 -- global variables / defines
137 local bxor = bit32.bxor
138 local bbit = bit32.extract
139 local input = utils.input
140 local confirm = utils.confirm
143 -- init ansicolor-values & ansicolors switch
144 local colored_output = true
155 -- default colors (change to whatever you want)
156 function load_colors(onoff)
159 acgreen = ansicolors.green
160 accyan = ansicolors.cyan
161 acred = ansicolors.red
162 acyellow= ansicolors.yellow
163 acblue = ansicolors.blue
164 acmagenta= ansicolors.magenta
165 acoff = ansicolors.reset
179 -- curency-codes for Legic-Cash-Segments (ISO 4217)
188 -- This is only meant to be used when errors occur
190 print(acred.."ERROR: "..acoff ,err)
198 print("Version: "..version)
199 print("Example usage: "..example)
203 -- table check helper
205 return type(t) == 'table'
209 -- creates a 'deep copy' of a table (a=b only references)
210 function deepCopy(object)
211 local lookup_table = {}
212 local function _copy(object)
213 if type(object) ~= "table" then
215 elseif lookup_table[object] then
216 return lookup_table[object]
219 lookup_table[object] = new_table
220 for index, value in pairs(object) do
221 new_table[_copy(index)] = _copy(value)
223 return setmetatable(new_table, getmetatable(object))
230 function xorme(hex, xor, index)
231 if ( index >= 23 ) then
232 return ('%02x'):format(bxor( tonumber(hex,16) , tonumber(xor,16) ))
239 -- (de)obfuscate bytes
240 function xorBytes(inBytes, crc)
242 for index = 1, #inBytes do
243 bytes[index] = xorme(inBytes[index], crc, index)
245 if (#inBytes == #bytes) then
247 bytes[5] = string.sub(crc,-2)
250 print("error: byte-count missmatch")
256 -- check availability of file
257 function file_check(file_name)
258 local file_found=io.open(file_name, "r")
259 if file_found==nil then
267 -- split csv-string into table
268 local function split(str, sep)
269 local sep = sep or ','
271 local matchfunc = string.gmatch(str, "([^"..sep.."]+)")
272 if not matchfunc then return {str} end
273 for str in matchfunc do
274 table.insert(fields, str)
280 -- put a string into a bytes-table
281 function str2bytes(s)
283 if (string.len(s)%2~=0) then return print("stamp should be a even hexstring e.g.: deadbeef or 0badc0de") end
284 for i=1, string.len(s), 2 do
285 table.insert(res, string.sub(s,i,(i+1)))
291 -- put certain bytes into a new table
292 function bytesToTable(bytes, bstart, bend)
294 for i=0, (bend-bstart) do
301 -- read file into table
302 function getInputBytes(infile)
305 local fhi,err = io.open(infile)
306 if err then oops("faild to read from file ".. infile); return false; end
309 if line == nil then break end
310 for byte in line:gmatch("%w+") do
311 table.insert(bytes, byte)
315 if (bytes[7]=='00') then return false end
316 print(#bytes .. " bytes from "..infile.." loaded")
321 -- create tag-table helper
322 function createTagTable()
348 -- put bytes into tag-table
349 function bytesToTag(bytes, tag)
350 if(istable(tag)) then
360 tag.Type=getTokenType(tag.DCFl);
361 tag.OLE=bbit("0x"..tag.DCFl,7,1)
362 tag.WRP=("%d"):format(bbit("0x"..bytes[8],0,4))
363 tag.WRC=("%d"):format(bbit("0x"..bytes[8],4,3))
364 tag.RD=("%d"):format(bbit("0x"..bytes[8],7,1))
365 tag.Stamp_len=(tonumber(0xfc,10)-tonumber(bbit("0x"..tag.DCFh,0,8),10))
366 tag.data=bytesToTable(bytes, 10, 13)
367 tag.Bck=bytesToTable(bytes, 14, 20)
368 tag.MTC=bytesToTable(bytes, 21, 22)
370 print(acgreen.."Tag-Type: ".. tag.Type..acoff)
371 if (tag.Type=="SAM" and #bytes>23) then
372 tag=segmentsToTag(bytes, tag)
373 print(acgreen..(#tag.SEG+1).." Segment(s) found"..acoff)
374 -- unsegmented Master-Token
378 table.insert(tag.data, tag.Bck[i])
380 tag.data[#tag.data]=tag.MTC[0]
382 --tag.MTC[0]=tag.MTC[1]
385 print(accyan..#bytes.." bytes for Tag processed"..acoff)
388 return oops("tag is no table in: bytesToTag ("..type(tag)..")")
392 -- put segments from byte-table to tag-table
393 function segmentsToTag(bytes, tag)
397 if (istable(tag)) then
400 tag.SEG[i]=getSegmentData(bytes, start, ("%02d"):format(i))
401 if (tag.Type=="SAM") then
402 if (checkKghCrc(tag, i)) then tag.SEG[i].kgh=true end
404 start=start+tag.SEG[i].len
405 until ((tag.SEG[i].valid==0) or tag.SEG[i].last==1 or i==126)
407 else return oops("tag is no table in: segmentsToTag ("..type(tag)..")") end
408 else print("no Segments: must be a MIM22") end
412 -- read Tag-Table in bytes-table
413 function tagToBytes(tag)
414 if (istable(tag)) then
418 table.insert(bytes, tag.MCD)
419 table.insert(bytes, tag.MSN0)
420 table.insert(bytes, tag.MSN1)
421 table.insert(bytes, tag.MSN2)
422 table.insert(bytes, tag.MCC)
423 table.insert(bytes, tag.DCFl)
424 table.insert(bytes, tag.DCFh)
425 table.insert(bytes, tag.raw)
426 table.insert(bytes, tag.SSC)
428 for i=0, #tag.data do
429 table.insert(bytes, tag.data[i])
432 if(istable(tag.Bck)) then
434 table.insert(bytes, tag.Bck[i])
437 -- token-create-time / master-token crc
439 table.insert(bytes, tag.MTC[i])
442 if (type(tag.SEG[0])=='table') then
444 for i2=1, #tag.SEG[i].raw+1 do
445 table.insert(bytes, #bytes+1, tag.SEG[i].raw[i2])
447 table.insert(bytes, #bytes+1, tag.SEG[i].crc)
448 for i2=0, #tag.SEG[i].data-1 do
449 table.insert(bytes, #bytes+1, tag.SEG[i].data[i2])
454 for i=#bytes+1, 1024 do
455 table.insert(bytes, i, '00')
459 return oops("tag is no table in tagToBytes ("..type(tag)..")")
464 -- read from pm3 into virtual-tag
465 function readFromPM3()
466 local tag, bytes, infile
468 core.console("hf legic reader")
469 core.console("hf legic save "..infile)
475 -- write virtual Tag to real Tag
476 function writeToTag(tag)
478 local filename='MylegicClone.hex'
480 if(utils.confirm(acred.."\nplace the (empty) Tag onto the PM3\nand confirm writing to this Tag: "..acoff) == false) then
483 -- get used bytes / tag-len
484 if(istable(tag.SEG)) then
485 if (istable(tag.Bck)) then
487 taglen=taglen+tag.SEG[i].len+5
490 local uid_old=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
491 -- read new tag into memory so we can xor the new data with the new MCC
493 outbytes=tagToBytes(outTAG)
494 -- copy 'inputbuffer' to 'outputbuffer'
495 tag.MCD = outbytes[1]
496 tag.MSN0 = outbytes[2]
497 tag.MSN1 = outbytes[3]
498 tag.MSN2 = outbytes[4]
499 tag.MCC = outbytes[5]
500 -- recheck all segments-crc/kghcrc (only on a credential)
501 if(istable(tag.Bck)) then
504 local uid_new=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
506 if (check43rdPartyCash1(uid_old, tag.SEG[i].data)) then
507 io.write(accyan.."\nfixing known checksums"..acoff.." ... ")
508 if (fix3rdPartyCash1(uid_new, tag.SEG[i].data)) then
509 io.write(acgreen.." done\n"..acoff)
510 else oops("\nsomething went wrong at the repair of the 3rd-party-cash-segment") end
514 bytes=tagToBytes(tag)
516 if (tag.Type~="SAM") then bytes[22]=calcMtCrc(bytes) end
518 print("write temp-file '"..filename.."'")
520 writeFile(bytes, filename)
521 --writeToTag(bytes, taglen, 'MylegicClone.hex')
525 -- write data to file
527 WriteBytes = utils.input(acyellow.."enter number of bytes to write?"..acoff, taglen)
528 -- load file into pm3-buffer
529 if (type(filename)~="string") then filename=input(acyellow.."filename to load to pm3-buffer?"..acoff,"legic.temp") end
530 cmd = 'hf legic load '..filename
532 -- write pm3-buffer to Tag
533 for i=0, WriteBytes do
534 if ( i<5 or i>6) then
535 cmd = ('hf legic write 0x%02x 0x01'):format(i)
539 -- write DCF in reverse order (requires 'mosci-patch')
540 cmd = 'hf legic write 0x05 0x02'
541 print(acgreen..cmd..acoff)
545 print(acgreen.."skip byte 0x05 - will be written next step"..acoff)
554 -- read file into virtual-tag
555 function readFile(filename)
559 if (file_check(filename)==false) then
560 return oops("input file: "..filename.." not found")
562 bytes = getInputBytes(filename)
563 if (bytes == false) then return oops('couldnt get input bytes')
566 bytes = xorBytes(bytes,bytes[5])
567 print("create virtual tag from ".. #bytes .. " bytes")
568 -- create Tag for plain bytes
570 -- load plain bytes to tag-table
572 tag=bytesToTag(bytes, tag)
579 -- write bytes to file
580 function writeFile(bytes, filename)
581 if (filename~='MylegicClone.hex') then
582 if (file_check(filename)) then
583 local answer = confirm("\nthe output-file "..filename.." alredy exists!\nthis will delete the previous content!\ncontinue?")
584 if (answer==false) then return print("user abort") end
589 local fho,err = io.open(filename, "w")
590 if err then oops("OOps ... faild to open output-file ".. filename) end
591 bytes=xorBytes(bytes, bytes[5])
595 elseif (bcnt <= 7) then
596 line=line.." "..bytes[i]
599 -- write line to new file
600 fho:write(line.."\n")
601 -- reset counter & line
608 print("\nwrote ".. #bytes .." bytes to " .. filename)
615 function makeTagMap()
618 tagMap['name']=input(accyan.."enter Name for this Map: "..acoff , "newTagMap")
619 tagMap['mappings']={}
621 -- insert fixed Tag-CRC
622 table.insert(tagMap.crc8, {name='TAG-CRC', pos=5, seq={1, 4}})
625 print(accyan.."new tagMap created"..acoff)
630 -- save mapping to file
631 function saveTagMap(map, filename)
632 if (string.len(filename)>0) then
633 if (file_check(filename)) then
634 local answer = confirm("\nthe output-file "..filename.." alredy exists!\nthis will delete the previous content!\ncontinue?")
635 if (answer==false) then return print("user abort") end
640 local fho,err = io.open(filename, "w")
641 if err then oops("OOps ... faild to open output-file ".. filename) end
643 -- write line to new file
644 for k, v in pairs(map) do
646 for k2, v2 in pairs(v) do
647 if (k=='mappings') then
648 fho:write(k..","..k2..","..v2['name']..","..v2['start']..","..v2['end']..","..((v2['highlight']) and "1" or "0").."\n")
649 elseif (k=="crc8") then
651 tmp=k..","..k2..","..v2['name']..","..v2['pos']..","
652 tmp=tmp..tbl2seqstr(v2['seq'])
657 fho:write(k..","..v.."\n")
666 function toggleHighlight(tbl)
667 if (tbl['highlight']) then tbl['highlight']=false
668 else tbl['highlight']=true end
673 -- return table od seqence-string
674 function seqstr2tbl(seqstr)
675 local s=split(seqstr)
678 for sk, sv in pairs(s) do
681 table.insert(res, s2[1])
682 table.insert(res, s2[2])
690 -- return sequence-string from table
691 function tbl2seqstr(seqtbl)
693 if (istable(seqtbl)) then
694 for sk, sv in pairs(seqtbl) do
695 res=res..sv..((sk%2==0) and "," or "-")
697 if (string.sub(res, string.len(res))==",") then
698 res=string.sub(res, 1, string.len(res)-1)
705 -- read map-file into map
706 function loadTagMap(filename)
707 local map={mappings={}, crc8={}, crc16={}}
713 if (file_check(filename)==false) then
714 return oops("input file: "..filename.." not found")
716 local fhi,err = io.open(filename)
725 if (fields[1]=='offset') then
726 offset=tonumber(fields[2],10)
729 map[fields[1]]=fields[2]
730 elseif (fields[1]=='mappings') then
734 temp['name']=fields[3]
735 temp['start']=tonumber(fields[4], 10)
736 temp['end']=tonumber(fields[5], 10)
737 if(temp['start']>22) then
738 temp['start']=temp['start']+offset
739 temp['end']=temp['end']+offset
741 if (tonumber(fields[6], 10)==1) then temp['highlight']= true
742 else temp['highlight']= false end
743 table.insert(map['mappings'], m, temp)
744 elseif (fields[1]=='crc8') then
748 temp['name']=fields[3]
749 temp['pos']=tonumber(fields[4], 10)+offset
750 local s=string.sub(line, string.len(fields[1]..","..fields[2]..","..fields[3]..",")+1, string.len(line))
751 temp['seq']=seqstr2tbl(s)
752 for k, v in pairs(temp['seq']) do
753 if(tonumber(v, 10)>22) then v=tonumber(v, 10)+offset end
754 temp['seq'][k]=tonumber(v, 10)
756 table.insert(map.crc8, temp)
765 -- dump tagMap (mappings only)
766 function dumpTagMap(tag, tagMap)
767 if(#tagMap.mappings>0) then
768 bytes=tagToBytes(tag)
771 -- start display mappings
772 for k, v in pairs(tagMap.mappings) do
773 if ((lastend+1)<v['start']) then
776 if (isPosCrc8(tagMap, v['start'])>0) then
777 if ( checkMapCrc8(tagMap, bytes, isPosCrc8(tagMap, v['start']) ) ) then
778 io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..acgreen..v['name']..acoff..":")
780 io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..acred..v['name']..acoff..":")
783 io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..((v['highlight']) and acmagenta or acyellow)..v['name']..acoff..":")
786 for i=((string.len(v['name']))/10), 2 do
789 for i=v['start'], v['end'] do
790 temp=temp..bytes[i].." "
800 function isPosCrc8(tagMap, pos)
802 if (#tagMap.crc8>0) then
803 for k, v in pairs(tagMap.crc8) do
804 if(v['pos']==pos) then res=k end
812 function checkMapCrc8(tagMap, bytes, n)
814 if (#tagMap.crc8>0) then
815 if(istable(tagMap.crc8[n])) then
817 for k2, v2 in pairs(tagMap.crc8[n]) do
818 if (istable(v2)) then
819 temp=temp..tbl2seqstr(v2)
823 local tempres=getSequences(bytes, temp)
824 tempres=("%02x"):format(utils.Crc8Legic(tempres))
825 if (bytes[tagMap.crc8[n]['pos']]==tempres) then
835 function editTagMap(tag, tagMap)
837 Data: dm = show dr = dump raw
838 Mappings: im = insert am = add rm = remove
839 CRC8: ac8 = add sc8 = show rc8 = remove
842 --if(#tagMap.mappings==0) then oops("no mappings in tagMap"); return tagMap end
843 print("tagMap edit-mode submenu")
845 x=input('tagMap submenu:', 'h')
846 if (x=='h') then print(t)
847 elseif (x=='dm') then tagMmap=dumpTagMap(tag, tagMap)
848 elseif (x=='dr') then tagMmap=dumpMap(tag, tagMap)
849 elseif (x=='rc8') then
850 if (istable(tagMap.crc8)) then
851 local x1 = selectTableEntry(tagMap.crc8, "select number of CRC8 to remove:")
852 if (istable(tagMap.crc8[x1])) then
853 table.remove(tagMap.crc8, x1)
856 elseif (x=='ac8') then
857 local p=tonumber(input("enter byte-addr of crc8", '0'),10)
859 local i1=input("enter comma-seperated byte-sequences (e.g.: '1-4,23-26')", '1-4,23-26')
860 local s1=split(i1, ',')
863 for k, v in pairs(s1) do
866 table.insert(temp.seq, v1[1])
867 table.insert(temp.seq, v1[2])
871 temp['name']=input("enter a name for the CRC8", "CRC "..(#tagMap.crc8+1))
872 table.insert(tagMap.crc8, temp)
875 elseif (string.sub(x, 1, 3)=='sc8') then
876 local bytes=tagToBytes(tag)
878 -- trigger manually by sc8 <'4digit' checkadd> <'seqeuence-string'>
879 -- e.g.: sc8 0027 1-4,23-36
880 if (string.len(x)>=9) then
881 pos=tonumber(string.sub(x, 5, 8), 10)
882 x=string.sub(x, 9, string.len(x))
883 print("x: "..x.." - pos:"..pos)
885 x=selectTableEntry(tagMap.crc8, "select CRC:")
886 if(istable(tagMap.crc8[x])) then
887 pos=tagMap.crc8[x]['pos']
888 x=tbl2seqstr(tagMap.crc8[x]['seq'])
891 if (type(x)=='string') then
892 res=("%02x"):format(utils.Crc8Legic(getSequences(bytes, x)))
893 print(accyan.."Sequence:\t"..acoff..x)
894 print(accyan.."Bytes:\t\t"..acoff..getSequences(bytes, x))
895 print(accyan.."calculated: "..acoff..res..accyan.." bytes["..pos.."]: "..acoff..bytes[pos].." ("..compareCrc(utils.Crc8Legic(getSequences(bytes, x)), bytes[pos])..")")
897 elseif (x=="tm") then
898 x=selectTableEntry(tagMap.mappings, "select number of Mapping:")
899 tagMap.mappings[x]=toggleHighlight(tagMap.mappings[x])
900 elseif (x=='am') then tagMap=addMapping(tag, tagMap)
901 elseif (x=='im') then tagMap=addMapping(tag, tagMap, selectTableEntry(tagMap.mappings, "select List-Position for insert:"))
902 elseif (x=='rm') then tagMap=deleteMapping(tag, tagMap)
903 elseif (x=='mas') then tagMap=mapTag(tagMap); tagMap=mapAllSegments(tag, tagMap)
904 elseif (type(actions[string.sub(x, 3)])=='function') then actions[string.sub(x, 3)]()
907 print("exit sub-Menu")
912 -- dump raw mapped and unmapped
913 function dumpMap(tag, tagMap)
916 local bytes = tagToBytes(tag)
917 local stats = getSegmentStats(bytes)
918 dend=stats[#stats]['end']
919 print(accyan.."Tag uses "..dend.." bytes:"..acoff)
920 for i=dstart, dend do
921 if (check4MappedByte(i, tagMap) and not check4MapCrc8(i, tagMap) and not check4Highlight(i, tagMap)) then io.write(""..acyellow)
922 elseif (check4MapCrc8(i, tagMap)) then
923 if ( checkMapCrc8(tagMap, bytes, isPosCrc8(tagMap, i) ) ) then
924 io.write(""..acgreen)
931 -- highlighted mapping
932 if (check4Highlight(i, tagMap)) then io.write(""..acmagenta) end
935 if (i%8==0) then io.write("\n")
936 else io.write(" ") end
939 io.write("\n"..acoff)
943 -- show bytes used for crc-calculation
944 function getSequences(bytes, seqstr)
945 if (type(seqstr)~="string") then seqstr=input("enter comma-seperated sequences (e.g.: '1-4,23-26')", '1-4,23-26') end
946 local seqs=split(seqstr, ',')
949 for k, v in pairs(seqs) do
950 local seq = split(v,'-')
952 for i=seq[1], seq[2] do
953 res=res..bytes[i].." "
956 if(string.len(res)>0) then res=res.." " end
959 oops("no sequence found in '"..seqstr.."'")
965 -- check if byte-addr is a know crc
966 function check4MapCrc8(addr, tagMap)
968 for i=1, #tagMap.crc8 do
969 if (addr == tagMap.crc8[i]['pos']) then
977 -- check if byte-addr is a know crc
978 function check4MapCrc16(addr, tagMap)
980 for i=1, #tagMap.crc16 do
981 if (addr == tagMap.crc16[i]['pos']) then
989 -- check if byte is mapped or not
990 function check4MappedByte(addr, tagMap)
992 for _, v in pairs(tagMap.mappings) do
993 if (addr >= v['start'] and addr <= v['end'] ) then
1001 -- check if byte is highlighted or not
1002 function check4Highlight(addr, tagMap)
1004 for _, v in pairs(tagMap.mappings) do
1005 if (addr >= v['start'] and addr <= v['end'] ) then
1013 -- add interactive mapping
1014 function addMapping(tag, tagMap, x)
1015 if (type(x)~="number") then x=#tagMap.mappings+1 end
1016 local bytes=tagToBytes(tag)
1018 myMapping['name'] =input(accyan.."enter Maping-Name:"..acoff, string.format("mapping %d", #tagMap.mappings+1))
1019 myMapping['start']=tonumber(input(accyan.."enter start-addr:"..acoff, '1'), 10)
1020 myMapping['end'] =tonumber(input(accyan.."enter end-addr:"..acoff, #bytes), 10)
1021 myMapping['highlight']=confirm("set highlighted")
1022 table.insert(tagMap.mappings, x, myMapping)
1028 function deleteMapping(tag, tagMap)
1029 if(#tagMap.mappings>0) then
1030 local d = selectTableEntry(tagMap.mappings, "select number of Mapping to remove:")
1031 if (type(d)=='number') then
1032 table.remove(tagMap.mappings, d)
1033 else oops("deleteMapping: got type = "..type(d).." - expected type = 'number'")
1040 -- select a mapping from a tagmap
1041 function selectTableEntry(table, action)
1042 if (type(action)~="string") then action="select number of item:" end
1043 for k, v in pairs(table) do
1044 print(accyan..k..acoff.."\t-> "..accyan..v['name']..acoff)
1046 local res = tonumber(input(action , 0), 10)
1047 if (istable(table[res])) then
1056 function mapAllSegments(tag, tagMap)
1057 local bytes=tagToBytes(tag)
1059 segs=getSegmentStats(bytes)
1060 if (istable(segs)) then
1061 for k, v in pairs(segs) do
1062 -- wrp (write proteted) = byte 2
1063 WRP = tonumber(bytes[v['start']+2],16)
1064 -- wrc (write control) - bit 4-6 of byte 3
1065 WRC = tonumber(bbit("0x"..bytes[v['start']+3],4,3),16)
1066 --tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." HDR", v['start'], v['start']+3)
1067 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." CRC", v['start']+4, v['start']+4, true)
1068 table.insert(tagMap.crc8, {name = 'Segment '..("%02d"):format(v['index']).." CRC", pos=v['start']+4, seq={1,4,v['start'],v['start']+3}} )
1071 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRC", v['start']+5, v['start']+5+WRC-1, true)
1072 elseif (WRP>WRC and WRC>0) then
1074 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRC", v['start']+5, v['start']+5+WRC-1, true)
1075 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRP", v['start']+WRC+5, v['start']+5+WRP-1, true)
1078 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRP", v['start']+5, v['start']+5+WRP-1, true)
1080 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." data", v['start']+5+WRPC, v['end'], false)
1083 print(#segs.." Segments mapped")
1085 oops("autoMapSegments failed: no Segments found")
1091 -- map all token data
1092 function mapTokenData(tagMap, mname, mstart, mend, mhigh)
1093 --if ( not mhigh ) then mhigh=false end
1095 myMapping['name'] =mname
1096 myMapping['start']=mstart
1097 myMapping['end'] =mend
1098 myMapping['highlight']=mhigh
1099 table.insert(tagMap.mappings, myMapping)
1105 function mapTag(tagMap)
1107 tagMap=mapTokenData(tagMap, 'Tag-ID', 1, 4, true)
1108 tagMap=mapTokenData(tagMap, 'Tag-CRC', 5, 5, false)
1109 tagMap=mapTokenData(tagMap, 'DCF', 6, 7, true)
1110 tagMap=mapTokenData(tagMap, 'THDR-Raw/Stamp-Len', 8, 8, true)
1111 tagMap=mapTokenData(tagMap, 'SSC', 9, 9, true)
1112 tagMap=mapTokenData(tagMap, 'Header', 10, 13, false)
1113 tagMap=mapTokenData(tagMap, 'Backup', 14, 19, true)
1114 tagMap=mapTokenData(tagMap, 'Bck-CRC', 20, 20, false)
1115 tagMap=mapTokenData(tagMap, 'TokenTime', 21, 22, false)
1121 -- dump virtual Tag-Data
1122 function dumpTag(tag)
1128 res =acyellow.."\nCDF: System Area"..acoff
1129 res= res.."\n"..dumpCDF(tag)
1130 -- segments (user-token area)
1131 if(tag.Type=="SAM") then
1132 res = res..acyellow.."\n\nADF: User Area"..acoff
1133 for i=0, #tag.SEG do
1134 res=res.."\n"..dumpSegment(tag, i).."\n"
1141 -- dump tag-system area
1142 function dumpCDF(tag)
1147 if (istable(tag)) then
1148 res = res..accyan.."MCD: "..acoff..tag.MCD..accyan.." MSN: "..acoff..tag.MSN0.." "..tag.MSN1.." "..tag.MSN2..accyan.." MCC: "..acoff..tag.MCC.."\n"
1149 res = res.."DCF: "..tag.DCFl.." "..tag.DCFh..", Token_Type="..tag.Type.." (OLE="..tag.OLE.."), Stamp_len="..tag.Stamp_len.."\n"
1150 res = res.."WRP="..tag.WRP..", WRC="..tag.WRC..", RD="..tag.RD..", raw="..tag.raw..((tag.raw=='9f') and (", SSC="..tag.SSC.."\n") or "\n")
1152 -- credential (end-user tag)
1153 if (tag.Type=="SAM") then
1154 res = res.."Remaining Header Area\n"
1155 for i=0, (#tag.data) do
1156 res = res..tag.data[i].." "
1158 res = res.."\nBackup Area\n"
1159 for i=0, (#tag.Bck) do
1160 res = res..tag.Bck[i].." "
1162 res = res.."\nTime Area\n"
1163 for i=0, (#tag.MTC) do
1164 res = res..tag.MTC[i].." "
1167 -- Master Token specific
1169 res = res .."Master-Token Area\nStamp: "
1170 res= res..tag.SSC.." "
1171 for i=0, tag.Stamp_len-2 do
1172 res = res..tag.data[i].." "
1174 res=res.."\nunused payload\n"
1175 for i=0, (#tag.data-tag.Stamp_len-1) do
1176 res = res..tag.data[i].." "
1178 bytes=tagToBytes(tag)
1179 local mtcrc=calcMtCrc(bytes)
1180 res=res.."\nMaster-Token CRC: "
1181 res = res ..tag.MTC[1].." ("..((tag.MTC[1]==mtcrc) and "valid" or "error")..")"
1184 else print("no valid Tag in dumpCDF") end
1188 -- dump single segment
1189 function dumpSegment(tag, index)
1192 local dp=0 --data-position in table
1193 local res="" --result
1194 local raw="" --raw-header
1196 if ( (istable(tag.SEG[i])) and tag.Type=="SAM") then
1197 if (istable(tag.SEG[i].raw)) then
1198 for k,v in pairs(tag.SEG[i].raw) do
1204 res = res..accyan.."Segment "..("%02d"):format(tag.SEG[i].index)..acoff..": "
1205 res = res .."raw header: "..string.sub(raw,0,-2)..", flag="..tag.SEG[i].flag..", (valid="..("%x"):format(tag.SEG[i].valid)..", last="..("%x"):format(tag.SEG[i].last).."), "
1206 res = res .."len="..("%04d"):format(tag.SEG[i].len)..", WRP="..("%02x"):format(tag.SEG[i].WRP)..", WRC="..("%02x"):format(tag.SEG[i].WRC)..", "
1207 res = res .."RD="..("%02x"):format(tag.SEG[i].RD)..", CRC="..tag.SEG[i].crc.." "
1208 res = res .."("..(checkSegmentCrc(tag, i) and acgreen.."valid" or acred.."error") ..acoff..")"
1213 if ((tag.SEG[i].WRC>0)) then
1214 res = res .."\nWRC protected area:\n"
1215 for i2=dp, dp+tag.SEG[i].WRC-1 do
1216 res = res..tag.SEG[i].data[i2].." "
1222 if (tag.SEG[i].WRP>tag.SEG[i].WRC) then
1223 res = res .."\nRemaining write protected area:\n"
1224 for i2=dp, dp+(tag.SEG[i].WRP-tag.SEG[i].WRC)-1 do
1225 res = res..tag.SEG[i].data[i2].." "
1231 if (#tag.SEG[i].data-dp>0) then
1232 res = res .."\nRemaining segment payload:\n"
1233 for i2=dp, #tag.SEG[i].data-2 do
1234 res = res..tag.SEG[i].data[dp].." "
1237 if (tag.SEG[i].kgh) then
1238 res = res..tag.SEG[i].data[dp].." (KGH: "..(checkKghCrc(tag, i) and acgreen.."valid" or acred.."error") ..acoff..")"
1239 else res = res..tag.SEG[i].data[dp] end
1244 return print("Segment not found")
1249 -- return bytes 'sstrat' to 'send' from a table
1250 function dumpTable(tab, header, tstart, tend)
1252 for i=tstart, tend do
1253 res=res..tab[i].." "
1255 if (string.len(header)==0) then return res
1256 else return (header.." #"..(tend-tstart+1).."\n"..res) end
1260 -- dump 3rd Party Cash
1261 function dump3rdPartyCash1(tag , seg)
1262 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1263 local stamp=tag.SEG[seg].data[0].." "..tag.SEG[seg].data[1].." "..tag.SEG[seg].data[2]
1264 local datastamp=tag.SEG[seg].data[20].." "..tag.SEG[seg].data[21].." "..tag.SEG[seg].data[22]
1265 local balance=tonumber(tag.SEG[seg].data[32]..tag.SEG[seg].data[33] ,16)
1266 local balancecrc=utils.Crc8Legic(uid..tag.SEG[seg].data[32]..tag.SEG[seg].data[33])
1267 local mirror=tonumber(tag.SEG[seg].data[35]..tag.SEG[seg].data[36] ,16)
1268 local mirrorcrc=utils.Crc8Legic(uid..tag.SEG[seg].data[35]..tag.SEG[seg].data[36])
1269 local lastbal0=tonumber(tag.SEG[seg].data[39]..tag.SEG[seg].data[40] ,16)
1270 local lastbal1=tonumber(tag.SEG[seg].data[41]..tag.SEG[seg].data[42] ,16)
1271 local lastbal2=tonumber(tag.SEG[seg].data[43]..tag.SEG[seg].data[44] ,16)
1274 -- display decoded/known stuff
1275 print("\n------------------------------")
1276 print("Tag-ID:\t\t "..uid)
1277 print("Stamp:\t\t "..stamp)
1278 print("UID-Mapping: \t\t"..("%06d"):format(tonumber(tag.SEG[seg].data[46]..tag.SEG[seg].data[47]..tag.SEG[seg].data[48], 16)))
1279 print("------------------------------")
1280 print("checksum 1:\t\t "..tag.SEG[seg].data[31].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 19, 30)), tag.SEG[seg].data[31])..")")
1281 print("checksum 2:\t\t "..tag.SEG[seg].data[34].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 32, 33)), tag.SEG[seg].data[34])..")")
1282 print("checksum 3:\t\t "..tag.SEG[seg].data[37].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 35, 36)), tag.SEG[seg].data[37])..")")
1284 print("checksum 4:\t\t "..tag.SEG[seg].data[55].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 46, 54)), tag.SEG[seg].data[55])..")")
1285 print("checksum 5:\t\t "..tag.SEG[seg].data[62].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 56, 61)), tag.SEG[seg].data[62])..")")
1286 print("checksum 6:\t\t "..tag.SEG[seg].data[73].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 63, 72)), tag.SEG[seg].data[73])..")")
1287 print("checksum 7:\t\t "..tag.SEG[seg].data[89].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 74, 88)), tag.SEG[seg].data[89])..")")
1288 print("------------------------------")
1289 print(string.format("Balance:\t\t %3.2f", balance/100).." ".."("..compareCrc(balancecrc, tag.SEG[seg].data[34])..")")
1290 print(string.format("Shadow:\t\t\t %3.2f", mirror/100).." ".."("..compareCrc(balancecrc, tag.SEG[seg].data[37])..")")
1291 print("------------------------------")
1292 print(string.format("History 1:\t\t %3.2f", lastbal0/100))
1293 print(string.format("History 2:\t\t %3.2f", lastbal1/100))
1294 print(string.format("History 3:\t\t %3.2f", lastbal2/100))
1295 print("------------------------------\n")
1299 -- dump Legic-Cash data
1300 function dumpLegicCash(tag, x)
1301 if (istable(tag.SEG[x])) then
1302 io.write("in Segment "..tag.SEG[x].index.." :\n")
1303 print("--------------------------------\n\tLegic-Cash Values\n--------------------------------")
1304 local limit, curr, balance, rid, tcv
1305 -- currency of balance & limit
1306 curr=currency[tag.SEG[x].data[8]..tag.SEG[x].data[9]]
1308 limit=string.format("%4.2f", tonumber(tag.SEG[x].data[10]..tag.SEG[x].data[11]..tag.SEG[x].data[12], 16)/100)
1310 balance=string.format("%4.2f", tonumber(tag.SEG[x].data[15]..tag.SEG[x].data[16]..tag.SEG[x].data[17], 16)/100)
1311 -- reader-id who wrote last transaction
1312 rid=tonumber(tag.SEG[x].data[18]..tag.SEG[x].data[19]..tag.SEG[x].data[20], 16)
1313 -- transaction counter value
1314 tcv=tonumber(tag.SEG[x].data[29], 16)
1315 print("Currency:\t\t "..curr)
1316 print("Limit:\t\t\t "..limit)
1317 print("Balance:\t\t "..balance)
1318 print("Transaction Counter:\t "..tcv)
1319 print("Reader-ID:\t\t "..rid.."\n--------------------------------\n")
1325 function print3rdPartyCash1(tag, x)
1326 if (istable(tag.SEG[x])) then
1327 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1328 print("\n\t\tStamp : "..dumpTable(tag.SEG[x].data, "", 0 , 2))
1329 print("\t\tBlock 0: "..dumpTable(tag.SEG[x].data, "", 3 , 18))
1331 print("\t\tBlock 1: "..dumpTable(tag.SEG[x].data, "", 19, 30))
1332 print("checksum 1: Tag-ID .. Block 1 => LegicCrc8 = "..tag.SEG[x].data[31].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 19, 30)), tag.SEG[x].data[31])..")")
1334 print("\t\tBlock 2: "..dumpTable(tag.SEG[x].data, "", 32, 33))
1335 print("checksum 2: Block 2 => LegicCrc8 = "..tag.SEG[x].data[34].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 32, 33)), tag.SEG[x].data[34])..")")
1337 print("\t\tBlock 3: "..dumpTable(tag.SEG[x].data, "", 35, 36))
1338 print("checksum 3: Block 3 => LegicCrc8 = "..tag.SEG[x].data[37].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 35, 36)), tag.SEG[x].data[37])..")")
1340 print("\t\tyet unknown: "..tag.SEG[x].data[38])
1342 print("\t\tHisatory 1: "..dumpTable(tag.SEG[x].data, "", 39, 40))
1343 print("\t\tHisatory 2: "..dumpTable(tag.SEG[x].data, "", 41, 42))
1344 print("\t\tHisatory 3: "..dumpTable(tag.SEG[x].data, "", 43, 44))
1346 print("\t\tyet unknown: "..tag.SEG[x].data[45])
1348 print("\t\tKGH-UID HEX: "..dumpTable(tag.SEG[x].data, "", 46, 48))
1349 print("\t\tBlock 4: "..dumpTable(tag.SEG[x].data, "", 49, 54))
1350 print("checksum 4: Tag-ID .. KGH-UID .. Block 4 => LegicCrc8 = "..tag.SEG[x].data[55].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 46, 54)), tag.SEG[x].data[55])..")")
1352 print("\t\tBlock 5: "..dumpTable(tag.SEG[x].data, "", 56, 61))
1353 print("checksum 5: Tag-ID .. Block 5 => LegicCrc8 = "..tag.SEG[x].data[62].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 56, 61)), tag.SEG[x].data[62])..")")
1355 print("\t\tBlock 6: "..dumpTable(tag.SEG[x].data, "", 63, 72))
1356 print("checksum 6: Tag-ID .. Block 6 => LegicCrc8 = "..tag.SEG[x].data[73].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 63, 72)), tag.SEG[x].data[73])..")")
1358 print("\t\tBlock 7: "..dumpTable(tag.SEG[x].data, "", 74, 88))
1359 print("checksum 7: Tag-ID .. Block 7 => LegicCrc8 = "..tag.SEG[x].data[89].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 74, 88)), tag.SEG[x].data[89])..")")
1361 print("\t\tBlock 8: "..dumpTable(tag.SEG[x].data, "", 90, 94))
1365 --- Token related --
1368 function makeToken()
1370 ['Type'] = {"SAM", "SAM63", "SAM64", "IAM", "GAM"},
1371 ['DCF'] = {"60ea", "31fa", "30fa", "80fa", "f0fa"},
1372 ['WRP'] = {"15", "2", "2", "2", "2"},
1373 ['WRC'] = {"01", "02", "02", "00", "00"},
1374 ['RD'] = {"01", "00", "00", "00", "00"},
1375 ['Stamp'] = {"00", "00", "00", "00", "00"},
1376 ['Segment'] = {"0d", "c0", "04", "00", "be", "01", "02", "03", "04", "01", "02", "03", "04"}
1379 for k, v in pairs(mt.Type) do
1380 ttype=ttype..k..") "..v.." "
1382 mtq=tonumber(input("select number for Token-Type\n"..ttype, '1'), 10)
1383 if (type(mtq)~="number") then return print("selection invalid!")
1384 elseif (mtq>#mt.Type) then return print("selection invalid!")
1385 else print("Token-Type '"..mt.Type[mtq].."' selected") end
1386 local raw=calcHeaderRaw(mt.WRP[mtq], mt.WRC[mtq], mt.RD[mtq])
1389 bytes={"01", "02", "03", "04", "cb", string.sub(mt.DCF[mtq], 0, 2), string.sub(mt.DCF[mtq], 3), raw,
1390 "00", "00", "00", "00", "00", "00", "00", "00",
1391 "00", "00", "00", "00", "00", "00"}
1393 for i=0, #mt.Segment do
1394 table.insert(bytes, mt.Segment[i])
1399 for i=#bytes, 1023 do table.insert(bytes, "00") end
1400 -- if Master-Token -> calc Master-Token-CRC
1401 if (mtq>1) then bytes[22]=calcMtCrc(bytes) end
1402 local tempTag=createTagTable()
1403 -- remove segment if MasterToken
1404 if (mtq>1) then tempTag.SEG[0]=nil end
1405 return bytesToTag(bytes, tempTag)
1410 function editTag(tag)
1411 -- for simulation it makes sense to edit everything
1412 local edit_sim="MCD MSN0 MSN1 MSN2 MCC DCFl DCFh WRP WRC RD"
1413 -- on real tags it makes only sense to edit DCF, WRP, WRC, RD
1414 local edit_real="DCFl DCFh WRP WRC RD"
1415 if (confirm(acyellow.."do you want to edit non-writeable values (e.g. for simulation)?"..acoff)) then
1417 else edit_tag=edit_real end
1419 if(istable(tag)) then
1420 for k,v in pairs(tag) do
1421 if(type(v)~="table" and type(v)~="boolean" and string.find(edit_tag, k)) then
1422 tag[k]=input("value for: "..accyan..k..acoff, v)
1426 if (tag.Type=="SAM") then ttype="Header"; else ttype="Stamp"; end
1427 if (confirm(acyellow.."do you want to edit "..ttype.." Data?"..acoff)) then
1428 -- master-token specific
1429 if(istable(tag.Bck)==false) then
1430 -- stamp-data length=(0xfc-DCFh)
1431 -- on MT: SSC holds the Starting Stamp Character (Stamp0)
1432 tag.SSC=input(ttype.."0: ", tag.SSC)
1433 -- rest of stamp-bytes are in tag.data 0..n
1434 for i=0, (tonumber(0xfc ,10)-("%d"):format('0x'..tag.DCFh))-2 do
1435 tag.data[i]=input(ttype.. i+1 ..": ", tag.data[i])
1438 --- on credentials byte7 should always be 9f and byte8 ff
1439 -- on Master-Token not (even on SAM63/64 not)
1440 -- tag.SSC=input(ttype.."0: ", tag.SSC)
1441 for i=0, #tag.data do
1442 tag.data[i]=input(ttype.. i ..": ", tag.data[i])
1447 bytes=tagToBytes(tag)
1449 --- check data-consistency (calculate tag.raw)
1450 bytes[8]=calcHeaderRaw(tag.WRP, tag.WRC, tag.RD)
1452 --- Master-Token specific
1453 -- should be triggered if a SAM was converted to a non-SAM (user-Token to Master-Token)
1454 -- or a Master-Token has being edited (also SAM64 & SAM63 - which are in fact Master-Token)
1455 if(tag.Type~="SAM" or bytes[6]..bytes[7]~="60ea") then
1456 -- calc new Master-Token crc
1457 bytes[22]=calcMtCrc(bytes)
1459 -- ensure tag.SSC set to 'ff' on credential-token (SAM)
1461 -- if a Master-Token was converted to a Credential-Token
1462 -- lets unset the Time-Area to 00 00 (will contain Stamp-Data on MT)
1467 tag=bytesToTag(bytes, tag)
1472 -- calculates header-byte (addr 0x07)
1473 function calcHeaderRaw(wrp, wrc, rd)
1475 wrp=("%02x"):format(tonumber(wrp, 10))
1477 res=("%02x"):format(tonumber(wrp, 16)+tonumber(wrc.."0", 16)+((rd>0) and tonumber("8"..(rd-1), 16) or 0))
1482 -- determine TagType (bits 0..6 of DCFlow)
1483 function getTokenType(DCFl)
1489 local tt = tonumber(bbit("0x"..DCFl,0,7),10)
1490 if (tt >= 0 and tt <= 47) then tt = "IAM"
1491 elseif (tt == 49) then tt = "SAM63"
1492 elseif (tt == 48) then tt = "SAM64"
1493 elseif (tt >= 50 and tt <= 111) then tt = "SAM"
1494 elseif (tt >= 112 and tt <= 127) then tt = "GAM"
1500 -- clear beackup-area of a virtual tag
1501 function clearBackupArea(tag)
1502 for i=1, #tag.Bck do
1508 --- Segment related --
1510 -- get segmemnt-data from byte-table
1511 function getSegmentData(bytes, start, index)
1518 ['raw'] = {'00', '00', '00', '00'},
1526 if (bytes[start]) then
1529 segment.index = index
1530 -- flag = high nibble of byte 1
1531 segment.flag = string.sub(bytes[start+1],0,1)
1532 -- valid = bit 6 of byte 1
1533 segment.valid = bbit("0x"..bytes[start+1],6,1)
1534 -- last = bit 7 of byte 1
1535 segment.last = bbit("0x"..bytes[start+1],7,1)
1536 -- len = (byte 0)+(bit0-3 of byte 1)
1537 segment.len = tonumber(bbit("0x"..bytes[start+1],0,4)..bytes[start],16)
1538 -- raw segment header
1539 segment.raw = {bytes[start], bytes[start+1], bytes[start+2], bytes[start+3]}
1540 -- wrp (write proteted) = byte 2
1541 segment.WRP = tonumber(bytes[start+2],16)
1542 -- wrc (write control) - bit 4-6 of byte 3
1543 segment.WRC = tonumber(bbit("0x"..bytes[start+3],4,3),16)
1544 -- rd (read disabled) - bit 7 of byte 3
1545 segment.RD = tonumber(bbit("0x"..bytes[start+3],7,1),16)
1547 segment.crc = bytes[start+4]
1548 -- segment-data starts at segment.len - segment.header - segment.crc
1549 for i=0, (segment.len-5) do
1550 segment.data[i]=bytes[start+5+i]
1558 -- get index, start-aadr, length and content
1559 function getSegmentStats(bytes)
1561 local sValid, sLast, sLen, sStart, x
1566 -- valid = bit 6 of byte 1
1567 sValid = bbit("0x"..bytes[sStart+1],6,1)
1568 -- last = bit 7 of byte 1
1569 sLast = bbit("0x"..bytes[sStart+1],7,1)
1570 -- len = (byte 0)+(bit0-3 of byte 1)
1571 sLen = tonumber(bbit("0x"..bytes[sStart+1],0,4)..bytes[sStart],16)
1572 --print("index: "..("%02d"):format(x).." Len: "..sLen.." start:"..sStart.." end: "..(sStart+sLen-1))
1575 s['end']=sStart+sLen-1
1577 if ( (sStart+sLen-1)>sStart ) then
1578 table.insert(sStats, s)
1582 until (sLast==1 or sValid==0 or x==126)
1583 if (#sStats>0 ) then return sStats
1584 else return false; end
1588 -- regenerate segment-header (after edit)
1589 function regenSegmentHeader(segment)
1591 local raw = segment.raw
1593 -- len bit0..7 | len=12bit=low nibble of byte1..byte0
1594 raw[1]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),0,8))
1595 -- high nibble of len bit6=valid , bit7=last of byte 1 | ?what are bit 5+6 for? maybe kgh?
1596 raw[2]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),8,4)+bbit("0x"..("%02x"):format((seg.valid*64)+(seg.last*128)),0,8))
1598 raw[3]=("%02x"):format(bbit("0x"..("%02x"):format(seg.WRP),0,8))
1600 raw[4]=("%02x"):format(tonumber((seg.WRC*16)+(seg.RD*128),10))
1602 seg.flag=string.sub(raw[2],0,1)
1603 --print(raw[1].." "..raw[2].." "..raw[3].." "..raw[4])
1604 if(#seg.data>(seg.len-5)) then
1605 print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5)
1606 print(acyellow.."Data-Length has being reduced:"..acgreen.." auto-removing "..acyellow.. #seg.data-(seg.len-5) ..acgreen .." bytes from Payload!"..acoff);
1607 for i=(seg.len-5), #seg.data-1 do
1608 table.remove(seg.data)
1610 elseif (#seg.data<(seg.len-5)) then
1611 print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5)
1612 print(acyellow.."Data-Length has being extended:"..acgreen.." auto-adding "..acyellow..(seg.len-5)-#seg.data ..acgreen .." bytes to Payload!"..acoff);
1613 for i=#seg.data, (seg.len-5-1) do
1614 table.insert(seg.data, '00')
1621 -- edit segment helper
1622 function editSegment(tag, index)
1624 local edit_possible="valid len RD WRP WRC Stamp Payload"
1625 if (istable(tag.SEG[index])) then
1626 for k,v in pairs(tag.SEG[index]) do
1627 if(string.find(edit_possible,k)) then
1628 tag.SEG[index][k]=tonumber(input(accyan..k..acoff..": ", v),10)
1631 else print("Segment with Index: "..("%02d"):format(index).." not found in Tag")
1634 regenSegmentHeader(tag.SEG[index])
1635 print("\n"..dumpSegment(tag, index).."\n")
1640 -- edit Segment Data
1641 function editSegmentData(data)
1642 local lc=check4LegicCash(data)
1644 if (istable(data)) then
1646 data[i]=input(accyan.."Data"..i..acoff..": ", data[i])
1648 if (lc) then data=fixLegicCash(data) end
1651 print("no Segment-Data found")
1656 -- list available segmets in virtual tag
1657 function segmentList(tag)
1660 if (istable(tag.SEG[0])) then
1661 for i=0, #tag.SEG do
1662 res = res .. tag.SEG[i].index .. " "
1665 else print("no Segments found in Tag")
1671 -- helper to selecting a segment
1672 function selectSegment(tag)
1674 if (istable(tag.SEG[0])) then
1675 print("availabe Segments:\n"..segmentList(tag))
1676 sel=input("select Segment: ", '00')
1677 sel=tonumber(sel,10)
1678 if (sel) then return sel
1681 print("\nno Segments found")
1688 function addSegment(tag)
1696 ['raw'] = {'0d', '40', '04', '00'},
1704 if (istable(tag.SEG[0])) then
1705 tag.SEG[#tag.SEG].last=0
1706 table.insert(tag.SEG, segment)
1708 tag.SEG[#tag.SEG].data[i]=("%02x"):format(i)
1710 tag.SEG[#tag.SEG].index=("%02d"):format(#tag.SEG)
1713 print("no Segment-Table found")
1718 -- get only the stamp-bytes of a segment
1719 function getSegmentStamp(seg, bytes)
1722 --- the 'real' stamp on MIM is not really easy to tell for me since the 'data-block' covers stamp0..stampn+data0..datan
1723 -- there a no stamps longer than 7 bytes & they are write-protected by default , and I have not seen user-credntials
1724 -- with stamps smaller 3 bytes (except: Master-Token)
1725 -- WRP -> Read/Write Protection
1726 -- WRC -> Read/Write Condition
1727 -- RD depends on WRC - if WRC > 0 and RD=1: only reader with matching #WRC of Stamp-bytes in thier Database have Read-Access to the Tag
1728 if (seg.WRP<7) then stamp_len=(seg.WRP) end
1729 for i=1, (stamp_len) do
1730 stamp=stamp..seg.data[i-1]
1733 stamp=str2bytes(stamp)
1735 else return stamp end
1739 -- edit stamp of a segment
1740 function editStamp(new_stamp, uid, data)
1741 local stamp=str2bytes(new_stamp)
1742 for i=0, #stamp-1 do
1745 -- now fill in stamp
1746 for i=0, (string.len(new_stamp)/2)-1 do
1749 return fix3rdPartyCash1(uid, data)
1753 -- autoselect special/known segments
1754 function autoSelectSegment(tag, s)
1755 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1758 io.write("autoSelect ")
1759 --- search for desired segment-type
1760 -- 3rd Party Segment
1761 if (s=="3rdparty") then
1765 res=check43rdPartyCash1(uid, tag.SEG[x].data)
1766 until ( res or x==0 )
1768 -- Legic-Cash Segment
1769 if (s=="legiccash") then
1773 res=check4LegicCash(tag.SEG[x].data)
1774 until ( res or x==0 )
1779 io.write("\nautoselected Index: "..string.format("%02d", x).."\n")
1784 io.write(acyellow.."no Segment found\n"..acoff)
1789 -- delete segment (except segment 00)
1790 function delSegment(tag, index)
1791 if (istable(tag.SEG[0])) then
1793 if (type(index)=="string") then index=tonumber(index,10) end
1795 table.remove(tag.SEG, index)
1796 for i=0, #tag.SEG do
1797 tag.SEG[i].index=("%02d"):format(i)
1800 if(istable(tag.SEG[#tag.SEG])) then tag.SEG[#tag.SEG].last=1 end
1806 -- edit uid 3rd party cash
1807 function edit3rdUid(mapid, uid, data)
1808 mapid=("%06x"):format(tonumber(mapid, 10))
1809 data[46]=string.sub(mapid, 1 ,2)
1810 data[47]=string.sub(mapid, 3 ,4)
1811 data[48]=string.sub(mapid, 5 ,6)
1812 return fix3rdPartyCash1(uid, data)
1816 -- edit balance 3rd party cash
1817 function edit3rdCash(new_cash, uid, data)
1818 new_cash=("%04x"):format(new_cash)
1819 data[32]=string.sub(new_cash, 0,2)
1820 data[33]=string.sub(new_cash, 3,4)
1821 data[34]=("%02x"):format(utils.Crc8Legic(uid..new_cash))
1822 data[35]=string.sub(new_cash, 0,2)
1823 data[36]=string.sub(new_cash, 3,4)
1824 data[37]=("%02x"):format(utils.Crc8Legic(uid..new_cash))
1825 data[39]=string.sub(new_cash, 0,2)
1826 data[40]=string.sub(new_cash, 3,4)
1831 return fix3rdPartyCash1(uid, data)
1835 -- edit 3rd-party cash
1836 function edit3rdPartyCash1(tag, x)
1837 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1838 if (confirm("\nedit Balance?")) then
1839 local new_cash=input("enter new Balance≤\nwithout comma and without currency-sign! (0-65535)", "100")
1840 tag.SEG[x].data=edit3rdCash(new_cash, uid, tag.SEG[x].data)
1842 -- change User-ID (used for online-account-mapping)
1843 if (confirm("\nedit UserID-Mapping?")) then
1844 local new_mapid=input("enter new UserID (6-digit value)", "012345")
1845 tag.SEG[x].data=edit3rdUid(new_mapid, uid, tag.SEG[x].data)
1847 if (confirm("\nedit Stamp?")) then
1848 local new_stamp=input("enter new Stamp", getSegmentStamp(tag.SEG[x]))
1849 tag.SEG[x].data=editStamp(new_stamp, uid, tag.SEG[x].data)
1850 new_stamp=getSegmentStamp(tag.SEG[x], 'true')
1851 print("stamp_bytes: "..#new_stamp)
1852 -- replace stamp in 'block 1' also
1853 io.write("editing stamp in Block 1 also ")
1854 for i=20, (20+#new_stamp-1) do
1855 tag.SEG[x].data[i]=new_stamp[i-19]
1859 -- fix known checksums
1860 tag.SEG[x].data=fix3rdPartyCash1(uid, tag.SEG[x].data)
1867 function editLegicCash(data)
1868 local limit, curr, balance, rid, tcv
1869 -- currency of balance & limit
1870 curr=currency[data[8]..data[9]]
1872 limit=string.format("%4.2f", tonumber(data[10]..data[11]..data[12], 16)/100)
1874 balance=string.format("%4.2f", tonumber(data[15]..data[16]..data[17], 16)/100)
1875 -- reader-id who wrote last transaction
1876 rid=tonumber(data[18]..data[19]..data[20], 16)
1877 -- transaction counter value
1878 tcv=tonumber(data[29], 16)
1881 if (confirm(accyan.."change Currency?"..acoff)) then
1882 for k, v in pairs(currency) do io.write(k .. " = " .. v .. "\n") end
1883 curr=input(accyan.."enter the 4-digit Hex for the new Currency:"..acoff, data[8]..data[9])
1884 data[8]=string.sub(curr, 1, 2)
1885 data[9]=string.sub(curr, 3, 4)
1889 if (confirm(accyan.."change Limit?"..acoff)) then
1890 limit=string.format("%06x", input(accyan.."enter the Decimal for the new Limit:"..acoff, limit))
1891 data[10]=string.sub(limit, 1, 2)
1892 data[11]=string.sub(limit, 3, 4)
1893 data[12]=string.sub(limit, 5, 6)
1897 if (confirm(accyan.."change Balance?"..acoff)) then
1898 balance=string.format("%06x", input(accyan.."enter the Decimal for the new Balance:"..acoff, balance))
1899 print("Balance: "..balance)
1900 data[15]=string.sub(balance, 1, 2)
1901 data[16]=string.sub(balance, 3, 4)
1902 data[17]=string.sub(balance, 5, 6)
1905 -- edit transaction-counter
1906 if (confirm(accyan.."change Transaction-Counter?"..acoff)) then
1907 tcv=string.format("%02x", input(accyan.."enter the 4-digit Hex for the new Currency:"..acoff, data[29]))
1912 if (confirm(accyan.."change Last-Reader-ID?"..acoff)) then
1913 rid=string.format("%06x", input(accyan.."enter the Decimal for the new Balance:"..acoff, rid))
1914 print("Balance: "..balance)
1915 data[18]=string.sub(rid, 1, 2)
1916 data[19]=string.sub(rid, 3, 4)
1917 data[20]=string.sub(rid, 5, 6)
1920 return fixLegicCash(data)
1924 -- chack for signature of a 'Legic-Cash-Segment'
1925 function check4LegicCash(data)
1927 local stamp_len=(#data-25)
1929 for i=0, stamp_len-1 do
1930 stamp=stamp..data[i].." "
1932 if (data[7]=="01") then
1933 if (("%04x"):format(utils.Crc16(dumpTable(data, "", 0, 12))) == data[13]..data[14]) then
1934 if (("%04x"):format(utils.Crc16(dumpTable(data, "", 15, 20))) == data[21]..data[22]) then
1935 if (("%04x"):format(utils.Crc16(dumpTable(data, "", 23, 29))) == data[30]..data[31]) then
1936 io.write(accyan.."Legic-Cash Segment detected "..acoff)
1947 -- chack for signature of a '3rd Party Cash-Segment' - not all bytes know until yet !!
1948 function check43rdPartyCash1(uid, data)
1950 -- too explicit checking will avoid fixing ;-)
1951 if (string.find(compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 19, 30)), data[31]),"valid")) then
1952 --if (compareCrc(utils.Crc8Legic(uid..data[32]..data[33]), data[34])=="valid") then
1953 --if (compareCrc(utils.Crc8Legic(uid..data[35]..data[36]), data[37])=="valid") then
1954 --if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 56, 61)), data[62])=="valid") then
1955 --if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 74, 88)), data[89])=="valid") then
1956 io.write(accyan.."3rd Party Cash-Segment detected "..acoff)
1969 -- build segmentCrc credentials
1970 function segmentCrcCredentials(tag, segid)
1971 if (istable(tag.SEG[0])) then
1972 local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1973 cred = cred ..tag.SEG[segid].raw[1]..tag.SEG[segid].raw[2]..tag.SEG[segid].raw[3]..tag.SEG[segid].raw[4]
1975 else return print(acyellow.."Master-Token / unsegmented Tag!"..acoff) end
1979 -- build kghCrc credentials
1980 function kghCrcCredentials(tag, segid)
1981 if (istable(tag) and istable(tag.SEG[0])) then
1983 if (type(segid)=="string") then segid=tonumber(segid,10) end
1984 if (segid>0) then x='93' end
1985 local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2..("%02x"):format(tag.SEG[segid].WRP)
1986 cred = cred..("%02x"):format(tag.SEG[segid].WRC)..("%02x"):format(tag.SEG[segid].RD)..x
1987 for i=0, #tag.SEG[segid].data-2 do
1988 cred = cred..tag.SEG[segid].data[i]
1995 -- compare two bytes
1996 function compareCrc(calc, guess)
1997 calc=("%02x"):format(calc)
1998 if (calc==guess) then return acgreen.."valid"..acoff
1999 else return acred.."error "..acoff..calc.."!="..guess end
2004 function compareCrc16(calc, guess)
2005 calc=("%04x"):format(calc)
2006 if (calc==guess) then return acgreen.."valid"..acoff
2007 else return acred.."error "..acoff..calc.."!="..guess end
2011 -- repair / fix crc's of a 'Legic-Cash-Segment'
2012 function fixLegicCash(data)
2013 if(#data==32 and data[7]=="01") then
2014 local crc1, crc2, crc3
2015 -- set shadow-balance equal to balance
2019 -- set shadow-last-reader to last-reader
2023 -- calculate all crc's
2024 crc1=("%04x"):format(utils.Crc16(dumpTable(data, "", 0, 12)))
2025 crc2=("%04x"):format(utils.Crc16(dumpTable(data, "", 15, 20)))
2026 crc3=("%04x"):format(utils.Crc16(dumpTable(data, "", 23, 29)))
2028 data[13]=string.sub(crc1, 1, 2)
2029 data[14]=string.sub(crc1, 3, 4)
2030 data[21]=string.sub(crc2, 1, 2)
2031 data[22]=string.sub(crc2, 3, 4)
2032 data[30]=string.sub(crc3, 1, 2)
2033 data[31]=string.sub(crc3, 3, 4)
2039 -- repair / fix (yet known) crc's of a '3rd Party Cash-Segment' - not all bytes know until yet !!
2040 function fix3rdPartyCash1(uid, data)
2043 data[31]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 19, 30)))
2045 data[34]=("%02x"):format(utils.Crc8Legic(uid..data[32]..data[33]))
2047 data[37]=("%02x"):format(utils.Crc8Legic(uid..data[35]..data[36]))
2049 data[55]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 46, 54)))
2051 data[62]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 56, 61)))
2053 data[73]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 63, 72)))
2055 data[89]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 74, 88)))
2061 -- calculate Master-Token crc
2062 function calcMtCrc(bytes)
2064 local cmd=bytes[1]..bytes[2]..bytes[3]..bytes[4]..bytes[7]..bytes[6]..bytes[8]
2065 local len=(tonumber(0xfc ,10)-("%d"):format('0x'..bytes[7]))
2069 local res=("%02x"):format(utils.Crc8Legic(cmd))
2074 -- calculate segmentCRC for a given segment
2075 function calcSegmentCrc(tag, segid)
2076 if (istable(tag.SEG[0])) then
2077 -- check if a 'Kaber Group Header' exists
2078 local data=segmentCrcCredentials(tag, segid)
2079 return ("%02x"):format(utils.Crc8Legic(data))
2084 -- calcuate kghCRC for a given segment
2085 function calcKghCrc(tag, segid)
2086 if (istable(tag.SEG[0])) then
2087 -- check if a 'Kaber Group Header' exists
2089 local data=kghCrcCredentials(tag, segid)
2090 return ("%02x"):format(utils.Crc8Legic(data))
2095 -- check all segmnet-crc
2096 function checkAllSegCrc(tag)
2097 if (istable(tag.SEG[0])) then
2098 for i=0, #tag.SEG do
2099 crc=calcSegmentCrc(tag, i)
2102 else return print(acyellow.."Master-Token / unsegmented Tag"..acoff) end
2106 -- check all segmnet-crc
2107 function checkAllKghCrc(tag)
2108 if (istable(tag.SEG[0])) then
2109 for i=0, #tag.SEG do
2110 crc=calcKghCrc(tag, i)
2111 if (tag.SEG[i].kgh) then
2112 tag.SEG[i].data[#tag.SEG[i].data-1]=crc
2119 -- validate segmentCRC for a given segment
2120 function checkSegmentCrc(tag, segid)
2121 local data=segmentCrcCredentials(tag, segid)
2122 if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].crc) then
2129 -- validate kghCRC to segment in tag-table
2130 function checkKghCrc(tag, segid)
2131 if (type(tag.SEG[segid])=='table') then
2132 if (tag.data[3]=="11" and tag.raw=="9f" and tag.SSC=="ff") then
2133 local data=kghCrcCredentials(tag, segid)
2134 if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].data[tag.SEG[segid].len-5-1]) then return true; end
2135 else return false; end
2136 else oops(acred.."'Kaba Group header' detected but no Segment-Data found"..ansocolors.reset) end
2140 -- helptext for modify-mode
2141 function modifyHelp()
2144 Data I/O Segment Manipulation Token-Data
2145 ----------------- -------------------- ---------------------
2146 rt => read Tag as => add Segment mt => make Token
2147 wt => write Tag es => edit Segment Header et => edit Token data
2148 ed => edit Segment Data tk => toggle KGH-Flag
2149 File I/O rs => remove Segment
2150 ----------------- cc => check Segment-CRC
2151 lf => load File ck => check KGH
2152 sf => save File ds => dump Segments
2156 Virtual Tags tagMap (partial) known Segments
2157 -------------------------------- --------------------- ---------------------------
2158 ct => copy mainTag to backupTag mm => make (new) Map dlc => dump Legic-Cash
2159 tc => copy backupTag to mainTag em => edit Map submenu elc => edit Legic-Cash
2160 tt => switch mainTag & backupTag lm => load map from file d3p => dump 3rd-Party-Cash
2161 di => dump mainTag sm => save map to file e3p => edit 3rd-Party-Cash
2162 do => dump backupTag
2164 h => this help q => quit
2170 -- modify Tag (interactive)
2171 function modifyMode()
2172 local i, backupTAG, outTAG, inTAG, outfile, infile, sel, segment, bytes, outbytes, tagMap
2178 print(" Version: "..version);
2179 print(modifyHelp().."\n".."tags im Memory: "..(istable(inTAG) and ((currentTag=='inTAG') and acgreen.."*mainTAG"..acoff or "mainTAG") or "").." "..(istable(backupTAG) and ((currentTag=='backupTAG') and acgreen.."*backupTAG"..acoff or "backupTAG") or ""))
2182 -- read real Tag with PM3 into virtual 'mainTAG'
2183 ["rt"] = function(x)
2184 inTAG=readFromPM3();
2188 -- write content of virtual 'mainTAG' to real Tag with PM3
2189 ["wt"] = function(x)
2193 -- copy mainTAG to backupTAG
2194 ["ct"] = function(x)
2195 print(accyan.."copy mainTAG to backupTAG"..acoff)
2196 backupTAG=deepCopy(inTAG)
2199 -- copy backupTAG to mainTAG
2200 ["tc"] = function(x)
2201 print(accyan.."copy backupTAG to mainTAG"..acoff)
2202 inTAG=deepCopy(backupTAG)
2205 -- toggle between mainTAG and backupTAG
2206 ["tt"] = function(x)
2207 -- copy main to temp
2208 outTAG=deepCopy(inTAG)
2209 -- copy backup to main
2210 inTAG=deepCopy(backupTAG)
2211 print(accyan.."toggle to "..accyan..((currentTag=='inTAG') and "backupTAG" or "mainTAG")..acoff)
2212 if(currentTag=="inTAG") then currentTag='backupTAG'
2213 else currentTag='inTAG' end
2214 -- copy temp (main) to backup
2215 backupTAG=deepCopy(outTAG)
2218 -- load file into mainTAG
2219 ["lf"] = function(x)
2221 if (type(x)=='string' and file_check(x)) then filename=x
2222 else filename=input("enter filename: ", "legic.temp") end
2223 inTAG=readFile(filename)
2224 -- check for existing tagMap
2225 if (file_check(filename..".map")) then
2226 if(confirm(accyan.."Mapping-File for "..acoff..filename..accyan.." found - load it also?"..acoff)) then
2227 tagMap=loadTagMap(filename..".map")
2232 -- save values of mainTAG to a file (xored with MCC of mainTAG)
2233 ["sf"] = function(x)
2234 if(istable(inTAG)) then
2235 outfile=input("enter filename:", "legic.temp")
2236 bytes=tagToBytes(inTAG)
2237 --bytes=xorBytes(bytes, inTAG.MCC)
2239 writeFile(bytes, outfile)
2244 -- save values of mainTAG to a file (xored with 'specific' MCC)
2245 ["xf"] = function(x)
2246 if(istable(inTAG)) then
2247 outfile=input("enter filename:", "legic.temp")
2248 crc=input("enter new crc: ('00' for a plain dump)", inTAG.MCC)
2249 print("obfuscate witth: "..crc)
2250 bytes=tagToBytes(inTAG)
2253 writeFile(bytes, outfile)
2258 -- dump mainTAG (and all Segments)
2259 ["di"] = function(x)
2260 if (istable(inTAG)) then
2261 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2262 if(istable(inTAG.SEG[0])) then
2263 for i=0, #inTAG.SEG do
2264 if(check43rdPartyCash1(uid, inTAG.SEG[i].data)) then
2265 io.write(accyan.."in Segment index: "..inTAG.SEG[i].index ..acoff.. "\n")
2266 elseif(check4LegicCash(inTAG.SEG[i].data)) then
2267 io.write(accyan.."in Segment index: "..inTAG.SEG[i].index..acoff.."\n")
2269 lci=inTAG.SEG[i].index;
2273 print("\n"..dumpTag(inTAG).."\n")
2274 if (lc) then actions["dlc"](lci) end
2279 -- dump backupTAG (and all Segments)
2280 ["do"] = function(x) if (istable(backupTAG)) then print("\n"..dumpTag(backupTAG).."\n") end end,
2282 -- create a empty tagMap
2283 ["mm"] = function(x)
2284 -- clear existing tagMap and init
2285 if (istable(inTAG)) then
2291 ["em"] = function(x)
2292 if (istable(inTAG)==false) then
2293 if (confirm("no mainTAG in memory!\nread from PM3?")) then
2295 elseif (confirm("load from File?")) then
2300 if (istable(tagMap)==false) then actions['mm']() end
2302 tagMap=editTagMap(inTAG, tagMap)
2306 ["sm"] = function(x)
2307 if (istable(tagMap)) then
2308 if (istable(tagMap) and #tagMap.mappings>0) then
2309 print(accyan.."Map contains "..acoff..#tagMap..accyan.." mappings"..acoff)
2310 saveTagMap(tagMap, input(accyan.."enter filename:"..acoff, "Legic.map"))
2312 print(acyellow.."no mappings in tagMap!"..acoff)
2318 ["lm"] = function(x)
2319 tagMap=loadTagMap(input(accyan.."enter filename:"..acoff, "Legic.map"))
2322 -- dump single segment
2323 ["ds"] = function(x)
2324 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2325 else sel=selectSegment(inTAG) end
2326 if (sel) then print("\n"..(dumpSegment(inTAG, sel) or acred.."no Segments available") ..acoff.."\n") end
2329 -- edit segment header
2330 ["es"] = function(x)
2331 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2332 else sel=selectSegment(inTAG) end
2334 if(istable(inTAG.SEG[0])) then
2335 inTAG=editSegment(inTAG, sel)
2336 inTAG.SEG[sel]=regenSegmentHeader(inTAG.SEG[sel])
2337 else print(acyellow.."no Segments in Tag"..acoff) end
2342 ["as"] = function(x)
2343 if (istable(inTAG.SEG[0])) then
2344 inTAG=addSegment(inTAG)
2345 inTAG.SEG[#inTAG.SEG-1]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG-1])
2346 inTAG.SEG[#inTAG.SEG]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG])
2347 else print(accyan.."Master-Token / unsegmented Tag!"..acoff)
2352 ["rs"] = function(x)
2353 if (istable(inTAG.SEG[0])) then
2354 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2355 else sel=selectSegment(inTAG) end
2356 inTAG=delSegment(inTAG, sel)
2357 for i=0, #inTAG.SEG do
2358 inTAG.SEG[i]=regenSegmentHeader(inTAG.SEG[i])
2363 -- edit data-portion of single segment
2364 ["ed"] = function(x)
2365 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2366 else sel=selectSegment(inTAG) end
2367 if (istable(inTAG.SEG[sel])) then
2368 inTAG.SEG[sel].data=editSegmentData(inTAG.SEG[sel].data)
2372 -- edit Tag (MCD, MSN, MCC etc.)
2373 ["et"] = function(x)
2374 if (istable(inTAG)) then
2379 -- make (dummy) Token
2380 ["mt"] = function(x) inTAG=makeToken(); actions.di() end,
2382 -- fix segment-crc on single segment
2383 ["ts"] = function(x)
2384 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2385 else sel=selectSegment(inTAG) end
2386 regenSegmentHeader(inTAG.SEG[sel])
2389 -- toggle kgh-crc-flag on a single segment
2390 ["tk"] = function(x)
2391 if (istable(inTAG) and istable(inTAG.SEG[0])) then
2392 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2393 else sel=selectSegment(inTAG) end
2394 if(inTAG.SEG[sel].kgh) then inTAG.SEG[sel].kgh=false
2395 else inTAG.SEG[sel].kgh=true end
2399 -- calculate LegicCrc8
2401 if (type(x)=="string" and string.len(x)>0) then
2402 print(("%02x"):format(utils.Crc8Legic(x)))
2407 ["xb"] = function(x)
2410 -- print string for LegicCrc8-calculation about single segment
2411 ["xc"] = function(x)
2412 if (istable(inTAG) and istable(inTAG.SEG[0])) then
2413 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2414 else sel=selectSegment(inTAG) end
2415 print("k "..kghCrcCredentials(inTAG, sel))
2419 -- fix legic-cash checksums
2420 ["flc"] = function(x)
2421 if (type(x)=="string" and string.len(x)>0) then x=tonumber(x,10)
2422 else x=selectSegment(inTAG) end
2423 inTAG.SEG[x].data=fixLegicCash(inTAG.SEG[x].data)
2426 -- edit legic-cash values fixLegicCash(data)
2427 ["elc"] = function(x)
2428 x=autoSelectSegment(inTAG, "legiccash")
2429 inTAG.SEG[x].data=editLegicCash(inTAG.SEG[x].data)
2432 -- dump legic-cash human-readable
2433 ["dlc"] = function(x)
2434 -- if segment index was user defined
2435 if (type(x)=="string" and string.len(x)>0) then
2437 print(string.format("User-Selected Index %02d", x))
2438 -- or try to find match
2439 else x=autoSelectSegment(inTAG, "legiccash") end
2441 dumpLegicCash(inTAG, x)
2444 -- dump 3rd-party-cash-segment
2445 ["d3p"] = function(x)
2446 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2447 -- if segment index was user defined
2448 if (type(x)=="string" and string.len(x)>0) then
2450 print(string.format("User-Selected Index %02d", x))
2451 -- or try to find match
2452 else x=autoSelectSegment(inTAG, "3rdparty") end
2453 if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then
2454 uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2455 if (check43rdPartyCash1(uid, inTAG.SEG[x].data)) then
2456 dump3rdPartyCash1(inTAG, x)
2461 -- dump 3rd-party-cash-segment (raw blocks and checksums over 'known areas')
2462 ["r3p"] = function(x)
2463 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2464 -- if segment index was user defined
2465 if (type(x)=="string" and string.len(x)>0) then
2467 print(string.format("User-Selected Index %02d", x))
2468 -- or try to find match
2469 else x=autoSelectSegment(inTAG, "3rdparty") end
2470 print3rdPartyCash1(inTAG, x)
2473 -- edit 3rd-party-cash-segment values (Balance, Mapping-UID, Stamp)
2474 ["e3p"] = function(x)
2475 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2476 -- if segment index was user defined
2477 if (type(x)=="string" and string.len(x)>0) then
2479 print(string.format("User-Selected Index %02d", x))
2480 -- or try to find match
2481 else x=autoSelectSegment(inTAG, "3rdparty") end
2482 if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then
2483 inTAG=edit3rdPartyCash1(inTAG, x)
2484 dump3rdPartyCash1(inTAG, x)
2488 -- force fixing 3rd-party-checksums
2489 ["f3p"] = function(x)
2490 if(type(x)=="string" and string.len(x)>=2) then x=tonumber(x, 10)
2491 else x=selectSegment(inTAG) end
2492 if (istable(inTAG.SEG[x])) then
2493 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2494 inTAG.SEG[x].data=fix3rdPartyCash1(uid, inTAG.SEG[x].data)
2498 -- get stamp from single segment
2499 ["gs"] = function(x)
2500 if(type(x)=="string" and string.len(x)>=2) then x=tonumber(x, 10)
2501 else x=selectSegment(inTAG) end
2502 local stamp=getSegmentStamp(inTAG.SEG[x])
2503 print("Stamp : "..stamp)
2504 stamp=str2bytes(stamp)
2505 print("lenght: "..#stamp)
2509 ["c6"] = function(x) local crc16=string.format("%4.04x", utils.Crc16(x))
2510 print(string.sub(crc16, 0,2).." "..string.sub(crc16, 3,4))
2513 -- check & fix segments-crc of all segments
2514 ["cc"] = function(x) if (istable(inTAG)) then checkAllSegCrc(inTAG) end end,
2516 -- set backup-area-bytes to '00'
2517 ["cb"] = function(x)
2518 if (istable(inTAG)) then
2519 print(accyan.."purge BackupArea"..acoff)
2520 inTAG=clearBackupArea(inTAG)
2524 -- check and fix all segments inTAG.SEG[x].kgh toggled 'on'
2525 ["ck"] = function(x) if (istable(inTAG)) then checkAllKghCrc(inTAG) end end,
2527 -- check and fix all segments inTAG.SEG[x].kgh toggled 'on'
2528 ["tac"] = function(x)
2529 if (colored_output) then
2530 colored_output=false
2534 load_colors(colored_output)
2538 -- defualt message / prompt
2539 ic=input("Legic command? ('h' for help - 'q' for quit)", "h")
2540 -- command actions decisions (first match, longer commands before shorter)
2541 if (type(actions[string.lower(string.sub(ic,0,3))])=='function') then
2542 actions[string.lower(string.sub(ic,0,3))](string.sub(ic,5))
2543 elseif (type(actions[string.lower(string.sub(ic,0,2))])=='function') then
2544 actions[string.lower(string.sub(ic,0,2))](string.sub(ic,4))
2545 elseif (type(actions[string.lower(string.sub(ic,0,1))])=='function') then
2546 actions[string.lower(string.sub(ic,0,1))](string.sub(ic,3))
2547 else actions.h('') end
2548 until (string.sub(ic,0,1)=="q")
2554 -- set init colors/switch (can be toggled with 'tac' => 'toggle ansicolors')
2555 load_colors(colored_output)
2556 if (#args == 0 ) then modifyMode() end
2558 local inTAG, backupTAG, outTAG, outfile, interactive, crc, ofs, cfs, dfs
2559 -- just a spacer for better readability
2562 for o, a in getopt.getopt(args, 'hrmi:do:c:') do
2564 if o == "h" then return help(); end
2565 -- read tag from PM3
2566 if o == "r" then inTAG=readFromPM3() end
2568 if o == "i" then inTAG=readFile(a) end
2570 if o == "d" then dfs=true end
2571 -- interacive modifying
2572 if o == "m" then interactive=true; modifyMode() end
2573 -- xor (e.g. for clone or plain file)
2574 if o == "c" then cfs=true; crc=a end
2576 if o == "o" then outfile=a; ofs=true end
2579 -- file conversion (output to file)
2581 -- dump infile / tag-read
2583 print("-----------------------------------------")
2584 print(dumpTag(inTAG))
2586 bytes=tagToBytes(inTAG)
2588 -- xor willl be done in function writeFile
2589 -- with the value of byte[5]
2594 writeFile(bytes, outfile)
2595 --- read real tag into virtual tag
2596 -- inTAG=readFromPM3() end
2597 --- or simply use the bytes that where wriiten
2598 inTAG=bytesToTag(bytes, inTAG)
2601 print("-----------------------------------------")
2602 print(dumpTag(inTAG))