#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "type.h"
#include "liste.h"
#include "malloc.h"
#include "signature.h"

/*
	Auteur: Benoit Papillault
	Creation: Novembre 1997

	Derniere modification: 08/01/1998

	Objectif:	1/ analyser les .obj et .lib
			2/ les transformer en fichier signature

	Note: les enregistrements FIXUPP sont TOUJOURS relatifs a
	l'enregistrement LIDATA/LEDATA precedent, le plus pres. (cf
	le fichier <doc/ss0288_4.txt> (1). De toute facon, le cas FIXUPP
	suivant LIDATA est tres complexe, ne sera pas traite et n'apparait pas
	dans les librairies (on le verifiera quand meme).

	(1) Disponible sur le serveur ftp ftp.microsoft.com.
*/

#define	THEADR	0x80
#define	COMENT	0x88
#define	MODEND	0x8a
#define	EXTDEF	0x8c
#define	PUBDEF	0x90
#define	LNAMES	0x96
#define	SEGDEF	0x98
#define	GRPDEF	0x9a
#define	FIXUPP	0x9c
#define	LEDATA	0xa0
#define	LIDATA	0xa2

#define	MSLIBR	0xf0
#define	MSLEND	0xf1

/* pour faire des statistiques */

int type_reloc_rencontre[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };

struct pubdef
{
	char	*name;
	char	*seg;
	int	off;
};

LISTE_h(liste_pubdef,struct pubdef);
LISTE_c(liste_pubdef,struct pubdef);

struct ledata
{
	char	*seg;
	int	off;
	int	size;	/* nb d'octets des zones suivantes */
	byte	*code;
	byte	*mask;
};

LISTE_h(liste_ledata,struct ledata);
LISTE_c(liste_ledata,struct ledata);

struct liste_pubdef list_pubdef;
struct liste_ledata list_ledata;
struct ledata	*last_ledata = NULL;	/* pointeur dans la liste precedente */

int	lidata_present = 0;

char **	list_lname = NULL;
int	nb_lname = 0;

void ajouter_lname(const char *name)
{
	list_lname = (char **)realloc(list_lname,(++nb_lname)*sizeof(char *));
	list_lname[nb_lname-1] = strdup(name);
}

char **	list_lseg = NULL;
int	nb_lseg = 0;

void ajouter_lseg(const char *name)
{
	list_lseg = (char **)realloc(list_lseg,(++nb_lseg)*sizeof(char *));
	list_lseg[nb_lseg-1] = strdup(name);
}

char **	list_lgroup = NULL;
int	nb_lgroup = 0;

void ajouter_lgroup(const char *name)
{
	list_lgroup = (char **)realloc(list_lgroup,(++nb_lgroup)*
			sizeof(char *));
	list_lgroup[nb_lgroup-1] = strdup(name);
}

char **	list_extern = NULL;
int	nb_extern = 0;

void ajouter_extern(const char *name)
{
	list_extern = (char **)realloc(list_extern,(++nb_extern)*
			sizeof(char *));
	list_extern[nb_extern-1] = strdup(name);
}

void initialiser_list()
{
	int i;

	for (i=0;i<nb_lname;i++)
		free(list_lname[i]);
	free(list_lname);

	for (i=0;i<nb_lseg;i++)
		free(list_lseg[i]);
	free(list_lseg);

	for (i=0;i<nb_lgroup;i++)
		free(list_lgroup[i]);
	free(list_lgroup);

	for (i=0;i<nb_extern;i++)
		free(list_extern[i]);
	free(list_extern);

	liste_pubdef_done(&list_pubdef);
	liste_pubdef_init(&list_pubdef);

	liste_ledata_done(&list_ledata);
	liste_ledata_init(&list_ledata);
	last_ledata = NULL;

	list_lname = list_lseg = list_lgroup = list_extern = NULL;
	nb_lname = nb_lseg = nb_lgroup = nb_extern = 0;
}

char *get_name(byte *p,int len)
{
	int i;
	static char buffer[257];

	for (i=0;i<p[0] && i<len;i++)
		buffer[i] = p[i+1];
	buffer[i] = 0;

	return buffer;
}

/* retourne le nombre d'octets utilises */

int get_index(byte *p,int *index)
{
	if ((p[0] & 0x80) != 0)
	{
		*index = ((p[0]&0x7f)<<8) | p[1];
		return 2;
	}
	else
	{
		*index = p[0];
		return 1;
	}
}

void analyser_theadr(byte *p,int len)
{
	printf("THREADR\t'%s'\n",get_name(p,len));
}

void analyser_pubdef(byte *p,int len)
{
	int group, segment, offset, type;
	int i,rlen = 0,n;
	char *s;
	struct pubdef public;

	printf("PUBDEF\t");
	n = get_index(p,&group);
	p += n; len -= n;
	n = get_index(p,&segment);
	p += n; len -= n;
	printf("group %d (%s), segment %d (%s)\n",group,
		group==0?"":list_lgroup[group-1],segment,
		segment==0?"":list_lseg[segment-1]);
	if (segment == 0 && group == 0)
	{
		/* calcul de "frame number (0 a 2 octets)" */
		p += 2;
		len -= 2;
	}
	for (i=1;rlen<len;i++)
	{
		s = get_name(p+rlen,len-rlen);
		rlen += p[rlen]+1;
		offset = (p[rlen+1]<<8) | p[rlen];
		rlen += 2;
		rlen += get_index(p+rlen,&type);
		printf("\t'%s'\toffset=%d type=%x\n",s,offset,type);

		if (segment != 0)
		{
			public.name = strdup(s);
			public.seg = strdup(list_lseg[segment-1]);
			public.off = offset;
			liste_pubdef_ajouter(&list_pubdef,&public);
		}
	}	
}

void analyser_segdef(byte *p,int len)
{
	int a,c,b;
	int length, segment, class, overlay;

	printf("SEGDEF\t");

	a = (p[0] >> 5) & 7;
	c = (p[0] >> 2) & 7;
	b = (p[0] >> 1) & 1;
	p += 1;

	switch (a)
	{
	case 0:
		p += 3;
		break;
	}

	length = (p[1]<<8) | p[0];
	p += 2;
	p += get_index(p,&segment);
	p += get_index(p,&class);
	p += get_index(p,&overlay);

	ajouter_lseg(list_lname[segment-1]);

	printf("%d->length=%d, segment '%s', class '%s', overlay '%s'\n",
		nb_lseg,length,
		list_lname[segment-1],list_lname[class-1],
		list_lname[overlay-1]);
}

void analyser_grpdef(byte *p,int len)
{
	int	group, lseg, n;

	printf("GRPDEF\t");

	n = get_index(p,&group);
	p += n; len -= n;

	ajouter_lgroup(list_lname[group-1]);
	printf("%d -> '%s':\n",nb_lgroup,list_lname[group-1]);

	while (len > 0)
	{
		p += 1; len -= 1;	/* mysterieux */
		n = get_index(p,&lseg);
		p += n; len -= n;
		printf("\t'%s'\n",list_lseg[lseg-1]);		
	}
}

void analyser_extdef(byte *p,int len)
{
	char *s;
	int n,type;

	printf("EXTDEF\n");

	while (len > 0)
	{
		s = get_name(p,len);
		ajouter_extern(s);
		n = p[0]+1;
		p += n; len -= n;
		n = get_index(p,&type);
		p += n; len -= n;
		printf("\textern %d '%s'\ttype=%d\n",nb_extern,s,type);
	}
}

void analyser_lnames(byte *p,int len)
{
	int i,rlen;
	char *s;

	printf("LNAMES\n");

	rlen = 0;
	for (i=1;rlen<len;i++)
	{
		s = get_name(p+rlen,len-rlen);
		ajouter_lname(s);
		printf("\tName %d (rlen=%d): '%s'\n",nb_lname,rlen,s);
		rlen += p[rlen]+1;
	}	
}

void analyser_coment(byte *p,int len)
{
	printf("COMENT ...\n");
}

#define	LARGEUR	16

void analyser_ledata(byte *p,int len)
{
	int i,lseg, n, offset;
	struct ledata data;

	n = get_index(p,&lseg);
	p += n; len -= n;

	offset = (p[1]<<8) | p[0];
	p += 2; len -= 2;

	printf("LEDATA\tsegment '%s'\toffset %d, %d byte(s)",
		lseg==0?"":list_lseg[lseg-1],offset,len);

	for (i=0;i<len;i++)
	{
		if ((i%LARGEUR) == 0)	printf("\n\t");
		printf("%02X ",p[i]);
	}
	printf("\n");

	/* on enregistre ces precieuses informations */

	if (lseg != 0)
	{
		data.seg = strdup(list_lseg[lseg-1]);
		data.off = offset;
		data.size = len;
		data.code = (byte *)malloc(len);
		data.mask = (byte *)malloc(len);
		memcpy(data.code,p,len);
		memset(data.mask,0xff,len);

		liste_ledata_ajouter(&list_ledata,&data);

		/* une petite entorse a la programmation propre */
		last_ledata = &list_ledata.liste[list_ledata.nb-1];
	}

	lidata_present = 0;
}

void analyser_lidata(byte *p,int len)
{
	int lseg, n, offset;
	/* int nrep, count; */

	n = get_index(p,&lseg);
	p += n; len -= n;

	offset = (p[1]<<8) | p[0];
	p += 2; len -= 2;

	printf("LIDATA\tsegment '%s'\toffset %d, %d byte(s)\n",
		lseg==0?"":list_lseg[lseg-1],offset,len);

/*	while (len > 0)
	{
		nrep = (p[1]<<8) | p[0];
		p += 2; len -= 2;
		count = (p[1]<<8) | p[0];
		p += 2; len -= 2;

		p+= count; len -= count;
	}

	if (len != 0)	printf("\t*** len = %d ***\n",len);
*/
	lidata_present = 1;
}

void analyser_mslibr(byte *p,int len)
{
	int dir_offset, dir_pages;

	dir_offset = p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
	p += 4;
	dir_pages = p[0] | (p[1]<<8);

	printf("MSLIBR\tdir_offset=0x%x, dir_pages=%d\n",dir_offset,dir_pages);	
}

void analyser_modend(byte *p,int len)
{
	int attr;

	attr = (p[0] >> 6) & 3;
	printf("MODEND\t");
	switch (attr)
	{
	case 0:	printf("Non-main, no Start Address");	break;
	case 1:	printf("Non-main, Start Address");	break;
	case 2:	printf("Main, no Start Address");	break;
	case 3:	printf("Main, Start Address");		break;
	}
	printf("\n");
}

void analyser_fixupp(byte *p,int len)
{
	int n,l_index, index_present;

	printf("FIXUPP\n");

	while (len>0)
	{
		if ((p[0] & 0x80) == 0)
		{
			printf("\tTHREAD subrecord\n");
			index_present = 0;
			if ((p[0] & 0x10) == 0)
				index_present = 1;
			p ++; len --;
			if (index_present)
			{
				n = get_index(p,&l_index);
				p += n; len -= n;
			}
		}
		else
		{
			int data_record_offset, location;
			int f,t,o;
			int frame, target, tmp;
			int i, off, l;

			data_record_offset = ((p[0]&3)<<8) | p[1];
			location = (p[0] >> 2) & 0x7;
			printf("\tFixUp at offset %03x ",data_record_offset);
			if ((p[0] & 0x40) == 0)
				printf("Mode: Self ");
			else	printf("Mode: Seg ");

			switch (location)
			{
			case 0:	printf("LOBYTE ");	break;
			case 1:	printf("OFFSET ");	break;
			case 2:	printf("BASE ");	break;
			case 3:	printf("POINTER ");	break;
			case 4:	printf("HIBYTE ");	break;
			default:
				printf("Loc: %d ",location);
				break;
			}

			/* on ajuste la zone mask de la derniere zone LEDATA
				LIDATA rencontree */

			if (!lidata_present && last_ledata != NULL)
			{
				switch (location)
				{
				case 0:	off = data_record_offset;
					l = 1;
					break;
				case 1:	off = data_record_offset;
					l = 2;
					break;
				case 2:	off = data_record_offset+2;
					l = 2;
					break;
				case 3:	off = data_record_offset;
					l = 4;
					break;
				case 4:	off = data_record_offset+1;
					l = 1;
					break;
				default:	off = len = 0; /* pas bo */
					break;
				}

				for (i=off;i<off+l;i++)
					last_ledata->mask[i] = 0;
			}
			else printf("\n*** Erreur FixUpp ***\n");

			type_reloc_rencontre[location]++;

			frame = (p[2] >> 4) & 7;
			target = p[2] & 3;
			f = (p[2] >> 7) & 1;
			t = (p[2] >> 3) & 1;
			o = (p[2] >> 2) & 1;

			p += 3;	len -= 3;
			if (f==0)
			{
				printf("FRAME method %d ",frame);
				if (frame <= 3)
				{
					n = get_index(p,&tmp);
					p += n;	len -= n;
					printf("index %d ",tmp);
				}

				/* on decode tmp */

				switch (frame)
				{
				case 0:	printf("(segment %s) ",
						list_lseg[tmp-1]);
					break;
				case 1:	printf("(group %s) ",
						list_lgroup[tmp-1]);
					break;
				case 2:	printf("(extern %s) ",
						list_extern[tmp-1]);
					break;
				case 3:	printf("(frame %x) ",tmp);
					break;
				}
			}
			else
				printf("FRAME thread %d ",frame);

			if (t==0)
			{
				n = get_index(p,&tmp);
				p += n;	len -= n;
				printf("TARGET method %d index %d ",target,tmp);

				/* on decode tmp */

				switch (target&3)
				{
				case 0:	printf("(segment %s) ",
						list_lseg[tmp-1]);
					break;
				case 1:	printf("(group %s) ",
						list_lgroup[tmp-1]);
					break;
				case 2:	printf("(extern %s) ",
						list_extern[tmp-1]);
					break;
				case 3:	printf("(frame %x) ",tmp);
					break;
				}
			}
			else
				printf("TARGET thread %d ",target);

			if (o==0)
			{
				tmp = p[0] | (p[1]<<8);
				p += 2;	len -= 2;
				printf("TARGET offset %x ",tmp);
			}

			printf("\n");
		}
	}
	if (len != 0)
	printf("\t*** len=%d ***\n",len);
}

void analyser_mslend(byte *p,int len)
{
	printf("MSLEND\n");
}

void creer_signature(FILE *fp,const struct pubdef *pub,
	const struct ledata *data)
{
	struct signature sign;

	signature_init(&sign);

	sign.name = strdup(pub->name);
	sign.pt_entree = pub->off - data->off;
	sign.size = data->size;
	sign.code = (byte *)malloc(sign.size);
	sign.mask = (byte *)malloc(sign.size);

	memcpy(sign.code,data->code,sign.size);
	memcpy(sign.mask,data->mask,sign.size);

	if (!signature_ecrire(fp,&sign))
		printf("Erreur: impossible d'ecrire la signature\n");
	signature_afficher(&sign);
	signature_done(&sign);
}

void examiner_pubdef(FILE *fp_sig,const struct pubdef *pub)
{
	int i;
	int	trouve = 0;
	struct ledata *p;

	/* on cherche le LEDATA associe */

	for (i=0;i<list_ledata.nb;i++)
	{
		p = &list_ledata.liste[i];
		if (strcmp(p->seg,pub->seg) == 0 
			&& p->off <= pub->off && pub->off < p->off+p->size)
		{
			if (trouve)
				printf("Erreur: deux LEDATA reference '%s'\n",
					pub->name);
			else
			{
				printf("%s (%d) in block %s:%d\n",pub->name,
					pub->off,p->seg,p->off);
				creer_signature(fp_sig,pub,p);
			}
			trouve = 1;
		}
	}

	if (!trouve)
		printf("Erreur: pas de LEDATA pour '%s'\n",pub->name);
}

void fabriquer_signature(FILE *fp_sig)
{
	int i;

	/* on examine toutes les donnees que l'on possede, on commence
		par regarder la liste de tous les symboles publiques
		et on chercher le LEDATA corespondant */

	for (i=0;i<list_pubdef.nb;i++)
		examiner_pubdef(fp_sig,&list_pubdef.liste[i]);
}

void analyser_obj_lib(FILE *fp,FILE *fp_sig)
{
	byte type;
	word len;
	byte *p;
	byte	buffer[10];
	int	is_library = 0, page_size, continuer = 1;
	long pos;

	while (continuer)
	{
		pos = ftell(fp);

		if (fread(&type,1,1,fp)!=1)
		{
			printf("read error in reading record type\n");
			return ;
		}

		if (fread(buffer,2,1,fp)!=1)
		{
			printf("read error in reading record length\n");
			return;
		}

		len = buffer[0] | (buffer[1]<<8) ;
		if (len == 0)
			continue;

		p = (char *)malloc(len);
		if (p==NULL)
		{
			printf("malloc error\n");
			return;
		}

		if (fread(p,len,1,fp)!=1)
		{
			printf("error in reading record content (%d octets)\n",
				len);
			return;
		}

		printf("%lx\t",pos);

	/* le (-1) s'explique par le fait que l'on neglige le checksum */

		switch (type)
		{
		case THEADR:
			analyser_theadr(p,len-1);
			break;
		case LNAMES:
			analyser_lnames(p,len-1);
			break;
		case COMENT:
			analyser_coment(p,len-1);
			break;
		case PUBDEF:
			analyser_pubdef(p,len-1);
			break;
		case SEGDEF:
			analyser_segdef(p,len-1);
			break;
		case GRPDEF:
			analyser_grpdef(p,len-1);
			break;
		case EXTDEF:
			analyser_extdef(p,len-1);
			break;
		case LEDATA:
			analyser_ledata(p,len-1);
			break;
		case LIDATA:
			analyser_lidata(p,len-1);
			break;
		case MSLIBR:
			is_library = 1;
			page_size = len+3;
			printf("page size is %d bytes\n",page_size);
			analyser_mslibr(p,len-1);
			break;
		case MODEND:
			analyser_modend(p,len-1);
			fabriquer_signature(fp_sig);
			initialiser_list();
			if (is_library)
			{
				int n;

				n = ftell(fp);
				n = (n + page_size -1)/ page_size;
				n = n * page_size;
				fseek(fp,n,SEEK_SET);
			}
			break;
		case FIXUPP:
			analyser_fixupp(p,len-1);
			break;
		case MSLEND:
			analyser_mslend(p,len-1);
			continuer = 0;
			break;
		default:
			printf("record type = %02X, record len = %d\n",
				type,len);
			break;
		}

		free(p);
	}
}

void usage()
{
	printf("usage: objlist file.obj|file.lib signature_file\n");
	exit(-1);
}

int main(int argc,char *argv[])
{
	FILE *	fp_lib, *fp_sig;
	int	i;


	if (argc != 3)
		usage();

	fp_lib = fopen(argv[1],"rb");
	if (fp_lib == NULL)
	{
		printf("unable to open '%s'\n",argv[1]);
		return -1;
	}

	fp_sig = fopen(argv[2],"wb");
	if (fp_sig == NULL)
	{
		printf("unable to open '%s'\n",argv[2]);
		return -1;
	}

	initialiser_list();
	analyser_obj_lib(fp_lib,fp_sig);
	initialiser_list();

	fclose(fp_lib);
	fclose(fp_sig);

	/* affichage des statistiques */

	for (i=0;i<8;i++)
		printf("reloc de type %d : %d\n",i,type_reloc_rencontre[i]);

	return 0;
}
