/*
**	:ts=4
**
**	Amiga support module for HYDRA protocol sample implementation.
**
**	Written by	Olaf Barthel
**			Brabeckstrasse 35
**			D-30559 Hannover
**
**			eMail: olsen@sourcery.han.de
**
**	Freely distributable.
*/

	/* System includes. */

#include <intuition/intuitionbase.h>

#include <libraries/gadtools.h>
#include <libraries/locale.h>
#include <libraries/asl.h>

#include <graphics/gfxbase.h>

#include <utility/date.h>

#include <devices/conunit.h>
#include <devices/serial.h>
#include <devices/timer.h>

#include <hardware/cia.h>

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

#include <exec/memory.h>

#include <clib/intuition_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/graphics_protos.h>
#include <clib/utility_protos.h>
#include <clib/locale_protos.h>
#include <clib/timer_protos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/asl_protos.h>
#include <clib/macros.h>

#include <pragmas/intuition_pragmas.h>
#include <pragmas/gadtools_pragmas.h>
#include <pragmas/graphics_pragmas.h>
#include <pragmas/utility_pragmas.h>
#include <pragmas/locale_pragmas.h>
#include <pragmas/timer_pragmas.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/asl_pragmas.h>

#include <errno.h>

//#define DB(x)	x
#define DB(x) ;

void __stdargs kprintf(STRPTR,...);

#include "Rendezvous.h"
#include "hydracom.h"

	/* Difference between UTC and Amiga time. */

#define UTC_OFFSET			252482400

	/* Minimum of lines to reserve for local input. */

#define MIN_LINES			3

	/* Serial buffer size. */

#define BUFFER_SIZE			8192

	/* Maximum length of a file name for now. */

#define MAX_FILENAME_LENGTH	256

	/* A handy macro. */

#define ClrSignal(s)	SetSignal(0,s)

	/* this macro lets us long-align structures on the stack */

#define D_S(type,name) char a_##name[sizeof(type)+3]; \
                       type *name = (type *)((LONG)(a_##name+3) & ~3);

	/* Signal masks. */

#define SIG_SERREAD		(1UL << ReadPort->mp_SigBit)
#define SIG_SERWRITE	(1UL << WritePort->mp_SigBit)
#define SIG_CONREAD		(1UL << ConsoleReadPort->mp_SigBit)
#define SIG_TIMER		(1UL << TimePort->mp_SigBit)
#define SIG_WINDOW		(1UL << WindowPort->mp_SigBit)
#define SIG_HANDSHAKE	SIGF_SINGLE
#define SIG_KILL		SIGBREAKF_CTRL_C

	/* A serial buffer structure. */

struct SerialBuffer
{
	struct IOExtSer	*SerialRequest;
	UBYTE			*SerialBuffer,
					*SerialIndex,
					*SerialTop;
	LONG			 SerialSize,
					 SerialFilled;
	BOOL			 IsClone,
					 IsBusy;
};

	/* Special rendezvous data. */

STATIC struct RendezvousData		*RendezvousData;
STATIC struct RendezvousSemaphore	*RendezvousSemaphore;

	/* Library bases. */

extern struct ExecBase		*SysBase;
extern struct DosLibrary	*DOSBase;

struct IntuitionBase		*IntuitionBase;
struct GfxBase				*GfxBase;
struct LocaleBase			*LocaleBase;
struct Library				*GadToolsBase,
							*UtilityBase,
							*TimerBase,
							*AslBase;

	/* Timer data. */

struct MsgPort		*TimePort;
struct timerequest	*TimeRequest;

	/* Serial data. */

struct MsgPort		*ReadPort,
					*WritePort;

struct SerialBuffer	*ThisBuffer,
					*NextBuffer,
					*ReadBuffer;

	/* Console data. */

struct MsgPort		*WindowPort;

struct Window		*FileWindow,
					*RemoteWindow,
					*LocalWindow,
					*LogWindow;

struct MsgPort		*ConsoleWritePort,
					*ConsoleReadPort;
struct IOStdReq		*ConsoleReadRequest;
UBYTE				 ConsoleChar;
BOOL				 ConsoleReady = FALSE,
					 WindowReady = FALSE;

struct IOStdReq		*FileRequest,
					*RemoteRequest,
					*LocalRequest,
					*LogRequest;

	/* DOS Data. */

struct Process		*ThisProcess;
APTR				 OldPtr;
LONG				 OldPri;

struct AnchorPath	*Anchor;
BOOL				 AnchorUsed = FALSE;

	/* Screen data. */

struct Screen		*PublicScreen,
					*Screen;

	/* Menu data. */

APTR				 VisualInfo;
struct Menu			*Menu;

struct NewMenu MenuTemplate[] =
{
	{ NM_TITLE, "Project",				 0 ,	0,	0,	(APTR)0},
	{  NM_ITEM, "Toggle chat",			"C",	0,	0,	(APTR)Alt_C},
	{  NM_ITEM, NM_BARLABEL,			 0 ,	0,	0,	(APTR)0},
	{  NM_ITEM, "Hang up",				"H",	0,	0,	(APTR)Alt_H},
	{  NM_ITEM, "Toggle duplex",		"E",	0,	0,	(APTR)Alt_E},
	{  NM_ITEM, "Toggle 7 bits/8 bits",	"B",	0,	0,	(APTR)Alt_B},
	{  NM_ITEM, NM_BARLABEL,			 0 ,	0,	0,	(APTR)0},
	{  NM_ITEM, "Start upload",			"U",	0,	0,	(APTR)PgUp},
	{  NM_ITEM, "Start download",		"D",	0,	0,	(APTR)PgDn},
	{  NM_ITEM, NM_BARLABEL,			 0 ,	0,	0,	(APTR)0},
	{  NM_ITEM, "Abort Hydra session",	".",	0,	0,	(APTR)Esc},
	{  NM_ITEM, NM_BARLABEL,			 0 ,	0,	0,	(APTR)0},
	{  NM_ITEM, "Exit HydraCom",		"Q",	0,	0,	(APTR)Alt_X},
	{ NM_END }
};

	/* Time data. */

LONG			 GMT_Offset = UTC_OFFSET;

	/* Standard file requester. */

struct FileRequester *FileRequester;

	/* Version ID. */

STRPTR VersionTag = "$VER: hydracom 1.0r7 (15.12.95)\r\n";

	/* CloseWindowSafely(struct Window *Window):
	 *
	 *	Close a window sharing its UserPort with other
	 *	windows.
	 */

STATIC VOID
CloseWindowSafely(struct Window *Window)
{
	Forbid();

	if(Window->UserPort)
	{
		struct Node *Node,*Next;

		for(Node = Window->UserPort->mp_MsgList.lh_Head ; Next = Node->ln_Succ ; Node = Next)
		{
			if(((struct IntuiMessage *)Node)->IDCMPWindow == Window)
			{
				Remove(Node);
				ReplyMsg((struct Message *)Node);
			}
		}

		Window->UserPort = NULL;
	}

	ModifyIDCMP(Window,NULL);

	Permit();

	CloseWindow(Window);
}

	/* UpdateTime(struct timeval *Now):
	 *
	 *	Get the current time and/or update the current
	 *	time offset data.
	 */

STATIC VOID
UpdateTime(struct timeval *Now)
{
	if(Now)
		Now->tv_secs = Now->tv_micro = 0;

	if(TimePort = CreateMsgPort())
	{
		if(TimeRequest = (struct timerequest *)CreateIORequest(TimePort,sizeof(struct timerequest)))
		{
			if(!OpenDevice(TIMERNAME,UNIT_VBLANK,TimeRequest,NULL))
			{
				TimerBase = (struct Library *)TimeRequest->tr_node.io_Device;

				if(LocaleBase = (struct LocaleBase *)OpenLibrary("locale.library",38))
				{
					struct Locale *Locale = OpenLocale(NULL);

					GMT_Offset = 60 * Locale->loc_GMTOffset + UTC_OFFSET;
					CloseLocale(Locale);

					CloseLibrary(LocaleBase);
					LocaleBase = NULL;
				}

				if(Now)
					GetSysTime(Now);

				TimerBase = NULL;
				CloseDevice(TimeRequest);
			}

			DeleteIORequest(TimeRequest);
			TimeRequest = NULL;
		}

		DeleteMsgPort(TimePort);
		TimePort = NULL;
	}
}

STATIC VOID
AddString(STRPTR Buffer,LONG MaxLen,STRPTR NewString)
{
	LONG i,Len,BufferLen;
	BOOL HasSpace;

	Len			= strlen(NewString);
	BufferLen	= strlen(Buffer);
	HasSpace	= FALSE;

	for(i = 0 ; i < Len ; i++)
	{
		if(NewString[i] == ' ')
		{
			HasSpace = TRUE;
			Len += 2;
			break;
		}
	}

	Len++;

	if(BufferLen + Len < MaxLen)
	{
		if(HasSpace)
			sprintf(&Buffer[BufferLen],"\"%s\" ",NewString);
		else
			sprintf(&Buffer[BufferLen],"%s ",NewString);
	}
}

	/* GetFiles(char *Buffer,int MaxLen):
	 *
	 *	Get a list of file names.
	 */

char *
GetFiles(char *Buffer,int MaxLen)
{
	if(AslRequestTags(FileRequester,
		ASLFR_Window,		LocalWindow,
		ASLFR_SleepWindow,	TRUE,
	TAG_DONE))
	{
		if(FileRequester->fr_NumArgs > 0)
		{
			UBYTE	LocalBuffer[MAX_FILENAME_LENGTH];
			LONG	Len,Count,i;

			for(i = Count = 0 ; i < FileRequester->fr_NumArgs ; i++)
			{
				if(FileRequester->fr_ArgList[i].wa_Lock)
				{
					if(!NameFromLock(FileRequester->fr_ArgList[i].wa_Lock,LocalBuffer,sizeof(LocalBuffer)))
					{
						Count = -1;
						break;
					}
				}
				else
				{
					Len = strlen(FileRequester->fr_Drawer);

					if(Len >= sizeof(LocalBuffer))
					{
						Count = -1;
						break;
					}
					else
						strcpy(LocalBuffer,FileRequester->fr_Drawer);
				}

				if(AddPart(LocalBuffer,FileRequester->fr_ArgList[i].wa_Name,sizeof(LocalBuffer)))
				{
					LONG j;

					Len = strlen(LocalBuffer) + 1;

					for(j = 0 ; j < Len ; j++)
					{
						if(j == ' ')
						{
							Len += 2;
							break;
						}
					}

					if(Len < sizeof(LocalBuffer))
					{
						if(Count + Len < MaxLen)
							Count += Len;
						else
						{
							Count = -1;
							break;
						}
					}
				}
			}

			if(Count > 0)
			{
				Buffer[0] = 0;

				for(i = 0 ; i < FileRequester->fr_NumArgs ; i++)
				{
					if(FileRequester->fr_ArgList[i].wa_Lock)
					{
						if(!NameFromLock(FileRequester->fr_ArgList[i].wa_Lock,LocalBuffer,sizeof(LocalBuffer)))
							break;
					}
					else
					{
						Len = strlen(FileRequester->fr_Drawer);

						if(Len >= sizeof(LocalBuffer))
							break;
						else
							strcpy(LocalBuffer,FileRequester->fr_Drawer);
					}

					if(AddPart(LocalBuffer,FileRequester->fr_ArgList[i].wa_Name,sizeof(LocalBuffer)))
						AddString(Buffer,MaxLen,LocalBuffer);
				}

				return(Buffer);
			}
		}
		else
		{
			if(FileRequester->fr_File[0])
			{
				LONG Len;

				Len = strlen(FileRequester->fr_Drawer) + strlen(FileRequester->fr_File) + 2 + 2;

				if(Len <= MaxLen)
				{
					strcpy(Buffer,FileRequester->fr_Drawer);
					AddPart(Buffer,FileRequester->fr_File,MaxLen);

					return(Buffer);
				}
			}
		}
	}

	return(NULL);
}

	/* OpenConsole():
	 *
	 *	Open a console window.
	 */

STATIC BOOL
OpenConsole(struct Screen *Screen,LONG Top,LONG Height,STRPTR Title,BOOL Resize,struct Window **WindowPtr,struct IOStdReq **ConsolePtr)
{
	struct Window *Window;

	if(Window = OpenWindowTags(NULL,
		WA_Left,			0,
		WA_Top,				Top,
		WA_Width,			Screen->Width,
		WA_Height,			Height,
		WA_Title,			Title,
		WA_SimpleRefresh,	TRUE,
		WA_DepthGadget,		TRUE,
		WA_DragBar,			TRUE,
		WA_SizeGadget,		Resize,
		WA_SizeBRight,		TRUE,
		WA_CustomScreen,	Screen,
		WA_NewLookMenus,	TRUE,
	TAG_DONE))
	{
		Window->UserPort = WindowPort;

		SetMenuStrip(Window,Menu);

		if(ModifyIDCMP(Window,IDCMP_MENUPICK))
		{
			struct IOStdReq *ConsoleRequest;

			if(Window->RPort->Font->tf_Flags & FPF_PROPORTIONAL)
				SetFont(Window->RPort,GfxBase->DefaultFont);

			if(ConsoleRequest = (struct IOStdReq *)CreateIORequest(ConsoleWritePort,sizeof(struct IOStdReq)))
			{
				ConsoleRequest->io_Data = Window;

				if(!OpenDevice("console.device",CONU_CHARMAP,ConsoleRequest,CONFLAG_DEFAULT))
				{
					WindowLimits(Window,Window->BorderLeft + 10 * Window->RPort->Font->tf_XSize * 10 + Window->BorderRight,Window->BorderTop + 2 * Window->RPort->Font->tf_YSize + Window->BorderBottom,Screen->Width,Screen->Height);

						/* Turn off the cursor. */

					ConPrintf(ConsoleRequest,"\033[0 p");

					*WindowPtr	= Window;
					*ConsolePtr	= ConsoleRequest;

					return(TRUE);
				}

				DeleteIORequest(ConsoleRequest);
			}
		}

		ClearMenuStrip(Window);

		CloseWindowSafely(Window);
	}

	return(FALSE);
}

	/* CloseConsole():
	 *
	 *	Close a console window.
	 */

STATIC VOID
CloseConsole(struct Window **WindowPtr,struct IOStdReq **ConsolePtr)
{
	if(*ConsolePtr)
	{
		CloseDevice(*ConsolePtr);

		DeleteIORequest(*ConsolePtr);

		*ConsolePtr = NULL;
	}

	if(*WindowPtr)
	{
		ClearMenuStrip(*WindowPtr);

		CloseWindowSafely(*WindowPtr);

		*WindowPtr = NULL;
	}
}

	/* CloneSerialBuffer():
	 *
	 *	Clone a SerialBuffer structure.
	 */

STATIC struct SerialBuffer *
CloneSerialBuffer(struct SerialBuffer *Source,struct MsgPort *MsgPort)
{
	struct SerialBuffer *Buffer;

	if(Buffer = (struct SerialBuffer *)AllocVec(sizeof(struct SerialBuffer) + Source->SerialSize,MEMF_ANY | MEMF_PUBLIC))
	{
		Buffer->SerialBuffer	= Buffer->SerialIndex = (UBYTE *)(Buffer + 1);
		Buffer->SerialFilled	= 0;
		Buffer->SerialTop		= Buffer->SerialBuffer + Source->SerialSize;
		Buffer->SerialSize		= Source->SerialSize;
		Buffer->IsClone			= TRUE;
		Buffer->IsBusy			= FALSE;

		if(Buffer->SerialRequest = (struct IOExtSer *)AllocVec(sizeof(struct IOExtSer),MEMF_ANY | MEMF_PUBLIC))
		{
			CopyMem(Source->SerialRequest,Buffer->SerialRequest,sizeof(struct IOExtSer));

			Buffer->SerialRequest->IOSer.io_Message.mn_ReplyPort = MsgPort;

			return(Buffer);
		}
		else
			cprint("Could not create serial request\n");

		FreeVec(Buffer);
	}
	else
		cprint("Could not create serial buffer\n");

	return(NULL);
}

	/* DeleteSerialBuffer():
	 *
	 *	Delete a SerialBuffer structure.
	 */

STATIC VOID
DeleteSerialBuffer(struct SerialBuffer *Buffer)
{
	if(Buffer)
	{
		if(Buffer->IsBusy)
		{
			if(!CheckIO(Buffer->SerialRequest))
				AbortIO(Buffer->SerialRequest);

			WaitIO(Buffer->SerialRequest);
		}

		if(Buffer->IsClone)
			FreeVec(Buffer->SerialRequest);
		else
		{
			CloseDevice(Buffer->SerialRequest);

			DeleteIORequest(Buffer->SerialRequest);
		}

		FreeVec(Buffer);
	}
}

	/* CreateSerialBuffer():
	 *
	 *	Create a serial buffer structure.
	 */

STATIC struct SerialBuffer *
CreateSerialBuffer(STRPTR Device,LONG Unit,LONG Size,struct MsgPort *MsgPort)
{
	struct SerialBuffer *Buffer;

	if(Buffer = (struct SerialBuffer *)AllocVec(sizeof(struct SerialBuffer) + Size,MEMF_ANY | MEMF_PUBLIC))
	{
		Buffer->SerialBuffer	= Buffer->SerialIndex = (UBYTE *)(Buffer + 1);
		Buffer->SerialFilled	= 0;
		Buffer->SerialTop		= Buffer->SerialBuffer + Size;
		Buffer->SerialSize		= Size;
		Buffer->IsClone			= FALSE;
		Buffer->IsBusy			= FALSE;

		if(Buffer->SerialRequest = (struct IOExtSer *)CreateIORequest(MsgPort,sizeof(struct IOExtSer)))
		{
			Buffer->SerialRequest->io_SerFlags = SERF_SHARED|SERF_7WIRE;

			if(!OpenDevice(Device,Unit,Buffer->SerialRequest,NULL))
				return(Buffer);
			else
			{
				cprint("Could not open \"%s\", unit %d\n",Device,Unit);

				DeleteIORequest(Buffer->SerialRequest);
			}
		}
		else
			cprint("Could not create serial request\n");

		FreeVec(Buffer);
	}
	else
		cprint("Could not create serial buffer\n");

	return(NULL);
}

	/* OpenAll():
	 *
	 *	Allocate all the resources required.
	 */

STATIC BOOL
OpenAll(STRPTR Device,LONG Unit)
{
	LONG Top,Lines,BorderSize,FontSize,ExtraLines,RemainingLines,TotalHeight;
	UWORD Pens = (UWORD)~0;

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

	if(pri != 1000)
	{
		if(pri < -128)
			pri = 128;
		else
		{
			if(pri > 127)
				pri = 127;
		}

		OldPri = SetTaskPri(ThisProcess,pri);
	}

	if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",37)))
	{
		cprint("Could not open intuition.library v37\n");

		return(FALSE);
	}

	if(!(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",37)))
	{
		cprint("Could not open graphics.library v37\n");

		return(FALSE);
	}

	if(!(GadToolsBase = OpenLibrary("gadtools.library",37)))
	{
		cprint("Could not open gadtools.library v37\n");

		return(FALSE);
	}

	if(!(UtilityBase = OpenLibrary("utility.library",37)))
	{
		cprint("Could not open utility.library v37\n");

		return(FALSE);
	}

	if(!(AslBase = OpenLibrary("asl.library",37)))
	{
		cprint("Could not open asl.library v37\n");

		return(FALSE);
	}

	if(!(FileRequester = (struct FileRequester *)AllocAslRequestTags(ASL_FileRequest,
		ASLFR_TitleText,		"Select file(s) to upload",
		ASLFR_InitialPattern,	"#?",
		ASLFR_Flags1,			FRF_PRIVATEIDCMP | FRF_DOMULTISELECT | FRF_DOPATTERNS,
	TAG_DONE)))
	{
		cprint("Could not allocate file requester.\n");

		return(FALSE);
	}

	if(LocaleBase = (struct LocaleBase *)OpenLibrary("locale.library",38))
	{
		struct Locale *Locale;

		if(Locale = OpenLocale(NULL))
		{
			GMT_Offset = 60 * Locale->loc_GMTOffset + UTC_OFFSET;

			CloseLocale(Locale);
		}

		CloseLibrary(LocaleBase);
	}

	if(!(TimePort = CreateMsgPort()))
	{
		cprint("Could not create timer port\n");

		return(FALSE);
	}

	if(!(TimeRequest = (struct timerequest *)CreateIORequest(TimePort,sizeof(struct timerequest))))
	{
		cprint("Could not create timer request\n");

		return(FALSE);
	}

	if(OpenDevice(TIMERNAME,UNIT_VBLANK,TimeRequest,NULL))
	{
		cprint("Could not open timer\n");

		return(FALSE);
	}

	TimerBase = (struct Library *)TimeRequest->tr_node.io_Device;

	if(!(Anchor = (struct AnchorPath *)AllocVec(sizeof(struct AnchorPath) + 512,MEMF_ANY | MEMF_CLEAR)))
	{
		cprint("Could not allocate pattern matching buffe\n");

		return(FALSE);
	}

	Anchor->ap_Strlen = 512;

	if(!(ReadPort = CreateMsgPort()))
	{
		cprint("Could not create serial read port\n");

		return(FALSE);
	}

	if(!(WritePort = CreateMsgPort()))
	{
		cprint("Could not create serial write port\n");

		return(FALSE);
	}

	Forbid();

	if(RendezvousSemaphore = (struct RendezvousSemaphore *)FindSemaphore(Device))
	{
		ObtainSemaphore(RendezvousSemaphore);

		if(!(RendezvousData = (*RendezvousSemaphore->rs_Login)(ReadPort,WritePort,NULL)))
		{
			Permit();

			ReleaseSemaphore(RendezvousSemaphore);

			RendezvousSemaphore = NULL;

			cprint("Could not link to `term' port \"%s\"\n",Device);

			return(FALSE);
		}
	}

	Permit();

	if(!(ConsoleReadPort = CreateMsgPort()))
	{
		cprint("Could not create console read port\n");

		return(FALSE);
	}

	if(!(ConsoleWritePort = CreateMsgPort()))
	{
		cprint("Could not create console write port\n");

		return(FALSE);
	}

	if(!(ConsoleReadRequest = (struct IOStdReq *)AllocVec(sizeof(struct IOStdReq),MEMF_ANY | MEMF_PUBLIC | MEMF_CLEAR)))
	{
		cprint("Could not create console read request\n");

		return(FALSE);
	}

	if(!RendezvousData)
	{
		if(!(ReadBuffer = CreateSerialBuffer(Device,Unit,BUFFER_SIZE,ReadPort)))
			return(FALSE);
	}
	else
	{
		if(ReadBuffer = (struct SerialBuffer *)AllocVec(sizeof(struct SerialBuffer) + BUFFER_SIZE,MEMF_ANY | MEMF_PUBLIC))
		{
			ReadBuffer->SerialBuffer	= ReadBuffer->SerialIndex = (UBYTE *)(ReadBuffer + 1);
			ReadBuffer->SerialFilled	= 0;
			ReadBuffer->SerialTop		= ReadBuffer->SerialBuffer + BUFFER_SIZE;
			ReadBuffer->SerialSize		= BUFFER_SIZE;
			ReadBuffer->IsClone			= TRUE;
			ReadBuffer->IsBusy			= FALSE;
			ReadBuffer->SerialRequest	= &RendezvousData->rd_ReadRequest;

				/* Ralph Seichter suggested reusing the data
				 * stored in the rendezvous data.
				 */

			com_speed = ReadBuffer->SerialRequest->io_Baud;

			if(cur_speed > com_speed)
				cur_speed = com_speed;

			if(ReadBuffer->SerialRequest->io_ReadLen == 7 && ReadBuffer->SerialRequest->io_WriteLen == 7 && (ReadBuffer->SerialRequest->io_SerFlags & (SERF_PARTY_ON|SERF_PARTY_ODD)) == SERF_PARTY_ON && !(ReadBuffer->SerialRequest->io_SerFlags & (SEXTF_MSPON|SEXTF_MARK)))
			{
				parity = true;
				hydra_options |= HOPT_HIGHBIT;
			}
			else
				parity = false;
		}
		else
		{
			cprint("Could not create serial ReadBuffer\n");

			return(FALSE);
		}
	}

	if(!(ThisBuffer = CloneSerialBuffer(ReadBuffer,WritePort)))
		return(FALSE);

	if(!(NextBuffer = CloneSerialBuffer(ReadBuffer,WritePort)))
		return(FALSE);

	if(!(PublicScreen = LockPubScreen(NULL)))
	{
		cprint("Could not find default public screen\n");

		return(FALSE);
	}

	if(RendezvousData)
	{
		if(!(Screen = RendezvousData->rd_Screen))
			Screen = PublicScreen;
	}
	else
	{
		if(!(Screen = OpenScreenTags(NULL,
			SA_DisplayID,	GetVPModeID(&PublicScreen->ViewPort),
			SA_Overscan,	OSCAN_TEXT,
			SA_Depth,		2,
			SA_Title,		PRGNAME " " VERSION " " HC_OS " Amiga rev 8, ported by Olaf `Olsen' Barthel",
			SA_Behind,		TRUE,
			SA_SysFont,		1,
			SA_Pens,		&Pens,
			SA_Interleaved,	TRUE,
		TAG_DONE)))
		{
			cprint("Could not open screen\n");

			return(FALSE);
		}
	}

	if(!(VisualInfo = GetVisualInfo(Screen,TAG_DONE)))
	{
		cprint("Could not obtain screen visual info\n");

		return(FALSE);
	}

	if(!(Menu = CreateMenus(MenuTemplate,TAG_DONE)))
	{
		cprint("Could not create menus\n");

		return(FALSE);
	}

	if(!LayoutMenus(Menu,VisualInfo,
		GTMN_TextAttr,		Screen->Font,
		GTMN_NewLookMenus,	TRUE,
	TAG_DONE))
	{
		cprint("Could not layout menus\n");

		return(FALSE);
	}

	Top				= Screen->BarHeight + 1;
	BorderSize		= Screen->WBorTop + Screen->Font->ta_YSize + 1 + Screen->WBorBottom;
	FontSize		= GfxBase->DefaultFont->tf_YSize;
	TotalHeight		= Screen->Height - Top;
	Lines			= (TotalHeight - 3 - 4 * BorderSize) / FontSize;
	ExtraLines		= Lines > MIN_LINES ? (Lines - MIN_LINES) / 3 : 0;
	RemainingLines	= Lines > MIN_LINES + ExtraLines * 3 ? Lines - (MIN_LINES + ExtraLines * 3) : 0;

	if(Lines < MIN_LINES)
	{
		cprint("Screen size too small (need at least %d text lines, can get only %d)\n",MIN_LINES,Lines);

		return(FALSE);
	}

	if(!(WindowPort = CreateMsgPort()))
	{
		cprint("Could not create window port\n");

		return(FALSE);
	}

	if(!OpenConsole(Screen,Top,BorderSize + (6 + ExtraLines) * FontSize,"Log",TRUE,&LogWindow,&LogRequest))
	{
		cprint("Could not open console window #1\n");

		return(FALSE);
	}

	TotalHeight	-= LogWindow->Height + 1;
	Top			+= LogWindow->Height + 1;

	if(!OpenConsole(Screen,Top,BorderSize + 2 * FontSize,"File",FALSE,&FileWindow,&FileRequest))
	{
		cprint("Could not open console window #2\n");

		return(FALSE);
	}

	ConPrintf(FileRequest,"\033[?7l");

	TotalHeight	-= FileWindow->Height + 1;
	Top			+= FileWindow->Height + 1;

	if(!OpenConsole(Screen,Top,BorderSize + (8 + RemainingLines) * FontSize,"Remote",TRUE,&RemoteWindow,&RemoteRequest))
	{
		cprint("Could not open console window #3\n");

		return(FALSE);
	}

	TotalHeight	-= RemoteWindow->Height + 1;
	Top			+= RemoteWindow->Height + 1;

	if(!OpenConsole(Screen,Top,TotalHeight,"Local (Press [Amiga+C] to start/end chat mode, [Esc] to abort Hydra session)",TRUE,&LocalWindow,&LocalRequest))
	{
		cprint("Could not open console window #4\n");

		return(FALSE);
	}

	CopyMem(LocalRequest,ConsoleReadRequest,sizeof(struct IOStdReq));

	ConsoleReadRequest->io_Message.mn_ReplyPort = ConsoleReadPort;

		/* Turn the cursors back on. */

	ConPrintf(LocalRequest,"\33[ p");
	ConPrintf(RemoteRequest,"\33[ p");

	ConsoleReadRequest->io_Command	= CMD_READ;
	ConsoleReadRequest->io_Data		= &ConsoleChar;
	ConsoleReadRequest->io_Length	= 1;

	ClrSignal(SIG_CONREAD);
	SendIO(ConsoleReadRequest);

	OldPtr = ThisProcess->pr_WindowPtr;

	ThisProcess->pr_WindowPtr = (APTR)LocalWindow;

	if(!quiet)
	{
		ScreenToFront(Screen);

		ActivateWindow(LocalWindow);
	}

	UnlockPubScreen(NULL,PublicScreen);

	PublicScreen = NULL;

	if(!RendezvousData)
		com_flow(flowflags);

	return(TRUE);
}

	/* CloseAll():
	 *
	 *	Close all the resources.
	 */

STATIC VOID
CloseAll(VOID)
{
	if(FileRequester)
		FreeAslRequest(FileRequester);

	if(LocalWindow)
		ClearMenuStrip(LocalWindow);

	if(Menu)
		FreeMenus(Menu);

	if(VisualInfo)
		FreeVisualInfo(VisualInfo);

	if(AnchorUsed)
		MatchEnd(Anchor);

	if(Anchor)
		FreeVec(Anchor);

	if(ThisProcess)
		ThisProcess->pr_WindowPtr = OldPtr;

	if(ConsoleReadRequest)
	{
		if(ConsoleReadRequest->io_Device)
		{
			if(!CheckIO(ConsoleReadRequest))
				AbortIO(ConsoleReadRequest);

			WaitIO(ConsoleReadRequest);
		}

		FreeVec(ConsoleReadRequest);
	}

	CloseConsole(&LocalWindow,&LocalRequest);
	CloseConsole(&RemoteWindow,&RemoteRequest);
	CloseConsole(&FileWindow,&FileRequest);
	CloseConsole(&LogWindow,&LogRequest);

	if(WindowPort)
		DeleteMsgPort(WindowPort);

	if(!RendezvousData && Screen)
		CloseScreen(Screen);

	if(PublicScreen)
		UnlockPubScreen(NULL,PublicScreen);

	DeleteSerialBuffer(NextBuffer);
	DeleteSerialBuffer(ThisBuffer);

	if(RendezvousData)
	{
		if(ReadBuffer->IsBusy)
		{
			if(!CheckIO(ReadBuffer->SerialRequest))
				AbortIO(ReadBuffer->SerialRequest);

			WaitIO(ReadBuffer->SerialRequest);
		}

		FreeVec(ReadBuffer);
	}
	else
		DeleteSerialBuffer(ReadBuffer);

	if(ConsoleWritePort)
		DeleteMsgPort(ConsoleWritePort);

	if(ConsoleReadPort)
		DeleteMsgPort(ConsoleReadPort);

	if(WritePort)
		DeleteMsgPort(WritePort);

	if(ReadPort)
		DeleteMsgPort(ReadPort);

	if(TimeRequest)
	{
		if(TimeRequest->tr_node.io_Device)
			CloseDevice(TimeRequest);

		DeleteIORequest(TimeRequest);
	}

	if(TimePort)
		DeleteMsgPort(TimePort);

	if(UtilityBase)
		CloseLibrary(UtilityBase);

	if(AslBase)
		CloseLibrary(AslBase);

	if(GadToolsBase)
		CloseLibrary(GadToolsBase);

	if(GfxBase)
		CloseLibrary(GfxBase);

	if(IntuitionBase)
		CloseLibrary(IntuitionBase);

	if(RendezvousData)
	{
		(*RendezvousSemaphore->rs_Logoff)(RendezvousData);

		RendezvousData = NULL;
	}

	if(RendezvousSemaphore)
	{
		ReleaseSemaphore(RendezvousSemaphore);

		RendezvousSemaphore = NULL;
	}

	if(pri != 1000)
		OldPri = SetTaskPri(ThisProcess,OldPri);
}

	/* ConPutc():
	 *
	 *	Output a single character.
	 */

VOID __stdargs
ConPutc(struct IOStdReq *Request,UBYTE Char)
{
	Request->io_Command	= CMD_WRITE;
	Request->io_Data	= &Char;
	Request->io_Length	= 1;

	DoIO(Request);
}

	/* ConPuts():
	 *
	 *	Output a string.
	 */

VOID
ConPuts(struct IOStdReq *Request,STRPTR String)
{
	Request->io_Command	= CMD_WRITE;
	Request->io_Data	= String;
	Request->io_Length	= strlen(String);

	DoIO(Request);
}

	/* ConPrintf():
	 *
	 *	Formatted console output.
	 */

VOID __stdargs
ConPrintf(struct IOStdReq *Request,STRPTR Format,...)
{
	STATIC UBYTE Buffer[512];

	va_list	VarArgs;
	LONG	Len;

	va_start(VarArgs,Format);
	vsprintf(Buffer,Format,VarArgs);
	va_end(VarArgs);

	Len = strlen(Buffer);

	if(Buffer[0] != '\033' && Request == FileRequest)
	{
		struct ConUnit *Unit = (struct ConUnit *)Request->io_Unit;

		if(Unit->cu_XCCP + Len > Unit->cu_XMax)
		{
			if((Len = Unit->cu_XMax - Unit->cu_XCCP) < 1)
				return;
		}
	}

	Request->io_Command	= CMD_WRITE;
	Request->io_Data	= Buffer;
	Request->io_Length	= Len;

	DoIO(Request);
}

	/* ConMove():
	 *
	 *	Move the cursor to a new position.
	 */

VOID
ConMove(struct IOStdReq *Request,LONG x,LONG y)
{
	ConPrintf(Request,"\33[%ld;%ldH",y,x);
}

	/* ConClear():
	 *
	 *	Clear the console window.
	 */

VOID
ConClear(struct IOStdReq *Request)
{
	struct ConUnit *ConUnit = (struct ConUnit *)Request->io_Device;
	LONG x,y;

	x = ConUnit->cu_XCCP;
	y = ConUnit->cu_YCCP;

	ConPrintf(Request,"\033[2J\33[%ld;%ldH",y,x);
}

	/* ConGetKey():
	 *
	 *	Read a character from a console window.
	 */

int
ConGetKey()
{
	if(ConsoleReady)
	{
		int Result = ConsoleChar;

		ConsoleReady = FALSE;

		ConsoleReadRequest->io_Command	= CMD_READ;
		ConsoleReadRequest->io_Data		= &ConsoleChar;
		ConsoleReadRequest->io_Length	= 1;

		ClrSignal(SIG_CONREAD);
		SendIO(ConsoleReadRequest);

		return(Result);
	}
	else
	{
		int Result = 0;

		if(WindowReady)
		{
			struct IntuiMessage *IntuiMessage;
			ULONG MsgClass;
			UWORD MsgCode;

			while(IntuiMessage = (struct IntuiMessage *)GetMsg(LocalWindow->UserPort))
			{
				MsgClass	= IntuiMessage->Class;
				MsgCode		= IntuiMessage->Code;

				ReplyMsg(IntuiMessage);

				if(MsgClass == IDCMP_MENUPICK)
				{
					struct MenuItem *Item;

					while(MsgCode != MENUNULL)
					{
						if(Item = ItemAddress(Menu,MsgCode))
						{
							if(MENU_USERDATA(Item))
							{
								if(!Result)
									Result = (int)MENU_USERDATA(Item);
							}

							MsgCode = Item->NextSelect;
						}
						else
							break;
					}
				}
			}

			WindowReady = FALSE;
		}

		return(Result);
	}
}

	/* ConScanKey():
	 *
	 *	Check for a keyboard event.
	 */

int
ConScanKey()
{
	if(ConsoleReady || WindowReady)
		return(1);
	else
	{
		int Result = 0;

		if(CheckSignal(SIG_WINDOW))
		{
			WindowReady = TRUE;

			Result = 1;
		}

		if(CheckIO(ConsoleReadRequest))
		{
			if(!WaitIO(ConsoleReadRequest))
			{
				ConsoleReady = TRUE;

				return(1);
			}
			else
			{
				ConsoleReadRequest->io_Command	= CMD_READ;
				ConsoleReadRequest->io_Data		= &ConsoleChar;
				ConsoleReadRequest->io_Length	= 1;

				ClrSignal(SIG_CONREAD);
				SendIO(ConsoleReadRequest);
			}
		}

		return(Result);
	}

	return(0);
}

	/* dtr_out(byte flag):
	 *
	 *	If flag == 0->drop DTR signal, else set it.
	 */

VOID
dtr_out(byte flag)
{
	if(!flag && !RendezvousData)
	{
		if(ThisBuffer->IsBusy)
		{
			WaitIO(ThisBuffer->SerialRequest);

			ThisBuffer->IsBusy		= FALSE;
			ThisBuffer->SerialIndex	= ThisBuffer->SerialBuffer;
		}

		if(ReadBuffer->IsBusy)
		{
			if(!CheckIO(ReadBuffer->SerialRequest))
				AbortIO(ReadBuffer->SerialRequest);

			WaitIO(ReadBuffer->SerialRequest);

			ReadBuffer->IsBusy			= FALSE;
			ReadBuffer->SerialFilled	= ReadBuffer->SerialRequest->IOSer.io_Actual;
			ReadBuffer->SerialIndex		= ReadBuffer->SerialBuffer;

			ReadBuffer->SerialRequest->IOSer.io_Command = SDCMD_QUERY;

			DoIO(ReadBuffer->SerialRequest);

			if(ReadBuffer->SerialRequest->IOSer.io_Actual)
			{
				LONG Size = ReadBuffer->SerialSize - ReadBuffer->SerialFilled;

				if(Size > 0)
				{
					if(Size > ReadBuffer->SerialRequest->IOSer.io_Actual)
						Size = ReadBuffer->SerialRequest->IOSer.io_Actual;

					ReadBuffer->SerialRequest->IOSer.io_Command	= CMD_READ;
					ReadBuffer->SerialRequest->IOSer.io_Data	= ReadBuffer->SerialBuffer + ReadBuffer->SerialFilled;
					ReadBuffer->SerialRequest->IOSer.io_Length	= Size;

					DoIO(ReadBuffer->SerialRequest);

					ReadBuffer->SerialFilled += ReadBuffer->SerialRequest->IOSer.io_Actual;
				}
			}
		}

		CloseDevice(ReadBuffer->SerialRequest);

		TimeRequest->tr_node.io_Command	= TR_ADDREQUEST;
		TimeRequest->tr_time.tv_secs	= 1;
		TimeRequest->tr_time.tv_micro	= 0;

		DoIO(TimeRequest);

		if(OpenDevice(device,port,ReadBuffer->SerialRequest,NULL))
		{
			CloseAll();

			exit(10);
		}
		else
		{
			ReadBuffer->SerialRequest->io_Baud		= ThisBuffer->SerialRequest->io_Baud;
			ReadBuffer->SerialRequest->io_ReadLen	= ThisBuffer->SerialRequest->io_ReadLen;
			ReadBuffer->SerialRequest->io_WriteLen	= ThisBuffer->SerialRequest->io_WriteLen;
			ReadBuffer->SerialRequest->io_SerFlags	= ThisBuffer->SerialRequest->io_SerFlags;

			ReadBuffer->SerialRequest->IOSer.io_Command = SDCMD_SETPARAMS;

			DoIO(ReadBuffer->SerialRequest);

			CopyMem(ReadBuffer->SerialRequest,ThisBuffer->SerialRequest,sizeof(struct IOExtSer));

			ThisBuffer->SerialRequest->IOSer.io_Message.mn_ReplyPort = WritePort;

			CopyMem(ReadBuffer->SerialRequest,NextBuffer->SerialRequest,sizeof(struct IOExtSer));

			NextBuffer->SerialRequest->IOSer.io_Message.mn_ReplyPort = WritePort;
		}
	}
}

	/* com_flow(byte flags):
	 *
	 *	The bit mask `flags' determines the style(s) of
	 *	handshaking:
	 *
	 *	if (flags & 9)->enable xON/xOFF software handshaking,
	 *	if (flags & 2)->enable RTS/CTS hardware handshaking
	 */

VOID
com_flow(byte flags)
{
	if(ThisBuffer->IsBusy)
	{
		WaitIO(ThisBuffer->SerialRequest);

		ThisBuffer->IsBusy		= FALSE;
		ThisBuffer->SerialIndex	= ThisBuffer->SerialBuffer;
	}

	if(ReadBuffer->IsBusy)
	{
		if(!CheckIO(ReadBuffer->SerialRequest))
			AbortIO(ReadBuffer->SerialRequest);

		WaitIO(ReadBuffer->SerialRequest);

		ReadBuffer->IsBusy			= FALSE;
		ReadBuffer->SerialFilled	= ReadBuffer->SerialRequest->IOSer.io_Actual;
		ReadBuffer->SerialIndex		= ReadBuffer->SerialBuffer;

		ReadBuffer->SerialRequest->IOSer.io_Command = SDCMD_QUERY;

		DoIO(ReadBuffer->SerialRequest);

		if(ReadBuffer->SerialRequest->IOSer.io_Actual)
		{
			LONG Size = ReadBuffer->SerialSize - ReadBuffer->SerialFilled;

			if(Size > 0)
			{
				if(Size > ReadBuffer->SerialRequest->IOSer.io_Actual)
					Size = ReadBuffer->SerialRequest->IOSer.io_Actual;

				ReadBuffer->SerialRequest->IOSer.io_Command	= CMD_READ;
				ReadBuffer->SerialRequest->IOSer.io_Data	= ReadBuffer->SerialBuffer + ReadBuffer->SerialFilled;
				ReadBuffer->SerialRequest->IOSer.io_Length	= Size;

				DoIO(ReadBuffer->SerialRequest);

				ReadBuffer->SerialFilled += ReadBuffer->SerialRequest->IOSer.io_Actual;
			}
		}
	}

	if(flags & 2)
		ReadBuffer->SerialRequest->io_SerFlags |= SERF_7WIRE;
	else
		ReadBuffer->SerialRequest->io_SerFlags &= ~SERF_7WIRE;

	if(flags & 9)
		ReadBuffer->SerialRequest->io_SerFlags &= ~SERF_XDISABLED;
	else
		ReadBuffer->SerialRequest->io_SerFlags |= SERF_XDISABLED;

	ReadBuffer->SerialRequest->IOSer.io_Command = SDCMD_SETPARAMS;

	DoIO(ReadBuffer->SerialRequest);

	ThisBuffer->SerialRequest->io_SerFlags = ReadBuffer->SerialRequest->io_SerFlags;
	NextBuffer->SerialRequest->io_SerFlags = ReadBuffer->SerialRequest->io_SerFlags;
}

	/* com_setspeed(word speed):
	 *
	 *	Set the transfer speed (in bits/second).
	 */

VOID
com_setspeed(word speed)
{
	if(ThisBuffer->IsBusy)
	{
		WaitIO(ThisBuffer->SerialRequest);

		ThisBuffer->IsBusy		= FALSE;
		ThisBuffer->SerialIndex	= ThisBuffer->SerialBuffer;
	}

	if(ReadBuffer->IsBusy)
	{
		if(!CheckIO(ReadBuffer->SerialRequest))
			AbortIO(ReadBuffer->SerialRequest);

		WaitIO(ReadBuffer->SerialRequest);

		ReadBuffer->IsBusy			= FALSE;
		ReadBuffer->SerialFilled	= ReadBuffer->SerialRequest->IOSer.io_Actual;
		ReadBuffer->SerialIndex		= ReadBuffer->SerialBuffer;

		ReadBuffer->SerialRequest->IOSer.io_Command = SDCMD_QUERY;

		DoIO(ReadBuffer->SerialRequest);

		if(ReadBuffer->SerialRequest->IOSer.io_Actual)
		{
			LONG Size = ReadBuffer->SerialSize - ReadBuffer->SerialFilled;

			if(Size > 0)
			{
				if(Size > ReadBuffer->SerialRequest->IOSer.io_Actual)
					Size = ReadBuffer->SerialRequest->IOSer.io_Actual;

				ReadBuffer->SerialRequest->IOSer.io_Command	= CMD_READ;
				ReadBuffer->SerialRequest->IOSer.io_Data	= ReadBuffer->SerialBuffer + ReadBuffer->SerialFilled;
				ReadBuffer->SerialRequest->IOSer.io_Length	= Size;

				DoIO(ReadBuffer->SerialRequest);

				ReadBuffer->SerialFilled += ReadBuffer->SerialRequest->IOSer.io_Actual;
			}
		}
	}

	ReadBuffer->SerialRequest->io_Baud = speed;

	if(parity)
	{
		ReadBuffer->SerialRequest->io_ReadLen = ReadBuffer->SerialRequest->io_WriteLen = 7;
		ReadBuffer->SerialRequest->io_SerFlags |= SERF_PARTY_ON;
	}
	else
	{
		ReadBuffer->SerialRequest->io_ReadLen = ReadBuffer->SerialRequest->io_WriteLen = 8;
		ReadBuffer->SerialRequest->io_SerFlags &= ~SERF_PARTY_ON;
	}

	ReadBuffer->SerialRequest->IOSer.io_Command = SDCMD_SETPARAMS;

	DoIO(ReadBuffer->SerialRequest);

	ThisBuffer->SerialRequest->io_Baud		= ReadBuffer->SerialRequest->io_Baud;
	ThisBuffer->SerialRequest->io_ReadLen	= ReadBuffer->SerialRequest->io_ReadLen;
	ThisBuffer->SerialRequest->io_WriteLen	= ReadBuffer->SerialRequest->io_WriteLen;
	ThisBuffer->SerialRequest->io_SerFlags	= ReadBuffer->SerialRequest->io_SerFlags;

	NextBuffer->SerialRequest->io_Baud		= ReadBuffer->SerialRequest->io_Baud;
	NextBuffer->SerialRequest->io_ReadLen	= ReadBuffer->SerialRequest->io_ReadLen;
	NextBuffer->SerialRequest->io_WriteLen	= ReadBuffer->SerialRequest->io_WriteLen;
	NextBuffer->SerialRequest->io_SerFlags	= ReadBuffer->SerialRequest->io_SerFlags;
}

	/* com_putblock(byte *s,word len):
	 *
	 *	Send a data block asynchronously.
	 */

VOID
com_putblock(byte *s,word len)
{
	struct SerialBuffer *Swap = ThisBuffer;

	if(ThisBuffer->IsBusy)
		WaitIO(ThisBuffer->SerialRequest);
	else
	{
		if(ThisBuffer->SerialIndex > ThisBuffer->SerialBuffer)
		{
			ThisBuffer->SerialRequest->IOSer.io_Command	= CMD_WRITE;
			ThisBuffer->SerialRequest->IOSer.io_Data	= ThisBuffer->SerialBuffer;
			ThisBuffer->SerialRequest->IOSer.io_Length	= ThisBuffer->SerialIndex - ThisBuffer->SerialBuffer;

			DoIO(ThisBuffer->SerialRequest);
		}
	}

	ThisBuffer->IsBusy		= FALSE;
	ThisBuffer->SerialIndex	= ThisBuffer->SerialBuffer;

	ThisBuffer = NextBuffer;
	NextBuffer = Swap;

	if(ThisBuffer->SerialIndex > ThisBuffer->SerialBuffer)
	{
		ThisBuffer->SerialRequest->IOSer.io_Command	= CMD_WRITE;
		ThisBuffer->SerialRequest->IOSer.io_Data	= ThisBuffer->SerialBuffer;
		ThisBuffer->SerialRequest->IOSer.io_Length	= ThisBuffer->SerialIndex - ThisBuffer->SerialBuffer;

		DoIO(ThisBuffer->SerialRequest);
	}

	ThisBuffer->IsBusy		= FALSE;
	ThisBuffer->SerialIndex	= ThisBuffer->SerialBuffer;

	while(len > 2 * ThisBuffer->SerialSize)
	{
		ThisBuffer->SerialRequest->IOSer.io_Command	= CMD_WRITE;
		ThisBuffer->SerialRequest->IOSer.io_Data	= s;
		ThisBuffer->SerialRequest->IOSer.io_Length	= ThisBuffer->SerialSize;

		s	+= ThisBuffer->SerialSize;
		len	-= ThisBuffer->SerialSize;

		DoIO(ThisBuffer->SerialRequest);
	}

	CopyMem(s,ThisBuffer->SerialBuffer,MIN(len,ThisBuffer->SerialSize));

	ThisBuffer->IsBusy							= TRUE;
	ThisBuffer->SerialIndex						= ThisBuffer->SerialBuffer + MIN(len,ThisBuffer->SerialSize);
	ThisBuffer->SerialRequest->IOSer.io_Command	= CMD_WRITE;
	ThisBuffer->SerialRequest->IOSer.io_Data	= ThisBuffer->SerialBuffer;
	ThisBuffer->SerialRequest->IOSer.io_Length	= MIN(len,ThisBuffer->SerialSize);

	len	-= ThisBuffer->SerialRequest->IOSer.io_Length;
	s	+= ThisBuffer->SerialRequest->IOSer.io_Length;

	ClrSignal(SIG_SERWRITE);
	SendIO(ThisBuffer->SerialRequest);

	if(len > 0)
	{
		CopyMem(s,NextBuffer->SerialBuffer,len);

		NextBuffer->SerialIndex = NextBuffer->SerialBuffer + len;
	}
}

	/* breakfunc():
	 *
	 *	Cleanup routine for SAS/C.
	 */

static int
breakfunc(void)
{
	CloseAll();

	return(1);
}

	/* sys_init(VOID):
	 *
	 *	Initialize this driver implementation.
	 */

VOID
sys_init(VOID)
{
	if(!OpenAll(device,port))
	{
		CloseAll();

		endprog(2);
	}

	onbreak(breakfunc);
}

	/* sys_reset(VOID):
	 *
	 *	Perform cleanup for this driver implementation.
	 */

VOID
sys_reset(VOID)
{
	CloseAll();
}

	/* sys_idle(VOID):
	 *
	 *	This routine gets called when the system is idle.
	 *	That's a nice one. We are supposed to call the
	 *	system scheduler, etc.
	 */

VOID
sys_idle(VOID)
{
	ULONG Signals;

	if(ReadBuffer->SerialFilled <= 0 && !ReadBuffer->IsBusy)
	{
		ReadBuffer->IsBusy							= TRUE;
		ReadBuffer->SerialIndex						= ReadBuffer->SerialBuffer;
		ReadBuffer->SerialRequest->IOSer.io_Command	= CMD_READ;
		ReadBuffer->SerialRequest->IOSer.io_Data	= ReadBuffer->SerialBuffer;
		ReadBuffer->SerialRequest->IOSer.io_Length	= 1;

		ClrSignal(SIG_SERREAD);
		SendIO(ReadBuffer->SerialRequest);
	}

	TimeRequest->tr_node.io_Command	= TR_ADDREQUEST;
	TimeRequest->tr_time.tv_secs	= 1;
	TimeRequest->tr_time.tv_micro	= 0;

	ClrSignal(SIG_TIMER);
	SendIO(TimeRequest);

	Signals = Wait(SIG_SERREAD | SIG_SERWRITE | SIG_CONREAD | SIG_WINDOW | SIG_TIMER);

	if(!(Signals & SIG_TIMER))
	{
		if(!CheckIO(TimeRequest))
			AbortIO(TimeRequest);
	}

	WaitIO(TimeRequest);

	if(Signals & SIG_SERREAD)
	{
		WaitIO(ReadBuffer->SerialRequest);

		ReadBuffer->IsBusy			= FALSE;
		ReadBuffer->SerialFilled	= ReadBuffer->SerialRequest->IOSer.io_Actual;
		ReadBuffer->SerialIndex		= ReadBuffer->SerialBuffer;

		ReadBuffer->SerialRequest->IOSer.io_Command = SDCMD_QUERY;

		DoIO(ReadBuffer->SerialRequest);

		if(ReadBuffer->SerialRequest->IOSer.io_Actual)
		{
			LONG Size = ReadBuffer->SerialSize - ReadBuffer->SerialFilled;

			if(Size > 0)
			{
				if(Size > ReadBuffer->SerialRequest->IOSer.io_Actual)
					Size = ReadBuffer->SerialRequest->IOSer.io_Actual;

				ReadBuffer->SerialRequest->IOSer.io_Command	= CMD_READ;
				ReadBuffer->SerialRequest->IOSer.io_Data	= ReadBuffer->SerialBuffer + ReadBuffer->SerialFilled;
				ReadBuffer->SerialRequest->IOSer.io_Length	= Size;

				DoIO(ReadBuffer->SerialRequest);

				ReadBuffer->SerialFilled += ReadBuffer->SerialRequest->IOSer.io_Actual;
			}
		}
	}

	if(Signals & SIG_SERWRITE)
	{
		struct SerialBuffer *Swap = ThisBuffer;

		WaitIO(ThisBuffer->SerialRequest);

		ThisBuffer->IsBusy		= FALSE;
		ThisBuffer->SerialIndex	= ThisBuffer->SerialBuffer;

		ThisBuffer = NextBuffer;
		NextBuffer = Swap;
	}

	if(Signals & SIG_CONREAD)
	{
		WaitIO(ConsoleReadRequest);

		ConsoleReady = TRUE;
	}

	if(Signals & SIG_WINDOW)
		WindowReady = TRUE;
}

	/* com_outfull(VOID):
	 *
	 *	Return number of bytes still to be transferred.
	 */

int
com_outfull(VOID)
{
	return(ThisBuffer->SerialIndex - ThisBuffer->SerialBuffer + NextBuffer->SerialIndex - NextBuffer->SerialBuffer);
}

	/* carrier(VOID):
	 *
	 *	Return current carrier status.
	 */

int
carrier(VOID)
{
	if(nocarrier)
		return(1);
	else
	{
		if(!ThisBuffer->IsBusy)
		{
			ThisBuffer->SerialRequest->IOSer.io_Command = SDCMD_QUERY;

			DoIO(ThisBuffer->SerialRequest);

			if(ThisBuffer->SerialRequest->io_Status & CIAF_COMCD)
				return(0);
			else
				return(1);
		}
		else
		{
			NextBuffer->SerialRequest->IOSer.io_Command = SDCMD_QUERY;

			DoIO(NextBuffer->SerialRequest);

			if(NextBuffer->SerialRequest->io_Status & CIAF_COMCD)
				return(0);
			else
				return(1);
		}
	}
}

	/* com_flush(VOID):
	 *
	 *	Make sure all pending data gets written.
	 */

VOID
com_flush(VOID)
{
	if(ThisBuffer->IsBusy)
	{
		WaitIO(ThisBuffer->SerialRequest);

		ThisBuffer->IsBusy		= FALSE;
		ThisBuffer->SerialIndex	= ThisBuffer->SerialBuffer;
	}

	if(NextBuffer->SerialIndex > NextBuffer->SerialBuffer)
	{
		NextBuffer->SerialRequest->IOSer.io_Command	= CMD_WRITE;
		NextBuffer->SerialRequest->IOSer.io_Data	= NextBuffer->SerialBuffer;
		NextBuffer->SerialRequest->IOSer.io_Length	= NextBuffer->SerialIndex - NextBuffer->SerialBuffer;

		DoIO(NextBuffer->SerialRequest);

		NextBuffer->SerialIndex = NextBuffer->SerialBuffer;
	}
}

	/* com_putbyte(byte c):
	 *
	 *	Transmit a single byte, queueing it if necessary.
	 */

VOID
com_putbyte(byte c)
{
	if(ThisBuffer->IsBusy)
	{
		if(NextBuffer->SerialIndex + 1 >= NextBuffer->SerialTop)
		{
			struct SerialBuffer *Swap = ThisBuffer;

			WaitIO(ThisBuffer->SerialRequest);

			ThisBuffer->IsBusy		= FALSE;
			ThisBuffer->SerialIndex	= ThisBuffer->SerialBuffer;

			ThisBuffer = NextBuffer;
			NextBuffer = Swap;

			ThisBuffer->IsBusy							= TRUE;
			ThisBuffer->SerialRequest->IOSer.io_Command	= CMD_WRITE;
			ThisBuffer->SerialRequest->IOSer.io_Data	= ThisBuffer->SerialBuffer;
			ThisBuffer->SerialRequest->IOSer.io_Length	= ThisBuffer->SerialIndex - ThisBuffer->SerialBuffer;

			ClrSignal(SIG_SERWRITE);
			SendIO(ThisBuffer->SerialRequest);
		}

		*NextBuffer->SerialIndex++ = c;
	}
	else
	{
		if(ThisBuffer->SerialIndex + 1 < ThisBuffer->SerialTop)
		{
			*ThisBuffer->SerialIndex++ = c;

			ThisBuffer->IsBusy							= TRUE;
			ThisBuffer->SerialRequest->IOSer.io_Command	= CMD_WRITE;
			ThisBuffer->SerialRequest->IOSer.io_Data	= ThisBuffer->SerialBuffer;
			ThisBuffer->SerialRequest->IOSer.io_Length	= 1;

			ClrSignal(SIG_SERWRITE);
			SendIO(ThisBuffer->SerialRequest);
		}
		else
		{
			ThisBuffer->IsBusy							= TRUE;
			ThisBuffer->SerialRequest->IOSer.io_Command	= CMD_WRITE;
			ThisBuffer->SerialRequest->IOSer.io_Data	= ThisBuffer->SerialBuffer;
			ThisBuffer->SerialRequest->IOSer.io_Length	= ThisBuffer->SerialIndex - ThisBuffer->SerialBuffer;

			ClrSignal(SIG_SERWRITE);
			SendIO(ThisBuffer->SerialRequest);

			*NextBuffer->SerialIndex++ = c;
		}
	}
}

	/* com_purge(VOID):
	 *
	 *	Clear the read/write buffers.
	 */

VOID
com_purge(VOID)
{
	if(ThisBuffer->IsBusy)
	{
		if(!CheckIO(ThisBuffer->SerialRequest))
			AbortIO(ThisBuffer->SerialRequest);

		WaitIO(ThisBuffer->SerialRequest);
	}

	ThisBuffer->IsBusy		= FALSE;
	ThisBuffer->SerialIndex	= ThisBuffer->SerialBuffer;

	NextBuffer->IsBusy		= FALSE;
	NextBuffer->SerialIndex	= NextBuffer->SerialBuffer;

	if(ReadBuffer->IsBusy)
	{
		if(!CheckIO(ReadBuffer->SerialRequest))
			AbortIO(ReadBuffer->SerialRequest);

		WaitIO(ReadBuffer->SerialRequest);
	}

	ReadBuffer->IsBusy			= FALSE;
	ReadBuffer->SerialIndex		= ReadBuffer->SerialBuffer;
	ReadBuffer->SerialFilled	= 0;

	ThisBuffer->SerialRequest->IOSer.io_Command = CMD_CLEAR;
	DoIO(ThisBuffer->SerialRequest);
}

	/* com_dump(VOID):
	 *
	 *	Wait for asynchronous write request to terminate.
	 */

VOID
com_dump(VOID)
{
	com_flush();
}

	/* com_getbyte(VOID):
	 *
	 *	Read a single byte from the serial line. If not available,
	 *	return EOF.
	 */

int
com_getbyte(VOID)
{
	int Result;

	if(ReadBuffer->SerialFilled <= 0)
	{
		if(ReadBuffer->IsBusy)
		{
			if(!CheckIO(ReadBuffer->SerialRequest))
				return(EOF);
			else
				WaitIO(ReadBuffer->SerialRequest);

			ReadBuffer->IsBusy			= FALSE;
			ReadBuffer->SerialFilled	= ReadBuffer->SerialRequest->IOSer.io_Actual;
			ReadBuffer->SerialIndex		= ReadBuffer->SerialBuffer;

			ReadBuffer->SerialRequest->IOSer.io_Command = SDCMD_QUERY;

			DoIO(ReadBuffer->SerialRequest);

			if(ReadBuffer->SerialRequest->IOSer.io_Actual)
			{
				LONG Size = ReadBuffer->SerialSize - ReadBuffer->SerialFilled;

				if(Size > 0)
				{
					if(Size > ReadBuffer->SerialRequest->IOSer.io_Actual)
						Size = ReadBuffer->SerialRequest->IOSer.io_Actual;

					ReadBuffer->SerialRequest->IOSer.io_Command	= CMD_READ;
					ReadBuffer->SerialRequest->IOSer.io_Data	= ReadBuffer->SerialBuffer + ReadBuffer->SerialFilled;
					ReadBuffer->SerialRequest->IOSer.io_Length	= Size;

					DoIO(ReadBuffer->SerialRequest);

					ReadBuffer->SerialFilled += ReadBuffer->SerialRequest->IOSer.io_Actual;
				}
			}
		}
		else
		{
			ReadBuffer->SerialRequest->IOSer.io_Command = SDCMD_QUERY;

			DoIO(ReadBuffer->SerialRequest);

			if(!ReadBuffer->SerialRequest->IOSer.io_Actual)
				return(EOF);
			else
			{
				ReadBuffer->SerialRequest->IOSer.io_Command	= CMD_READ;
				ReadBuffer->SerialRequest->IOSer.io_Data	= ReadBuffer->SerialBuffer;
				ReadBuffer->SerialRequest->IOSer.io_Length	= MIN(ReadBuffer->SerialRequest->IOSer.io_Actual,ReadBuffer->SerialSize);

				DoIO(ReadBuffer->SerialRequest);

				if(!ReadBuffer->SerialRequest->IOSer.io_Actual)
					return(EOF);
				else
				{
					ReadBuffer->SerialFilled	= ReadBuffer->SerialRequest->IOSer.io_Actual;
					ReadBuffer->SerialIndex		= ReadBuffer->SerialBuffer;
				}
			}
		}
	}

	Result = *ReadBuffer->SerialIndex++;

	ReadBuffer->SerialFilled--;

	if(ReadBuffer->SerialFilled <= 0)
	{
		ReadBuffer->IsBusy					= TRUE;
		ReadBuffer->SerialIndex				= ReadBuffer->SerialBuffer;

		ReadBuffer->SerialRequest->IOSer.io_Command	= CMD_READ;
		ReadBuffer->SerialRequest->IOSer.io_Data	= ReadBuffer->SerialBuffer;
		ReadBuffer->SerialRequest->IOSer.io_Length	= 1;

		ClrSignal(SIG_SERREAD);
		SendIO(ReadBuffer->SerialRequest);
	}

	return(Result);
}

	/* setstamp(STRPTR Name,LONG Time):
	 *
	 *	Set time/date of a file.
	 */

VOID
setstamp(char *Name,long Time)
{
	struct DateStamp Date;
	ULONG Seconds;

	DB(kprintf("setstamp |%s| %ld\n",Name,Time));

		/* Translate it into an Amiga date. */

	if(Time > GMT_Offset)
		Seconds = Time - GMT_Offset;
	else
		Seconds = 0;

	Date.ds_Days	= Seconds / (24 * 60 * 60);
	Date.ds_Minute	= (Seconds % (24 * 60 * 60)) / 60;
	Date.ds_Tick	= (Seconds % 60) * TICKS_PER_SECOND;

	SetFileDate(Name,&Date);
}

	/* freespace(STRPTR DrivePath):
	 *
	 *	Get free disk space for specified drive.
	 */

long
freespace(char *DrivePath)
{
	struct DevProc	*DevProc;
	struct DosList	*DosList;
	BOOL			 GoodDevice;
	LONG			 Size;

	if(!DrivePath)
		DrivePath = "";

	DB(kprintf("freespace |%s|\n",DrivePath));

	DevProc = GetDeviceProc(DrivePath,NULL);

	GoodDevice = FALSE;
	Size = (LONG)((ULONG)~0 >> 2);

	if(DosList = LockDosList(LDF_DEVICES | LDF_READ))
	{
		while(DosList = NextDosEntry(DosList,LDF_DEVICES))
		{
			if(DosList->dol_Task == DevProc->dvp_Port)
			{
				struct FileSysStartupMsg *FSSM = (struct FileSysStartupMsg *)BADDR(DosList->dol_misc.dol_handler.dol_Startup);

				if(TypeOfMem(FSSM))
				{
					struct DosEnvec *DosEnvec = (struct DosEnvec *)BADDR(FSSM->fssm_Environ);
					STRPTR Name = (STRPTR)BADDR(FSSM->fssm_Device);

					if(TypeOfMem(DosEnvec) && TypeOfMem(Name))
					{
						if(Name[0] > 0 && !Name[(WORD)Name[0] + 1])
						{
							struct IOStdReq IORequest;

							memset(&IORequest,0,sizeof(IORequest));
							IORequest.io_Message.mn_Length = sizeof(IORequest);

							if(!OpenDevice(Name + 1,FSSM->fssm_Unit,&IORequest,FSSM->fssm_Unit))
							{
								CloseDevice(&IORequest);

								if(DosEnvec->de_TableSize > 0 && DosEnvec->de_LowCyl <= DosEnvec->de_HighCyl)
									GoodDevice = TRUE;
							}
						}
					}
				}
			}
		}

		UnLockDosList(LDF_DEVICES | LDF_READ);
	}

	FreeDeviceProc(DevProc);

	if(GoodDevice)
	{
		struct InfoData *InfoData;

		if(InfoData = (struct InfoData *)AllocVec(sizeof(struct InfoData),MEMF_ANY | MEMF_PUBLIC))
		{
			UBYTE NewName[MAX_FILENAME_LENGTH],*Index;
			BPTR FileLock;

			memcpy(NewName,DrivePath,sizeof(NewName) - 1);

			NewName[sizeof(NewName) - 1] = 0;

			Index = PathPart(NewName);

			*Index = 0;

			FileLock = Lock(NewName,ACCESS_READ);

			if(FileLock)
			{
				if(Info(FileLock,InfoData))
					Size = InfoData->id_BytesPerBlock * (InfoData->id_NumBlocks - InfoData->id_NumBlocksUsed);

				UnLock(FileLock);
			}

			FreeVec(InfoData);
		}
	}

	return(Size);
}

	/* ffirst(char *FileSpec):
	 *
	 *	Return name of first file matching the given specs.
	 */

char *
ffirst(char *filespec)
{
	AnchorUsed = TRUE;

	DB(kprintf("ffirst |%s|\n",filespec));

	if(MatchFirst(filespec,Anchor))
		return(NULL);
	else
		return((char *)Anchor->ap_Buf);
}

	/* fnext(VOID):
	 *
	 *	Return name of next file matching the given specs.
	 */

char *
fnext(VOID)
{
	AnchorUsed = TRUE;

	DB(kprintf("fnext\n"));

	if(MatchNext(Anchor))
		return(NULL);
	else
		return((char *)Anchor->ap_Buf);
}

	/* time(time_t *timeptr):
	 *
	 *	Get the current time.
	 */

time_t
time(time_t *timeptr)
{
	struct timeval	Now;
	time_t			CurrentTime;

	DB(kprintf("time 0x%08lx\n",timeptr));

		/* If the timer is already available,
		 * just read the time. Otherwise, open what
		 * we need to tell the time.
		 */

	if(TimerBase)
		GetSysTime(&Now);
	else
		UpdateTime(&Now);

		/* Determine the current time, taking the time
		 * zone into account.
		 */

	CurrentTime = (time_t)(Now.tv_secs + GMT_Offset);

	if(timeptr)
		*timeptr = CurrentTime;

	return(CurrentTime);
}

	/* localtime(const time_t *t):
	 *
	 *	Convert UTC into local time.
	 */

struct tm *
localtime(const time_t *t)
{
	STATIC struct tm Time;

	ULONG				Seconds,
						Delta;
	struct ClockData	ClockData;
	BOOL				CloseIt = FALSE;

	DB(kprintf("localtime 0x%08lx\n",t));

		/* We need utility.library for the date conversion. */

	if(!UtilityBase)
	{
		if(UtilityBase = OpenLibrary("utility.library",37))
			CloseIt = TRUE;
	}

		/* Any luck? */

	if(!UtilityBase)
	{
		memset(&Time,0,sizeof(struct tm));

		return(&Time);
	}

		/* Update the time data. */

	if(!TimerBase)
		UpdateTime(NULL);

		/* Add the offset. */

	if(*t < GMT_Offset)
		Seconds = 0;
	else
		Seconds = (ULONG)*t - GMT_Offset;

		/* Convert the seconds into time data. */

	Amiga2Date(Seconds,&ClockData);

		/* Convert the time data. */

	Time.tm_sec		= ClockData.sec;
	Time.tm_min		= ClockData.min;
	Time.tm_hour	= ClockData.hour;
	Time.tm_mday	= ClockData.mday;
	Time.tm_mon		= ClockData.month - 1;
	Time.tm_year	= ClockData.year - 1900;
	Time.tm_wday	= ClockData.wday;

		/* No daylight savings time info is provided. */

	Time.tm_isdst = 0;

		/* We will need to fill in the yday entry. */

	ClockData.mday	= 1;
	ClockData.month	= 1;

	Delta = Date2Amiga(&ClockData);

	Time.tm_yday = (Seconds - Delta) / (24 * 60 * 60) + 1;

		/* Clean up if necessary. */

	if(CloseIt)
	{
		CloseLibrary(UtilityBase);

		UtilityBase = NULL;
	}

	return(&Time);
}

	/* stat(const char *file,struct stat *st):
	 *
	 *	Get information on a file.
	 */

int
stat(const char *file,struct stat *st)
{
	BPTR FileLock;
	D_S(struct FileInfoBlock,FileInfo);

	DB(kprintf("stat |%s| 0%08lx\n",file,st));

		/* Try to get a lock on the file. */

	if(FileLock = Lock((STRPTR)file,ACCESS_READ))
	{
		STATIC char	Volume[MAX_FILENAME_LENGTH],Comment[80];
		D_S(struct InfoData,InfoData);

			/* Get information on file and filing system. */

		if(Examine(FileLock,FileInfo) && Info(FileLock,InfoData))
		{
			unsigned short mode = 0;

				/* Try to get the name of the volume. */

			if(!NameFromLock(FileLock,Volume,sizeof(Volume)))
				Volume[0] = 0;
			else
			{
				WORD i;

					/* Chop off everything after the colon. */

				for(i = 0 ; i < sizeof(Volume) ; i++)
				{
					if(Volume[i] == ':')
					{
						Volume[i + 1] = 0;

						break;
					}
				}
			}

			UnLock(FileLock);

				/* Build the protection bits. */

			if(!(FileInfo->fib_Protection & FIBF_EXECUTE))
				mode |= S_IEXECUTE;

			if(!(FileInfo->fib_Protection & FIBF_DELETE))
				mode |= S_IDELETE;

			if(!(FileInfo->fib_Protection & FIBF_READ))
				mode |= S_IREAD;

			if(!(FileInfo->fib_Protection & FIBF_WRITE))
				mode |= S_IWRITE;

			if(!(FileInfo->fib_Protection & FIBF_ARCHIVE))
				mode |= S_IARCHIVE;

			if(FileInfo->fib_Protection & FIBF_PURE)
				mode |= S_IPURE;

			if(FileInfo->fib_Protection & FIBF_SCRIPT)
				mode |= S_ISCRIPT;

				/* Keep the comment. */

			CopyMem(FileInfo->fib_Comment,Comment,79);
			Comment[79] = 0;

				/* Fix the time if necessary. */

			if(!TimerBase)
				UpdateTime(NULL);

				/* Fill in the data. */

			st->st_mode		= mode;
			st->st_ino		= FileInfo->fib_DiskKey;
			st->st_dev		= InfoData->id_DiskType;
			st->st_rdev		= Volume;
			st->st_nlink	= FileInfo->fib_DirEntryType == ST_SOFTLINK || FileInfo->fib_DirEntryType == ST_LINKDIR || FileInfo->fib_DirEntryType == ST_LINKFILE;
			st->st_uid		= FileInfo->fib_OwnerUID;
			st->st_gid		= FileInfo->fib_OwnerGID;
			st->st_size		= FileInfo->fib_Size;
			st->st_atime	= GMT_Offset + (FileInfo->fib_Date.ds_Days * 24 * 60 + FileInfo->fib_Date.ds_Minute) * 60 + (FileInfo->fib_Date.ds_Tick / TICKS_PER_SECOND);
			st->st_mtime	= st->st_atime;
			st->st_ctime	= st->st_atime;
			st->st_type		= FileInfo->fib_DirEntryType;
			st->st_comment	= Comment;

				/* Success. */

			return(0);
		}

		UnLock(FileLock);
	}

		/* What else should I choose? */

	errno = _OSERR = EIO;

	return(-1);
}
