/* vi:set tabstop=3: */
#include <stdio.h>
#include <st-out.h>
#include <stdlib.h>
#include <errno.h>
#include <sgtty.h>

#define MAX_LISP_TYPE	25
#define PTRBITS 0x00FFFFFFL
#define FUZZY	1024	/* difference accepted if in range +- FUZZY */

extern long	_stksize = -1L;

int	no_questions = 0;
int	overwrite = 0;
int	no_warnings = 0;

unsigned char	*reloc_buffer;

void
add_rel(int n, int i)
{
	reloc_buffer = (unsigned char *) realloc(reloc_buffer, i+1);
	if(!reloc_buffer) {
		fprintf(stderr, "Unable to allocate %d bytes\n", i+1);
		exit(4);
	}
	reloc_buffer[i] = n;
}

unsigned char	*copy_buffer;

void
warning(int pos, int i, int j)
{
	if(!no_warnings) fprintf(stderr, "Unexpected difference @%d: %08X (basediff %08X)\tmemory: %06X\n", pos, i, j, &(copy_buffer[pos - A_TXTOFF(hdr)]));
}

char	known_types[128];

int
make_emacs(int file1, FILE *file2, int file3)
{
	struct aexec	hdr;
	long	base1, base2;
	int	i;
	long	pos;
	union {
		long	i;
		struct {
			unsigned int	gc_bit:1;
			unsigned int	type:7;
			unsigned int	ptr:24;
		} o;
	}	val1, val2;
	int	offset;
	int	start_offset;
	long	basediff;
	int	diff;
	int	fuzzy_relocs = 0;
	int	swap_files = 0;
	char	ch;

	printf("reading dump headers\n");
	fseek(file2, 0, SEEK_SET);
	fread(&hdr, sizeof(struct aexec), 1, file2);
	if(hdr.a_magic != 0x4544) return(2);
	base2 = hdr.a_AZero1;
	lseek(file1, 0, SEEK_SET);
	read(file1, &hdr, sizeof(struct aexec));
	if(hdr.a_magic != 0x4544) return(1);
	base1 = hdr.a_AZero1;
	basediff = base1 - base2;
	if(!basediff) {
		fprintf(stderr, "Files must not have same basepage address\n");
		exit(5);
	}
	if(basediff < 0) {
		basediff = -basediff;
		swap_files = 1;
	}
	base1 += 0x100;	/* sizeof(BASEPAGE) */
	hdr.a_AZero1 = 0;
	hdr.a_magic = 0x601A;
	printf("writing corrected header\n");
	printf("data start = %d\n", A_DATOFF(hdr));
	lseek(file3, 0, SEEK_SET);
	write(file3, &hdr, sizeof(struct aexec));
	printf("reading text and data\n");
	copy_buffer = (unsigned char *) malloc(A_STROFF(hdr) - A_TXTOFF(hdr));
	read(file1, copy_buffer, A_STROFF(hdr) - A_TXTOFF(hdr));
	printf("adding new relocation info for dumped data\n");
	pos = A_TXTOFF(hdr);
	start_offset = offset = 0;
	i = -1;
	while(pos < A_STROFF(hdr)) {
		val1.i = *(long *)(&(copy_buffer[pos - A_TXTOFF(hdr)]));
		fseek(file2, pos, SEEK_SET);
		fread(&val2.i, sizeof(long), 1, file2);
		diff = swap_files ? val2.i - val1.i : val1.i - val2.i;
		if(diff == basediff) {
		diff_ok:
			known_types[val1.o.type] = 1;
			if(!(i % 1000)) {
				printf("%4d %6d\r",i,pos);
				fflush(stdout);
			}
			val1.i -= base1;
			*(long *)(&(copy_buffer[pos - A_TXTOFF(hdr)])) = val1.i;
			if(!start_offset) start_offset = offset;
			else {
				while(offset > 254) {
					add_rel(1,++i);
					offset -= 254;
				}
				add_rel(offset,++i);
			}
			offset = 4;
			pos += 4;
		} else if((diff < basediff + FUZZY) &&
			  (diff > basediff - FUZZY)) {
			fuzzy_relocs++;
			goto diff_ok;
		} else {
			if(diff && !no_questions) {
				if(diff & 0xFFFF0000L) {
					warning(pos,diff,basediff);
					if(!(val1.o.ptr & 1L) &&
					   val1.o.type <= MAX_LISP_TYPE) {
						if(!overwrite || !no_warnings)
						printf(" val1 = %08X,  val2 = %08X,\n"
						       "base1 = %08X, base2 = %08X,\n"
						       " top1 = %08X relocate ? (y|n) "
						,val1.i,val2.i
						,base1,base2
						,base1+A_STROFF(hdr)-sizeof(hdr)+0x100);
						fflush(stdout);
						if(val1.o.ptr >= base1 &&
						   val1.o.ptr <= base1 +
								 A_STROFF(hdr) -
								 sizeof(hdr) +
								 0x100) {
							if(overwrite) {
								if(!no_warnings) printf("Y\n");
								fuzzy_relocs++;
								goto diff_ok;
							}
							ch = getchar();
							printf("\n");
							if(ch == 'Y' || ch == 'y') {
								fuzzy_relocs++;
								goto diff_ok;
							}
							printf("... ignored\n");
						 } else if(!overwrite || !no_warnings) {
							printf("N (outside of program)\n");
						 }
					}
				}
			}
			offset += 2;
			pos += 2;
		}
	}
	add_rel(0,++i);
	printf("last info was at position %d\n",pos);
	printf("writing text and data\n");
	write(file3, copy_buffer, A_STROFF(hdr) - A_TXTOFF(hdr));
	free(copy_buffer);
	write(file3, &start_offset, sizeof(long));
	printf("writing %d bytes of relocation info", i+1);
	if(fuzzy_relocs) printf(" (%d bytes guessed)", fuzzy_relocs);
	printf("\n");
	lseek(file3, A_STROFF(hdr) + sizeof(long), SEEK_SET);
	write(file3, reloc_buffer, i+1);
	free(reloc_buffer);
	printf("Used tag-fields for pointers:\t");
	for(i = 0; i < 127; i++) if(known_types[i]) printf("%3d ", i);
	printf("\n");
	return(0);
}

void
main(int argc, char *argv[])
{
	FILE	*file2;
	int	file1, file3;
	int	error;
	struct sgttyb	sg;
	char	*prg_name;

	gtty(0,&sg);
	sg.sg_flags |= CBREAK;
	stty(0,&sg);
	printf("Relocation fix for emacs dump files\n");
	prg_name = argv[0];
	while(argc > 1 && argv[1][0] == '-') {
		switch(argv[1][1]) {
			case 'n' : no_questions = 1; break;
			case 'o' : overwrite = 1; break;
			case 'w' : no_warnings = 1; break;
		}
		argc--;
		argv++;
	}
	if(argc < 4) {
		fprintf(stderr, "Usage: %s [-n] [-o] [-w] dump1 dump2 executable\n", prg_name);
		exit(3);
	}
	if(file1 = open(argv[1],0)) {
		if(file2 = fopen(argv[2],"rb")) {
			setvbuf(file2, NULL, _IOFBF, 10240);
			if(file3 = creat(argv[3],0666)) {
				switch(error = make_emacs(file1,file2,file3)) {
					case 1 :
						fprintf(stderr,
							"%s: Not an emacs dump file",
							argv[1]);
					case 2 :
						fprintf(stderr,
							"%s: Not an emacs dump file",
							argv[2]);
						break;
				}
				close(file3);
			} else perror(argv[3]);
			fclose(file2);
		} else perror(argv[2]);
		close(file1);
	} else perror(argv[1]);
	exit(error);
}
