| 1 | //----------------------------------------------------------------------------- |
| 2 | // Copyright (C) 2015 iceman <iceman at iuse.se> |
| 3 | // |
| 4 | // This code is licensed to you under the terms of the GNU GPL, version 2 or, |
| 5 | // at your option, any later version. See the LICENSE.txt file for the text of |
| 6 | // the license. |
| 7 | //----------------------------------------------------------------------------- |
| 8 | // CRC Calculations from the software reveng commands |
| 9 | //----------------------------------------------------------------------------- |
| 10 | |
| 11 | #ifdef _WIN32 |
| 12 | # include <io.h> |
| 13 | # include <fcntl.h> |
| 14 | # ifndef STDIN_FILENO |
| 15 | # define STDIN_FILENO 0 |
| 16 | # endif /* STDIN_FILENO */ |
| 17 | #endif /* _WIN32 */ |
| 18 | |
| 19 | #include <stdio.h> |
| 20 | #include <string.h> |
| 21 | #include <stdlib.h> |
| 22 | #include <ctype.h> |
| 23 | #include "cmdmain.h" |
| 24 | #include "cmdcrc.h" |
| 25 | #include "reveng/reveng.h" |
| 26 | #include "ui.h" |
| 27 | #include "util.h" |
| 28 | |
| 29 | #define MAX_ARGS 20 |
| 30 | |
| 31 | int uerr(char *msg){ |
| 32 | PrintAndLog("%s",msg); |
| 33 | return 0; |
| 34 | } |
| 35 | |
| 36 | int split(char *str, char *arr[MAX_ARGS]){ |
| 37 | int beginIndex = 0; |
| 38 | int endIndex; |
| 39 | int maxWords = MAX_ARGS; |
| 40 | int wordCnt = 0; |
| 41 | |
| 42 | while(1){ |
| 43 | while(isspace(str[beginIndex])){ |
| 44 | ++beginIndex; |
| 45 | } |
| 46 | if(str[beginIndex] == '\0') { |
| 47 | break; |
| 48 | } |
| 49 | endIndex = beginIndex; |
| 50 | while (str[endIndex] && !isspace(str[endIndex])){ |
| 51 | ++endIndex; |
| 52 | } |
| 53 | int len = endIndex - beginIndex; |
| 54 | char *tmp = calloc(len + 1, sizeof(char)); |
| 55 | memcpy(tmp, &str[beginIndex], len); |
| 56 | arr[wordCnt++] = tmp; |
| 57 | //PrintAndLog("DEBUG cnt: %d, %s",wordCnt-1, arr[wordCnt-1]); |
| 58 | beginIndex = endIndex; |
| 59 | if (wordCnt == maxWords) |
| 60 | break; |
| 61 | } |
| 62 | return wordCnt; |
| 63 | } |
| 64 | |
| 65 | int CmdCrc(const char *Cmd) |
| 66 | { |
| 67 | char name[] = {"reveng "}; |
| 68 | char Cmd2[50 + 7]; |
| 69 | memcpy(Cmd2, name, 7); |
| 70 | memcpy(Cmd2 + 7, Cmd, 50); |
| 71 | char *argv[MAX_ARGS]; |
| 72 | int argc = split(Cmd2, argv); |
| 73 | |
| 74 | if (argc == 3 && memcmp(argv[1],"-g",2)==0) { |
| 75 | CmdrevengSearch(argv[2]); |
| 76 | } else { |
| 77 | reveng_main(argc, argv); |
| 78 | } |
| 79 | //PrintAndLog("DEBUG argc: %d, %s %s Cmd: %s",argc, argv[0], Cmd2, Cmd); |
| 80 | for(int i = 0; i < argc; ++i){ |
| 81 | free(argv[i]); |
| 82 | } |
| 83 | |
| 84 | return 0; |
| 85 | } |
| 86 | |
| 87 | //returns array of model names and the count of models returning |
| 88 | // as well as a width array for the width of each model |
| 89 | int GetModels(char *Models[], int *count, uint8_t *width){ |
| 90 | /* default values */ |
| 91 | static model_t model = { |
| 92 | PZERO, /* no CRC polynomial, user must specify */ |
| 93 | PZERO, /* Init = 0 */ |
| 94 | P_BE, /* RefIn = false, RefOut = false, plus P_RTJUST setting in reveng.h */ |
| 95 | PZERO, /* XorOut = 0 */ |
| 96 | PZERO, /* check value unused */ |
| 97 | NULL /* no model name */ |
| 98 | }; |
| 99 | |
| 100 | int ibperhx = 8;//, obperhx = 8; |
| 101 | int rflags = 0, uflags = 0; /* search and UI flags */ |
| 102 | poly_t apoly, crc, qpoly = PZERO, *apolys = NULL, *pptr = NULL, *qptr = NULL; |
| 103 | model_t pset = model, *candmods, *mptr; |
| 104 | |
| 105 | /* stdin must be binary */ |
| 106 | #ifdef _WIN32 |
| 107 | _setmode(STDIN_FILENO, _O_BINARY); |
| 108 | #endif /* _WIN32 */ |
| 109 | |
| 110 | SETBMP(); |
| 111 | |
| 112 | int args = 0, psets, pass; |
| 113 | int Cnt = 0; |
| 114 | if (width[0] == 0) { //reveng -D |
| 115 | *count = mcount(); |
| 116 | if(!*count) |
| 117 | return uerr("no preset models available"); |
| 118 | |
| 119 | for(int mode = 0; mode < *count; ++mode) { |
| 120 | mbynum(&model, mode); |
| 121 | mcanon(&model); |
| 122 | size_t size = (model.name && *model.name) ? strlen(model.name) : 6; |
| 123 | char *tmp = calloc(size+1, sizeof(char)); |
| 124 | if (tmp==NULL) |
| 125 | return uerr("out of memory?"); |
| 126 | |
| 127 | memcpy(tmp, model.name, size); |
| 128 | Models[mode] = tmp; |
| 129 | width[mode] = plen(model.spoly); |
| 130 | } |
| 131 | mfree(&model); |
| 132 | } else { //reveng -s |
| 133 | |
| 134 | if(~model.flags & P_MULXN) |
| 135 | return uerr("cannot search for non-Williams compliant models"); |
| 136 | |
| 137 | praloc(&model.spoly, (unsigned long)width[0]); |
| 138 | praloc(&model.init, (unsigned long)width[0]); |
| 139 | praloc(&model.xorout, (unsigned long)width[0]); |
| 140 | if(!plen(model.spoly)) |
| 141 | palloc(&model.spoly, (unsigned long)width[0]); |
| 142 | else |
| 143 | width[0] = (uint8_t)plen(model.spoly); |
| 144 | |
| 145 | /* special case if qpoly is zero, search to end of range */ |
| 146 | if(!ptst(qpoly)) |
| 147 | rflags &= ~R_HAVEQ; |
| 148 | |
| 149 | |
| 150 | /* not going to be sending additional args at this time (maybe future?) |
| 151 | |
| 152 | // allocate argument array |
| 153 | args = argc - optind; |
| 154 | if(!(apolys = malloc(args * sizeof(poly_t)))) |
| 155 | return uerr("cannot allocate memory for argument list"); |
| 156 | |
| 157 | for(pptr = apolys; optind < argc; ++optind) { |
| 158 | if(uflags & C_INFILE) |
| 159 | *pptr++ = rdpoly(argv[optind], model.flags, ibperhx); |
| 160 | else |
| 161 | *pptr++ = strtop(argv[optind], model.flags, ibperhx); |
| 162 | } |
| 163 | // exit value of pptr is used hereafter! |
| 164 | |
| 165 | */ |
| 166 | |
| 167 | /* if endianness not specified, try |
| 168 | * little-endian then big-endian. |
| 169 | * NB: crossed-endian algorithms will not be |
| 170 | * searched. |
| 171 | */ |
| 172 | /* scan against preset models */ |
| 173 | if(~uflags & C_FORCE) { |
| 174 | pass = 0; |
| 175 | Cnt = 0; |
| 176 | do { |
| 177 | psets = mcount(); |
| 178 | //PrintAndLog("psets: %d",psets); |
| 179 | while(psets) { |
| 180 | mbynum(&pset, --psets); |
| 181 | |
| 182 | /* skip if different width, or refin or refout don't match */ |
| 183 | if(plen(pset.spoly) != width[0] || (model.flags ^ pset.flags) & (P_REFIN | P_REFOUT)) |
| 184 | continue; |
| 185 | /* skip if the preset doesn't match specified parameters */ |
| 186 | if(rflags & R_HAVEP && pcmp(&model.spoly, &pset.spoly)) |
| 187 | continue; |
| 188 | if(rflags & R_HAVEI && psncmp(&model.init, &pset.init)) |
| 189 | continue; |
| 190 | if(rflags & R_HAVEX && psncmp(&model.xorout, &pset.xorout)) |
| 191 | continue; |
| 192 | |
| 193 | //for additional args (not used yet, maybe future?) |
| 194 | apoly = pclone(pset.xorout); |
| 195 | if(pset.flags & P_REFOUT) |
| 196 | prev(&apoly); |
| 197 | |
| 198 | for(qptr = apolys; qptr < pptr; ++qptr) { |
| 199 | crc = pcrc(*qptr, pset.spoly, pset.init, apoly, 0); |
| 200 | if(ptst(crc)) { |
| 201 | pfree(&crc); |
| 202 | break; |
| 203 | } else |
| 204 | pfree(&crc); |
| 205 | } |
| 206 | pfree(&apoly); |
| 207 | if(qptr == pptr) { |
| 208 | |
| 209 | /* the selected model solved all arguments */ |
| 210 | |
| 211 | mcanon(&pset); |
| 212 | |
| 213 | size_t size = (pset.name && *pset.name) ? strlen(pset.name) : 6; |
| 214 | //PrintAndLog("Size: %d, %s, count: %d",size,pset.name, Cnt); |
| 215 | char *tmp = calloc(size+1, sizeof(char)); |
| 216 | if (tmp==NULL){ |
| 217 | PrintAndLog("out of memory?"); |
| 218 | return 0; |
| 219 | } |
| 220 | width[Cnt] = width[0]; |
| 221 | memcpy(tmp, pset.name, size); |
| 222 | Models[Cnt++] = tmp; |
| 223 | *count = Cnt; |
| 224 | uflags |= C_RESULT; |
| 225 | } |
| 226 | } |
| 227 | mfree(&pset); |
| 228 | |
| 229 | /* toggle refIn/refOut and reflect arguments */ |
| 230 | if(~rflags & R_HAVERI) { |
| 231 | model.flags ^= P_REFIN | P_REFOUT; |
| 232 | for(qptr = apolys; qptr < pptr; ++qptr) |
| 233 | prevch(qptr, ibperhx); |
| 234 | } |
| 235 | } while(~rflags & R_HAVERI && ++pass < 2); |
| 236 | } |
| 237 | //got everything now free the memory... |
| 238 | |
| 239 | if(uflags & C_RESULT) { |
| 240 | for(qptr = apolys; qptr < pptr; ++qptr) |
| 241 | pfree(qptr); |
| 242 | } |
| 243 | if(!(model.flags & P_REFIN) != !(model.flags & P_REFOUT)) |
| 244 | return uerr("cannot search for crossed-endian models"); |
| 245 | |
| 246 | pass = 0; |
| 247 | do { |
| 248 | mptr = candmods = reveng(&model, qpoly, rflags, args, apolys); |
| 249 | if(mptr && plen(mptr->spoly)) |
| 250 | uflags |= C_RESULT; |
| 251 | while(mptr && plen(mptr->spoly)) { |
| 252 | mfree(mptr++); |
| 253 | } |
| 254 | free(candmods); |
| 255 | if(~rflags & R_HAVERI) { |
| 256 | model.flags ^= P_REFIN | P_REFOUT; |
| 257 | for(qptr = apolys; qptr < pptr; ++qptr) |
| 258 | prevch(qptr, ibperhx); |
| 259 | } |
| 260 | } while(~rflags & R_HAVERI && ++pass < 2); |
| 261 | for(qptr = apolys; qptr < pptr; ++qptr) |
| 262 | pfree(qptr); |
| 263 | free(apolys); |
| 264 | if(~uflags & C_RESULT) |
| 265 | return uerr("no models found"); |
| 266 | mfree(&model); |
| 267 | } |
| 268 | return 1; |
| 269 | } |
| 270 | |
| 271 | //test call to GetModels |
| 272 | int CmdrevengTest(const char *Cmd){ |
| 273 | char *Models[80]; |
| 274 | int count = 0; |
| 275 | uint8_t widtharr[80] = {0}; |
| 276 | uint8_t width = 0; |
| 277 | width = param_get8(Cmd, 0); |
| 278 | //PrintAndLog("width: %d",width); |
| 279 | if (width > 89) |
| 280 | return uerr("Width cannot exceed 89"); |
| 281 | |
| 282 | widtharr[0] = width; |
| 283 | int ans = GetModels(Models, &count, widtharr); |
| 284 | if (!ans) return 0; |
| 285 | |
| 286 | PrintAndLog("Count: %d",count); |
| 287 | for (int i = 0; i < count; i++){ |
| 288 | PrintAndLog("Model %d: %s, width: %d",i,Models[i], widtharr[i]); |
| 289 | free(Models[i]); |
| 290 | } |
| 291 | return 1; |
| 292 | } |
| 293 | |
| 294 | //-c || -v |
| 295 | //inModel = valid model name string - CRC-8 |
| 296 | //inHexStr = input hex string to calculate crc on |
| 297 | //reverse = reverse calc option if true |
| 298 | //endian = {0 = calc default endian input and output, b = big endian input and output, B = big endian output, r = right justified |
| 299 | // l = little endian input and output, L = little endian output only, t = left justified} |
| 300 | //result = calculated crc hex string |
| 301 | int RunModel(char *inModel, char *inHexStr, bool reverse, char endian, char *result){ |
| 302 | /* default values */ |
| 303 | static model_t model = { |
| 304 | PZERO, // no CRC polynomial, user must specify |
| 305 | PZERO, // Init = 0 |
| 306 | P_BE, // RefIn = false, RefOut = false, plus P_RTJUST setting in reveng.h |
| 307 | PZERO, // XorOut = 0 |
| 308 | PZERO, // check value unused |
| 309 | NULL // no model name |
| 310 | }; |
| 311 | int ibperhx = 8, obperhx = 8; |
| 312 | int rflags = 0; // search flags |
| 313 | int c; |
| 314 | //unsigned long width; |
| 315 | poly_t apoly, crc; |
| 316 | |
| 317 | char *string; |
| 318 | |
| 319 | // stdin must be binary |
| 320 | #ifdef _WIN32 |
| 321 | _setmode(STDIN_FILENO, _O_BINARY); |
| 322 | #endif /* _WIN32 */ |
| 323 | |
| 324 | SETBMP(); |
| 325 | //set model |
| 326 | if(!(c = mbynam(&model, inModel))) { |
| 327 | fprintf(stderr,"error: preset model '%s' not found. Use reveng -D to list presets.\n", inModel); |
| 328 | return 0; |
| 329 | } |
| 330 | if(c < 0) |
| 331 | return uerr("no preset models available"); |
| 332 | |
| 333 | // must set width so that parameter to -ipx is not zeroed |
| 334 | //width = plen(model.spoly); |
| 335 | rflags |= R_HAVEP | R_HAVEI | R_HAVERI | R_HAVERO | R_HAVEX; |
| 336 | |
| 337 | //set flags |
| 338 | switch (endian) { |
| 339 | case 'b': /* b big-endian (RefIn = false, RefOut = false ) */ |
| 340 | model.flags &= ~P_REFIN; |
| 341 | rflags |= R_HAVERI; |
| 342 | /* fall through: */ |
| 343 | case 'B': /* B big-endian output (RefOut = false) */ |
| 344 | model.flags &= ~P_REFOUT; |
| 345 | rflags |= R_HAVERO; |
| 346 | mnovel(&model); |
| 347 | /* fall through: */ |
| 348 | case 'r': /* r right-justified */ |
| 349 | model.flags |= P_RTJUST; |
| 350 | break; |
| 351 | case 'l': /* l little-endian input and output */ |
| 352 | model.flags |= P_REFIN; |
| 353 | rflags |= R_HAVERI; |
| 354 | /* fall through: */ |
| 355 | case 'L': /* L little-endian output */ |
| 356 | model.flags |= P_REFOUT; |
| 357 | rflags |= R_HAVERO; |
| 358 | mnovel(&model); |
| 359 | /* fall through: */ |
| 360 | case 't': /* t left-justified */ |
| 361 | model.flags &= ~P_RTJUST; |
| 362 | break; |
| 363 | } |
| 364 | |
| 365 | mcanon(&model); |
| 366 | |
| 367 | if (reverse) { |
| 368 | // v calculate reversed CRC |
| 369 | /* Distinct from the -V switch as this causes |
| 370 | * the arguments and output to be reversed as well. |
| 371 | */ |
| 372 | // reciprocate Poly |
| 373 | prcp(&model.spoly); |
| 374 | |
| 375 | /* mrev() does: |
| 376 | * if(refout) prev(init); else prev(xorout); |
| 377 | * but here the entire argument polynomial is |
| 378 | * reflected, not just the characters, so RefIn |
| 379 | * and RefOut are not inverted as with -V. |
| 380 | * Consequently Init is the mirror image of the |
| 381 | * one resulting from -V, and so we have: |
| 382 | */ |
| 383 | if(~model.flags & P_REFOUT) { |
| 384 | prev(&model.init); |
| 385 | prev(&model.xorout); |
| 386 | } |
| 387 | |
| 388 | // swap init and xorout |
| 389 | apoly = model.init; |
| 390 | model.init = model.xorout; |
| 391 | model.xorout = apoly; |
| 392 | } |
| 393 | // c calculate CRC |
| 394 | |
| 395 | // validate inputs |
| 396 | /* if(plen(model.spoly) == 0) { |
| 397 | * fprintf(stderr,"%s: no polynomial specified for -%c (add -w WIDTH -p POLY)\n", myname, mode); |
| 398 | * exit(EXIT_FAILURE); |
| 399 | * } |
| 400 | */ |
| 401 | |
| 402 | /* in the Williams model, xorout is applied after the refout stage. |
| 403 | * as refout is part of ptostr(), we reverse xorout here. |
| 404 | */ |
| 405 | if(model.flags & P_REFOUT) |
| 406 | prev(&model.xorout); |
| 407 | |
| 408 | apoly = strtop(inHexStr, model.flags, ibperhx); |
| 409 | |
| 410 | if(reverse) |
| 411 | prev(&apoly); |
| 412 | |
| 413 | crc = pcrc(apoly, model.spoly, model.init, model.xorout, model.flags); |
| 414 | |
| 415 | if(reverse) |
| 416 | prev(&crc); |
| 417 | |
| 418 | string = ptostr(crc, model.flags, obperhx); |
| 419 | for (int i = 0; i < 50; i++){ |
| 420 | result[i] = string[i]; |
| 421 | if (result[i]==0) break; |
| 422 | } |
| 423 | free(string); |
| 424 | pfree(&crc); |
| 425 | pfree(&apoly); |
| 426 | return 1; |
| 427 | } |
| 428 | |
| 429 | //test call to RunModel |
| 430 | int CmdrevengTestC(const char *Cmd){ |
| 431 | int cmdp = 0; |
| 432 | char inModel[30] = {0x00}; |
| 433 | char inHexStr[30] = {0x00}; |
| 434 | char result[30]; |
| 435 | int dataLen; |
| 436 | char endian = 0; |
| 437 | dataLen = param_getstr(Cmd, cmdp++, inModel); |
| 438 | if (dataLen < 4) return 0; |
| 439 | dataLen = param_getstr(Cmd, cmdp++, inHexStr); |
| 440 | if (dataLen < 4) return 0; |
| 441 | bool reverse = (param_get8(Cmd, cmdp++)) ? true : false; |
| 442 | endian = param_getchar(Cmd, cmdp++); |
| 443 | |
| 444 | //PrintAndLog("mod: %s, hex: %s, rev %d", inModel, inHexStr, reverse); |
| 445 | int ans = RunModel(inModel, inHexStr, reverse, endian, result); |
| 446 | if (!ans) return 0; |
| 447 | |
| 448 | PrintAndLog("Result: %s",result); |
| 449 | return 1; |
| 450 | } |
| 451 | |
| 452 | //returns a calloced string (needs to be freed) |
| 453 | char *SwapEndianStr(const char *inStr, const size_t len, const uint8_t blockSize){ |
| 454 | char *tmp = calloc(len+1, sizeof(char)); |
| 455 | for (uint8_t block=0; block < (uint8_t)(len/blockSize); block++){ |
| 456 | for (size_t i = 0; i < blockSize; i+=2){ |
| 457 | tmp[i+(blockSize*block)] = inStr[(blockSize-1-i-1)+(blockSize*block)]; |
| 458 | tmp[i+(blockSize*block)+1] = inStr[(blockSize-1-i)+(blockSize*block)]; |
| 459 | } |
| 460 | } |
| 461 | return tmp; |
| 462 | } |
| 463 | |
| 464 | // takes hex string in and searches for a matching result (hex string must include checksum) |
| 465 | int CmdrevengSearch(const char *Cmd){ |
| 466 | char inHexStr[50] = {0x00}; |
| 467 | int dataLen = param_getstr(Cmd, 0, inHexStr); |
| 468 | if (dataLen < 4) return 0; |
| 469 | |
| 470 | char *Models[80]; |
| 471 | int count = 0; |
| 472 | uint8_t width[80]; |
| 473 | width[0] = 0; |
| 474 | uint8_t crcChars = 0; |
| 475 | char result[30]; |
| 476 | char revResult[30]; |
| 477 | int ans = GetModels(Models, &count, width); |
| 478 | bool found = false; |
| 479 | if (!ans) return 0; |
| 480 | |
| 481 | // try each model and get result |
| 482 | for (int i = 0; i < count; i++){ |
| 483 | /*if (found) { |
| 484 | free(Models[i]); |
| 485 | continue; |
| 486 | }*/ |
| 487 | // round up to # of characters in this model's crc |
| 488 | crcChars = ((width[i]+7)/8)*2; |
| 489 | // can't test a model that has more crc digits than our data |
| 490 | if (crcChars >= dataLen) |
| 491 | continue; |
| 492 | memset(result, 0, 30); |
| 493 | char *inCRC = calloc(crcChars+1, sizeof(char)); |
| 494 | memcpy(inCRC, inHexStr+(dataLen-crcChars), crcChars); |
| 495 | |
| 496 | char *outHex = calloc(dataLen-crcChars+1, sizeof(char)); |
| 497 | memcpy(outHex, inHexStr, dataLen-crcChars); |
| 498 | |
| 499 | //PrintAndLog("DEBUG: dataLen: %d, crcChars: %d, Model: %s, CRC: %s, width: %d, outHex: %s",dataLen, crcChars, Models[i], inCRC, width[i], outHex); |
| 500 | ans = RunModel(Models[i], outHex, false, 0, result); |
| 501 | if (ans) { |
| 502 | //test for match |
| 503 | if (memcmp(result, inCRC, crcChars)==0){ |
| 504 | PrintAndLog("\nFound a possible match!\nModel: %s\nValue: %s\n",Models[i], result); |
| 505 | //optional - stop searching if found... |
| 506 | found = true; |
| 507 | } else { |
| 508 | if (crcChars > 2){ |
| 509 | char *swapEndian = SwapEndianStr(result, crcChars, crcChars); |
| 510 | if (memcmp(swapEndian, inCRC, crcChars)==0){ |
| 511 | PrintAndLog("\nFound a possible match!\nModel: %s\nValue EndianSwapped: %s\n",Models[i], swapEndian); |
| 512 | //optional - stop searching if found... |
| 513 | found = true; |
| 514 | } |
| 515 | free(swapEndian); |
| 516 | } |
| 517 | } |
| 518 | } |
| 519 | |
| 520 | //if (!found){ |
| 521 | ans = RunModel(Models[i], outHex, true, 0, revResult); |
| 522 | if (ans) { |
| 523 | //test for match |
| 524 | if (memcmp(revResult, inCRC, crcChars)==0){ |
| 525 | PrintAndLog("\nFound a possible match!\nModel Reversed: %s\nValue: %s\n",Models[i], revResult); |
| 526 | //optional - stop searching if found... |
| 527 | found = true; |
| 528 | } else { |
| 529 | if (crcChars > 2){ |
| 530 | char *swapEndian = SwapEndianStr(revResult, crcChars, crcChars); |
| 531 | if (memcmp(swapEndian, inCRC, crcChars)==0){ |
| 532 | PrintAndLog("\nFound a possible match!\nModel Reversed: %s\nValue EndianSwapped: %s\n",Models[i], swapEndian); |
| 533 | //optional - stop searching if found... |
| 534 | found = true; |
| 535 | } |
| 536 | free(swapEndian); |
| 537 | } |
| 538 | } |
| 539 | } |
| 540 | //} |
| 541 | free(inCRC); |
| 542 | free(outHex); |
| 543 | free(Models[i]); |
| 544 | } |
| 545 | if (!found) PrintAndLog("\nNo matches found\n"); |
| 546 | return 1; |
| 547 | } |