// ipxnet.c

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <string.h>
#include <process.h>
#include <signal.h>
#ifndef _MSC_VER
#include <values.h>
#else
#include <limits.h>
#define MAXLONG LONG_MAX 
#endif

#include "ipxnet.h"

#ifdef _MSC_VER
// Redefine FP_OFF and FP_SEG to work with addresses and not pointers
// also, MS's inline assembler doesn't seem to like the SEG and OFFSET operators
#undef FP_OFF
#define FP_OFF(p) ((unsigned)(((unsigned long)(p)) & 0xffff))
#undef FP_SEG
#define FP_SEG(p) ((unsigned)(((unsigned long)(p)) >> 16))
#endif

/*
==========================================================================
===

                              IPX PACKET DRIVER

==========================================================================
===
*/

packet_t	packets[NUMPACKETS];

nodeadr_t	nodeadr[MAXNETNODES+1];  // first is local, last is broadcast

nodeadr_t	remoteadr;               // set by each GetPacket

localadr_t	localadr;           // set at startup

extern int socketid;

#ifndef _MSC_VER
void far (*IPX)(void);
#else
void (far *IPX)(void);

union _REGS regs;
struct _SREGS sregs;

BYTE _AL;
WORD _AX, _BX, _DX;
WORD _SI, _ES;

#endif

// localtime renamed local_time so as not to conflict w/ localtime()
// remotetime renamed as well
long	local_time;          // for time stamp in packets
long	remote_time;

//safty dictates we save and restore the di, si, ds and bp registers
#define PUSH_REGS __asm push di __asm push si __asm push ds __asm push bp
#define POP_REGS __asm pop bp __asm pop ds __asm pop si __asm pop di

void sigint(int);

//===========================================================================

int OpenSocket(short socketNumber)
	{
#ifdef DEBUG
	fprintf(stderr, "OpenSocket(0x%04x) - called\n", socketNumber);
#endif
#ifndef _MSC_VER
	_DX = socketNumber;
	_BX = 0;
	_AL = 0;
	IPX();
#else
	__asm
		{
		PUSH_REGS
		mov dx, socketNumber;
		mov bx, 0;
		mov al, 0;
		call IPX;
		POP_REGS
		mov _AL, al;
		mov _DX, dx;
		}
#endif

	if(_AL)
		Error ("OpenSocket: 0x%x", _AL);

#ifdef DEBUG
	fprintf(stderr, "OpenSocket() - returning 0x%4x\n", _DX);
#endif

	return _DX;
	}


void CloseSocket(short socketNumber)
	{
#ifdef DEBUG
	fprintf(stderr, "CloseSocket(0x%04x) - called\n", socketNumber);
#endif
#ifndef _MSC_VER
	_DX = socketNumber;
	_BX = 1;
	IPX();
#else
	__asm
		{
		PUSH_REGS
		mov dx, socketNumber;
		mov bx, 1;
		call IPX;
		POP_REGS
		}
#endif
#ifdef DEBUG
	fprintf(stderr, "CloseSocket() - returning\n");
#endif
	}

void ListenForPacket(ECB *ecb)
	{
#ifdef DEBUG
	fprintf(stderr, "ListenForPacket(%p) - called\n", ecb);
#endif
#ifndef _MSC_VER
	_SI = FP_OFF(ecb);
	_ES = FP_SEG(ecb);
	_BX = 4;
	IPX();
#else
	_SI = FP_OFF(ecb);
	_ES = FP_SEG(ecb);

	__asm
		{
		PUSH_REGS
		mov si, _SI;
		mov es, _ES;
		mov bx, 4;
		call IPX;
		POP_REGS
		mov _AL, al;
		}
#endif
	if(_AL)
		Error ("ListenForPacket: 0x%x", _AL);
#ifdef DEBUG
	fprintf(stderr, "ListenForPacket() - returning\n");
#endif
	}

void GetLocalAddress (void)
	{
#ifdef DEBUG 
	fprintf(stderr, "GetLocalAddress() - called\n");
#endif
#ifndef _MSC_VER
	_SI = FP_OFF(&localadr);
	_ES = FP_SEG(&localadr);
	_BX = 9;
	IPX();
#else
	_SI = FP_OFF(&localadr);
	_ES = FP_SEG(&localadr);

	__asm
		{
		PUSH_REGS
		mov si, _SI;
 		mov es, _ES;
		mov bx, 9;
		call IPX;
		POP_REGS
		}
#endif
#ifdef DEBUG 
	fprintf(stderr, "GetLocalAddress() - returning\n");
#endif
	}

/*
====================
=
= InitNetwork
=
====================
*/

void InitNetwork (void)
	{
	int     i,j;

#ifdef DEBUG
	fprintf(stderr, "InitNetWork() - called\n");
#endif
//
// get IPX function address
//
#ifndef _MSC_VER
	_AX = 0x7a00;
	geninterrupt(0x2f);
	if(_AL != 0xff)
		Error ("IPX not detected\n");
	IPX = MK_FP(_ES, _DI);
#else
	regs.x.ax = 0x7a00;
	_int86x(0x2f, &regs, &regs, &sregs);
	if(regs.h.al != 0xff)
		Error ("IPX not detected\n");
	IPX = _MK_FP(sregs.es, regs.x.di);
#endif

// Set signal so CloseSocket is called upon ctr-c
    signal(SIGINT, sigint);

//
// allocate a socket for sending and receiving
//

	socketid = OpenSocket ( (socketid>>8) + ((socketid&255)<<8) );

	GetLocalAddress();

//
// set up several receiving ECBs
//
	memset (packets,0,NUMPACKETS*sizeof(packet_t));

	for (i=1 ; i<NUMPACKETS ; i++)
		{
		packets[i].ecb.ECBSocket = socketid;
		packets[i].ecb.FragmentCount = 1;
		packets[i].ecb.fAddress[0] = FP_OFF(&packets[i].ipx);
		packets[i].ecb.fAddress[1] = FP_SEG(&packets[i].ipx);
		packets[i].ecb.fSize = sizeof(packet_t)-sizeof(ECB);

		ListenForPacket (&packets[i].ecb);
		}

//
// set up a sending ECB
//
	memset (&packets[0],0,sizeof(packets[0]));

	packets[0].ecb.ECBSocket = socketid;
	packets[0].ecb.FragmentCount = 2;
	packets[0].ecb.fAddress[0] = FP_OFF(&packets[0].ipx);
	packets[0].ecb.fAddress[1] = FP_SEG(&packets[0].ipx);
	for (j=0 ; j<4 ; j++)
		packets[0].ipx.dNetwork[j] = localadr.network[j];
	packets[0].ipx.dSocket[0] = socketid&255;
	packets[0].ipx.dSocket[1] = socketid>>8;
	packets[0].ecb.f2Address[0] = FP_OFF(&doomcom.data);
	packets[0].ecb.f2Address[1] = FP_SEG(&doomcom.data);

// known local node at 0
	for (i=0 ; i<6 ; i++)
		nodeadr[0].node[i] = localadr.node[i];

// broadcast node at MAXNETNODES
	for (j=0 ; j<6 ; j++)
		nodeadr[MAXNETNODES].node[j] = 0xff;
#ifdef DEBUG
	fprintf(stderr, "InitNetWork() - returning\n");
#endif
	}


/*
====================
=
= ShutdownNetwork
=
====================
*/

void ShutdownNetwork (void)
	{
#ifdef DEBUG
	fprintf(stderr, "ShutdownNetwork() - called\n");
#endif
	if (IPX)
		CloseSocket (socketid);
#ifdef DEBUG
	fprintf(stderr, "ShutdownNetwork() - returning\n");
#endif
	}

/*
============
=
= sigint
=
= Gracefully abort if ctr-c is pressed
============
*/
void sigint(int sig)
	{
	signal(SIGINT, SIG_IGN);
	ShutDownNetwork();
	abort();
	}

/*
==============
=
= SendPacket
=
= A destination of MAXNETNODES is a broadcast
==============
*/

void SendPacket (int destination)
	{
	int j;
#ifdef DEBUG
	fprintf(stderr, "SendPacket(%i) - called\n", destination);
#endif

// set the time
	packets[0].time = local_time;

// set the address
	for (j=0 ; j<6 ; j++)
		packets[0].ipx.dNode[j] = 
		packets[0].ecb.ImmediateAddress[j] =
               nodeadr[destination].node[j];

// set the length (ipx + time + datalength)
	packets[0].ecb.fSize = sizeof(IPXPacket) + 4;
	packets[0].ecb.f2Size = doomcom.datalength + 4;

// send the packet
#ifndef _MSC_VER
	_SI = FP_OFF(&packets[0]);
	_ES = FP_SEG(&packets[0]);
	_BX = 3;
	IPX();
	if(_AL)
		Error("SendPacket: 0x%x", _AL);
#else
	_SI = FP_OFF(&packets[0]);
	_ES = FP_SEG(&packets[0]);
	__asm
		{
		PUSH_REGS
		mov si, _SI;
 		mov es, _ES;
 		mov bx, 3;
 		call IPX;
		POP_REGS
		mov _AL, al;
		}

	if(_AL)
		Error("SendPacket: 0x%x", _AL);
#endif

	while(packets[0].ecb.InUseFlag != 0)
		{
          // IPX Relinquish Control - polled drivers MUST have this here!
#ifndef _MSC_VER
		_BX = 10;
		IPX();
#else
		__asm
			{
			PUSH_REGS
			mov bx, 10;
			call IPX;
			POP_REGS
			}
#endif
		}
#ifdef DEBUG
	fprintf(stderr, "SendPacket() - returning\n");
#endif
	}


unsigned short ShortSwap (unsigned short i)
	{
	return ((i&255)<<8) + ((i>>8)&255);
	}

/*
==============
=
= GetPacket
=
= Returns false if no packet is waiting
=
=ning 1=============
*/

int GetPacket (void)
	{
	int			packetnum;
	int			i, j;
	long		besttic;
    packet_t	*packet;

#ifdef DEBUG
	//fprintf(stderr, "GetPacket() - called\n");
#endif
// if multiple packets are waiting, return them in order by time

     besttic = MAXLONG;
     packetnum = -1;
     doomcom.remotenode = -1;

     for ( i = 1 ; i < NUMPACKETS ; i++)
		{
		if (packets[i].ecb.InUseFlag)
			{
			continue;
			}

		if (packets[i].time < besttic)
			{
			besttic = packets[i].time;
			packetnum = i;
			}
		}

	if (besttic == MAXLONG)
		{
#ifdef DEBUG
		//fprintf(stderr, "GetPacket() - returning 0 (no packets)\n");
#endif
		return 0;                           // no packets
		}

	packet = &packets[packetnum];

	if (besttic == -1 && local_time != -1)
		{
		ListenForPacket (&packet->ecb);
#ifdef DEBUG
		//fprintf(stderr, "GetPacket() - returning 0 (setup broadcast from other game)\n");
#endif
		return 0;            	// setup broadcast from other game
		}

	remote_time = besttic;

//
// got a good packet
//
	if (packet->ecb.CompletionCode)
		Error ("GetPacket: ecb.ComletionCode = 0x%x",packet->ecb.CompletionCode);

// set remoteadr to the sender of the packet
 	memcpy (&remoteadr, packet->ipx.sNode, sizeof(remoteadr));
	for (i=0 ; i<doomcom.numnodes ; i++)
		if (!memcmp(&remoteadr, &nodeadr[i], sizeof(remoteadr)))
			break;
	if (i < doomcom.numnodes)
		doomcom.remotenode = i;
	else
		{
		if (local_time != -1)
			{    // this really shouldn't happen
			ListenForPacket (&packet->ecb);
#ifdef DEBUG
		//fprintf(stderr, "GetPacket() - returning 0 (this really shouldn't happen)\n");
#endif
			return 0;
			}
		}

// copy out the data
	doomcom.datalength = ShortSwap(packet->ipx.PacketLength) - 38;
	memcpy (&doomcom.data, &packet->data, doomcom.datalength);

// repost the ECB
	ListenForPacket (&packet->ecb);

#ifdef DEBUG
	//fprintf(stderr, "GetPacket() - returning 1 (got a packet)\n");
#endif
	return 1;
	}

