/*
	Startup-code for BareED
	Written 10 & 11/99 by Jörg van de Loo, Hövel 15, 47559 Kranenburg, FRG.

	This  startup-code  is  freeware  and  it's especially written in mind the
	MaxonC++ compiler V4 (useful for C and ANSI-C modes only).

	CLI parser 'borrowed' from AZTEC-C package (modified).

	This  startup  codes  sets  up  argc, argv with the commandline parameters
	given   through  the  CLI  and  with  all  selected  files  given  through
	Workbench,  e.g. double click on project icon or shift select. Additional,
	it ensures the present of a 68020 processor and Kickstart 2.0 (beta).

	No  stdin,  stdout, stderr terminals will be set up since they are useless
	for BareED.

	SysBase  and  ExecBase  are  only  present  within this startup-code; this
	ensures  that the additional code of BareED doesn't refer to the variables
	set  up  by  the  startup-code.  This  is due to the fact that my compiler
	would  otherwise  address  them  as 32 bit addresses, that will cause each
	time  a  reloc32  hunk  entry.  What  I  do  is  to refer to _DOS_Base and
	_Exec_Base  once,  to set up DOSBase and SysBase in the additional code of
	BareED,  so  that  my compiler will them address from now on within BareED
	indirect to the processor register a4.

	When  you  compile  this  startup-code  ensure  that  no  68020  or higher
	instructions  are  used  before the processor check is executed, otherwise
	on a plain 68000 processor this startup-code will fail with a GURU.

	This  startup-code  checks  for  the amount of free stack, too. Since this
	startup-code  uses  very  less stack (56 bytes when running and only up to
	328  bytes  while  setting up variables) it should be enought when using a
	stack  size  of only 4096 bytes for the loadfile of BareED, even under 3rd
	party  graphic  emulation  systems  -  which  in fact do require much more
	stack  than the native Amiga OS 3 graphic system (up to 3Kb when BareED is
	running).

	One  goal  of  this  startup-code is, that it gains at lot of stack (up to
	1.1  Kb)  compared  to  the original startup-code that comes along with my
	compiler.
	Currently  it needs a given stack size of 4052 bytes when started off a CLI
	surround and 4046 bytes when started off the Workbench.

	11/99  -  modified  this  startup-code  to run-back when started off a CLI
	window  or  called  with  RunCommand()  or  within  a  batch  script of an
	application.

	If  you  are able to adapt this startup-code to your compiler please do me
	a  favour and return the adapted startup-code for free to me, in this case
	e-mail Marc Berson at:
		BersonM@aol.com
	and state clearly that the message is for me and concerned BareED.

	NOTE: startup.c/BareED.c  can  be only compiled under GNU-C in large mode!
		  This is due to a bug in GNU-C; spilled registers.
		  I  used  the  GNU-Compiler  that  can  be  found on Geek Gadgets II.
		  Thanks to Mr. Fish for this compilation.
*/


#include <exec/execbase.h>
#include <exec/libraries.h>
#include <exec/memory.h>

#include <dos/dosextens.h>
#include <dos/dostags.h>

#include <utility/tagitem.h>

#include <clib/exec_protos.h>
#include <clib/dos_protos.h>

#ifdef __GNUC__
#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>
#else
#include <pragma/exec_lib.h>
#include <pragma/dos_lib.h>
#endif

#include <dos/dosextens.h>

#include <workbench/startup.h>

struct ExecBase   *_Exec_Base;
struct DosLibrary *_DOS_Base;
struct Message	  *WBenchMsg;

#ifdef __MAXON__
  #ifndef __cplusplus
	static struct ExecBase	 *SysBase;	// Allow only to appear within startup.c
	static struct DosLibrary *DOSBase;	// Allow only to appear within startup.c
  #else
	extern struct ExecBase	 *SysBase;
	extern struct DosLibrary *DOSBase;
  #endif
#endif

#ifndef __MAXON__
	extern struct ExecBase	 *SysBase;
	extern struct DOSLibrary *DOSBase;
#endif


static BPTR  _prg_dir;				// Remembered start dir
static unsigned int    _argc,  _arg_len;
static unsigned char **_argv, *_arg_lin;

#ifdef __MAXON__
	extern "C" void GetBaseReg( void);
	#define geta4() GetBaseReg()
#else
	extern geta4();
#endif

struct LaunchStuff
{
	int		nextHunk;
	int		hunkSize;
	char	name[128];
	int		stack;
	int		pri;
	int		dummy;		// To make this table divisible by 8
};

struct LaunchStuff *LS = 0;
struct Process *MasterTask;

unsigned char DOSName[]				= "dos.library";
static unsigned char Console[]		= "CON:0/0/320/80/ERROR REPORT/AUTO/CLOSE/WAIT";
static unsigned char NotEnoughtMem[]= "ERROR: Not enought memory!\n";
static unsigned char NotEnoughtStack[]= "ERROR: Stack < 4096 bytes!\n";
static unsigned char WrongCPU[]		= "ERROR: CPU < 68020!\n";
static unsigned char WrongLIB[]		= "ERROR: Library versions < 36!\n";
static unsigned char WrongSYS[]		= "ERROR: System version < 36!\n";
static unsigned char WrongError[]	= "ERROR: Non describt fault!\n";	// ????


void _main_jmp( unsigned long parlen, unsigned char *parameter);
int main( unsigned long argc, unsigned char **argv);


// ################################################################

#ifdef __GNUC__
void INIT_0_run_me_at_first_place( unsigned long parlen __asm("d0"), unsigned char *parameter __asm("a0") )
#else
void INIT_0_run_me_at_first_place( register __d0 unsigned long parlen, register __a0 unsigned char *parameter)
#endif
{
	#ifndef __GNUC__	// Since GNU-C does not allow to compile BareED as base relative code
	geta4();
	#endif

	_Exec_Base = SysBase = *((struct ExecBase **) 4);	// From memory location 4 to exec library
	if (_Exec_Base -> LibNode . lib_Version <= 32)		// Kickstart lower than 33 (OS1.2) = die
		return;											// -> perhaps with a GURU (when started from WB)

	 _main_jmp( parlen, parameter);	// We never return! We jump to _main_jmp because if
									// we would do here the following stuff, d0 and a0
									// would be ever pre-reserved, which blows up the
									// objectfile un-necessary
}

// ################################################################

/* Following code is to avoid reloc32 entries - since they would otherwise
   (when linked with the objectfiles) called via "jsr" and not via "bsr" */
   
static unsigned int strlenNR( register const unsigned char *str)
{
	register unsigned int i = 0;

	while (*str++)
		i++;
	return i;
}

static void strncpyNR( register unsigned char *d, register const unsigned char *s, register unsigned int i)
{
	while (i)
	{
		*d++ = *s++;
		i--;
	}
	*d = 0;
}

static void strcpyNR( register unsigned char *d, register const unsigned char *s)
{
	while (*s)
		*d++ = *s++;
	*d = 0;
}

/* static void strcatNR( register unsigned char *d, register const unsigned char *s)
{
	while (*d)
		*d++;
	
	while( *s)
		*d++ = *s++;
	*d = 0;
} */

static void strncatNR( register unsigned char *d, register const unsigned char *s, register unsigned int i)
{
	while (*d)
		*d++;
	
	while( i)
	{
		*d++ = *s++;
		i--;
	}
	*d = 0;
}

// ################################################################

/* Function required by GNU-C linker, not necessary anymore - since
   all work already done by this startup-code! */

#ifdef __GNUC__
void __main( int argc, char **argv)
{
 return;
}
#endif

// ################################################################

/* Taken without permission from the AZTEC-package, which in fact
   can be found in books and magazins, too */

static void _cli_parse(struct Process *pp, unsigned long alen, register unsigned char *aptr)
{
	register unsigned char *cp;
	register struct CommandLineInterface *cli;
	register unsigned char c;

	cli = (struct CommandLineInterface *) ( (unsigned long) pp->pr_CLI << 2);
	cp = (unsigned char *) ( (unsigned long) cli->cli_CommandName << 2);

	_arg_len = (unsigned char) cp[0] + alen + 2;

	if ( (_arg_lin = (unsigned char *) AllocMem( ((_arg_len + 7) & -8), (0x10000) ) ) == 0)
		return;

	c = cp[0];
	strncpyNR( _arg_lin, cp + 1, c);
	_arg_lin[c] = ' ';
	_arg_lin[c + 1] = 0;
	strncatNR( _arg_lin, aptr, alen);
	_arg_lin[c] = 0;

	for (_argc = 1, aptr = cp = _arg_lin + c + 1; ; _argc++)
	{
		while ( (c = *cp) == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\n')
			cp++;
		if (*cp < ' ')
			break;
		if (*cp == '"')
		{
			cp++;
			while ( (c = *cp++) )
			{
				*aptr++ = c;
				if (c == '"')
				{
					if (*cp == '"')
					{
						cp++;
					}
					else
					{
						aptr[-1] = 0;
						break;
					}
				}
			}
		}
		else
		{
			while ( (c = *cp++) && c != ' ' && c != '\t' && c != '\f' && c != '\r' && c != '\n')
				*aptr++ = c;
			*aptr++ = 0;
		}
		if (c == 0)
			--cp;
	}

	*aptr = 0;
	if ( (_argv = (unsigned char **) AllocMem( (((_argc + 1) * 4 + 7) & -8 ), (0x10000) ) ) == 0 )
	{
		_argc = 0;
		return;
	}

	for (c=0, cp=_arg_lin; c < _argc; c++)
	{
		_argv[c] = cp;
		cp += strlenNR( cp) + 1;
	}

	_argv[c] = 0;
}

// ################################################################

/* Allows several Workbench passed in arguments for BareED */

static void _wb_parse( struct WBStartup *msg)
{
	int numargs, len, i;
	char str[256], *curr;	// Normally I'm against arrays on stack, but since we're
							// at front of a loadfile it doesn't matter (because there
							// is enough free stack available) and we can here avoid
							// memory fragmentation through AllocMem()

	numargs = msg -> sm_NumArgs; 
	_arg_len = (numargs + 2) * 4;	// Number of arguments into amount bytes (plus 2 longwords)


	/* Get length in bytes of all arguments including zero bytes and drawer terminators */
	i = 0;
	while (i < numargs)
	{
		if (msg -> sm_ArgList[i] . wa_Lock)
			NameFromLock( msg -> sm_ArgList[i] . wa_Lock, &str[0], 255);
		_arg_len += strlenNR( &str[0]);
		_arg_len += 2;	// For zero byte and perhaps for drawer terminator "/" !
		_arg_len += strlenNR( msg -> sm_ArgList[i] . wa_Name);
		i++;
	}

	/* Allocate needed space for strings */
	_arg_lin = (unsigned char *) AllocMem( ((_arg_len + 7) & -8), (0x10000) );
	if ( !_arg_lin)
		return;

	_argc = numargs;
	_argv = (unsigned char **) _arg_lin;
	curr = _arg_lin + ((numargs + 2) * 4);

	/* Create and copy drawer and filenames into allocated memory, behind the argument pointers! */
	i = 0;
	while (i < numargs)
	{
		_argv[i] = curr;
		if (msg -> sm_ArgList[i] . wa_Lock)
		{
			NameFromLock( msg -> sm_ArgList[i] . wa_Lock, &str[0], 255);
			strcpyNR( curr, &str[0]);
			len = strlenNR( curr);

			if ( curr[ len - 1] != ':')
			{
				curr[len] = '/';
				len ++;
			}
		}
		else
		{
			len = 0;
		}

		strcpyNR( curr + len, msg -> sm_ArgList[i] . wa_Name);
		len = strlenNR( curr);

		curr += len + 1;	// Behind zero byte
		i++;				// Next arg
	}

	_prg_dir = CurrentDir( msg -> sm_ArgList[0] . wa_Lock);		// Set up "progdir:"
}


// ################################################################

/* Print error code down to a console window, if there isn't one yet,
   open one and give the message. */

static void GiveFault( int error)
{
	BPTR	 newStdOut;
	unsigned char *errorTxt;


	if (_DOS_Base)		// DOSBase set up?
	{
		errorTxt = 0;

		if (_Exec_Base -> LibNode . lib_Version <= 35)	// OS 2.0
			Console[27] = 0;	// No, OS 1.x, so remove AUTO/CLOSE/WAIT from console description

		if (WBenchMsg)			// If WB-start, open console window
		{
			newStdOut = Open( Console, MODE_OLDFILE);
		}
		else
		{
			newStdOut = Output();
		}

		if (newStdOut)
		{
			if (error == 236)
				errorTxt = WrongSYS;
			if (error == 105)
				errorTxt = WrongCPU;
			if (error == 122)
				errorTxt = WrongLIB;
			if (error == 217)
				errorTxt = NotEnoughtStack;
			if (error == 103)
				errorTxt = NotEnoughtMem;
			if (errorTxt == 0)
				errorTxt = WrongError;	// ????

			Write( newStdOut, errorTxt, strlenNR( errorTxt) );

			if (WBenchMsg && _Exec_Base -> LibNode . lib_Version <= 35)	// If WB-start and OS 1.x
			{
				Delay( 5*60);		// Wait a while
				Close( newStdOut);	// Close console
			}
		}
	}
}

// ################################################################

/* Un-load startup-code and additional program code/data and bss.
   Free also the table we used, but restore the segment of our
   program first!
   This is only called if we are in 'RunBack' mode. */

static void LaunchExit( void)
{
	unsigned int *adr;

	adr = (unsigned int *) &(INIT_0_run_me_at_first_place);
	adr --;
	*adr = LS -> nextHunk;	// Restore the link to the additional hunks
	adr --;
	*adr = LS -> hunkSize;	// Restore first hunk's size (was set zero)

	FreeMem( LS, sizeof( struct LaunchStuff));

	Forbid();	// Must be called because we may not removed from memory since additional code must be run first!
	adr += 1;	// Point back to segment entry for DOS
	UnLoadSeg( (BPTR) ((unsigned long) adr/4) );
}

// ################################################################

/* Code to get program's return address and to call it
   That's the 'additional code that must be run first' */

static const unsigned short _finally_code[] =
{
 0x2400,				// move.l	D0,D2					Save error code
 0x93C9,				// suba.l	A1,A1
 0x2C78,0x0004,			// movea.l	(4).w,A6
 0x4EAE,0xFEDA,			// jsr		_LVOFindTask(A6)		Own Task
 0x2040,				// movea.l	D0,A0
 0x2068,0x00B0,			// movea.l	pr_ReturnAddr(A0),A0	Get system's exit address for us
 0x4FE8,0xFFFC,			// lea		-4(A0),sp				Restore initial stack
 0x2002,				// move.l	D2,D0					Error code to D0
 0x4E75					// rts								Back to system
};

// ################################################################

/* Since  my compiler doesn't allow to overload a function (stub), I use
   a different name: exitNR ! */

void exitNR( int error)
{
	#ifdef __GNUC__
	void (* _finally)( int err __asm("d0") );
	#else
	void (* _finally)( register __d0 int err);
	#endif

	geta4();
	(void *) _finally = (void *) _finally_code;

	if (error)
		GiveFault( error);

	((struct Process *) _Exec_Base ->ThisTask) -> pr_Result2 = error;

	if (WBenchMsg)
	{
		if (_arg_lin)
		{
			FreeMem( _arg_lin, ((_arg_len + 7) & -8) );
			if (_prg_dir)	// If it is zero (SYS:) ignore
				CurrentDir( _prg_dir);		// Lock of basic directory
		}
		Forbid();
		ReplyMsg( WBenchMsg);
	}
	else
	{
		if (_arg_lin)
		{
			FreeMem(_argv, (((_argc + 1) * 4 + 7) & -8) );
			FreeMem(_arg_lin, ((_arg_len + 7) & -8) );
		}
	}

	if (_DOS_Base)
		CloseLibrary( (struct Library *) _DOS_Base);

	// Check if our application did a detach from a CLI
	if (LS)
		LaunchExit();

	_finally( error);
}

// ################################################################

/* This function does not return to the caller ! */

static void CallMainPRG( void)
{
	exitNR( main( _argc, _argv));
}

// ################################################################

/* If we are in 'RunBack' mode we call the main() function. The code
   below is executed as first part of the new created process. */

static void LaunchMain( void)
{
	geta4();
	Signal( (struct Task *) MasterTask, (0x10000));
	CallMainPRG();
}

// ################################################################

/* Set up necessary thing to provide an auto-detach (RunBack mode).
   This is a little tricky one which cares about the priority and
   stack size of the original process which are the settings for
   the new created.
*/

static void LaunchStartup( void)
{
	struct MsgPort *msgp;
	struct Process *proc;
	unsigned int *adr;
	struct TagItem tag[5];
	struct CommandLineInterface *cli;
	unsigned char *cp;


	MasterTask = ((struct Process *) _Exec_Base -> ThisTask);
	LS = (struct LaunchStuff *) AllocMem( sizeof( struct LaunchStuff), MEMF_CLEAR|MEMF_PUBLIC);
	if (!LS)			// No table?
		CallMainPRG();	// We never return!

	cli = (struct CommandLineInterface *) ( (unsigned long) MasterTask->pr_CLI << 2);
	cp = (unsigned char *) ( (unsigned long) cli->cli_CommandName << 2);

	// Duplicate name: slave task's name equal to master's one
	strncpyNR( LS->name, cp + 1, cp[0]);
	// Duplicate priority
	LS->pri   = MasterTask -> pr_Task . tc_Node . ln_Pri;
	// Duplicate stack size
	LS->stack = (int) MasterTask->pr_Task.tc_SPUpper - (int) MasterTask->pr_Task.tc_SPLower;

	// Settings for task to create
	tag[0].ti_Tag = NP_Entry;
	tag[0].ti_Data = (ULONG) &(LaunchMain);
	tag[1].ti_Tag = NP_Name;
	tag[1].ti_Data = (ULONG) LS->name;
	tag[2].ti_Tag = NP_StackSize;
	tag[2].ti_Data = LS->stack;
	tag[3].ti_Tag = NP_Priority;
	tag[3].ti_Data = LS->pri;
	tag[4].ti_Tag = TAG_DONE;

/* An AmigaDOS 'Hunk' looks like this (in memory)
	-4 int  HunkSize
	 0 BPTR NextHunk	<- The segment starts here (as BPTR), see LoadSeg(), UnLoadSeg(), etc.
	+4 Code/Data/BSS
*/

	// Create slave task which becomes master task
	msgp = (struct MsgPort *) CreateNewProc( tag);
	if (!msgp)			// Failed to create task?
	{
		FreeMem( LS, sizeof( struct LaunchStuff));
		LS = 0;
		CallMainPRG();	// We never return!
	}

	// From message port of process (task) to process itself (slave)
	proc = (struct Process *) ((ULONG) msgp - sizeof( struct Task));
	Wait( (0x10000));		// Wait for slave task to signal this bit so that we can quit savely

	// First instruction of this startup-code is at location:
	adr = (unsigned int *) &(INIT_0_run_me_at_first_place);

	// Point to BPTR next hunk (seglist start)
	adr --;
	// Remember the hunk address 
	LS -> nextHunk = *adr;
	// Break the hunk list of this loadfile
	*adr = 0;

	// Point to size of 'this' hunk (first hunk of loadfile)
	adr --;
	// Remember hunk size (normally code size, it's the HUNK_CODE)
	LS -> hunkSize = *adr;
	// This hunk size forced to zero 
	*adr = 0;

	/* Why I did the things above?
		Remember ever that 2 tasks are running which share the same memory and instructions!

 		If  we  would  now  quit the first by the system (user) created task, the memory and
		therewith  the  instructions  are gone, too. Thus a crash isn't far. What I do is to
		tell  AmigaDOS that the first created process has got only a single hunk with a size
		of  zero  (ok.,  I know I'm a liar) so that AmigaDOS doesn't frees anything when the
		first   created  task  quits.  The  segment  (hunk-list)  is  restored  by  function
		LaunchExit()  and  the  memory  used by it is removed by a call to AmigaDOS function
		UnLoadSeg().  Since  UnLoadSeg()  instantly  gives  the  memory back to the system I
		forbid  task  switching and therwith memory allocation while the 2nd created task is
		staying  alive.  If it dies, the task switching is enabled automatically through the
		system.
	*/
}

// ################################################################

void _main_jmp( unsigned long parlen, unsigned char *parameter)
{
	int error;
	struct Process *proc;


	error = 0;
	proc = ((struct Process *) _Exec_Base -> ThisTask);

	if (proc -> pr_CLI)
	{
		WBenchMsg = 0;
	}
	 else
	{
		WaitPort( &proc -> pr_MsgPort);
	 	WBenchMsg = GetMsg( &proc -> pr_MsgPort);
	}

	if (_Exec_Base -> LibNode . lib_Version <= 35)		// Lower Kickstart 2.0 (beta)?
		error = 236;

	_DOS_Base = DOSBase = (struct DosLibrary *) OpenLibrary( DOSName, 33);
	if ( !_DOS_Base || _DOS_Base -> dl_lib . lib_Version <= 35)
		error = 122;

	// If we have a soft-kicked 2.0 ROM for 1.x machines we raise at least an Exec error (236)
	if (error)
		exitNR( error);

	if ( !(_Exec_Base -> AttnFlags & AFF_68020))		// At least a 68020 processor?
		exitNR( 105);


	// Do we have at least 4000 bytes of stack available? - NOTE: the variable proc is on
	// the top of the stack so using this address is the current upper bound of the stack!
	if ( (((int) proc->pr_Task.tc_SPUpper - (int) proc->pr_Task.tc_SPLower) - ( (int) proc->pr_Task.tc_SPUpper - (int) &proc) ) < 4000 )
		exitNR( 217);

	if (WBenchMsg)
	{
		_wb_parse( (struct WBStartup *) WBenchMsg);
		if ( !_arg_lin)
			exitNR( 103);	// Fail: no mem
	}
	else
	{
		_cli_parse( proc, parlen, parameter);
		if ( !_arg_lin)
			exitNR( 103);
	}

	if (WBenchMsg)
	{
		CallMainPRG();
	}
			else
	{
		LaunchStartup();	// Try detach from CLI, if it fails, go on normal
	}
}

// ################################################################
