[comp.sources.amiga] v02i024: deemu - user-modifiable executable embedded data

page@swan.ulowell.edu (Bob Page) (10/25/88)

Submitted-by: dillon@cory.berkeley.edu (Matt Dillon)
Posting-number: Volume 2, Issue 24
Archive-name: util/deemu.1

DEEMU allows end users to modify various parameters of a program by
directly modifying the executable in question through standard
programs.

# This is a shell archive.  Remove anything before this line
# then unpack it by saving it in a file and typing "sh file"
# (Files unpacked will be owned by you and have default permissions).
# This archive contains the following files:
#	Makefile
#	clock.c
#	config.c
#	deemu.doc
#	script.txt
#
if `test ! -s Makefile`
then
echo "writing Makefile"
cat > Makefile << '\Rogue\Monster\'

#   Makefile for config and clock	AZTEC C, +L (32 bit ints)
#
#   NOTE:  I rely on a precompiled include file.  If you do not have such
#   a file, it should not be too hard to add the appopriate #include's...
#   my precompiled include files (precompiled under +L and +H) has only
#   the sub-directory Amiga includes  (*/*.h).

SYMS=	include:symbols.m
SYMC=	include:local/makesymbols.c
CFLAGS= +L +I$(SYMS)


all:	$(SYMS) config clock

config:     config.o
    ln +Q config.o -lc32

clock:	    clock.o
    ln +Q clock.o -lc32

$(SYMS):    $(SYMC)
    make -f include:local/Makefile


\Rogue\Monster\
else
  echo "will not over write Makefile"
fi
if [ `wc -c Makefile | awk '{printf $1}'` -ne 587 ]
then
echo `wc -c Makefile | awk '{print "Got " $1 ", Expected " 587}'`
fi
if `test ! -s clock.c`
then
echo "writing clock.c"
cat > clock.c << '\Rogue\Monster\'

/*
 *  CLOCK.C	simple clock program
 */

#include <local/typedefs.h>

short Params[] = {
    'ST','RT', 0, 0,
    'NW','  ', 0, 8, -1, 10, 40, 20,
    'TE','XT', 0,16, 'HI','\0T','ES','T.', 0, 0, 0, 0,
    'EN','D ', 0, 0
};

#define WPOSX	8
#define WPOSY	9
#define WWIDTH	10
#define WHEIGHT 11

APTR IntuitionBase;
APTR GfxBase;

NW Nw = {
    0, 0, 0, 0, 0, 0,
    CLOSEWINDOW|REFRESHWINDOW|ACTIVEWINDOW|INACTIVEWINDOW,
    SIMPLE_REFRESH|WINDOWCLOSE|BACKDROP|RMBTRAP,
    NULL, NULL, (ubyte *)"", NULL, NULL, 4, 4, -1, -1, WBENCHSCREEN
};

short Sin[60+15] = {
      0,  27,  53,  79, 104, 128, 150, 171, 190, 207, 222, 234, 243, 250, 255,
    256, 255, 250, 243, 234, 222, 207, 190, 171, 150, 128, 104,  79,  53,  27,
      0, -27, -53, -79,-104,-128,-150,-171,-190,-207,-222,-234,-243,-250,-255,
   -256,-255,-250,-243,-234,-222,-207,-190,-171,-150,-128,-104, -79, -53, -27,
      0,  27,  53,  79, 104, 128, 150, 171, 190, 207, 222, 234, 243, 250, 255
};

#define Cos (Sin+15)

_main(ac, av)
char *av[];
{
    IOT  Iot;	    /*	timer request,	1 second internals  */
    SCR  Scr;
    WIN  *Win;
    PORT TPort;     /*	port for timer request		    */
    long TMask;
    long WMask;
    long error = 20;
    char notdone = 1;

    bzero(&Iot, sizeof(Iot));
    bzero(&TPort, sizeof(TPort));

    if (!(IntuitionBase = OpenLibrary("intuition.library", 0)))
	goto dieilib;
    if (!(GfxBase = OpenLibrary("graphics.library", 0)))
	goto dieglib;
    if (OpenDevice("timer.device", UNIT_VBLANK, &Iot, 0))
	goto diedev;
    if (!GetScreenData(&Scr, sizeof(Scr), WBENCHSCREEN, NULL))
	goto diewin;

    Nw.Width  = Params[WWIDTH];
    Nw.Height = Params[WHEIGHT];
    Nw.LeftEdge = Params[WPOSX];
    Nw.TopEdge	= Params[WPOSY];
    if (Nw.Width <= 0)
	Nw.Width += Scr.Width;
    if (Nw.Height <= 0)
	Nw.Height += Scr.Height;
    if (Nw.LeftEdge < 0)
	Nw.LeftEdge += Scr.Width - Nw.Width;
    if (Nw.TopEdge < 0)
	Nw.TopEdge  += Scr.Height- Nw.Height;
    if (Nw.LeftEdge < 0 || Nw.TopEdge < 0 || Nw.LeftEdge + Nw.Width > Scr.Width || Nw.TopEdge + Nw.Height > Scr.Height) {
	Nw.LeftEdge = Nw.TopEdge = 0;
	Nw.Width = 64;
	Nw.Height= 32;
    }

    if (!(Win = OpenWindow(&Nw)))
	goto diewin;

    ShowTitle(Win->WScreen, 0);

    TPort.mp_Node.ln_Type = NT_MSGPORT;
    TPort.mp_SigTask = FindTask(NULL);
    TPort.mp_SigBit = AllocSignal(-1);
    TPort.mp_Flags  = PA_SIGNAL;
    NewList(&TPort.mp_MsgList);

    Iot.tr_node.io_Message.mn_ReplyPort = &TPort;
    Iot.tr_node.io_Message.mn_Node.ln_Type = NT_MESSAGE;
    Iot.tr_node.io_Command = TR_ADDREQUEST;
    Iot.tr_time.tv_secs = 1;
    Iot.tr_time.tv_micro= 0;
    SendIO(&Iot);

    TMask = 1 << TPort.mp_SigBit;
    WMask = 1 << Win->UserPort->mp_SigBit;

    error = 0;
    while (notdone) {
	register long mask = Wait(TMask|WMask);
	if (mask & TMask) {
	    register short secs;
	    WaitIO(&Iot);
	    secs = UpdateWindow(Win);
	    Iot.tr_time.tv_secs = 65 - secs;
	    Iot.tr_time.tv_micro= 0;
	    Iot.tr_node.io_Message.mn_Node.ln_Type = NT_MESSAGE;
	    SendIO(&Iot);
	}
	if (mask & WMask) {
	    register IMESS *im;
	    while (im = GetMsg(Win->UserPort)) {
		switch(im->Class) {
		case CLOSEWINDOW:
		    notdone = 0;
		    break;
		case REFRESHWINDOW:
		    BeginRefresh(Win);
		    EndRefresh(Win);
		case ACTIVEWINDOW:
		case INACTIVEWINDOW:
		    UpdateWindow(Win);
		    break;
		}
		ReplyMsg(im);
	    }
	}
    }
    AbortIO(&Iot);
    WaitIO(&Iot);
    FreeSignal(TPort.mp_SigBit);

    CloseWindow(Win);
diewin:
    CloseDevice(&Iot);
diedev:
    CloseLibrary(GfxBase);
dieglib:
    CloseLibrary(IntuitionBase);
dieilib:
    _exit(error);
}

/*
 *  Update the window
 *
 *  (1) RectFill
 *  (2) minute and hour hands
 */

UpdateWindow(win)
register WIN *win;
{
    register RP *rp = win->RPort;
    DATESTAMP DS;
    short minutes;
    short hours;
    short radiusx, radiusy;
    short centerx, centery;

    DateStamp(&DS);
    minutes = DS.ds_Minute % 60;
    hours   = (DS.ds_Minute / 12) % 60;     /*  0 to 59 (partial hours indicator)   */
    radiusx = (win->Width - 2) >> 1;
    radiusy = (win->Height- 2) >> 1;
    centerx = 1 + radiusx;
    centery = 1 + radiusy;
    SetAPen(rp, 0);
    RectFill(rp, 0, 0, win->Width - 1, win->Height - 1);
    SetAPen(rp, 1);
    {
	register short i;
	for (i = 0; i < 60; i += 5) {
	    Move(rp, centerx + ((radiusx * Sin[i]) >> 8), centery + ((radiusy * -Cos[i]) >> 8));
	    Draw(rp, centerx + ((radiusx * Sin[i]) >> 8), centery + ((radiusy * -Cos[i]) >> 8));
	}
    }
    radiusx -= 2;
    radiusy -= 2;

    SetAPen(rp, 3);                 /*  minutes */
    Move(rp, centerx, centery);
    Draw(rp, centerx + ((radiusx * Sin[minutes]) >> 8), centery + ((radiusy * -Cos[minutes]) >> 8));
    SetAPen(rp, 2);                 /*  hours   */
    Move(rp, centerx, centery);
    Draw(rp, centerx + ((radiusx * Sin[hours]) / 384), centery + ((radiusy * -Cos[hours]) / 384));
    return(DS.ds_Tick / 50 % 60);  /*  seconds */
}

#asm

	    ; bzero(buf, bytes)
	    ;	    4sp   8sp
	    ;
	    ;	SIMPLE STUPID BZERO .. I don't even use DBF (don't want to
	    ;				have to think at this time of night)

_bzero:
	    moveq.l #0,D1
	    move.l  4(sp),A0
	    move.l  8(sp),D0
	    beq     bze
bz1	    move.b  D1,(A0)+
	    subq.l  #1,D0
	    bne     bz1
bze	    rts


#endasm

\Rogue\Monster\
else
  echo "will not over write clock.c"
fi
if [ `wc -c clock.c | awk '{printf $1}'` -ne 5293 ]
then
echo `wc -c clock.c | awk '{print "Got " $1 ", Expected " 5293}'`
fi
if `test ! -s config.c`
then
echo "writing config.c"
cat > config.c << '\Rogue\Monster\'

/*
 *  CONFIG.C
 *
 *  (C)Copyright 1988, Matthew Dillon, All Rights Reserved.
 *  Permission is granted to distribute for non-profit only.
 *
 *  config file1 file2 file3.... filen
 *
 *  Configure one or more executables.	The executables must conform to
 *  the Static User Modifiable Data Standard	    (SUMDS)
 *
 *  This program configures the static parameters of C programs which
 *  contain the appropriate structures.  Unknown parameters may be
 *  configured in raw hex if you know the format.
 */

#include <stdio.h>

#define arysize(array)  (sizeof(array)/sizeof(array[0]))

extern void *malloc();

typedef unsigned char	ubyte;
typedef unsigned short	uword;
typedef unsigned long	ulong;

char Show;
char XDebug;

main(ac, av)
char *av[];
{
    register short i;
    FILE *fi;
    long pos, len;

    puts("CONFIG V1.00, Matthew Dillon.");
    for (i = 1; i < ac; ++i) {
	fi = fopen(av[i], "r+");
	if (fi == NULL) {
	    printf("Could not open %s for reading\n", av[i]);
	    continue;
	}
	switch(FindData(fi, &pos, &len)) {
	case -2:
	    printf("%s contains unknown hunk #%ld\n", av[i], pos);
	    break;
	case -1:
	    printf("%s is not an executable\n", av[i]);
	    break;
	case  0:
	    printf("could not find configuration header in %s\n", av[i]);
	    break;
	case  1:
	    switch(Configure(fi, pos, len*4)) {
	    case -3:
		printf("Unable to malloc %ld bytes\n", len);
		break;
	    case -2:
		puts("Error reading data block or badly formatted data");
		break;
	    case -1:
		puts("No 'END ' entry found in header");
		break;
	    case 0:
		puts("No modifications made to file");
		break;
	    case 1:
		puts("File updated");
		break;
	    }
	    break;
	}
	fclose(fi);
    }
}

/*
 *  Search through the beginning of all HUNK_DATA hunks for 'STRT', 0
 *
 *  symb:   object modules only, not defined here.
 *	HUNK_OVERLAY cannot be handled yet.
 */

#define HUNK_UNIT	999	/*  N, Nlws of name	    */
#define HUNK_NAME	1000	/*  N, Nlws of name	    */
#define HUNK_CODE	1001	/*  N, Nlws of code	    */
#define HUNK_DATA	1002	/*  N, Nlws of data	    */
#define HUNK_BSS	1003	/*  N	(Nlws of bss)       */
#define HUNK_RELOC32	1004	/*  N, (N+1)lws, N ... 0    */
#define HUNK_RELOC16	1005	/*  N, (N+1)lws, N ... 0    */
#define HUNK_RELOC8	1006	/*  N, (N+1)lws, N ... 0    */
#define HUNK_EXT	1007	/*  N, symb,symb... 0	    */
#define HUNK_SYMBOL	1008	/*  N, symb,symb... 0	    */
#define HUNK_DEBUG	1009	/*  N, Nlws of debug	    */
#define HUNK_END	1010	/*  -			    */
#define HUNK_HEADER	1011	/*  N, Nlwsname, N.. 0, tabsize, F, L, F-L+1 sizes  */
#define HUNK_OVERLAY	1013	/*  tabsize, M+2,M+1 ... ovr data table 	    */
#define HUNK_BREAK	1014	/*  -			    */

FindData(fi, ppos, plen)
FILE *fi;
long *ppos;
long *plen;
{
    long type;
    long len;
    long pos;
    long ary[3];
    static long buf[256];

    if (fread(&type, 4, 1, fi) != 1 || type != HUNK_HEADER)
	return(-1);
    for (;;) {
	if (fread(&len, 4, 1, fi) != 1)
	    return(-1);
	if (len == 0)
	    break;
	fseek(fi, len*4, 1);
    }
    if (fread(ary, 4, 3, fi) != 3)
	return(-1);
    fseek(fi, 4*(ary[2] - ary[1] + 1), 1);

    while (fread(&type, 4, 1, fi) == 1) {
	len = 0;
	if (XDebug)
	    printf("type: %ld\n", type);
	switch(type) {
	case HUNK_CODE:
	    fread(&len, 4, 1, fi);
	    break;
	case HUNK_DATA:
	    fread(&len, 4, 1, fi);      /*  # of longwords  */
	    pos = ftell(fi);
	    if (len >= 4) {
		register short i;
		register short j = (len > arysize(buf)) ? arysize(buf) : len;
		register long *ptr;
		fread(buf, j, 4, fi);
		for (i = 0; i < j; ++i) {
		    ptr = buf + i;
		    if (ptr[0] == 'STRT' && ptr[1] == 0) {
			*ppos = pos+i*4;
			*plen = len - i;
			fseek(fi, *ppos, 0);
			return(1);
		    }
		    ptr = (long *)((short *)ptr + 1);
		    if (ptr[0] == 'STRT' && ptr[1] == 0) {
			*ppos = pos+i*4+2;
			*plen = len - i;
			fseek(fi, *ppos, 0);
			return(1);
		    }
		}
	    }
	    fseek(fi, pos, 0);
	    break;
	case HUNK_BSS:
	    fread(&len, 4, 1, fi);
	    len = 0;
	    break;
	case HUNK_RELOC32:
	case HUNK_RELOC16:
	case HUNK_RELOC8:
	    for (;;) {
		if (fread(&len, 4, 1, fi) != 1)
		    return(-1);
		if (len == 0)
		    break;
		++len;
		fseek(fi, len*4, 1);
	    }
	    break;
	case HUNK_SYMBOL:
	    for (;;) {
		if (fread(&len, 4, 1, fi) != 1)
		    return(-1);
		if (len == 0)
		    break;
		len = (len + 1) & 0x00FFFFFF;
		if (len <= 32)                  /*  reading is faster */
		    fread(buf, len, 4, fi);
		else				/*  very long name?   */
		    fseek(fi, len*4, 1);
	    }
	    break;
	case HUNK_DEBUG:
	    if (fread(&len, 4, 1, fi) != 1)
		return(-1);
	    break;
	case HUNK_END:
	    break;
	default:
	    *ppos = type;
	    return(-2);
	}
	fseek(fi, len*4, 1);
    }
    return(0);
}

Configure(fi, pos, len)
FILE *fi;
long pos;
long len;		/*  bytes   */
{
    register uword *buf = malloc(len+1);
    register long i;
    short j;
    long modified = 0;
    short numentries = 0;

    len >>= 1;		/*  Words   */

    if (buf == NULL)
	return(-3);
    fseek(fi, pos, 0);
    if (fread(buf, len, 2, fi) != 2)
	return(-2);
    for (i = 4; i < len;) {
	if (buf[i] == 'EN' && buf[i+1] == 'D ' && buf[i+2] == 0)
	    break;
	++numentries;
	i += 4 + ((buf[i+3]+1)>>1);
    }
    if (i > len)
	return(-2);
    if (i == len)
	return(-1);

    len = i;
    printf("%ld entries found\n\n", numentries);

    for (i = 4; i < len;) {
	register long data = (buf[i]<<16)|buf[i+1];
	long dlen = buf[i+3];
	uword *ptr = buf + i + 4;

	printf("----(%c%c%c%c,%2ld): ", data>>24, data>>16, data>>8, data, dlen);
	switch(data) {
	case 'NW  ':
	    puts("NEW WINDOW STRUCTURE");
	    modified += Edit_WINP(ptr, dlen);
	    break;
	case 'TEXT':
	    puts("TEXT");
	    modified += Edit_TEXT(ptr, dlen);
	    break;
	default:
	    puts("UNKNOWN STRUCTURE, HEX EDIT");
	    modified += Edit_Hex(ptr, dlen);
	    break;
	}
	i += 4 + ((dlen+1)>>1);
	puts("");
    }
    {
	long response = (modified) ? 0 : 'n';
	char ibuf[64];

	while (response != 'y' && response != 'n') {
	    printf("Write data back to file (y/n): ");
	    fflush(stdout);
	    if (gets(ibuf) == NULL)
		break;
	    response = ibuf[0] | 0x20;
	}
	if (response == 'y') {
	    fseek(fi, pos, 0);
	    fwrite(buf, len, 2, fi);
	    return(1);
	} else {
	    return(0);
	}
    }
}

Edit_WINP(buf, bytes)
char *buf;
short bytes;
{
    short i;
    short modified = 0;

    for (i = 0; i < bytes;) {
	switch(i) {
	case 0:     /*	LeftEdge    */
	    modified += strscanvalue(buf+i, 2, "LeftEdge", "(Negative = Relative to Screen Right)");
	    i += 2;
	    break;
	case 2:     /*	TopEdge     */
	    modified += strscanvalue(buf+i, 2, "TopEdge", "(Negative = Relative to Screen Bottom)");
	    i += 2;
	    break;
	case 4:     /*	Width	    */
	    modified += strscanvalue(buf+i, 2, "Width", "(0 = Full Width, < 0 = ScreenWidth - (-width)");
	    i += 2;
	    break;
	case 6:     /*	Height	*/
	    modified += strscanvalue(buf+i, 2, "Height", "(0 = Full Height, < 0 = ScreenHeight - (-height)");
	    i += 2;
	    break;
	case 8:     /*	DetailPen   */
	    modified += strscanvalue(buf+i, 1, "DetailPen", NULL);
	    ++i;
	    break;
	case 9:     /*	BlockPen    */
	    modified += strscanvalue(buf+i, 1, "BlockPen", NULL);
	    ++i;
	    break;
	case 10:    /*	IDCMP	    */
	    modified += strscanvalue(buf+i, 4, "IDCMP", NULL);
	    i += 4;
	    break;
	case 14:    /*	Flags	    */
	    modified += strscanvalue(buf+i, 4, "Flags", NULL);
	    i += 4;
	    break;
	default:
	    i = bytes;
	    break;
	}
    }
    return(modified);
}

/*
 *  Edit TEXT.	The buffer holds a 'type' string, \0, then space for a text
 *  string.
 */

Edit_TEXT(lb, len)
char *lb;
{
    register short i, slen;
    char buf[256];
    char *prompt = lb;

    while (*lb) {
	++lb;
	--len;
    }
    ++lb;
    --len;
    if (len <= 0) {
	puts("TEXT ERROR: UNTERMINATED BUFFER");
	return(0);
    }
loop:
    printf("%-15s (%ld chars max) (%15s) :", prompt, len - 1, lb);
    if (gets(buf) == NULL || buf[0] == 0)
	strcpy(buf, lb);
    if (strcmp(buf, lb) != 0) {
	slen = strlen(buf);
	if (slen > len - 1) {
	    printf("Maximum of %ld chars!", len - 1);
	    goto loop;
	}
	strcpy(lb, buf);
	return(1);
    }
    return(0);
}

Edit_Extended(i, size, lb)
short i;
char *lb;
{
    printf("    #%ld (Blind entry) ", i);
    return(scanvalue(lb, size));
}

Edit_Hex(lb, len)
char *lb;
long len;
{
    short modified = 0;
    short i;

    puts("    (Unknown Type, blind data entry)");
    for (i = 0; i < len; ++i) {
	printf("    Entry %2ld/%2ld ", i, len-1);
	modified += scanvalue(lb+i, 1);
    }
    return(modified);
}

strscanvalue(lb, size, prompt, desc)
char *lb;
char *prompt;
char *desc;
{
    if (desc)
	printf(" note: %s\n", desc);
    printf("%15s ", prompt);
    return(scanvalue(lb, size));
}

scanvalue(lb, size)
ubyte *lb;
{
    register long val;
    register char *str;
    short neg;
    char buf[128];

top:
    switch(size) {
    case 1:
	val = *lb;
	printf("(      $%02x     %3ld) :", val, (char)val);
	break;
    case 2:
	val = *(uword *)lb;
	printf("(    $%04x  %6ld) :", val, (short)val);
	break;
    case 4:
	val = *(long *)lb;
	printf("($%08lx %7ld) :", val, val);
	break;
    }
    fflush(stdout);

    neg = 1;
    if (gets(buf) && buf[0]) {
	val = 0;
	str = buf;
	if (*str == '-') {
	    neg = -1;
	    ++str;
	}
						    /*	hex */
	if (str[0] == '$' || (str[0] == '0' && (str[1]|0x20) == 'x')) {
	    if (*str++ != '$')
		++str;
	    for (; *str; ++str) {
		*str |= 0x20;
		if (*str >= '0' && *str <= '9') {
		    val = (val << 4) | (*str & 15);
		    continue;
		}
		if (*str >= 'a' && *str <= 'f') {
		    val = (val << 4) | (*str - 'a' + 10);
		    continue;
		}
		break;
	    }
	} else
	if (*str == '%') {                          /*  binary  */
	    for (++str; *str >= '0' && *str <= '1'; ++str)
		val = (val << 1) | (*str & 1);
	} else if (*str) {                          /*  decimal */
	    for (; *str >= '0' && *str <= '9'; ++str)
		val = val * 10 + *str - '0';
	}
	if (*str) {
	    printf("Illegal char '%c', try again\n    ? ", *str);
	    goto top;
	}
    }
    if (neg < 0)
	val = -val;
    switch(size) {
    case 1:
	if ((ubyte)val != *lb) {
	    *lb = val;
	    return(1);
	}
	break;
    case 2:
	if ((uword)val != *(uword *)lb) {
	    *(uword *)lb = val;
	    return(1);
	}
	break;
    case 4:
	if (val != *(long *)lb) {
	    *(long *)lb = val;
	    return(1);
	}
	break;
    }
    return(0);
}


\Rogue\Monster\
else
  echo "will not over write config.c"
fi
if [ `wc -c config.c | awk '{printf $1}'` -ne 10403 ]
then
echo `wc -c config.c | awk '{print "Got " $1 ", Expected " 10403}'`
fi
if `test ! -s deemu.doc`
then
echo "writing deemu.doc"
cat > deemu.doc << '\Rogue\Monster\'

    PRELIMINARY PRELIMINARY PRELIMINARY PRELIMINARY PRELIMINARY PRELIMINARY

				   DEEMU
		   USER MODIFIABLE EXECUTABLE EMBEDDED DATA

				Matthew Dillon		    (SNAIL)
				891 Regal Rd.
			      Berkeley, Ca. 94708
				     USA

			    dillon@ucbvax.Berkeley.EDU	    (APRANET)
			    ...!ihnp4!ucbvax!dillon	    (USENET)


				 30 July 1988

    Includes:
	CONFIG	    -V1.00 of my config program
	CLOCK	    -V1.00 of my really-small non-intrusive clock program
		     (which uses DEEMU for window placement)
	DEEMU.DOC   -Docs on DEEMU
	SCRIPT.TXT  -Example script of configuring the clock executable
	MAKEFILE
	CONFIG.C
	CLOCK.C


    This document describes DEEMU, which allows end users to modify various
parameters of a program by directly modifying the executable in question
through standard programs.  This is what happens:

    (1) The programmer embedds a DEEMU structure at the very beginning
	of his initialized data hunk.  Since the C startup module usually
	contains only BSS declarations, a simple C declaration at the
	beginning of your first module will suffice.

	    ** THIS HAS BEEN TESTED WITH AZTEC C.  COULD SOMEBODY PLEASE
	       TEST IT WITH LATTICE?


    (2) The user comes along and can modify various DEEMU entries in the
	executable to customize the program to his needs.

    Thus, the programmer need not put any extra code into his program to
    allow the user to modify parameters from the program.  Additionaly,
    the script or initialization files that would otherwise have been
    required are now nonexistant, making the user's enviroment that much
    more streamlined and clean.

				STRUCTURE FORMAT

    The programm should put the following declaration at the beginning of
    his first C (or assembly, or whatever) module:

    long Deemu[] = {
	'STRT', 0,
	(other DEEMU entries)
	'END ', 0
    };

    Note that the name of the array is arbitrary.  This is an array of
    longwords consisting of a START entry, intermediate entries, and an
    END entry.	The format of each entry is:

	long type;		-longword describing the structure type
	uword flags;		-RESERVED, SET TO 0
	uword bytes;		-# of bytes
	char data[bytes] + PAD0 -The structure data, padded to a WORD BOUNDRY

    The CONFIG program (yours, mine, anybody's) will check for 'STRT', 0 at
    the beginning of every DATA HUNK in the program until it finds one,
    then verifies the entire structure all the way to the 'END ', 0 is
    correct, then prompts the user with the various structures allowing him
    to modify them.

    The user gets very nice descriptive prompts for structure types that
    CONFIG knows about.  The user can still modify structures CONFIG does
    not know about, but will not get the descriptive prompts.

    ALL STRUCTURES ARE WORD ALIGNED.  That is, if a structure is not word
    aligned it should be padded with a 0 at the end to make it so.  Thus,
    EVEN WHEN THE #BYTES IS NOT WORD SIZED, the #bytes is extended to a
    longword boundry to find the next DEEMU entry: bytes = (bytes+1)&~1

    Fields within the structure need not be word aligned.  This allows you
    to embed standard Amiga structures.


			    STANDARD STRUCTURES

    Since this is the first release, only a couple custom structures have
    been defined so far.

    NOTE #1:	The entire structure need not be entered.  It is perfectly
		acceptable to cut out the tail ends of structures that you
		do not want to bother with.  For instance, you might have
		the following for a NewWindow structure:

		'NW  ', 10, (short leftedge, topedge, width, height,
			     ubyte detpen, blockpen)

					OR

		'NW  ',  9, (short leftedge, topedge, width, height,
			     ubyte detpen)
			+ one byte padding.

		In this case, the CONFIG program only prompts for those
		fields that exist.

    NOTE #2:	Structures may be longer than the 'standard' size.  For
		future compatibility.  The DEEMU program will prompt for
		these fields but will not be able to give descriptions of
		them to the user.

    STRT {	The START structure, must be the first DEEMU structure.
	long type = 'STRT';
	word flags= 0;
	word size = 0;
    };

    END  {	The END structure, must be the last DEEMU structure.
	long type = 'END ';
	word flags= 0;
	word size = 0;
    };

    TEXT {
	long type = 'TEXT';
	word flags= 0;
	word size = N
	char prompt[A];     N = A + B.	This includes the \0 after each string
	char contents[B];
    };


	The contents string need not be exactly B long, but may terminate
	with a \0 before it reaches the maximum allowed length.  In this
	case the extra bytes at the end may be garbage.

	The contents string is thus limited to N-strlen(A)-2 character bytes,
	not including the \0.

	THE PROMPT STRING MAY NOT BE MODIFIED BY THE USER.  SEE CLOCK.C FOR
	EXAMPLE USAGE OF 'TEXT'.

    NOP {
	long type = 'NOP ';
	word flags = 0;
	word size = N;
    };

	No operation.  The data, if any, can be modified, but will probably
	not be used by the program.

    DATA {
	long type = 'DATA';
	word flags = 0;
	word size = N;
    };

	Program dependant data.  Contents and ordering are defined by the
	program.  The documentation for the program will tell you which
	word means what (the CONFIG program prompts for the data in WORD
	sized chunks).

    TRCT {
	long type = 'TRCT';
	word flags = 0;
	word size = N;
	short pri;
	short cpu;
	long chipmem;
	long generalmem;
    };

	Task/Resource Control.	Dynamic specification of the task priority.
	pri:

	    Specify the task priority.	If bit 8 ($100) is set, an absolute
	    priority is given in the low byte.	If bit 8 is clear, a
	    priority relative to the startup-priority is given. (i.e. a
	    1 means, 1 over my priority when I started up).

	cpu:	0-FFFF

	    Specify how much of a CPU hog this program can be, in
	    percentage (FFFF=100%).  Most programs ignore the field.

	chipmem/generalmem:

	    some value representing the maximum #bytes of memory you want
	    this program to use.  These fields do not apply to most
	    programs but could come in useful in, say, a disk cache program
	    or something like that.

	    NOTE that there is no specification for 'fast' memory. chipmem
	    refers to specific CHIP allocations the program makes, and
	    generalmem refers to non-specific allocations the program might
	    make.


    NW {
	long type = 'NW  ';
	word flags= 0;
	word size = N;	    (usually 8 or 10 bytes.. partial NewWindow structure)
	(A partial NewWindow structure)
    };

    struct NewWindow NW;
	'NW  ', (0-sizeof(NewWindow))L, <data>

	The NewWindow structure.  However, the contents of LeftEdge,
	TopEdge, Width, and Height, are allowed to be NEGATIVE.

	TopEdge  < 0	    : specify right edge relative to screen right
	LeftEdge < 0	    : specify bottom edge relative to screen bottom
	Width	<= 0	    : specify width relative to screen Width
	Height	<= 0	    : specify height relative to screen height

	The programmer should do a sanity check of parameters after
	adjusting them properly.  The GetScreenData() should be used to
	retrieve screen information.

	For instance, specifying -1, -1 places the window as follows:

	ActualLeftEdge = Screen.Width - Nw.Width - 1;
	ActualTopEdge  = Screen.Height- Nw.Height- 1;

	SPECIFYING 0 FOR THE Nw.Width and/or Nw.Height should cause the
	window to be openned to its fullest size in that dimension.

	IF YOU USE THE 'NW  ' DEEMU STRUCTURE, YOU MUST BE ABLE TO HANDLE
	THESE SPECIAL CONDITION!


				EXAMPLE

short Deemu[] = {
    'ST','RT', 0, 0,
    'NW','  ', 0, 9, -16, -8, 64, 32, -1, 0,
    'TE','XT', 0, 16, 'HI', '\0T', 'ES', 'T.', 0, 0, 0, 0,
    'EN','ND', 0, 0
};


    We have a STRT structure of 0 length (it MUST be 0 length).

    We then have the first 9 bytes of a NewWindow structure:
	short LeftEdge = -16;
	short TopEdge  = -8;
	short Width    = 64;
	short Height   = 32;
	char  DetailPen= -1;

	NOTE: The NW structure is padded to a word boundry with a 0, this
	0 is *NOT*, I repeat *NOT* the char BlockPen; specification, which
	normally comes after DetailPen.

    And, before the END we have a 'TEXT' entry with the prompt "HI" and
    the contents "TEST".  The user is allowed to modify the contents and
    up to 12 characters (not including the \0) may be used.  For TEXT, no
    more than 128 bytes total should be allocated.

				  LIMITATIONS

    Currently the config program only checks the first 32 longwords of
    each DATA hunk.  This is done mainly to prevent the possibility that
    non DEEMU data might look like a DEEMU header, but also taking into
    account that the linker might put some amount of data in the hunk
    before it gets to the DEEMU data.

    The Aztec linker (don't know about the others) sticks stuff before
    your own (jump tables when using the small code model).  If your
    program is big enough, the DEEMU data might be out of reach.  The
    solution is to place the DEEMU data in its own hunk, which can be
    accomplished with a little more work and various linker options.

    Future CONFIG programs will fix the problem.

\Rogue\Monster\
else
  echo "will not over write deemu.doc"
fi
if [ `wc -c deemu.doc | awk '{printf $1}'` -ne 9048 ]
then
echo `wc -c deemu.doc | awk '{print "Got " $1 ", Expected " 9048}'`
fi
if `test ! -s script.txt`
then
echo "writing script.txt"
cat > script.txt << '\Rogue\Monster\'


	    Example session with Config, configuring CLOCK.

	    Please refer to CLOCK.C to see how the DEEMU structure
	    is embedded and how the clock program handles the data.

    This particular script file shows placing the analog clock as a
    32x16 window in the upper right hand corner of the screen.

				    -Matt


1> config clock
CONFIG V1.00, Matthew Dillon
2 entries found

----(NW  , 8): NEW WINDOW STRUCTURE
 note: (Negative = Relative to Screen Right)
       LeftEdge (    $0000      0) : -1
 note: (Negative = Relative to Screen Bottom)
       TopEdge	(    $0000      0) : 10
 note: (0 = Full Width, < 0 = ScreenWidth - (-width)
       Width	(    $0020     32) :
 note: (0 = Full Height,< 0 = ScreenHeight- (-height)
       Height	(    $0010     16) :

----(TEXT,16): TEXT
HI		(12 chars max) (          TEST.) : IWasHere

Write data back to file (y/n): y
File updated
1>


\Rogue\Monster\
else
  echo "will not over write script.txt"
fi
if [ `wc -c script.txt | awk '{printf $1}'` -ne 891 ]
then
echo `wc -c script.txt | awk '{print "Got " $1 ", Expected " 891}'`
fi
echo "Finished archive 1 of 1"
# if you want to concatenate archives, remove anything after this line
exit
-- 
Bob Page, U of Lowell CS Dept.  page@swan.ulowell.edu  ulowell!page
Have five nice days.