/*
    lzma2eva - convert lzma-compressed file to AVM EVA bootloader format
    Copyright (C) 2007  Enrik Berkhan <Enrik.Berkhan@inka.de>

    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; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
*/

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h> /* ntohl */

#define BPB 8 /* bits/byte */

static uint32_t crc32_table[1<<BPB];

static void init_crc32()
{
	const uint32_t poly = ntohl(0x2083b8ed);
	int n;

	for (n = 0; n < 1<<BPB; n++) {
		uint32_t crc = n;
		int bit;

		for (bit = 0; bit < BPB; bit++)
			crc = (crc & 1) ? (poly ^ (crc >> 1)) : (crc >> 1);

		crc32_table[n] = crc;
	}
}

static uint32_t crc32(uint32_t crc, unsigned char *buf, size_t len)
{
	crc ^= 0xffffffff;
	for (; len; len--, buf++)
		crc = crc32_table[(uint8_t)crc ^ *buf] ^ (crc >> BPB);
	return ~crc;
}

#define checksum_add32(csum, data) \
  csum += ((uint8_t *)&data)[0]; \
  csum += ((uint8_t *)&data)[1]; \
  csum += ((uint8_t *)&data)[2]; \
  csum += ((uint8_t *)&data)[3];

void
usage(void)
{
  fprintf(stderr, "usage: lzma2eva <loadadddr> <entry> <lzmafile> <evafile>\n");
  exit(1);
}

void
pexit(const char *msg)
{
  perror(msg);
  exit(1);
}

int
main(int argc, char *argv[])
{

  const char *infile, *outfile;
  FILE *in, *out;
  static uint8_t buf[4096];
  size_t elems;

  uint8_t properties;
  uint32_t dictsize;
  uint64_t datasize;

  uint32_t magic = 0xfeed1281L;
  uint32_t reclength = 0;
  fpos_t reclengthpos;
  uint32_t loadaddress = 0;
  uint32_t type = 0x075a0201L; /* might be 7Z 2.1? */
  uint32_t checksum = 0;

  uint32_t compsize = 0;
  fpos_t compsizepos;
  uint32_t datasize32 = 0;
  uint32_t datacrc32;

  uint32_t zero = 0;
  uint32_t entry = 0;

  if (argc != 5)
    usage();

  init_crc32();
  datacrc32 = crc32(0, 0, 0);

  /* "parse" command line */
  loadaddress = strtoul(argv[1], 0, 0);
  entry = strtoul(argv[2], 0, 0);
  infile = argv[3];
  outfile = argv[4];

  in = fopen(infile, "rb");
  if (!in)
    pexit("fopen");
  out = fopen(outfile, "w+b");
  if (!out)
    pexit("fopen");

  /* read LZMA header */
  if (1 != fread(&properties, sizeof properties, 1, in))
    pexit("fread");
  if (1 != fread(&dictsize, sizeof dictsize, 1, in))
    pexit("fread");
  if (1 != fread(&datasize, sizeof datasize, 1, in))
    pexit("fread");

  /* write EVA header */
  if (1 != fwrite(&magic, sizeof magic, 1, out))
    pexit("fwrite");
  if (fgetpos(out, &reclengthpos))
    pexit("fgetpos");
  if (1 != fwrite(&reclength, sizeof reclength, 1, out))
    pexit("fwrite");
  if (1 != fwrite(&loadaddress, sizeof loadaddress, 1, out))
    pexit("fwrite");
  if (1 != fwrite(&type, sizeof type, 1, out))
    pexit("fwrite");

  /* write EVA LZMA header */
  if (fgetpos(out, &compsizepos))
    pexit("fgetpos");
  if (1 != fwrite(&compsize, sizeof compsize, 1, out))
    pexit("fwrite");
  /* XXX check length */
  datasize32 = (uint32_t)datasize;
  if (1 != fwrite(&datasize32, sizeof datasize32, 1, out))
    pexit("fwrite");
  if (1 != fwrite(&datacrc32, sizeof datacrc32, 1, out))
    pexit("fwrite");

  /* write modified LZMA header */
  if (1 != fwrite(&properties, sizeof properties, 1, out))
    pexit("fwrite");
  if (1 != fwrite(&dictsize, sizeof dictsize, 1, out))
    pexit("fwrite");
  if (1 != fwrite(&zero, 3, 1, out))
    pexit("fwrite");

  /* copy compressed data, calculate crc32 */
  while (0 < (elems = fread(&buf, sizeof buf[0], sizeof buf, in))) {
    compsize += elems;
    if (elems != fwrite(&buf, sizeof buf[0], elems, out))
      pexit("fwrite");
    datacrc32 = crc32(datacrc32, buf, elems);
  }
  if (ferror(in))
    pexit("fread");
  fclose(in);

  /* re-write record length */
  reclength = compsize + 24;
  if (fsetpos(out, &reclengthpos))
    pexit("fsetpos");
  if (1 != fwrite(&reclength, sizeof reclength, 1, out))
    pexit("fwrite");

  /* re-write EVA LZMA header including size and data crc */
  if (fsetpos(out, &compsizepos))
    pexit("fsetpos");
  if (1 != fwrite(&compsize, sizeof compsize, 1, out))
    pexit("fwrite");
  if (1 != fwrite(&datasize32, sizeof datasize32, 1, out))
    pexit("fwrite");
  if (1 != fwrite(&datacrc32, sizeof datacrc32, 1, out))
    pexit("fwrite");

  /* calculate record checksum */
  checksum += reclength;
  checksum += loadaddress;
  checksum_add32(checksum, type);
  checksum_add32(checksum, compsize);
  checksum_add32(checksum, datasize32);
  checksum_add32(checksum, datacrc32);
  if (fseek(out, 0, SEEK_CUR))
    pexit("fseek");
  while (0 < (elems = fread(&buf, sizeof buf[0], sizeof buf, out))) {
    size_t i;
    for (i = 0; i < elems; ++i)
      checksum += buf[i];
  }
  if (ferror(out))
    pexit("fread");
  if (fseek(out, 0, SEEK_CUR))
    pexit("fseek");

  checksum = ~checksum + 1;
  if (1 != fwrite(&checksum, sizeof checksum, 1, out))
    pexit("fwrite");

  /* write entry record */
  if (1 != fwrite(&zero, sizeof zero, 1, out))
    pexit("fwrite");
  if (1 != fwrite(&entry, sizeof entry, 1, out))
    pexit("fwrite");

  if (fclose(out))
    pexit("fclose");

  return 0;
}
