/* Tool to make gzipped second stage binary
   
   Copyright (C) 1996,1998 Jakub Jelinek
   
   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., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
   USA.  */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <arpa/inet.h>

FILE *f, *e;
unsigned char buffer[2048];
unsigned char buffer2[2048];
unsigned short diffs[4][2048];
int ndiffs[4];
unsigned int lastv[4];
unsigned int curoff;
unsigned int prevlen;
unsigned int prevoff;

#define MAX_CHANGE	65535

void save(FILE *out, int len, int type)
{
    int i, j, k;
    int curlen = len;

    while (len > 0) {
        i = 2048;
        if (len < i) i = len;
        if (fread (buffer, 1, i, f) != i) {
	    fprintf(stderr, "read %d from arg3 type %d failed\n", i, type);
	    exit(1);
	}
        if (fread (buffer2, 1, i, e) != i) {
	    fprintf(stderr, "read %d from arg4 type %d failed\n", i, type);
	    exit(1);
	}
        for (j = 0; j < i; j++) {
    	    if (buffer[j] != buffer2[j]) {
		if (buffer2[j] == buffer[j] + 4) {
		    if (curoff + j > lastv[type] + MAX_CHANGE) {
			if (type || !prevlen || curoff + j >= prevoff + MAX_CHANGE)
			    goto bad1;
			k = lastv[type] + MAX_CHANGE;
			if (k < prevoff - prevlen)
			    goto bad1;
			if (k >= prevoff)
			    k = prevoff - 1;
			diffs[type][ndiffs[type]] = htons(k - lastv[type]);
			lastv[type] = k;
			ndiffs[type]++;
		    }
		    diffs[type][ndiffs[type]] = htons(curoff + j - lastv[type]);
		    lastv[type] = curoff + j;
		    ndiffs[type]++;
		} else if (buffer2[j] == buffer[j] + 16) {
		    if (curoff + j > lastv[type+1] + MAX_CHANGE) {
			if (type || !prevlen || curoff + j >= prevoff + MAX_CHANGE)
			    goto bad2;
			k = lastv[type+1] + MAX_CHANGE;
			if (k < prevoff - prevlen)
			    goto bad2;
			if (k >= prevoff)
			    k = prevoff - 1;
			diffs[type+1][ndiffs[type+1]] =
				htons(k - lastv[type+1]);
			lastv[type+1] = k;
			ndiffs[type+1]++;
		    }
		    diffs[type+1][ndiffs[type+1]] =
			htons(curoff + j - lastv[type+1]);
		    lastv[type+1] = curoff + j;
		    ndiffs[type+1]++;
		} else {
		    fprintf(stderr, "Strange, small and large images differ in something"
			    " different to R_SPARC_32 and R_SPARC_HI22\n");
		    exit(1);
		}
	    }
        }
	if (fwrite (buffer, 1, i, out) != i) {
	    fprintf(stderr, "write %d failed\n", i);
	    exit(1);
	}
        len -= i;
        curoff += i;
    }
    prevlen = curlen;
    prevoff = curoff;
    return;
bad2:
    type++;
bad1:
    fprintf(stderr, "Distance between two changes larger than %dK %d %d %d\n",
	    MAX_CHANGE / 1024, type, curoff + j, lastv[type]);
    exit(1);
}

int main(int argc, char **argv)
{
    FILE *g, *h;
    int reloc = SMALL_RELOC;
    int first_start, first_end, second_start, second_end;
    int end, rodata_start, rodata_end;
    int net = 0;
    int i = 1;

    if (!strcmp (argv[i], "-a")) {
	net = 1;
	i++;
    }

    f = fopen(argv[i++], "r");
    while (fgets (buffer, 256, f)) {
	char sym[256];
	unsigned int addr;

	if (sscanf(buffer, "%x %*c %s\n", &addr, sym) == 2) {
	    if (!strcmp (sym, "start"))
		reloc = addr;
	    else if (!strcmp (sym, "main_text_start"))
		first_start = addr -  reloc;
	    else if (!strcmp (sym, "main_text_end"))
		first_end = addr - reloc;
	    else if (!strcmp (sym, "main_data_start"))
		second_start = addr - reloc;
	    else if (!strcmp (sym, "main_data_end"))
		second_end = addr - reloc;
	    else if (!strcmp (sym, "main_rodata_start"))
		rodata_start = addr - reloc;
	    else if (!strcmp (sym, "main_rodata_end"))
		rodata_end = addr - reloc;
	    else if (!strcmp (sym, "__bss_start"))
		end = addr - reloc;
	}
    }
    fclose (f);
    f = fopen(argv[i++], "r");
    e = fopen(argv[i++], "r");
    g = fopen(argv[i++], "w");
    h = fopen(argv[i++], "w");
    if (fread(buffer, 1, 32, f) != 32) exit(1);
    if (fread(buffer2, 1, 32, e) != 32) exit(1);
    if (memcmp(buffer, buffer2, 32)) {
    	fprintf(stderr, "Strange. Images for 2.5MB and 3.5MB differ in a.out header\n");
    	exit(1);
    }

    if (!net) {
       memset (buffer, 0, 2048);
       if (fwrite(buffer, 1, 2048, g) != 2048) exit(1);
    } else {
       if (fwrite (buffer, 1, 32, g) != 32) exit(1);
    }

    save (g, first_start, 0);
    save (h, first_end - first_start, 2);
    save (g, rodata_start - first_end, 0);
    save (h, rodata_end - rodata_start, 2);
    save (g, second_start - rodata_end, 0);
    save (h, second_end - second_start, 2);
    save (g, end - second_end, 0);
    fwrite(diffs[0], 2, ndiffs[0]+1, g);
    fwrite(diffs[1], 2, ndiffs[1]+1, g);
    fwrite(diffs[2], 2, ndiffs[2]+1, h);
    fwrite(diffs[3], 2, ndiffs[3]+1, h);
    fclose (f);
    fclose (e);
    fclose (g);
    fclose (h);
    exit (0);
}
