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

	06/00 bug fixing, revising and adapting to GNU-C by Gunther Nikl.
	Thanks, Gunther.

	- I (Jörg) thought that this startup-code was nearly perfect; Gunther took
	  me all my illusions. Here his statements:

(1)		The  technique  used  to  "runback" is working but the wrong approach.
		There  is  a  better way than using "SegList splitting" to achieve the
		same by using cli_Module.
(2)		The lock handling in WB mode is IMHO incorrect.....
(3)		....replaced  most #ifdefs with appropriate MACROS....to make the code
		more readable and maintainable.

	-  What,  Gunther,  would  you  tell me when I offer you for free BareED's
	   full  developer  package,  inclusive BareED's source code.... do I need
	   then a psychiatrist?


	Anyhow,
	this  startup-code is freeware (written by J.v.d.Loo and Gunther Nikl) and
	it's  especially  written  in  mind the MaxonC++ compiler V4 and the GNU-C
	compiler V2.95.# (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 (MaxonC++, Jörg).
	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: According  to  Gunther,  GNU-C  2.95.#  allows startup.c/BareED.c to
		  be compiled in small (base relative) mode!

		  I  originally  used  the  GNU-Compiler  that  can  be  found on Geek
		  Gadgets II.
		  Thanks to Mr. Fish for this compilation.

		  And  thank  you  Gunther  for  your annotations, although they first
		  brought me down.

		  This startup code was bug fixed and improved by Gunther Nikl!

		  Rework for MaconC++ and StormC done by J.v.d.Loo .

		  NOTE: The handling of "__inline" in GNU-C / SAS-C does not complain
				with legal rules! Thus "__inline" has been removed for Storm
				and Maxon C/C++ compilers!
*/


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

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

#include <workbench/startup.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

#if defined(__MAXON__) || defined(__STORM__)
  #define __stdargs
  #define __inline
#endif

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

#ifdef __GNUC__
  #define REG(reg,arg) arg __asm(#reg)
#else
  #define REG(reg,arg) register __##reg arg
#endif

#ifdef __cplusplus	// Tells if MAXON or STORM compilers run in C++ mode
	#define main main__UjPPUc	// C++ name for main
	extern "ASM" main__UjPPUc( unsigned long argc, unsigned char **argv);	// Real function name and parameters
	#pragma -		// Turn off C++ compile mode (use ANSI-C instead)
#endif

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

static struct ExecBase		*SysBase;	// Allow only to appear within startup.c
static struct DosLibrary	*DOSBase;	// Allow only to appear within startup.c


static BPTR _prg_dir = -1;				// Initial value, no directory remembered
static unsigned int    _argc,  _arg_len;
static unsigned char **_argv, *_arg_lin;
static struct Process *MasterTask = 0;

#define DOSName			(UBYTE *)"dos.library"
#define Console			(UBYTE *)"CON:0/0/320/80/ERROR REPORT/AUTO/CLOSE/WAIT"
#define NotEnoughtMem	(UBYTE *)"ERROR: Not enought memory!\n"
#define NotEnoughtStack	(UBYTE *)"ERROR: Stack < 4096 bytes!\n"
#define WrongCPU		(UBYTE *)"ERROR: CPU < 68020!\n"
#define WrongLIB		(UBYTE *)"ERROR: Library versions < 36!\n"
#define WrongSYS		(UBYTE *)"ERROR: System version < 36!\n"
#define WrongError		(UBYTE *)"ERROR: Non describt fault!\n"	// ????


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


// ################################################################
/* Gunther has enhanced the start-up code so that it's now possible
   (when started off the CLI) that this routine is twice called:
   1st time when initialising; 2nd time, when used as main entry
   for detached (new created) task! */

int __saveds INIT_0_run_me_at_first_place( REG(d0,unsigned long parlen), REG(a0,unsigned char *parameter) )
{
	#ifdef __MAXON__				// Since MaxonC/C++ does not understand __saveds
	GetBaseReg();
	#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 122;										// -> perhaps with a GURU (when started from WB)

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

	return 0;						// Return code (for CLI), in case detach was a success!
}

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

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

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

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

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

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

static __inline 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(void)
{
}
#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 *) BADDR( pp->pr_CLI );
	cp = (unsigned char *) BADDR( cli->cli_CommandName );

	_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 __inline 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
			}
		}
	}
}

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

/* Code to get program's return address and to call it */

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 !

   Gunther,  take  this  into  account: The base register is trashed (a4); the
   programmer  want to leave at least the system (giving the system the chance
   to  remove the task), but what happens when this function is called in this
   case? - GURU - ! So a __saveds statement isn't inappropriate!
   Opened windows and screens can be closed by the user with a system monitor,
   e.g. XOpa!!! */

void exitNR( int error)
{
	void (* _finally)( REG(d0,int err) ) = (void (*)(REG(d0,int))) _finally_code;

	if (error)
		GiveFault( error);

	((struct Process *) FindTask( NULL)) -> pr_Result2 = error;

	if (WBenchMsg)
	{
		if (_arg_lin)
		{
			FreeMem( _arg_lin, ((_arg_len + 7) & -8) );
			if (_prg_dir != -1)				// If it's the same as initial, ignore
				CurrentDir( _prg_dir);		// Else, 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);

	_finally( error);
}

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

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

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

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

/* Set up necessary things 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.

   Enhanced by G. Nikl (06/00)
*/

static APTR LaunchStartup( void)
{
	struct CommandLineInterface *cli;
	struct Process *pr;
	unsigned char *cp;
	struct TagItem tag[5];
	char buf[256];

	pr = (struct Process *) FindTask( NULL);

	cli = (struct CommandLineInterface *) BADDR(pr->pr_CLI);
	cp = (unsigned char *) BADDR(cli->cli_CommandName);

	// Duplicate name: slave task's name equal to master's
	strncpyNR( buf, cp + 1, cp[0]);

	// Settings for task to create
	tag[0].ti_Tag = NP_Seglist;
	tag[0].ti_Data = (ULONG) cli->cli_Module;
	tag[1].ti_Tag = NP_FreeSeglist;
	tag[1].ti_Data = TRUE;
	tag[2].ti_Tag = NP_Name;
	tag[2].ti_Data = (ULONG) buf;
	tag[3].ti_Tag = NP_StackSize;
	tag[3].ti_Data = (int) pr->pr_Task.tc_SPUpper - (int) pr->pr_Task.tc_SPLower;
	tag[4].ti_Tag = TAG_DONE;

	// Added by Gunther, I (Jörg) hate the use of Forbid() in a multiple tasks OS!
	Forbid();

	// Create slave task which becomes master task
	if ( (MasterTask = CreateNewProc( tag)) )			// Process created ?
	{
		cli->cli_Module = 0;
	}

	// Enable
	Permit();

	// Return whether success or not...
	return MasterTask;
}

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

/* This function can be twice called in case we've started from the
   CLI:
   First, when we fired up regulary through the user (MasterTask == 0);
   when we have called the second time (MasterTask != 0), we are the
   through LauchStartup() created task! */

void _main_jmp( unsigned long parlen, unsigned char *parameter)
{
	if ( !MasterTask)	// First time fired up?
	{
	   	int error;
	   	struct Process *proc;

	   	error = 0;
	   	proc = (struct Process *) FindTask( NULL);

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

	   	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);
	   	}

	   	// Try detach from CLI, if it fails, go on normal
	   	if (WBenchMsg || !LaunchStartup())
	   	{
	   		CallMainPRG();
	   	}
	}
	else	// ...we're the task that detached off CLI!
	{
		CallMainPRG();
	}
}

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