/*
 *							<<< dviadd >>>
 *
 *		A program to embed/extract binary files into/from a DVI file
 *
 *							Written by SHIMA
 *							 September 1999
 */


//				Validitate in case of UNIX
// #define	UNIX	1

//				Validitate if SHIFT JIS is used for a filename 
#define	SHIFT_JIS	1

/*					%%% Contents of an extended DVI file  %%%
 *  PRE(247)
 *	...			% PREAMBRE %
 *	...
 *
 *  BOP(139)
 *  c[10][4]
 *  p[4]						-1
 *  ...			% Contents of the first pages
 *  EOP(140)
 *
 *  BOP(139)
 *  ...			% Contents of pages
 *  ...
 *
 *  BOP(139)	% top of the last page
 *  c[10][4]
 *  p[4]						address of BOP at the top of the former page
 *  ...			% Contents of the last page
 *  EOP(140)	% end of the last page
 *
 *  			% Added part %
 *    POST(248)
 *    Data[num_add][?]					series of data
 *    FileNames[num_add][?]				series of strings NULL terminated
 *    pt_Data[num_add][BOD[4], LOD[4]]	pointer and length of data
 *
 *							% 20 byte
 *    BOF[4]						top of series of strings
 *	  EOF[4]						end of series of strings
 *    num_add[4]					number of data
 *	  top_add[4]					address of POST at the top of added part
 *    AdID[3]						'A', 'd', 'O'
 *    EOP(140)  % End of Added part %
 *
 *  POST(248)	% Top of POSTAMBRE  %
 *	p[4]								address of BOP at the top of the 
 *										last page
 *  ....
 *  POST_POST(249)
 *  top_post[4]							address of POST at the top of POSTAMBRE
 *  2 (or 3), 223,...,223
 */

/*
 *		The extended DVI file should be the following
 *
 * 	1. The 8 byte preceeding to the top of the POSTAMBRE:
 *
 *  						top_add[4], 'A', 'd', 'O', 140(=EOP)
 *
 *  2.  DVI[top_add[4]]		140(=EOP)
 *      DVI[top_add[4]-1]	248(=POS)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#ifdef	UNIX
#define	PATH_SEP		'/'
#define	READ_TEXT		"r"
#define	WRITE_BINARY	"w"
#else
#define	PATH_SEP		'\\'
#define	PATH_SEPU		'/'
#define	READ_TEXT		"rt"
#define	WRITE_BINARY	"wb"
#endif

#define	MAX_INCL	2048		/* maximal number of embedded files */
#define	MAX_PATH	0x200		/* maximal length of path name */

#define	ID			2
#define	IDP			3
#define	END_DVI		223

#define SET_CHAR_0      0
#define SET1            128
#define SET_RULE        132
#define PUT1            133
#define PUT_RULE        137
#define NOP             138
#define BOP             139
#define EOP             140
#define PUSH            141
#define POP             142
#define RIGHT1          143
#define W0              147
#define W1              148
#define X0              152
#define X1              153
#define DOWN1           157
#define Y0              161
#define Y1              162
#define Z0              166
#define Z1              167
#define FNT_NUM_0       171
#define FNT1            235
#define XXX1            239
#define FNT_DEF_1       243
#define PRE             247
#define POST            248
#define POST_POST       249
#define EOFNC           255		/* end of func */


#define DVIFILE_INFO struct DVIFILE_INFO_REC

#define	STRIP	1
#define	EXPAND	2

struct DVIFILE_INFO_REC {
	FILE *file_ptr;
	char *file_name;
	long post, last_bop;
	long pt_post;
};

DVIFILE_INFO dvi_info;


void read_post(DVIFILE_INFO *dvi);
void PrintFile(void);
void ReadFile(char *name);
int WriteDVI(DVIFILE_INFO *dvi);

char infile[MAX_PATH];
char outfile[MAX_PATH];
char outdir[MAX_PATH];
int  len_outdir;

int bofn, eofn, num_add, top_add, last_add, f_expand;

int	pt_bod[MAX_INCL], lod[MAX_INCL];
char *pt_name[MAX_INCL];

const int AdID = (('A'<<24)+('d'<<16)+('O'<<8)+EOP);

#define	issjis1(x)	((x)>=0x81&&(x)<=0xfc&&((x)<=0x9f||(x)>=0xe0))
#define	read_byte(x)	getc(x)
#define	write_byte(x,y)	putc(x,y)

void write_long(int x, FILE *fp)
{
	write_byte(x >> 24, fp);
	write_byte((x >> 16) & 0xff, fp);
	write_byte((x >> 8) & 0xff, fp);
	write_byte(x & 0xff, fp);
}

int read_long(FILE *fp)
{
	int i;

	i  = read_byte(fp) << 24;
	i += read_byte(fp) << 16;
	i += read_byte(fp) << 8;
	return i + read_byte(fp);
}

char *GetFname(unsigned char *name)
{
	int i, j, ch;

	i = j = (
#ifndef	UNIX
		!issjis1(name[0]) && 
#endif
		name[0] && name[1] == ':')?2:0;
	for(; name[i] > ' '; i++){
#ifndef	SHIFT_JIS
		ch = name[i];
		if(issjis1(ch)){
			i++;
			if(!name[i])
				break;
			continue;
		}
#endif
		if(name[i] == PATH_SEP
#ifndef	UNIX
			|| name[i] == PATH_SEPU
#endif
		)
			j = i+1;
	}
	return name + j;
}

void DelFile(unsigned char *name)
{
	int i, j, size;
	unsigned char *s;
#ifdef	SHIFT_JIS
	int	f_sjis;
#endif

	name = GetFname(name);
	for(size = 0; name[size] > ' '; size++);

	for(i = 0; i < last_add; i++){
#ifdef	SHIFT_JIS
		f_sjis = 0;
#endif
		s = pt_name[i];
		for(j = 0; ;j++){
			if(j == size){
				if(!s[j] && name[j] <= ' '){
					printf("- %s\n", s);
					s[0] = 0;
					i = num_add;
				}
				break;
			}
#ifdef	SHIFT_JIS
			if(!f_sjis){
#endif
				if(tolower(s[j]) != tolower(name[j]))
				break;
#ifdef	SHIFT_JIS
				f_sjis = issjis1(s[j]);
			}else{
				if(s[j] != name[j])
					break;
				f_sjis = 0;
			}
#endif
		}
	}
}

void AddFile(char *name)
{
	int i, size;
	char *s;

	if(last_add >= MAX_INCL){
		fprintf(stderr, "Too many include files");
		exit(2);
	}
	DelFile(name);
	for(size = 0; name[size] > ' '; size++);
	s = pt_name[last_add++] = malloc(size+1);
	for(i = 0; i < size; i++)
		s[i] = name[i];
	s[i] = 0;
}

void usage(void)
{
	fprintf(stderr,
	"\t    Add binary files into a DVI file (a utility for dviout)\n"
	"\t               Ver.0.4  written by SHIMA, 1999\n\n"
	"Usage: dviadd [[+|-]file1] [[+|-]file2] ... [@FILE] dvi_file\n"
	"         default or +: add/replace a file         -: delate a file\n"
	"         In a FILE a file_name is written in each line such as\n"
	"           - foo1.bmc                 (delate foo1.bmc)\n"
	"           # comment line             (ignored)\n"
    "           + foo2.jpg                 (add/replace foo2.jpg)\n"
    "         file_name may be given with a full path, but it is recorded in\n"
    "         dvi_file by omitting the drive and directory name.\n"
    "         Comments may be put after file_name by separating with a space.\n"
	"         The above + may be omitted.\n\n"
	"       dviadd [?] dvi_file            (show added files)\n"
	"       dviadd d dvi_file              (strip dvi_file)\n"
	"       dviadd e[=directory] dvi_file  (expand added files in [directory])\n"
	"       dviadd x[=directory] dvi_file  (expand added files and strip dvi_file)\n"
	);
	exit(1);
}

int main(int argc, char **argv)
{
	int i;

	if(argc <= 1)
		usage();
	strcpy(infile, argv[argc-1]);
	i = strlen(infile);
	if(i < 4 || stricmp(infile + i - 4, ".dvi")){
		strcat(infile, ".dvi");
		i += 4;
	}
	dvi_info.file_name = infile;
	strcpy(outfile, infile);
	strcpy(outfile + i - 3, "$$$");

	if ((dvi_info.file_ptr = fopen(dvi_info.file_name, "rb")) == NULL){
		fprintf(stderr, "Cannot open %s\n", infile);
		exit(1);
	}
	read_post(&dvi_info);

	if(argc == 2)
		goto prf;
	for(i = 1; i < argc-1; i++){
		switch(argv[i][0]){
			case '?':
prf:			PrintFile();
				exit(0);
			case '+':
				AddFile(argv[i]+1);
				break;
			case '-':
				DelFile(argv[i]+1);
				break;
			case '@':
				ReadFile(argv[i]+1);
				break;
			case 'd':
				if(argc == 3 && !argv[i][1]){
					f_expand = STRIP;
					break;
				}
				goto add;
			case 'x':
				if(argc == 3){
					if(!argv[i][1]){
						f_expand = STRIP|EXPAND;
						break;
					}else if(argv[i][1] == '='){
						f_expand = STRIP|EXPAND;
						goto set_dir;
					}
				}
				goto add;
			case 'e':
				if(argc == 3){
					if(!argv[i][1]){
						f_expand = EXPAND;
						break;
					}else if(argv[i][1] == '='){
						f_expand = EXPAND;
						strcpy(outdir, argv[i] + 2);
set_dir:				len_outdir = strlen(outdir);
						if(len_outdir > 1 && outdir[len_outdir-1] != PATH_SEP){
							outdir[len_outdir++] = PATH_SEP;
							outdir[len_outdir] = 0;
						}
						break;
					}
				}
				goto add;
			default:
add:			AddFile(argv[i]);
				break;
		}
	}
	if(WriteDVI(&dvi_info)){
		unlink(infile);
		rename(outfile, infile);
		printf("  %s\n", infile);
	}
	return 0;
}


int WriteDVI(DVIFILE_INFO *dvi)
{
	FILE *fp, *fd;
	int	i, j, size, ch, num;
	char *s;

	if( f_expand && !(f_expand & STRIP) )
		goto outf;
						/* Open a file to be output */
	fp = fopen(outfile, WRITE_BINARY);
	if(fp == NULL){
		fprintf(stderr, "Write error\n");
		exit(4);
	}
						/* Write PREAMBLE & Pages */
	fseek(dvi->file_ptr, 0, SEEK_SET);
	for(i = top_add; i-- > 0;)
		write_byte(read_byte(dvi->file_ptr), fp);

outf:					/* Write PRE-POSTAMBRE */
	if(f_expand & EXPAND){				/* recover added files */
		for(j = 0; j < num_add; j++){
			strcpy(outdir + len_outdir, pt_name[j]);
			fd = fopen(outdir, WRITE_BINARY);
			if(fd == NULL)
				goto err_open;
			fseek(dvi->file_ptr, pt_bod[j], SEEK_SET);
			for(i = lod[j]; i-- > 0; )
				write_byte(read_byte(dvi->file_ptr), fd);
			fclose(fd);
			printf("%s\n", outdir);
		}
		if(!(f_expand & STRIP))
			exit(0);
	}
	if(f_expand & STRIP)
		goto out_post;
								/* add data */
	for(j = num = size = 0; j < num_add; j++){
		if(!*pt_name[j])
			continue;
		if(!num++){
			size = 1;
			write_byte(POST, fp);
		}
		fseek(dvi->file_ptr, pt_bod[j], SEEK_SET);
		pt_bod[j] = ftell(fp);
		for(i = lod[j]; i-- > 0; )
			write_byte(read_byte(dvi->file_ptr), fp);
		size += lod[j];
	}
	for( ; j < last_add; j++){
		if(!*pt_name[j])
			continue;
		if(!num++){
			size = 1;
			write_byte(POST, fp);
		}
		pt_bod[j] = ftell(fp);
		fd = fopen(pt_name[j], "rb");
		if(fd == NULL){
err_open:	fprintf(stderr, "Cannot open %s\n", pt_name[j]);
			fclose(fp);
			fclose(dvi->file_ptr);
			unlink(outfile);
			exit(4);
		}
		pt_bod[j] = ftell(fp);
		i = 0;
		while( (ch = getc(fd)) != EOF ){
			write_byte(ch, fp);
			i++;
		}
		fclose(fd);
		size += lod[j] = i;
	}
	if(!num)
		goto out_post;

							/* write filenames */
	bofn = ftell(fp);
	for(j = 0; j < last_add; j++){
		if(!*pt_name[j])
			continue;
		s = GetFname(pt_name[j]);
		if(j >= num_add){
			printf("+ ");
			while(*s > ' '){
				putchar(*s);
				write_byte(*s++, fp);
				size++;
			}
			putchar('\n');
		}else while(*s > ' '){
			write_byte(*s++, fp);
			size++;
		}
		write_byte(0, fp);
		size++;
	}
							/* to an integer boundary */
	eofn = ftell(fp);
	while(size++ & 3)
		write_byte(0, fp);
							/* set pointers for added data */
	for(j = 0; j < last_add; j++){
		if(!*pt_name[j])
			continue;
		write_long(pt_bod[j], fp);
		write_long(lod[j], fp);
	}
	write_long(bofn, fp);
	write_long(eofn, fp);
	write_long(num, fp);
	write_long(top_add, fp);
	write_long(AdID, fp);

out_post:			/* Write POSTAMBRE */
	fseek(dvi->file_ptr, dvi->post, SEEK_SET);
	size = ftell(fp);
	i = dvi->pt_post - dvi->post;
	while(i-- > 0)
		write_byte(read_byte(dvi->file_ptr), fp);

	write_long(size, fp);
	read_long(dvi->file_ptr);

	while( (ch = read_byte(dvi->file_ptr)) != EOF )
		write_byte(ch, fp);
	fclose(fp);
	fclose(dvi->file_ptr);
	return 1;
}

void ReadFile(char *name)
{
	FILE *fp;
	char tmp[0x1000], *s;
	int f_del;

	if((fp = fopen(name, READ_TEXT)) == NULL){
		fprintf(stderr,"Cannot open %s\n", name);
		exit(3);
	}
	while(fgets(s = tmp, 0x1000, fp)){
		f_del = 0;
		if(*s == '#')
			continue;
		if(*s == '-'){
			s++;
			f_del = 1;
		}else if(*tmp == '+')
			s++;
		while(*s && *s <= ' ')
			s++;
		if(!*s)
			continue;
		if(f_del)
			DelFile(s);
		else
			AddFile(s);
	}
	fclose(fp);
}

void read_post(DVIFILE_INFO *dvi)
	/* read POSTAMBLE */
{
	int code, i, j, size, endofs;
	char *s;

	if ( read_byte(dvi->file_ptr) != PRE ||
		((code = read_byte(dvi->file_ptr)) != ID && code != IDP) ){
err:	fprintf(stderr, "%s is not correct DVI file\n", dvi->file_name);
		exit(254);
	}
	for (endofs = -3L; fseek(dvi->file_ptr, endofs, SEEK_END),
		 (code = read_byte(dvi->file_ptr)) != ID && code != IDP; endofs--)
		/* Search id number */
		if (code == EOF || code != END_DVI)
			goto err;
	fseek(dvi->file_ptr, endofs - 4L, SEEK_END);
	dvi->pt_post = ftell(dvi->file_ptr);
	if ((top_add = dvi->post = read_long(dvi->file_ptr)) <= 0)
		goto err;
	/* Read the position of POSTAMBLE */

	fseek(dvi->file_ptr, dvi->post - 4, SEEK_SET);
	/* Set file-ptr at POSTAMBLE */

	if(AdID == read_long(dvi->file_ptr))
		num_add = 1;
	if((code = read_byte(dvi->file_ptr)) != POST)
		goto err;
	if((dvi->last_bop = read_long(dvi->file_ptr)) <= 0)
		goto err;
	fseek(dvi->file_ptr, dvi->post - 20, SEEK_SET);

	if(num_add){
		bofn = read_long(dvi->file_ptr);
		eofn = read_long(dvi->file_ptr);
		num_add = last_add = read_long(dvi->file_ptr);
		top_add = read_long(dvi->file_ptr);
		fseek(dvi->file_ptr, dvi->post - 20 - 8*num_add, SEEK_SET);
		for(i = 0; i < num_add; i++){
			pt_bod[i] = read_long(dvi->file_ptr);
			lod[i] = read_long(dvi->file_ptr);
		}
		fseek(dvi->file_ptr, bofn, SEEK_SET);
		size = eofn - bofn;
		s = malloc(size);
		for(i = 0; i < size; i++)
			s[i] = read_byte(dvi->file_ptr);
		pt_name[0] = s;
		for(i = j = 0; i < size; ){
			if(!s[i++])
				pt_name[++j] = s + i;
		}
	}
}

void PrintFile(void)
{
	int	i;

	for(i = 0; i < num_add; i++)
		printf("%16s : %8d byte\n", pt_name[i], lod[i]);
	for( ; i < last_add; i++){
		if(pt_name[i])
			printf("%s\n", pt_name[i]);
	}
	if(last_add == 0)
		printf("No files are added.\n");
}
