/*    SCCS Id: @(#)splitter.c		3.1   95/07/25
/*    Copyright (c) Kenneth Lorber, Bethesda, Maryland, 1992, 1993, 1995 */
/* NetHack may be freely redistributed.  See license for details. */

#define SOUT				/* split output files */
#define SPLITSIZE (800 * 1024)		/* somewhat < one floppy */

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

#include <proto/exec.h>
#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>

#include "split.h"
#include "amiout.h"
#include "arg.h"

int main(int,char **);
char *stripname(char *);

char *code_proto="%n.c%C";
char *data_proto="%n.d%D";
char *dir_proto="%n.dir";

char trace[MAXTRACEVAR];		/* debugging info */
char *basename;
int datacount;				/* for ssubst - should be replaced */
int codecount;
int data_file_count=0;			/* actual maxima */
int code_file_count=0;
struct hheader hheader;
struct shunk (*hlist)[];
char buf[80];
int wsf_count;

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

    arg_init("C:D:d:t:T",argc,argv);
    while((cur_arg=arg_next())!=ARG_DONE){
	switch(cur_arg){
	case 'C':	/* code prototype */
		code_proto=strdup(argarg);break;
	case 'D':	/* data prototype */
		data_proto=strdup(argarg);break;
	case 'd':	/* directions prototype */
		dir_proto=strdup(argarg);break;
	case 't':	/* trace (debug) */
		{
		int dtype=0,dlevel=0;	/* rude defaults */
		sscanf(argarg,"%d=%d",&dtype,&dlevel);
		if(dtype<0 || dtype>=MAXTRACEVAR){
		    fprintf(stderr,"-t: bad trace num ignored\n");
		}else{
		    trace[dtype]=dlevel?dlevel:1;
		}
		break;
		}
	case 'T':	/* trace everything */
		{
		int dtype;
		for(dtype=0;dtype<MAXTRACEVAR;dtype++)trace[dtype]=255;
		}
		break;
	default:
		fprintf(stderr,"Unrecognized option.\n");
		/* FALLTHROUGH */
	case ARG_ERROR:
		panic("Error processing arguments.");
	case ARG_FREE:
		basename=strdup(argarg);
			/*
			 * Force the output files into the current directory.
			 * Note that this leaks memory from the start of the
			 * string (but we don't care in this case).
			 */
		basename=stripname(basename);
		read_load_file(argarg);break;
	}
    }
    renumber();

    out_start(code_proto);
    write_header();
    write_code_file();
    out_stop();

    out_start(data_proto);
    write_data_file();
    out_stop();

    write_dir_file();
    exit(0);
}

char *
ssubst(buf,pat)
    char *buf;
    const char *pat;
{
    char *buf1=buf;

    while(*pat){
	if(*pat!='%'){
	    *buf++=*pat++;
	} else {
	    pat++;
	    switch(*pat++){
	    case '%': *buf++='%';break;
	    case 'n': strcpy(buf,basename);buf=eos(buf);break;
	    case 'D': sprintf(buf,"%02d",datacount);buf=eos(buf);break;
	    case 'C': sprintf(buf,"%02d",codecount);buf=eos(buf);break;
	    default:  panic("pattern substitution error");
	    }
	}
    }
    *buf='\0';
    return buf1;
}

void
panic(s)
    char *s;
{
    fprintf(stderr,"\npanic: %s\n",s);
    exit(1);
}

char *
eos(s)
    char *s;
{
    while(*s)s++;
    return s;
}

/*
 * input routines
 */

	/* macro for reading the next long.  If e==EOF_OK, caller MUST check
	 * for EOF condition via hreadval or assure it can't occur */
static int hreadval=0;		/* macro internal temporary */
#define EOF_OK	1
#define EOF_BAD	0
#define READLONG(e)	\
	((4!=(hreadval=read(f->fd,&(READLONGx),4)))		\
	?((0==hreadval && (e)					\
		?0						\
		:rderror()))					\
	:READLONGx)
static long READLONGx;
#define READSHORT(e)	\
	((2!=(hreadval=read(f->fd,&(READSHORTx),2)))		\
	?((0==hreadval && (e)					\
		?0						\
		:rderror()))					\
	:READSHORTx)
static short READSHORTx;

#define LONGLEN(x)	(strlen(x)+3 >>2)	/* # longs for a string */

void
read_load_file(name)
    char *name;
{
    int t;
    int hc;
    file *f=NewFile(name);

		/* read HUNK_HEADER */
    t=READLONG(EOF_BAD);if(t!=HUNK_HEADER)panic("no HUNK_HEADER");
    t=READLONG(EOF_BAD);if(t)while(t--)READLONG(EOF_BAD); /* eat any name */
    hheader.hcount=READLONG(EOF_BAD);
    hheader.first=READLONG(EOF_BAD);
    hheader.last=READLONG(EOF_BAD);
    if(hheader.hcount !=(hheader.last-hheader.first+1))panic("can't count");
    hheader.sizes=calloc(hheader.hcount,sizeof(int*));
    for(t=0;t<hheader.hcount;t++)
	(*hheader.sizes)[t]=READLONG(EOF_BAD);

    hlist=calloc(hheader.hcount,sizeof(struct shunk));
    for(hc=0;hc<hheader.hcount;hc++){
	struct shunk *th = &(*hlist)[hc];
			/* read each hunk */
	th->h=ReadHunk(f);
    }
    close(f->fd);
}

/*
 * write routines
 */
#define S_CODE	0
#define S_DATA	1

void
write_header(){
    int x;
    int target=0;

    owrite_long(HUNK_HEADER);
    owrite_long(0);
    owrite_long(hheader.hcount);
    owrite_long(hheader.first);
    owrite_long(hheader.last);

    for(x=0;x<hheader.hcount;x++){
	hunk *hp = (*hlist)[x].h;
	if(hp->hunknum==target){
	    owrite_long((*hheader.sizes)[x]);
	    target++;
	}
    }
    for(x=0;x<hheader.hcount;x++){
	hunk *hp = (*hlist)[x].h;
	if(hp->hunknum==target){
	    owrite_long((*hheader.sizes)[x]);
	    target++;
	}
    }
    if(target!=hheader.hcount)panic("lost hunks?");
}

void
write_code_file(){
    code_file_count=write_split_file(S_CODE)-1;
}

void
write_data_file(){
    data_file_count=write_split_file(S_DATA)-1;
}

void
write_dir_file(){
    int x;
    FILE *fp=fopen(ssubst(buf,dir_proto),"w");

    fprintf(fp,"# split binary direction file\n");
    fprintf(fp,"# Each line consists of:\n");
    fprintf(fp,"#   A single C or D for the type of the file (Code or Data)\n");
    fprintf(fp,"#   The full path of the file.\n");

    for(x=0;x<=code_file_count;x++){
	codecount=x;
	fprintf(fp,"C %s\n",ssubst(buf,code_proto));
    }
    for(x=0;x<=data_file_count;x++){
	datacount=x;
	fprintf(fp,"D %s\n",ssubst(buf,data_proto));
    }
    fclose(fp);
}

/* macro to eliminate type bits, leaving the length of a memory allocation */
#define HT(x)	((x) & ~MEM_OBJ_EXTEND)

int
write_split_file(fl)
    int fl;
{
    int hc;
    for(hc=0;hc<hheader.hcount;hc++){
	hunk *hp = (*hlist)[hc].h;
	if(fl==S_CODE && HT(hp->rb->id)==HUNK_CODE){
	    wsf_hunk(hp);
	} else if(fl==S_DATA && HT(hp->rb->id)==HUNK_DATA){
	    wsf_hunk(hp);
	} else if(fl==S_DATA && HT(hp->rb->id)==HUNK_BSS){
	    wsf_hunk(hp);
	}
    }
    return wsf_count;
}

void
wsf_hunk(hp)
    hunk *hp;
{
    listlist *el;

    switch(HT(hp->rb->id)){
    case HUNK_CODE:
    case HUNK_DATA:
	owrite(hp->rb->b,(2+hp->rb->b[1])*sizeof(long));
	break;
    case HUNK_BSS:
	owrite(hp->rb->b,2*sizeof(long));
	break;
    default:panic("wsf_hunk: bad type");
    }
    foreach(el,&(hp->reloc),(listlist*)){
	write_lreloc(hp,el);
    }
    owrite_long(HUNK_END);
}

void
write_lreloc(hp,ll)
    hunk *hp;listlist *ll;
    {
    block *bp;

#ifdef EMIT_32s
    int x;
    ULONG *p;

		/* can we write the entire block with a HUNK_RELOC32s? */
    foreach(bp,&(ll->list),(block*)){
	if((((*hlist)[bp->b[1]]).h->hunknum)>0xffff)goto no_32s; /* no */
	for(p= &(bp->b[2]), x=bp->b[0];x;x--,p++){
	    	if(*p>0xffff)goto no_32s;	/* no, offset too big */
	}
    }
		/* yes */
    owrite_long(HUNK_RELOC32s);
    foreach(bp,&(ll->list),(block*)){
	owrite_long(bp->b[0]);
	owrite_short(((*hlist)[bp->b[1]]).h->hunknum);
	for(p= &(bp->b[2]), x=bp->b[0];x;x--,p++){
	    owrite_short(*p);
	}
			/* force long alignment.  Not documented, but makes
			 * reading dumps easier */
	if((bp->b[0] & 1) == 0){	/* note hunknum also short */
	    owrite_short(0);
	}
    }
    owrite_long(0);
    return;
no_32s:
#endif
    owrite_long(HUNK_RELOC32);
    foreach(bp,&(ll->list),(block*)){
	owrite_long(bp->b[0]);
	owrite_long(((*hlist)[bp->b[1]]).h->hunknum);
	owrite(&(bp->b[2]),bp->b[0]*sizeof(long));
    }
    owrite_long(0);
}

void
renumber()
{
    int n;
    n=renumber2(S_CODE,0);
    renumber2(S_DATA,n);
}

renumber2(fl,n)
    int fl;
    int n;
{
    int hc;
    for(hc=0;hc<hheader.hcount;hc++){
	hunk *hp = (*hlist)[hc].h;
	if(fl==S_CODE && HT(hp->rb->id)==HUNK_CODE){
	    hp->hunknum=n++;
	} else if(fl==S_DATA && HT(hp->rb->id)==HUNK_DATA){
	    hp->hunknum=n++;
	} else if(fl==S_DATA && HT(hp->rb->id)==HUNK_BSS){
	    hp->hunknum=n++;
	}
    }
    return n;
}

/*
 * output package
 */
#ifndef SOUT
/* NB - this version does NOT cope with multiple output files per type
 *      define SOUT to do that
 */
int ofile;

void
out_start(prot)
    char *prot;
{
    datacount=codecount=0;
    file=open(ssubst(buf,prot),O_WRONLY|O_CREAT|O_TRUNC);
    if(ofile<0)panic("can't open output file");
}

void
out_stop(){
    close(ofile);
}

void
owrite_long(literal)
    long literal;
{
    long x=literal;
    owrite(&x,sizeof(x));
}

void
owrite_short(literal)
    short literal;
{
    short x=literal;
    owrite(&x,sizeof(x));
}

void
owrite(where,len)
    void *where;
    long len;
{
    write(ofile,where,len);
}
#else /* SOUT */
/* split output files - these are read via multi.  This code will eventually
 * migrate into multi as well.
 */
int ofile=0;
int osize;
char *oprot;
void
out_start(prot)
    char *prot;
{
    datacount=codecount=wsf_count=0;
    oprot=prot;
    new_file();
}

void
out_stop(){
    close(ofile);
    ofile=0;
}

void
owrite_long(literal)
    long literal;
{
    long x=literal;
    if((osize+sizeof(x))>SPLITSIZE)new_file();
    owrite(&x,sizeof(x));
    osize += sizeof(x);
}

void
owrite_short(literal)
    int literal;
{
    short x=literal;
    if((osize+sizeof(x))>SPLITSIZE)new_file();
    owrite(&x,sizeof(x));
    osize += sizeof(x);
}

void
owrite(where,len)
    void *where;
    long len;
{
    while((osize+len)>SPLITSIZE){
	write(ofile,where,SPLITSIZE-osize);
	len -= (SPLITSIZE-osize);
	where = (void *)((char *)where + SPLITSIZE-osize);
	new_file();
    }
    write(ofile,where,len);
    osize += len;
}

void
new_file(){
    if(ofile)close(ofile);
    ofile=open(ssubst(buf,oprot),O_WRONLY|O_CREAT|O_TRUNC);
    if(ofile<0)panic("can't open output file");
    wsf_count++,datacount++,codecount++;
    osize=0;
}
#endif /* SOUT */

/*
 * list functions with error checking
 */
struct Node *Head(l)
    struct List *l;
{
    if(!l)panic("Head(NULL)\n");
    return l->lh_Head->ln_Succ?l->lh_Head:0;
}
struct Node *Tail(l)
    struct List *l;
{
    if(!l)panic("Tail(NULL)\n");
    return (l->lh_TailPred==(NODE_P)l)?0:l->lh_TailPred;
}
struct Node *Next(n)
    struct Node *n;
{
    if(!n)printf("Warning: Next(NULL)\n");
    return n?(n->ln_Succ->ln_Succ?n->ln_Succ:0):0;
}
struct Node *Prev(n)
    struct Node *n;
{
    if(!n)printf("Warning: Prev(NULL)\n");
    return n?(n->ln_Pred->ln_Pred?n->ln_Pred:0):0;
}

struct List *_fortemp;	/* scratch for foreach macro */

#if 0
/* debugging routines - enable if needed */
void
dump_after_read(struct List *root){
    file *f;
    foreach(f,root,(file *)){
        punit *p;
        printf("FILE '%s'\n",f->name);
        foreach(p,&(f->punits),(punit *)){
	    hunk *h;
	    print_text_block("\tPUNIT %.*s\n",p->unit_header);
	    if(p->libsize){
		printf("\tlibsize=%08x\n",p->libsize);
	    } else {
		/* */
	    }
	    foreach(h,&(p->hunks),(hunk *)){
		print_text_block("\t\tHUNK %.*s",h->name);
		printf(" @%08x\n",h);
		print_bin_block(h->rb);
		printf("\t\t\tCode Reloc\n");
		printf("\t\t\tData Reloc\n");
		if(h->merge)printf("\t\t\tmerge(%08x)\n",h->merge);
		if(h->hunkstart)printf("\t\t\thunkstart\n");
		if(h->hunkchain)printf("\t\t\thunkchain\n");
		if(h->hunkgone)printf("\t\t\thunkgone\n");
		printf("\t\t\toverlay(%08x) hunknum(%08x) offset(%08x)\n",
		  h->overlay,h->hunknum,h->hunkoffset);
	    }
        }
    }
}

void
print_text_block(char *fmt,block *b){
    if(!b){
	printf(fmt,10,"(no block)");
    } else {
	if(b->sw){
	    printf(fmt,13,"(swapped out)");
	} else {
	    if(!(b->b[1]) || !*(char*)&(b->b[2])){
		printf(fmt,6,"(null)");
	    } else {
		printf(fmt,b->b[1]*4,&(b->b[2]));
	    }
	}
    }
}

void
print_bin_block(block *b){
    if(b->sw){
	printf("\t\t\t(swapped out)\n");
    } else {
	printf("\t\t\tid1=%08x id2=%08x len=%08x\n", b->id,b->b[0],b->b[1]);
    }
}
#endif

/*
 * read routines
 */

/*
 * ReadSimpleBlock
 * If the given id is recognized as a simple block (id, length, data),
 * allocate and fill in a block structure.  Include the id in the block.
 */
block *ReadSimpleBlock(f,id)
    file *f;
    long id;
{
    long len;
    long hid;
    block *b;

    hid=id & 0x0fffffff;
    if(	hid !=HUNK_UNIT && hid != HUNK_NAME && hid != HUNK_CODE &&
    	hid != HUNK_DATA && hid != HUNK_BSS && hid != HUNK_DEBUG
      ){
	printf("%08x\n",id);
	panic("ReadSImpleBlock");
    }

    len=READLONG(EOF_BAD);
    b=NewBlock();
    b->id=id;
    b->sw=0;
    b->b=NewData((hid==HUNK_BSS)?2:len+2);
    b->b[0]=id;
    b->b[1]=len;
    if(hid != HUNK_BSS)read(f->fd,&(b->b[2]),len*4);
    return(b);
}

/*
 * TossSimpleBlock
 * Skip past something we don't need.
 */
int TossSimpleBlock(f)
    file *f;
{
    long len=READLONG(EOF_BAD);

    if(len)if( lseek(f->fd,len*4,1) == -1)panic("Toss failed\n");
    return(len);
}

/*
 * ReadHunk
 * Read an entire hunk, building lists of each block type in the given hunk
 * structure.  If we are listing, do the listing as we read so we can see
 * where things die if we hit a type code we don't recognize.
 */
hunk *ReadHunk(f)
    file *f;
{
    long id;
    hunk *h=NewHunk();

    while(1){
	id=READLONG(EOF_OK);
	switch(id & 0x0fffffff){	/* ignore memory type bits */
	case 0: return 0;		/* EOF - not good test */
	case HUNK_RELOC32:
	    LIST{printf("Reloc32:\n");}
	    ReadReloc(f,id,&h->reloc);break;
	case HUNK_CODE:
	    h->rb=ReadSimpleBlock(f,id);
	    LIST{printf("Code size %d\n",block_size(h->rb)*4);};
	    break;
	case HUNK_DATA:
	    h->rb=ReadSimpleBlock(f,id);
	    LIST{printf("Data size %d\n",block_size(h->rb)*4);};
	    break;
	case HUNK_BSS:
	    h->rb=ReadSimpleBlock(f,id);
	    LIST{printf("Bss size %d\n",block_size(h->rb)*4);};
	    break;
	case HUNK_SYMBOL:
	    while(TossSimpleBlock(f))READLONG(EOF_BAD);
	    LIST{printf("Symbols skipped\n");};
	    break;
	case HUNK_DEBUG:
	    (void)TossSimpleBlock(f);
	    LIST{printf("Debug hunk skipped\n");};
	    break;
	case HUNK_END:	LIST{printf("End of hunk\n");};return h;
	case HUNK_BREAK:LIST{printf("End of overlay\n");};break;
	default:
			printf("Lost id=0x%x\n",id);exit(2);
	}
    }
    return 0;
}

/*
 * ReadReloc
 * Read a relocation block and build a linked list of the sections.
 * If we are listing, do that now.
 */
void ReadReloc(f,id,ls)
    file *f;
    long id;
    struct List *ls;
{
    long len;
    block *cur;
    listlist *blist=NewListList();

    AddTail(ls, (struct Node *)blist);
    blist->id=id;
    len=READLONG(EOF_BAD);
    while(len){
	cur=NewBlock();
	cur->b=NewData(len+2);
	read(f->fd,&(cur->b[1]),len*4+4);
	cur->b[0]=len;
	LIST{printf("\thunk #%d - %d items\n",cur->b[1],len);}
	AddTail(&blist->list, (struct Node *)cur);
	len=READLONG(EOF_BAD);
    }
}

int rderror(){
    panic("read error\n");
    return 0;	/* just to make it quiet - NOTREACHED */
}

long block_size(blk)
    block *blk;
{
    return(blk->b[1]);
}

/* Allocation routines - if this was C++ then this code would be buried in the
 * constructors.  Doing it this way means we can re-write the allocation later
 * to allocate things we'll need lots of in larger blocks to avoid the time and
 * space penalties of malloc. */
file *NewFile(fname)
    char *fname;
    {
    file *ret=calloc(sizeof(file),1);

    NewList(&ret->punits);
    ret->name=strdup(fname);
    ret->fd= open(fname,O_RDONLY);
    return(ret);
}

punit *NewPunit(){
	punit *ret=calloc(sizeof(punit),1);
	NewList(&ret->hunks);
	return(ret);
}

hunk *NewHunk(){
    hunk *ret=calloc(sizeof(hunk),1);

    NewList(&ret->reloc);
    NewList(&ret->dreloc);
    NewList(&ret->extsym);
    ret->overlay=UNASSIGNED_HUNK;
    return(ret);
}

block *NewBlock(){
    return calloc(sizeof(block),1);
}

listlist *NewListList(){
    listlist *ret=calloc(sizeof(listlist),1);

    NewList(&ret->list);
    return(ret);
}

long *NewData(longs)
    long longs;	
    {
    return(malloc(longs*4));
}

/* filename cleanup */
char *
stripname(p)
    char *p;
    {
    char *n;

    while(n=strchr(p,':')){
	p=n+1;
    }
    while(n=strchr(p,'/')){
	p=n+1;
    }

    return p;
}
