/*
  
  Marvell Libertas wireless driver - tools

  Copyright (c) 2005 Luc Saillard <luc@saillard.org>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; see the file COPYING.  If not, write to
  the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
  Boston, MA 02110-1301, USA.

  Build
    gcc -o mrv8k_extract_fw mrv8k_extract_fw.c -DUSE_OPENSSL -lcrypto
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>

#include <sys/types.h>		/* for stat */
#include <sys/stat.h>
#include <unistd.h>

#include <sys/time.h>		/* for gettimeofday */
#include <time.h>

#include <sys/mman.h>		/* for mmap */
#include <fcntl.h>		/* for fcntl  */
#include <sys/select.h>		/* for select */

#ifdef USE_OPENSSL
#include <openssl/sha.h>
#endif

static const unsigned char *mmap_read_file(const char *filename, struct stat *sts);
static void search_n_extract_firmware(const void *mem, size_t memlen);
static void search_n_extract_bootloader(const void *mem, size_t memlen);
static const void *search_mem32(const unsigned int *mem, size_t memlen,
 				const unsigned int *magic, size_t magiclen);

int main(int argc, char **argv)
{
  const unsigned char *mem;
  size_t memsize;
  struct stat sts;

  if (argc < 2) {
     fprintf(stderr, "%s: MRV8KAxx.sys\n", argv[0]);
     exit(1);
  }

  mem = mmap_read_file(argv[1], &sts);
  if (mem == NULL) {
     fprintf(stderr, "Can't mmap file %s\n", argv[1]);
     exit(1);
  }
  memsize = sts.st_size;
  
  search_n_extract_bootloader(mem, memsize);
  search_n_extract_firmware(mem, memsize);

  munmap((void *)mem, memsize);
  
  return 0;
}

static const unsigned char *mmap_read_file(const char *filename, struct stat *sts)
{
  int fd;
  void *map;

  fd=open(filename,O_RDONLY);
  if (fd<0)
    goto error;
  if (fstat(fd,sts)<0)
    goto close_fd;

  map = mmap(0,sts->st_size,PROT_READ,MAP_PRIVATE,fd,0);
  if (map == MAP_FAILED)
    goto close_fd;
  close(fd);

  return map;

close_fd:
  close(fd);

error:
  fprintf(stderr, "Can't open file \"%s\"\n", filename);
  return NULL;
}

static int export_to_c8(const char *filename, const char *arrayname,
   		        const void *mem, size_t memlen)
{
  FILE *f;
  int c, i, units;
  const unsigned char *p = (unsigned char *)mem;

  units = memlen;

  f = fopen(filename,"w");
  if (f == NULL)
    return -1;
  fprintf(f, "const unsigned char %s[%d] = {\n", arrayname, units);
  for (c=i=0; i<units; i++) {
     fprintf(f, " 0x%2.2x", *p++);
     c++;
     if (i+1<units)
       fprintf(f,",");
     if (c>7) {
	fprintf(f,"\n");
	c=0;
     }
  }
  fprintf(f, "};\n");
  fclose(f);
  return 0;
}

static int export_to_bin(const char *filename, const void *mem, size_t memlen, const unsigned char *sig)
{
#if USE_OPENSSL
  unsigned char md[SHA_DIGEST_LENGTH];
#endif
  FILE *f;

  f = fopen(filename,"w");
  if (f == NULL)
    return -1;
  fwrite(mem, memlen, 1, f);
  fclose(f);

#if USE_OPENSSL
  SHA1(mem, memlen, md);
  if (memcmp(md, sig, SHA_DIGEST_LENGTH)) {
     printf("Warning: Bad signature for the firmware\n");
     printf("got    %.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x\n",
	 md[0], md[1], md[2], md[3], md[4], md[5], md[6], md[7], md[8], md[9],
	 md[10], md[11], md[12], md[13], md[14], md[15], md[16], md[17], md[18], md[19]);
     printf("wanted %.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x\n",
	 sig[0], sig[1], sig[2], sig[3], sig[4], sig[5], sig[6], sig[7], sig[8], sig[9],
	 sig[10], sig[11], sig[12], sig[13], sig[14], sig[15], sig[16], sig[17], sig[18], sig[19]);
  }
#endif

  return 0;
}

static const void *search_mem32(const unsigned int *mem, size_t memlen,
 				const unsigned int *magic, size_t magiclen)
{
  int i=0;

  if (magiclen&4)	/* We search using a 32 bit pointer */
    return NULL;

  memlen   = memlen/4;
  magiclen = magiclen/4;

  while (--memlen) {
     if (magic[i] == *mem) {
	i++;
	if (i == magiclen)
	  return mem-i+1;
     } else {
	i = 0;
     }
     mem++;
  }
  return NULL;
}


struct fw {
   char *driver_name;
   char *driver_version;
   unsigned int  main_size;
   unsigned char boot_sig[20];
   unsigned char main_sig[20];
} list_of_fw[] = {
    {
      "ASUS WL-138G", "2.3.0.19", 0x2DF00,
      {
	0x6e, 0x05, 0xab, 0xc8, 0xc4, 0xb2, 0x59, 0x7e, 0x1d, 0xad,
	0x63, 0xb0, 0xa1, 0xbb, 0x8d, 0x45, 0xd5, 0xe3, 0x29, 0xee
      },
      {
	0xa9, 0x4d, 0x98, 0xf1, 0xe7, 0x67, 0xa4, 0x13, 0xec, 0x8a,
	0xc6, 0xe7, 0x68, 0x87, 0xd8, 0x92, 0xfd, 0xce, 0x9c, 0x4f
      }
    },
    {
      "PLANET WL-3563", "2.3.0.3", 0x2DF00,
      {
	0x6e, 0x05, 0xab, 0xc8, 0xc4, 0xb2, 0x59, 0x7e, 0x1d, 0xad,
	0x63, 0xb0, 0xa1, 0xbb, 0x8d, 0x45, 0xd5, 0xe3, 0x29, 0xee
      },
      {
	0x4b, 0x4c, 0x67, 0x38, 0x81, 0x9f, 0x63, 0xfd, 0x3f, 0x32,
	0x48, 0xc4, 0x08, 0xc7, 0xa6, 0x17, 0x09, 0xbf, 0x1f, 0x3e,
      }
    },
    {
      "NETGEAR WG311v3", "3.1.1.7", 0x1df00,
      {

      },
      {

      }
    }
};


static void search_n_extract_bootloader(const void *mem, size_t memlen)
{
  const void *result = NULL;
  static unsigned int bootloader_magic[] = {
     0xE3A0BF40, 0xE59F40D8, 0xE5945000, 0xE2155002
  };

  result = search_mem32(mem, memlen, bootloader_magic, sizeof(bootloader_magic));
  if (!result) {
     printf("Bootloader not found\n");
     return;
  }
  printf("Bootloader found at %ld\n", (long)(result-mem));
  export_to_c8("mrv8k_fw_boot.h", "mrv8k_fw_boot", result, 0x100);
  export_to_bin("mrv8k-b.fw", result, 0x100, list_of_fw[0].main_sig);
}

static void search_n_extract_firmware(const void *mem, size_t memlen)
{
  unsigned long offset, len;
  const void *result = NULL;
  static unsigned int firmware_magic[] = {
     0xE59FF018, 0xE59FF018, 0xE59FF018, 0xE59FF018,
     0xE59FF018, 0x00000000, 0xE59FF018, 0xE59FF018
  };

  result = search_mem32(mem, memlen, firmware_magic, sizeof(firmware_magic));
  if (!result) {
     printf("Firmware not found\n");
     return;
  }
  offset = (unsigned long)(result-mem);
  printf("Firmware found at %ld\n", offset);
  if (offset + 0x2DF00 >= memlen) {
     len = 0x1df00;
     printf("Small firmware detected size = %lu\n", len);
  } else {
     len = 0x2DF00;
     printf("Long firmware detected size = %lu\n", len);
  }
  export_to_c8("mrv8k_fw_main.h", "mrv8k_fw_main", result, len);
  export_to_bin("mrv8k-f.fw", result, len, list_of_fw[0].main_sig);
}



