summaryrefslogtreecommitdiff
path: root/utils/nwztools/upgtools
diff options
context:
space:
mode:
authorAmaury Pouly <amaury.pouly@gmail.com>2012-11-12 18:35:00 +0100
committerAmaury Pouly <amaury.pouly@gmail.com>2012-11-13 18:25:00 +0100
commit214f226ca63c8c5d6f446d69ffe95aec91779254 (patch)
tree79331e1f87552db1733ca4eb702e355a7c12ed10 /utils/nwztools/upgtools
parent02f67224f9c424bf9c3a613741adfccaf4847d12 (diff)
downloadrockbox-214f226ca63c8c5d6f446d69ffe95aec91779254.tar.gz
rockbox-214f226ca63c8c5d6f446d69ffe95aec91779254.zip
upgtools: allow creation of a UPG archive + improvements
Change-Id: I9c3e2eb95f7eb6d41591b006328fd720dfcf93a5
Diffstat (limited to 'utils/nwztools/upgtools')
-rw-r--r--utils/nwztools/upgtools/fwp.c5
-rw-r--r--utils/nwztools/upgtools/fwp.h1
-rw-r--r--utils/nwztools/upgtools/mg.cpp4
-rw-r--r--utils/nwztools/upgtools/upgtool.c353
4 files changed, 320 insertions, 43 deletions
diff --git a/utils/nwztools/upgtools/fwp.c b/utils/nwztools/upgtools/fwp.c
index e739b8caef..c300f42f34 100644
--- a/utils/nwztools/upgtools/fwp.c
+++ b/utils/nwztools/upgtools/fwp.c
@@ -29,6 +29,11 @@ int fwp_read(void *in, int size, void *out, uint8_t *key)
29 return mg_decrypt_fw(in, size, out, key); 29 return mg_decrypt_fw(in, size, out, key);
30} 30}
31 31
32int fwp_write(void *in, int size, void *out, uint8_t *key)
33{
34 return mg_encrypt_fw(in, size, out, key);
35}
36
32static uint8_t g_key[NWZ_KEY_SIZE]; 37static uint8_t g_key[NWZ_KEY_SIZE];
33 38
34void fwp_setkey(char key[NWZ_KEY_SIZE]) 39void fwp_setkey(char key[NWZ_KEY_SIZE])
diff --git a/utils/nwztools/upgtools/fwp.h b/utils/nwztools/upgtools/fwp.h
index dcfe80027e..7b527d47ba 100644
--- a/utils/nwztools/upgtools/fwp.h
+++ b/utils/nwztools/upgtools/fwp.h
@@ -34,6 +34,7 @@ extern "C" {
34#define NWZ_DES_BLOCK 8 34#define NWZ_DES_BLOCK 8
35 35
36int fwp_read(void *in, int size, void *out, uint8_t *key); 36int fwp_read(void *in, int size, void *out, uint8_t *key);
37int fwp_write(void *in, int size, void *out, uint8_t *key);
37void fwp_setkey(char key[8]); 38void fwp_setkey(char key[8]);
38int fwp_crypt(void *buf, int size, int mode); 39int fwp_crypt(void *buf, int size, int mode);
39 40
diff --git a/utils/nwztools/upgtools/mg.cpp b/utils/nwztools/upgtools/mg.cpp
index 8816259755..21659ff3cf 100644
--- a/utils/nwztools/upgtools/mg.cpp
+++ b/utils/nwztools/upgtools/mg.cpp
@@ -33,6 +33,8 @@ namespace
33 33
34 inline int dec_des_ecb(void *in, int size, void *out, uint8_t *key) 34 inline int dec_des_ecb(void *in, int size, void *out, uint8_t *key)
35 { 35 {
36 if(size % 8)
37 return 42;
36 g_dec.SetKey(key, 8); 38 g_dec.SetKey(key, 8);
37 g_dec.ProcessData((byte*)out, (byte*)in, size); 39 g_dec.ProcessData((byte*)out, (byte*)in, size);
38 return 0; 40 return 0;
@@ -40,6 +42,8 @@ namespace
40 42
41 inline int enc_des_ecb(void *in, int size, void *out, uint8_t *key) 43 inline int enc_des_ecb(void *in, int size, void *out, uint8_t *key)
42 { 44 {
45 if(size % 8)
46 return 42;
43 g_enc.SetKey(key, 8); 47 g_enc.SetKey(key, 8);
44 g_enc.ProcessData((byte*)out, (byte*)in, size); 48 g_enc.ProcessData((byte*)out, (byte*)in, size);
45 return 0; 49 return 0;
diff --git a/utils/nwztools/upgtools/upgtool.c b/utils/nwztools/upgtools/upgtool.c
index 73303e72d3..54bbbf3270 100644
--- a/utils/nwztools/upgtools/upgtool.c
+++ b/utils/nwztools/upgtools/upgtool.c
@@ -50,7 +50,6 @@ static char *g_sig = NULL;
50 50
51enum keysig_search_method_t g_keysig_search = KEYSIG_SEARCH_NONE; 51enum keysig_search_method_t g_keysig_search = KEYSIG_SEARCH_NONE;
52 52
53
54#define let_the_force_flow(x) do { if(!g_force) return x; } while(0) 53#define let_the_force_flow(x) do { if(!g_force) return x; } while(0)
55#define continue_the_force(x) if(x) let_the_force_flow(x) 54#define continue_the_force(x) if(x) let_the_force_flow(x)
56 55
@@ -77,6 +76,8 @@ static void print_hex(void *p, int size, int unit)
77 } 76 }
78} 77}
79 78
79static void usage(void);
80
80/* key and signature */ 81/* key and signature */
81struct nwz_kas_t 82struct nwz_kas_t
82{ 83{
@@ -106,7 +107,7 @@ struct upg_header_t
106{ 107{
107 char sig[8]; 108 char sig[8];
108 uint32_t nr_files; 109 uint32_t nr_files;
109 uint32_t unk; 110 uint32_t pad; // make sure structure size is a multiple of 8
110} __attribute__((packed)); 111} __attribute__((packed));
111 112
112struct upg_entry_t 113struct upg_entry_t
@@ -186,7 +187,7 @@ static int do_upg(void *buf, long size)
186 if(g_model_index == -1 && g_keysig_search == KEYSIG_SEARCH_NONE && g_key == NULL && g_kas == NULL) 187 if(g_model_index == -1 && g_keysig_search == KEYSIG_SEARCH_NONE && g_key == NULL && g_kas == NULL)
187 { 188 {
188 cprintf(GREY, "A KAS or a keysig is needed to decrypt the firmware\n"); 189 cprintf(GREY, "A KAS or a keysig is needed to decrypt the firmware\n");
189 cprintf(GREY, "You have the following options(see hel for more details):\n"); 190 cprintf(GREY, "You have the following options(see help for more details):\n");
190 cprintf(GREY, "- select a model with a known KAS\n"); 191 cprintf(GREY, "- select a model with a known KAS\n");
191 cprintf(GREY, "- specify an explicit KAS or key(+optional sig)\n"); 192 cprintf(GREY, "- specify an explicit KAS or key(+optional sig)\n");
192 cprintf(GREY, "- let me try to find the keysig(slow !)\n"); 193 cprintf(GREY, "- let me try to find the keysig(slow !)\n");
@@ -327,7 +328,7 @@ static int do_upg(void *buf, long size)
327 else 328 else
328 cprintf(RED, " Can't check\n"); 329 cprintf(RED, " Can't check\n");
329 cprintf_field(" Files: ", "%d\n", hdr->nr_files); 330 cprintf_field(" Files: ", "%d\n", hdr->nr_files);
330 cprintf_field(" Unk: ", "0x%x\n", hdr->unk); 331 cprintf_field(" Pad: ", "0x%x\n", hdr->pad);
331 332
332 cprintf(BLUE, "Files\n"); 333 cprintf(BLUE, "Files\n");
333 struct upg_entry_t *entry = (void *)(hdr + 1); 334 struct upg_entry_t *entry = (void *)(hdr + 1);
@@ -344,14 +345,17 @@ static int do_upg(void *buf, long size)
344 if(g_out_prefix) 345 if(g_out_prefix)
345 { 346 {
346 char *str = malloc(strlen(g_out_prefix) + 32); 347 char *str = malloc(strlen(g_out_prefix) + 32);
347 sprintf(str, "%s/%d.bin", g_out_prefix, i); 348 sprintf(str, "%s%d.bin", g_out_prefix, i);
348 FILE *f = fopen(str, "wb"); 349 FILE *f = fopen(str, "wb");
349 if(f) 350 if(f)
350 { 351 {
351 int ret = fwp_read(buf + entry->offset, entry->size, 352 // round up size, there is some padding done with random data
353 int crypt_size = ROUND_UP(entry->size, 8);
354 int ret = fwp_read(buf + entry->offset, crypt_size,
352 buf + entry->offset, (void *)g_key); 355 buf + entry->offset, (void *)g_key);
353 if(ret) 356 if(ret)
354 return ret; 357 return ret;
358 // but write the *good* amount of data
355 fwrite(buf + entry->offset, 1, entry->size, f); 359 fwrite(buf + entry->offset, 1, entry->size, f);
356 360
357 fclose(f); 361 fclose(f);
@@ -364,10 +368,276 @@ static int do_upg(void *buf, long size)
364 return 0; 368 return 0;
365} 369}
366 370
371static int extract_upg(int argc, char **argv)
372{
373 if(argc == 0 || argc > 1)
374 {
375 if(argc == 0)
376 printf("You must specify a firmware file\n");
377 else
378 printf("Extra arguments after firmware file\n");
379 usage();
380 }
381
382 g_in_file = argv[0];
383 FILE *fin = fopen(g_in_file, "r");
384 if(fin == NULL)
385 {
386 perror("Cannot open boot file");
387 return 1;
388 }
389 fseek(fin, 0, SEEK_END);
390 long size = ftell(fin);
391 fseek(fin, 0, SEEK_SET);
392
393 void *buf = malloc(size);
394 if(buf == NULL)
395 {
396 perror("Cannot allocate memory");
397 return 1;
398 }
399
400 if(fread(buf, size, 1, fin) != 1)
401 {
402 perror("Cannot read file");
403 return 1;
404 }
405
406 fclose(fin);
407
408 int ret = do_upg(buf, size);
409 if(ret != 0)
410 {
411 cprintf(GREY, "Error: %d", ret);
412 if(!g_force)
413 cprintf(GREY, " (use --force to force processing)");
414 printf("\n");
415 ret = 2;
416 }
417 free(buf);
418
419 return ret;
420}
421
422static long filesize(FILE *f)
423{
424 long pos = ftell(f);
425 fseek(f, 0, SEEK_END);
426 long size = ftell(f);
427 fseek(f, pos, SEEK_SET);
428 return size;
429}
430
431static int create_upg(int argc, char **argv)
432{
433 if(argc == 0)
434 {
435 printf("You must specify a firmware filename\n");
436 usage();
437 }
438
439 if(g_model_index == -1 && (g_key == NULL || g_sig == NULL) && g_kas == NULL)
440 {
441 cprintf(GREY, "A KAS or a keysig is needed to encrypt the firmware\n");
442 cprintf(GREY, "You have the following options(see help for more details):\n");
443 cprintf(GREY, "- select a model with a known KAS\n");
444 cprintf(GREY, "- specify an explicit KAS or key+sig\n");
445 return 1;
446 }
447
448 struct nwz_kas_t kas;
449 char keysig[NWZ_KEYSIG_SIZE];
450
451 memset(kas.kas, '?', NWZ_KAS_SIZE);
452 memset(keysig, '?', NWZ_KEYSIG_SIZE);
453 keysig[32] = keysig[41] = keysig[50] = 0;
454
455 if(g_kas)
456 {
457 if(strlen(g_kas) != NWZ_KAS_SIZE)
458 {
459 cprintf(GREY, "The specified KAS has wrong length (must be %d hex digits)\n", NWZ_KAS_SIZE);
460 return 4;
461 }
462 memcpy(keysig, g_kas, NWZ_KAS_SIZE);
463 decrypt_keysig(keysig);
464 g_kas = keysig;
465 g_key = keysig + 33;
466 g_sig = keysig + 42;
467 }
468 else if(g_key)
469 {
470 if(strlen(g_key) != 8)
471 {
472 cprintf(GREY, "The specified key has wrong length (must be 8 hex digits)\n");
473 return 4;
474 }
475 if(strlen(g_sig) != 8)
476 {
477 cprintf(GREY, "The specified sig has wrong length (must be 8 hex digits)\n");
478 return 5;
479 }
480
481 memcpy(keysig + 33, g_key, 8);
482 if(!g_sig)
483 cprintf(GREY, "Warning: you have specified a key but no sig, I won't be able to do any checks\n");
484 else
485 memcpy(keysig + 42, g_sig, 8);
486 g_key = keysig + 33;
487 g_sig = keysig + 42;
488 }
489 else if(g_model_index != -1)
490 {
491 if(g_model_list[g_model_index].flags & HAS_KAS)
492 g_kas = g_model_list[g_model_index].kas.kas;
493 if(g_model_list[g_model_index].flags & HAS_KEY)
494 g_key = g_model_list[g_model_index].key;
495 if(g_model_list[g_model_index].flags & HAS_SIG)
496 g_sig = g_model_list[g_model_index].sig;
497
498 if(g_key && g_sig)
499 {
500 memcpy(keysig + 33, g_key, 8);
501 g_key = keysig + 33;
502 memcpy(keysig + 42, g_sig, 8);
503 g_sig = keysig + 42;
504 }
505 else if(g_kas)
506 {
507 memcpy(keysig, g_kas, NWZ_KAS_SIZE);
508 decrypt_keysig(keysig);
509 g_kas = keysig;
510 g_key = keysig + 33;
511 g_sig = keysig + 42;
512 }
513 else
514 {
515 printf("Target doesn't have enough information to get key and sig\n");
516 return 1;
517 }
518 }
519 else
520 {
521 printf("Kill me\n");
522 return 1;
523 }
524
525 if(!g_kas)
526 {
527 g_kas = keysig;
528 fwp_setkey("ed295076");
529 memcpy(kas.kas, g_key, 8);
530 fwp_crypt(kas.kas, 8, 0);
531 for(int i = 0; i < 8; i++)
532 {
533 g_kas[2 * i] = hex_digit((kas.kas[i] >> 4) & 0xf);
534 g_kas[2 * i + 1] = hex_digit(kas.kas[i] & 0xf);
535 }
536 memcpy(kas.kas + 8, g_sig, 8);
537 fwp_crypt(kas.kas + 8, 8, 0);
538 for(int i = 8; i < 16; i++)
539 {
540 g_kas[2 * i] = hex_digit((kas.kas[i] >> 4) & 0xf);
541 g_kas[2 * i + 1] = hex_digit(kas.kas[i] & 0xf);
542 }
543 }
544
545 cprintf(BLUE, "Keys\n");
546 cprintf_field(" KAS: ", "%."STR(NWZ_KAS_SIZE)"s\n", g_kas);
547 cprintf_field(" Key: ", "%s\n", g_key);
548 if(g_sig)
549 cprintf_field(" Sig: ", "%s\n", g_sig);
550
551 FILE *fout = fopen(argv[0], "wb");
552 if(fout == NULL)
553 {
554 printf("Cannot open output firmware file: %m\n");
555 return 1;
556 }
557
558 int nr_files = argc - 1;
559 FILE **files = malloc(nr_files * sizeof(FILE *));
560
561 for(int i = 0; i < nr_files; i++)
562 {
563 files[i] = fopen(argv[1 + i], "rb");
564 if(files[i] == NULL)
565 {
566 printf("Cannot open input file '%s': %m\n", argv[i + 1]);
567 return 1;
568 }
569 }
570
571 struct upg_md5_t md5;
572 memset(&md5, 0, sizeof(md5));
573 MD5_CTX c;
574 MD5_Init(&c);
575 // output a dummy md5 sum
576 fwrite(&md5, 1, sizeof(md5), fout);
577 // output the encrypted signature
578 struct upg_header_t hdr;
579 memcpy(hdr.sig, g_sig, 8);
580 hdr.nr_files = nr_files;
581 hdr.pad = 0;
582
583 int ret = fwp_write(&hdr, sizeof(hdr), &hdr, (void *)g_key);
584 if(ret)
585 return ret;
586 MD5_Update(&c, &hdr, sizeof(hdr));
587 fwrite(&hdr, 1, sizeof(hdr), fout);
588
589 // output file headers
590 long offset = sizeof(md5) + sizeof(hdr) + nr_files * sizeof(struct upg_entry_t);
591 for(int i = 0; i < nr_files; i++)
592 {
593 struct upg_entry_t entry;
594 entry.offset = offset;
595 entry.size = filesize(files[i]);
596 offset += ROUND_UP(entry.size, 8); // do it before encryption !!
597
598 ret = fwp_write(&entry, sizeof(entry), &entry, (void *)g_key);
599 if(ret)
600 return ret;
601 MD5_Update(&c, &entry, sizeof(entry));
602 fwrite(&entry, 1, sizeof(entry), fout);
603 }
604
605 cprintf(BLUE, "Files\n");
606 for(int i = 0; i < nr_files; i++)
607 {
608 long size = filesize(files[i]);
609 long r_size = ROUND_UP(size, 8);
610 cprintf(GREY, " File");
611 cprintf(RED, " %d\n", i);
612 cprintf_field(" Offset: ", "0x%lx\n", ftell(fout));
613 cprintf_field(" Size: ", "0x%lx\n", size);
614
615 void *buf = malloc(r_size);
616 memset(buf, 0, r_size);
617 fread(buf, 1, size, files[i]);
618 fclose(files[i]);
619
620 ret = fwp_write(buf, r_size, buf, (void *)g_key);
621 if(ret)
622 return ret;
623 MD5_Update(&c, buf, r_size);
624 fwrite(buf, 1, r_size, fout);
625
626 free(buf);
627 }
628
629 fseek(fout, 0, SEEK_SET);
630 MD5_Final(md5.md5, &c);
631 fwrite(&md5, 1, sizeof(md5), fout);
632 fclose(fout);
633
634 return 0;
635}
636
367static void usage(void) 637static void usage(void)
368{ 638{
369 color(OFF); 639 color(OFF);
370 printf("Usage: upgtool [options] firmware\n"); 640 printf("Usage: upgtool [options] firmware [files...]\n");
371 printf("Options:\n"); 641 printf("Options:\n");
372 printf(" -o <prefix>\t\tSet output prefix\n"); 642 printf(" -o <prefix>\t\tSet output prefix\n");
373 printf(" -f/--force\t\tForce to continue on errors\n"); 643 printf(" -f/--force\t\tForce to continue on errors\n");
@@ -375,10 +645,12 @@ static void usage(void)
375 printf(" -d/--debug\t\tDisplay debug messages\n"); 645 printf(" -d/--debug\t\tDisplay debug messages\n");
376 printf(" -c/--no-color\t\tDisable color output\n"); 646 printf(" -c/--no-color\t\tDisable color output\n");
377 printf(" -m/--model <model>\tSelect model (or ? to list them)\n"); 647 printf(" -m/--model <model>\tSelect model (or ? to list them)\n");
378 printf(" -l/--search <method>\tTry to find the keysig\n"); 648 printf(" -l/--search <method>\tTry to find the keysig (implies -e)\n");
379 printf(" -a/--kas <kas>\tForce KAS\n"); 649 printf(" -a/--kas <kas>\tForce KAS\n");
380 printf(" -k/--key <key>\tForce key\n"); 650 printf(" -k/--key <key>\tForce key\n");
381 printf(" -s/--sig <sig>\tForce sig\n"); 651 printf(" -s/--sig <sig>\tForce sig\n");
652 printf(" -e/--extract\t\tExtract a UPG archive\n");
653 printf(" -c/--create\t\tCreate a UPG archive\n");
382 printf("keysig search method:\n"); 654 printf("keysig search method:\n");
383 for(int i = KEYSIG_SEARCH_FIRST; i < KEYSIG_SEARCH_LAST; i++) 655 for(int i = KEYSIG_SEARCH_FIRST; i < KEYSIG_SEARCH_LAST; i++)
384 printf(" %s\t%s\n", keysig_search_desc[i].name, keysig_search_desc[i].comment); 656 printf(" %s\t%s\n", keysig_search_desc[i].name, keysig_search_desc[i].comment);
@@ -387,30 +659,38 @@ static void usage(void)
387 659
388int main(int argc, char **argv) 660int main(int argc, char **argv)
389{ 661{
662 bool extract = false;
663 bool create = false;
664
665 if(argc <= 1)
666 usage();
667
390 while(1) 668 while(1)
391 { 669 {
392 static struct option long_options[] = 670 static struct option long_options[] =
393 { 671 {
394 {"help", no_argument, 0, '?'}, 672 {"help", no_argument, 0, '?'},
395 {"debug", no_argument, 0, 'd'}, 673 {"debug", no_argument, 0, 'd'},
396 {"no-color", no_argument, 0, 'c'}, 674 {"no-color", no_argument, 0, 'n'},
397 {"force", no_argument, 0, 'f'}, 675 {"force", no_argument, 0, 'f'},
398 {"model", required_argument, 0, 'm'}, 676 {"model", required_argument, 0, 'm'},
399 {"search", required_argument, 0, 'l'}, 677 {"search", required_argument, 0, 'l'},
400 {"kas", required_argument, 0, 'a'}, 678 {"kas", required_argument, 0, 'a'},
401 {"key", required_argument, 0, 'k'}, 679 {"key", required_argument, 0, 'k'},
402 {"sig", required_argument, 0, 's'}, 680 {"sig", required_argument, 0, 's'},
681 {"extract", no_argument, 0, 'e'},
682 {"create", no_argument, 0 ,'c'},
403 {0, 0, 0, 0} 683 {0, 0, 0, 0}
404 }; 684 };
405 685
406 int c = getopt_long(argc, argv, "?dcfo:m:l:a:k:s:", long_options, NULL); 686 int c = getopt_long(argc, argv, "?dnfo:m:l:a:k:s:ec", long_options, NULL);
407 if(c == -1) 687 if(c == -1)
408 break; 688 break;
409 switch(c) 689 switch(c)
410 { 690 {
411 case -1: 691 case -1:
412 break; 692 break;
413 case 'c': 693 case 'n':
414 enable_color(false); 694 enable_color(false);
415 break; 695 break;
416 case 'd': 696 case 'd':
@@ -438,6 +718,7 @@ int main(int argc, char **argv)
438 cprintf(GREY, "Unknown keysig search method '%s'\n", optarg); 718 cprintf(GREY, "Unknown keysig search method '%s'\n", optarg);
439 return 1; 719 return 1;
440 } 720 }
721 extract = true;
441 break; 722 break;
442 case 'a': 723 case 'a':
443 g_kas = optarg; 724 g_kas = optarg;
@@ -448,6 +729,12 @@ int main(int argc, char **argv)
448 case 's': 729 case 's':
449 g_sig = optarg; 730 g_sig = optarg;
450 break; 731 break;
732 case 'e':
733 extract = true;
734 break;
735 case 'c':
736 create = true;
737 break;
451 default: 738 default:
452 abort(); 739 abort();
453 } 740 }
@@ -492,48 +779,28 @@ int main(int argc, char **argv)
492 cprintf(GREY, "Warning: unknown model %s\n", g_model); 779 cprintf(GREY, "Warning: unknown model %s\n", g_model);
493 } 780 }
494 781
495 if(argc - optind != 1) 782 if(!create && !extract)
496 {
497 usage();
498 return 1;
499 }
500
501 g_in_file = argv[optind];
502 FILE *fin = fopen(g_in_file, "r");
503 if(fin == NULL)
504 { 783 {
505 perror("Cannot open boot file"); 784 printf("You must specify an action (extract or create)\n");
506 return 1; 785 return 1;
507 } 786 }
508 fseek(fin, 0, SEEK_END);
509 long size = ftell(fin);
510 fseek(fin, 0, SEEK_SET);
511 787
512 void *buf = malloc(size); 788 if(create && extract)
513 if(buf == NULL)
514 { 789 {
515 perror("Cannot allocate memory"); 790 printf("You cannot specify both create and extract\n");
516 return 1; 791 return 1;
517 } 792 }
518 793
519 if(fread(buf, size, 1, fin) != 1) 794 int ret = 0;
520 { 795 if(create)
521 perror("Cannot read file"); 796 ret = create_upg(argc - optind, argv + optind);
522 return 1; 797 else if(extract)
523 } 798 ret = extract_upg(argc - optind, argv + optind);
524 799 else
525 fclose(fin);
526
527 int ret = do_upg(buf, size);
528 if(ret != 0)
529 { 800 {
530 cprintf(GREY, "Error: %d", ret); 801 printf("Die from lack of action\n");
531 if(!g_force) 802 ret = 1;
532 cprintf(GREY, " (use --force to force processing)");
533 printf("\n");
534 ret = 2;
535 } 803 }
536 free(buf);
537 804
538 color(OFF); 805 color(OFF);
539 806