/*
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.  
COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
*/
/*
 * $Source: f:/miner/source/main/rcs/modem.c $
 * $Revision: 2.13 $
 * $Author: john $
 * $Date: 1995/11/28 16:25:05 $
 * 
 * Modem support code
 *
 * $Log: modem.c $
 * Revision 2.13  1995/11/28  16:25:05  john
 * Added fixed for Rockwell voice modems thats waits for OK after sending
 * initial AT to modem to detect if there is a modem on the port.
 * 
 * Revision 2.12  1995/06/14  16:32:09  john
 * Fixed bug where all modem games were anarchy.
 * 
 * Revision 2.11  1995/05/29  16:17:59  john
 * Added support for Rockwell that takes out all net modes except for anarchy.
 * 
 * Revision 2.10  1995/04/23  16:06:38  john
 * Moved rinvul into modem/null modem menu.
 * 
 * Revision 2.9  1995/04/09  14:43:20  john
 * Took out mem-overwrite error when > 25 char phone numbers.
 * 
 * Revision 2.8  1995/04/06  12:13:36  john
 * Made phone numbers be 32 characters max.
 * 
 * Revision 2.7  1995/03/31  14:16:33  john
 * Mode phone numbers 30 chars long.
 * 
 * Revision 2.6  1995/03/30  16:39:44  john
 * Incread phone numbers to 25 characters.
 * 
 * Revision 2.5  1995/03/30  16:04:49  john
 * Increased modem number length.
 *  
 * 
 * Revision 2.4  1995/03/29  15:33:23  john
 * Took out RTS/CTS handshaking. Made it enabled with -RTSCTS.
 * 
 * Revision 2.3  1995/03/21  14:41:10  john
 * Ifdef'd out the NETWORK code.
 * 
 * Revision 2.2  1995/03/06  16:47:50  mike
 * destination saturn
 * 
 * Revision 2.1  1995/03/02  15:58:31  john
 * Made menu support modex.
 * 
 * Revision 2.0  1995/02/27  11:32:34  john
 * New version 2.0, which has no anonymous unions, builds with
 * Watcom 10.0, and doesn't require parsing BITMAPS.TBL.
 * 
 * Revision 1.157  1995/02/15  15:35:12  john
 * Added code to support modem tracking of kills during endlevel..
 * 
 * Revision 1.156  1995/02/15  15:27:16  john
 * Mode modem code work with kills after you leave mine.
 * 
 * Revision 1.155  1995/02/12  19:53:04  rob
 * Fixed up com flush.
 * 
 * Revision 1.154  1995/02/11  17:13:25  rob
 * Took out code fill stuff.
 * 
 * Revision 1.153  1995/02/11  14:26:25  rob
 * Added support for invul. controlcen.
 * 
 * Revision 1.152  1995/02/09  02:59:44  mike
 * check code for 00066xxx bugs.
 * 
 * Revision 1.151  1995/02/08  19:18:56  rob
 * Took out show IDs on HUD option.
 * 
 * Revision 1.150  1995/02/08  11:30:29  rob
 * Fixed some pretty minor bugs.
 * 
 * Revision 1.149  1995/02/07  20:39:54  rob
 * Added new modem game options for hud id's and automap show.
 * 
 * Revision 1.148  1995/02/06  18:42:09  rob
 * Removed debugging crap.
 * 
 * Revision 1.147  1995/02/06  18:17:17  rob
 * Fixed bug in killed tallys in modem game.
 * 
 * Revision 1.146  1995/02/05  17:04:29  rob
 * Removed performance monitoring debug message.
 * 
 * Revision 1.145  1995/02/05  17:02:48  rob
 * Speeding up cooperative/robo-anarchy modes.
 * 
 * Revision 1.142  1995/02/03  18:09:40  rob
 * Small fixes to modem setup menu..
 * 
 * Revision 1.141  1995/02/03  11:12:15  rob
 * Fixed a bug (again).  Somehow last checkin got lost.
 * 
 * Revision 1.140  1995/02/02  22:06:20  rob
 * Removed a function frm shareware.
 * 
 * Revision 1.139  1995/02/01  23:20:32  rob
 * Put baud rate in connect subtitle.
 * 
 * Revision 1.138  1995/01/30  21:22:11  rob
 * Fixed bug in sync pack memcpy len.
 * 
 * Revision 1.137  1995/01/30  21:18:31  rob
 * Simplified mission loading support.
 * 
 * Revision 1.136  1995/01/30  18:32:08  rob
 * Added include of multibot.h
 * 
 * Revision 1.135  1995/01/30  17:16:04  rob
 * Added new system for sending robots positions in modem games.
 * 
 * Revision 1.134  1995/01/30  15:29:44  rob
 * Localizing.
 * 
 * Revision 1.133  1995/01/29  15:20:21  rob
 * Fixed a bug in secret level sequencing for modem games.
 * 
 * Revision 1.132  1995/01/24  15:48:51  rob
 * fixed error dialog.
 * 
 * Revision 1.131  1995/01/23  18:51:59  rob
 * Added RTS/CTS to null-modem games to help IHHD folks.
 * Fixed a bad assert.
 * Added support for user-written mission files.
 * 
 * Revision 1.130  1995/01/23  16:01:36  rob
 * More reliable syncing.
 * Added mission selection.
 * 
 * Revision 1.129  1995/01/20  22:47:35  matt
 * Mission system implemented, though imcompletely
 * 
 * Revision 1.128  1995/01/19  18:18:05  rob
 * Fixed kill list display.
 * 
 * Revision 1.127  1995/01/19  16:33:48  rob
 * removed ambigous variable name in check_message.
 * 
 * Revision 1.126  1995/01/19  11:56:38  rob
 * Take out Int3 that could never get hit.
 * 
 * Revision 1.125  1995/01/18  19:01:46  rob
 * Added new asserts to check for proper game staus.
 * 
 * 
 * Revision 1.124  1995/01/18  15:39:25  rob
 * Fixed title of serial menu.
 * 
 * Revision 1.123  1995/01/18  11:54:23  rob
 * Fixing a bug with connect status.
 * 
 * Revision 1.122  1995/01/17  22:16:07  rob
 * Changed timer calls.
 * 
 * Revision 1.121  1995/01/17  13:58:31  rob
 * Fixed problem with not allowing non-anarchy modes in registered.
 * 
 * Revision 1.120  1995/01/15  15:46:18  rob
 * Fixed lost character on serial menu messages.
 * 
 * Revision 1.119  1995/01/12  22:03:15  rob
 * Added sound cue when connection is established.
 * 
 * Revision 1.118  1995/01/12  11:42:05  john
 * added better UART detection.
 * /.
 * Works under OS/2 and DOS.
 * 
 * 
 */

#ifdef NETWORK

#define DOS4G

#pragma off (unreferenced)
static char rcsid[] = "$Id: modem.c 2.13 1995/11/28 16:25:05 john Exp $";
#pragma on (unreferenced)

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <conio.h>
#include <string.h>
#include <time.h>
#include <io.h>

//#include "fast.h" // Commlib stuff //Don't have fast.h -KRB
#include "game.h"
#include "scores.h"
#include "modem.h"
#include "object.h"
#include "player.h"
#include "laser.h"
#include "error.h"
#include "collide.h"
#include "endlevel.h"
#include "network.h"
#include "mono.h"
#include "gauges.h"
#include "newmenu.h"
#include "menu.h"
#include "gamesave.h"
#include "netmisc.h"
#include "fuelcen.h"
#include "dpmi.h"
//#include "commlib.h" -Don't have these either! -KRB
//#include "glfmodem.h" -Don't have these either! -KRB
#include "multi.h"
#include "timer.h"
#include "text.h"
#include "pcx.h"
#include "palette.h"
#include "sounds.h"
#include "digi.h"
#include "multibot.h"
#include "args.h"

//This include is just to allow compiling. It doesn't mean it will work. Values in here are only dummy values
#include "nocomlib.h"

#define MIN_COMM_GAP 8000
#define INIT_STRING_LEN 20
#define LEN_PHONE_NUM_OLD 15
#define LEN_PHONE_NUM 	32
#define LEN_PHONE_NAME 12
#define NUM_PHONE_NUM 8

// How many times to repeat 'reliable' messages

#define EOR_MARK 0xaa

#define COM_PROCESS_NORMAL 0
#define COM_PROCESS_ENDLEVEL 1
#define COM_PROCESS_SYNC 2
#define COM_PROCESS_MENU 3

#define SELECTION_STARTGAME		1
#define SELECTION_NO_START			2
#define SELECTION_YES_START		3
#define SELECTION_STARTGAME_ABORT	4
#define SELECTION_CLOSE_LINK		5

int default_base[4] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8 };
int default_irq[4] = { 4, 3, 4, 3 };

//	Code to support modem/null-modem play

// Program determined variables for serial play

typedef struct com_sync_pack {
	char type;
	byte proto_version;
	long sync_time;
	byte level_num;
	char difficulty;
	char game_mode;
	char callsign[CALLSIGN_LEN+1];
	short kills[2];
	ushort seg_checksum;
#ifndef SHAREWARE
	byte sync_id;
	char mission_name[9];
	short killed;
	byte game_flags;
#endif
	char	dummy[3]; // Extra space for checksum & sequence number
} com_sync_pack;

PORT *com_port;
int serial_active;
int com_baud_rate = 0;
//--unused-- int sync_time = 0;
int com_open = 0;
int got_sync = 0;
int other_got_sync = 0;
int carrier_on = 0;
long com_type = -1; /* What type of UART is available */
static long synccnt;
static ubyte rx_seqnum = 0xff;
static ubyte tx_seqnum = 0;
int OtherPlayer; // Player num for modem opponent
int com_process_mode = COM_PROCESS_NORMAL;
int master = -1; // Am I the master or is the other guy the master?
com_sync_pack my_sync, other_sync;
int start_level_num;

int com_custom_port = -1;
int com_custom_irq = -1;
int com_custom_base = -1;

int other_menu_choice = 0;

int chars_sent = 0;

// Com buffers

static char syncbuffer[MAX_MULTI_MESSAGE_LEN+4];
static char sendbuf[MAX_MULTI_MESSAGE_LEN+4]; // +4 because of +1 for null and +3 for checksum/sequence

// Serial setup variables

int com_port_num = -1;
int com_speed = -1;
char modem_init_string[INIT_STRING_LEN+1];
char phone_num[NUM_PHONE_NUM+1][LEN_PHONE_NUM+1];
char phone_name[NUM_PHONE_NUM][LEN_PHONE_NAME+1];

fix  SerialLastMessage = 0;

/* Function prototypes for functions not exported through modem.h */

void com_param_setup(void);
void com_start_game(void);
void modem_dialout(void);
void modem_answer(void);
int com_sync(int id);
void com_sync_poll(int nitem, newmenu_item *menus, int *key, int citem);

#if 0
#define	codex(name_start, name_end)	\
void name_start(void)	\
{	\
	int	a,b,i;	\
	\
	a = 3;	\
	b = a + 4;	\
	\
	for (i=0; i<123; i++)	\
		a += i;	\
	\
	if (a < b)	\
		b += a;	\
	else if (a == b)	\
		a += b;	\
	else	\
		a += a + b;	\
	\
	while (a < b)	\
		a = b;	\
	\
}	\
	\
void name_end(void)	\
{	\
}
#else
#define codex(name_start, name_end)
#endif

codex(code_01s, code_01e)

int detect_UART(unsigned baseaddr, int * loc, int * code )
{
   // this function returns 0 if no UART is installed.
   // 1: 8250, 2: 16450 or 8250 with scratch reg., 3: 16550, 4: 16550A
   int x,olddata,temp;

	*loc = 0; *code = 0;

   // check if a UART is present.  This is code John hacked by looking at the return
	// values from peoples computers.  
   olddata=inp(baseaddr+4);	
   outp(baseaddr+4,0x1f);			// Enable Loopback mode, sets RTS & DTR to 1.
	delay(1);
	_disable();
	temp = inp(baseaddr+6);			// Read the state of RTS and DTR.
	temp = inp(baseaddr+6);			// Do this twice, so that lower 4 bits are clear. OS/2 returns 0xB0 after this,
											// instead of 0xff if no port is there.
	_enable();
   if ((temp&0x3f)!=0x30) {
		*loc = 1; *code = temp;
		return 0;
	}
   outp(baseaddr+4,olddata);		// Restore RTS & DTR
	delay(1);
   // next thing to do is look for the scratch register
   olddata=inp(baseaddr+7);
   outp(baseaddr+7,0x55);
	delay(1);
   if (inp(baseaddr+7)!=0x55) return 1;
   outp(baseaddr+7,0xAA);
	delay(1);
   if (inp(baseaddr+7)!=0xAA) return 1;
   outp(baseaddr+7,olddata); // we don't need to restore it if it's not there
	delay(1);
   // then check if there's a FIFO
   outp(baseaddr+2,1);
	delay(1);
   x=inp(baseaddr+2);
   // some old-fashioned software relies on this!
   outp(baseaddr+2,0x0);
	delay(1);
   if ((x&0x80)==0) return 2;
   if ((x&0x40)==0) return 3;
   return 4;
}
	
codex(code_02s, code_02e)

int 
com_type_detect()
{
//	static long port;
//	short *ptr;
	int loc, code;

	long port_addr[4] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8 };
	long portaddr;

	if (com_type != -1)
		return com_type;

	if ( (com_port_num != 0) && (com_port_num != 1) && (com_port_num != 2) && (com_port_num != 3) )
	{
		Int3();
		return -1; // Error, set com_port_num before calling this!	
	}

	if (com_port_num == com_custom_port)
		portaddr = com_custom_base;
	else
		portaddr = port_addr[com_port_num];

	mprintf((0, "com port %x.\n", portaddr));
	
	switch( detect_UART(portaddr, &loc, &code) )	{											 	
	case 0:  // No UART
		mprintf((0, "No UART detected. (LOC:%d, CODE:0x%x)\n", loc, code));
		return -1;
	case 1:	// 8250
		mprintf((0, "UART type is 8250.\n" ));
		return(16450);
	case 2:	// 16450 or 8250 with scratch reg.
		mprintf((0, "UART is type 16450, or an 8250 with scratch register.\n" ));
		return(16450);
	case 3:	// 16550
		mprintf((0, "UART is type 16550, with FIFO bug.\n" ));
		return(16450);		// 16550's FIFOs don't work. This is not a typo. (detected 16550, returned 16450 )
	case 4:	// 16550A,  which is the only UART the FIFO mode works with.
		mprintf((0, "UART is type 16550A, no FIFO bug.\n" ));
		return(16550);
	}
	return (-1);
}

#if !defined(NDEBUG) && !defined(NMONO)
void
com_dump_string(char *string)
{
	mprintf((0, "%s\n", string));
}
#else
#define com_dump_string()
#endif

codex(code_03s, code_03e)

int
com_enable() 
{
	// Detect and enable the COM port selected by the user

	int rc;

	if (com_open)
		return 0;

	rx_seqnum = 0xff;
	tx_seqnum = 0;

	// com_port_num and com_speed should be set before calling this func
	com_type = com_type_detect();

	if (com_custom_port == com_port_num)
		rc = Change8259Priority(com_custom_irq);
	else if ((com_port_num == COM2) || (com_port_num == COM4))
		rc = Change8259Priority( IRQ3 );
	else
		rc = Change8259Priority( IRQ4 );

	if (rc != ASSUCCESS) 
	{
		nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_SERIAL_OPEN_ERROR);
		return -1;
	}

// If our detection is wrong, we want them to be able to go ahead and play anyway.
//	if (com_type == -1)
//	{
//		nm_messagebox(TXT_ERROR, 1, TXT_OK, "Error 2\n%s", TXT_SERIAL_OPEN_ERROR);
//		return -1;
//	}

	if (com_port_num == com_custom_port )
	{
		rc = FastSetPortHardware(com_port_num, com_custom_irq, com_custom_base);
		if (rc != ASSUCCESS)
		{
			nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_SERIAL_OPEN_ERROR);
			return -1;
		}
	}

	if (com_type == 16550)
	{
		FastSet16550TriggerLevel( TRIGGER_04 );
		FastSet16550UseTXFifos( ON );
	}

	FastSavePortParameters(com_port_num);

	com_port = PortOpenGreenleafFast(com_port_num, com_speed, 'N', 8, 1);

#ifndef NDEBUG
	{
		int curr_irq, curr_base;
		//FastGetPortHardware(com_port_num, &curr_irq, &curr_base); //Removed , we don't have this function! -KRB
		mprintf((0, "Current port settings: base %x, irq %x.\n", curr_base, curr_irq ));
	}
#endif

	if ((com_port == 0) || (com_port->status != 0))
	{
		nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_SERIAL_OPEN_ERROR);
		return -1;
	}

	#ifndef NDEBUG
	mprintf((0, "Com port opened.  Status = %d\n", com_port->status));
	#endif

	SetDtr(com_port, ON);
	
	if ( FindArg( "-ctsrts" ) || FindArg( "-rtscts" )  )	
		UseRtsCts(com_port, ON); // Now used for null-modem as well, helps IHHD!
	else
		UseRtsCts(com_port, OFF);

	com_open = 1;

 	master = -1;

//	DumpPortStatus(com_port, com_dump_string);

	return 0;
}

void
com_disable()
{
	// close the com port and free associated structures.

	int rc;

	if (!com_open) 
		return;

// SetDtr(com_port, OFF);
//	UseRtsCts(com_port, OFF);

	rc = PortClose(com_port);

	FastRestorePortParameters(com_port_num);

	if (com_custom_port == com_port_num)
	{
		// Custom settings were in effect, roll them back
		rc = FastSetPortHardware(com_port_num, default_irq[com_port_num], default_base[com_port_num]);
	}

	if (rc != ASSUCCESS) {
		#ifndef NDEBUG
		mprintf((1, "PortClose returned %d!\n", rc));
		#endif
	}

	com_port = 0;
	com_open = 0;

	master = -1;

	#ifndef NDEBUG
	mprintf((0, "Com port released.\n"));
	#endif
}

codex(code_04s, code_04e)

void
com_abort(void)
{
	// this is the safest way to get out of some modem/serial negotiation
	// and back to the main menu.  Use this whenever this have gone too far
	// awry to repair.

	com_disable();

	N_players = 0;

	change_playernum_to(0);

	Viewer = ConsoleObject = &Objects[0];
	Game_mode = GM_GAME_OVER; // Force main menu selection
}

void
com_hangup(void)
{
	// Close the serial link

	com_send_choice(SELECTION_CLOSE_LINK);
	com_abort();
}

void
com_carrier_lost(void)
{
	// Carrier lost, inform and abort

	if (multi_in_menu > 0)
	{
		multi_leave_menu = 1;
		return;
	}

	Function_mode = FMODE_MENU;

	multi_quit_game = 1;
	nm_messagebox(NULL, 1, TXT_OK, TXT_CARRIER_LOST);
	com_abort();
}

codex(code_05s, code_05e)

extern ubyte cockpit_mode_save; // From object.c
extern int old_cockpit_mode; // From game.c

com_reset_game(void)
{
	int i;

	// Reset various parameters before starting a new game
	
	N_players = 2;

	for (i = 0; i < N_players; i++)
	{
		Players[i].connected = 1;
	}

//	master = -1;

	multi_new_game(); // Reset kill list, among other things
	Fuelcen_control_center_destroyed = 0;
	Endlevel_sequence = 0;
	
	// Yes, this really IS as ugly as it gets, kids...

	if (Cockpit_mode == CM_LETTERBOX)
	{
		select_cockpit(cockpit_mode_save);
	}
	else if (Cockpit_mode == CM_REAR_VIEW)
	{
		select_cockpit(old_cockpit_mode);
	}
}

codex(code_06s, code_06e)

void
com_save_settings(void)
{
	FILE *settings;
	int i;

	if ( (settings = fopen("serial.cfg", "wb")) == NULL)
		goto error;

	if (fwrite(&com_speed, sizeof(int), 1, settings) != 1)
		goto error;
	
	if (fwrite(&com_port_num, sizeof(int), 1, settings) != 1)
		goto error;

	if (fwrite(modem_init_string, 1, INIT_STRING_LEN+1, settings) != INIT_STRING_LEN+1)
		goto error;

	for (i = 0; i < NUM_PHONE_NUM; i++)
	{
		if (fwrite(phone_num[i], 1, LEN_PHONE_NUM+1, settings) != LEN_PHONE_NUM+1)
			goto error;
		if (fwrite(phone_name[i], 1, LEN_PHONE_NAME+1, settings) != LEN_PHONE_NAME+1)
			goto error;
	}

	if (fwrite(&com_custom_port, sizeof(int), 1, settings) != 1)
		goto error;
	if (fwrite(&com_custom_irq, sizeof(int), 1, settings) != 1)
		goto error;
	if (fwrite(&com_custom_base, sizeof(int), 1, settings) != 1)
		goto error;
	// 100 % success!

	fclose(settings);
	return;

error:
	nm_messagebox(NULL, 1, TXT_OK, TXT_ERROR_SERIAL_CFG);

	if (settings) {
		fclose(settings);
		unlink("serial.cfg");
	}
	
	return;
}

codex(code_07s, code_07e)

void
com_load_settings(void)
{
	FILE *settings;
	int i, cfg_size;

	if ((settings = fopen("serial.cfg", "rb")) == NULL)
		goto defaults;

	cfg_size = filelength(fileno(settings));

	// Read the data from the file
	
	if (fread(&com_speed, sizeof(int), 1, settings) != 1)
		goto error;
	if (! ((com_speed == 9600) || (com_speed == 19200) || (com_speed == 38400)) )
		goto error;

	if (fread(&com_port_num, sizeof(int), 1, settings) != 1)
		goto error;
	if ( (com_port_num < COM1) || (com_port_num > COM4) )
		goto error;

	if (fread(modem_init_string, 1, INIT_STRING_LEN+1, settings) != INIT_STRING_LEN+1)
		goto error;
	modem_init_string[INIT_STRING_LEN] = '\0';

	if (cfg_size <= 273 )	{				// Old 15 char LEN_PHONE_NUM's
		mprintf(( 1, "Reading old pre 1.1 phone.cfg\n" ));
		for (i = 0; i < NUM_PHONE_NUM; i++)
		{
			if (fread(phone_num[i], 1, LEN_PHONE_NUM_OLD+1, settings) != LEN_PHONE_NUM_OLD+1)
				goto error;
			phone_num[i][LEN_PHONE_NUM_OLD] = '\0';
			if (fread(phone_name[i], 1, LEN_PHONE_NAME+1, settings) != LEN_PHONE_NAME+1)
				goto error;
			phone_name[i][LEN_PHONE_NAME] = '\0';
		}
	} else {			// Normal Phone nums
		for (i = 0; i < NUM_PHONE_NUM; i++)
		{
			if (fread(phone_num[i], 1, LEN_PHONE_NUM+1, settings) != LEN_PHONE_NUM+1)
				goto error;
			phone_num[i][LEN_PHONE_NUM] = '\0';
			if (fread(phone_name[i], 1, LEN_PHONE_NAME+1, settings) != LEN_PHONE_NAME+1)
				goto error;
			phone_name[i][LEN_PHONE_NAME] = '\0';
		}
	}

	if (fread(&com_custom_port, sizeof(int), 1, settings) != 1) {
		mprintf((0, "Reading old file format for serial.cfg.\n"));
		goto close;
	}

	if ( (com_custom_port < -1) || (com_custom_port > COM4) )
		goto error;

	if (fread(&com_custom_irq, sizeof(int), 1, settings) != 1)
		goto error;
	if ( (com_custom_port < -1) || (com_custom_port > IRQ15) )
		goto error;

	if (fread(&com_custom_base, sizeof(int), 1, settings) != 1)
		goto error;
	if (com_custom_base < -1)
		goto error;

	//Everything was A-Ok!
close:
	fclose(settings);

	return;

error:
	nm_messagebox(NULL, 1, TXT_OK, TXT_ERR_SER_SETTINGS);
		
defaults:
	// Return some defaults
	com_speed = 19200; // UART speed
	strcpy(modem_init_string, "ATZ");
	com_port_num = COM2;
	com_custom_port = -1;
	for (i = 0; i < NUM_PHONE_NUM; i++)
	{
		phone_num[i][0] = '\0';
		strcpy(phone_name[i], TXT_EMPTY);
	}

	if (settings)
		fclose(settings);

	return;
}

void
serial_leave_game(void)
{
	#ifndef NDEBUG
	mprintf((0, "Called serial_leave_game.\n"));
	#endif
//	com_abort();
	serial_sync_abort(0); // Just in case the other guy is in sync mode
	Game_mode |= GM_GAME_OVER;
	Function_mode = FMODE_MENU;
}

codex(code_08s, code_08e)

void
com_send_data(char *ptr, int len, int repeat)
{
	int i;

	// Take the raw packet data specified by ptr and append the sequence
	// number and checksum, and pass it to com_send_ptr

	if (!com_port)
		return;

	if (Game_mode & GM_MODEM)
	{
		i = GetCd(com_port);
		if (i == 0)
			mprintf((0, "CARRIER LOST!\n"));
	}
	
	len += 3; // Checksum data is 3 bytes

	*(ubyte *)(ptr+(len-3)) = (tx_seqnum+1)%256;
	tx_seqnum = (tx_seqnum+1)%256;

	*(ushort *)(ptr+(len-2)) = netmisc_calc_checksum(ptr, len-2);

	com_send_ptr(ptr, len);
	if (repeat)
		for (i = 0; i < repeat; i++)
			com_send_ptr(ptr, len);
}

com_send_ptr(char *ptr, int len)
{
	register	int count;
	register char dat;

	for (count = 0, dat=ptr[0]; count < len; dat=ptr[++count])
	{
		WriteChar(com_port, dat);
		if (dat == EOR_MARK)
			WriteChar(com_port, EOR_MARK); // double in-band endmarkers
	}
	WriteChar(com_port, EOR_MARK);  // EOR
	WriteChar(com_port, 0);         // EOR
	chars_sent += len;
}

codex(code_09s, code_09e)

void
com_flush()
{
	// Get rid of all waiting data in the serial buffer

	int i = 0;

	if (!com_open)	
		return;

	mprintf((0, "COM FLUSH:"));

	while (ReadCharTimed(com_port, 100) >= 0)
		i++;
	mprintf((0, "%d characters.\n", i));
}

int
com_getchar()
{
	register int i;
	static int eor_recv = 0;

	// return values:
	//  -1 = Nothing in buffer
 	//  -2 = End of record

	if (!com_open)
		return(-1);

	i = ReadChar(com_port);

//	mprintf((0, "%c", i));

	if (i == ASBUFREMPTY)
		return (-1);

	if ((i == EOR_MARK) || eor_recv)
	{
		if (!eor_recv)
			i = ReadChar(com_port);

		if (i == ASBUFREMPTY)
		{
//			Assert(eor_recv == 0);
			eor_recv = 1;
			return(-1);
		}
		else if (i == EOR_MARK) 
		{
			eor_recv = 0;
			return(EOR_MARK); // Doubled EOR returns the character
		}
		else
		{
#ifndef NDEBUG
			if (i != 0) {
				mprintf((0, "EOR followed by unexpected value %d.\n", i));
			}
#endif
			eor_recv = 0;
			return(-2);							
		}
	}
	return(i);
}

#define SERIAL_IDLE_TIMEOUT F1_0*10

codex(code_10s, code_10e)

void
com_do_frame(void)
{
	static fix last_comm_time = 0;
	static int last_pos_skipped = 0;
	int rval = 0;

	if (Endlevel_sequence || (com_process_mode==COM_PROCESS_ENDLEVEL)) {	// Only recieve during endlevel
		int old_Endlevel_sequence = Endlevel_sequence;
		Endlevel_sequence = 1;
		com_process_input();
		Endlevel_sequence = old_Endlevel_sequence;
		return;
	}

	last_comm_time += FrameTime;

	if ((last_comm_time > MIN_COMM_GAP) || Network_laser_fired)
	{
#ifndef SHAREWARE
		if ((Game_mode & GM_MULTI_ROBOTS) && !last_pos_skipped) {
			rval = multi_send_robot_frame(0);
		}
		if (rval && !Network_laser_fired)
		{
			last_pos_skipped = 1;
			goto skippos;
		}
#endif
		last_pos_skipped = 0;
		multi_send_position(Players[Player_num].objnum);
		multi_send_fire(); // Will return w/o sending if we haven't fired

skippos:
//		mprintf((0, "%d chars sent, %f cps.\n", chars_sent, f2fl(fixdiv((chars_sent*F1_0),last_comm_time)) ));
		chars_sent = 0;
		last_comm_time = 0;
	}
//	else
//		multi_send_robot_frame(1);

	com_process_input();

	if (!Fuelcen_control_center_destroyed && (Function_mode == FMODE_GAME) && (SerialLastMessage+SERIAL_IDLE_TIMEOUT < GameTime))
	{
		SerialLastMessage = 0x7fffffff-SERIAL_IDLE_TIMEOUT; // Give no further warnings until next message arrives!
		nm_messagebox(TXT_WARNING, 1, TXT_OK, TXT_CONNECT_LOST, Players[OtherPlayer].callsign);
	}

	return;
}

int
com_check_message(char *checkbuf, int len)
{
	ushort check;
	int seqnum;

	if (len < 4)
	{
		#ifndef NDEBUG
		mprintf((0, "message type %d too short to be a real message!\n", checkbuf[0]));
		#endif
		goto error;
	}

	if (checkbuf[0] > MULTI_MAX_TYPE)
	{
		#ifndef NDEBUG
		mprintf((1, "message type %d out of range.\n", checkbuf[0]));
		#endif
		goto error;
	}

	if ( (len-3) != message_length[checkbuf[0]])
	{
		#ifndef NDEBUG
		mprintf((1, "message length %d != %d.\n", len-3, message_length[checkbuf[0]]));
		#endif
		goto error;
	}

	check = netmisc_calc_checksum(checkbuf, len-2);
	if (check != *(ushort *)(checkbuf+(len-2)))
	{
		#ifndef NDEBUG
		mprintf((0, "error in message type %d, length %d, checksum %d != %d\n", checkbuf[0], len, check, *(ushort *)(checkbuf+(len-2))));	
		#endif
		goto error;
	}

	seqnum = checkbuf[(len-3)];

	if (seqnum == rx_seqnum)
	{
		return -1;
	}
	
	if (seqnum != (rx_seqnum+1)%256)
	{
		#ifndef NDEBUG
		mprintf((0, "Warning, missed 1 or more messages.\n"));	
		#endif
	}
	rx_seqnum = seqnum;
//	mprintf((0, "message type %d len %d OK!\n", checkbuf[0], len));
	return 0; 

error:
	mprintf((1,"Line status: %d.\n", GetLineStatus(com_port)));
	ClearLineStatus(com_port);
	return -1;
}
	
codex(code_11s, code_11e)

void
com_process_menu(char *buf, int len)
{
	char text[80];

	len = len;

	mprintf((0, "com_process_menu: type %d.\n", buf[0]));

	switch(buf[0])
	{
		case MULTI_MESSAGE:
#ifndef SHAREWARE
			sprintf(text, "%s %s\n'%s'", Players[OtherPlayer].callsign, TXT_SAYS, buf+2);
#else
			sprintf(text, "%s %s\n'%s'", Players[OtherPlayer].callsign, TXT_SAYS, buf+3);
#endif
			nm_messagebox(NULL, 1, TXT_OK, text);
			break;
		case MULTI_MENU_CHOICE:
			other_menu_choice = buf[1];
			mprintf((0, "Other menu choice = %d.\n", other_menu_choice));
			break;
		case MULTI_BEGIN_SYNC:
			// If we get a sync at the menu, send an abort sync, we're not ready yet!
			serial_sync_abort(0);
			break;
	}
}

void
com_process_input(void)
{
	// Read all complete messages from the serial buffer and process
	// the contents.  Messages are read into global array snycbuffer.

	static int len = 0;
	int entry_com_mode = com_process_mode;
	register	int dat;

	if (!com_port)
		return;

nextmessage:
	if (Game_mode & GM_MODEM)
	{
		if (!GetCd(com_port))
			com_carrier_lost();
	}

	if (!com_port) {
		if (!multi_in_menu) {
			multi_quit_game = 1;
		}
		else {
			multi_leave_menu = 1;
		}
	}

	if (com_process_mode != entry_com_mode)
	{
//		mprintf((0, "Exiting com_process_input due to mode switch.\n"));
		return;
	}

	while ( (len <= MAX_MULTI_MESSAGE_LEN) && (dat = com_getchar()) > -1) // Returns -1 when serial pipe empty
	{
		syncbuffer[len++] = dat;
	}

	if ((dat == -2) || (len > MAX_MULTI_MESSAGE_LEN)) // Returns -2 when end of message reached
	{
		// End of message
		SerialLastMessage = GameTime;

		if (!com_check_message(syncbuffer, len))
		{
			switch(com_process_mode)
			{
				case COM_PROCESS_NORMAL:
				case COM_PROCESS_ENDLEVEL:
					multi_process_data(syncbuffer, len); break;
				case COM_PROCESS_MENU:
					if (!Endlevel_sequence) com_process_menu(syncbuffer, len); break;
				case COM_PROCESS_SYNC:
					if (!Endlevel_sequence) com_process_sync(syncbuffer, len); break;
				default:
					Int3(); // Bad com_process_mode switch set!
			}
		}
		len = 0;
		goto nextmessage;
	}
	if (dat == -3) // Returns -3 when carrier lost
	{
		com_abort();
		len = 0;
	}
	return ;
}

int
com_connect()
{
	my_sync.type = MULTI_BEGIN_SYNC;
	my_sync.difficulty = 0;
	memcpy(my_sync.callsign, Players[Player_num].callsign, CALLSIGN_LEN+1);
	my_sync.seg_checksum = 0;
	my_sync.game_mode = Game_mode;
	my_sync.level_num = 0;
								 
	#ifndef NDEBUG
	mprintf((0, "com_connect()\n"));
	#endif

	if(com_sync(-1))
		return(-1); // Failure in sync

	if (master == -1)
	{
		#ifndef NDEBUG
		mprintf((0, "My rand = %d, other rand = %d.\n", my_sync.sync_time, other_sync.sync_time));
		#endif

		// Figure out who is the master
		if (my_sync.sync_time > other_sync.sync_time)
		{
			mprintf((0, "Swtiching player to master.\n"));
			master=1;
			change_playernum_to(0);
		}
		else if (my_sync.sync_time < other_sync.sync_time)
		{
			mprintf((0, "Switching player to slave.\n"));
			master = 0;
			change_playernum_to(1);
		}
		else
			return(-1);  // Didn't sync properly, try again
	}
	
	// Copy the remote sync data into local variables
	
	OtherPlayer = (Player_num+1)%2;
	mprintf((0, "Other player is #%d.\n", OtherPlayer));
	memcpy(Players[OtherPlayer].callsign, other_sync.callsign, CALLSIGN_LEN+1);

	return(0);
}

#define ADD_ITEM(t,value,key)  do { m[num_options].type=NM_TYPE_MENU; menu_choice[num_options]=value; m[num_options].text=t; num_options++; } while (0)

#define MENU_MODEM_CALL				0
#define MENU_MODEM_ANSWER			1
#define MENU_SERIAL_LINK_START 	2
#define MENU_SERIAL_SETUP 			3
#define MENU_MODEM_HANGUP			4
#define MENU_SERIAL_GAME_START	5
#define MENU_SEND_MESSAGE			6

codex(code_12s, code_12e)

void
com_menu_poll(int nitems, newmenu_item *menus, int *key, int citem)
{
	// Watch the serial stream if we are connected and take appropriate actions

	int old_game_mode;

	menus = menus;
	citem = citem;
	nitems = nitems;

	if (! ( (Game_mode & GM_SERIAL) || (Game_mode & GM_MODEM) ) )
		return;

	com_process_mode = COM_PROCESS_MENU;
	old_game_mode = Game_mode;
	other_menu_choice = 0;	

	com_process_input();

	if ((old_game_mode != Game_mode) || other_menu_choice || (com_process_mode != COM_PROCESS_MENU))
		*key = -2;
	if (multi_leave_menu)
		*key = -2;
}

void
com_send_choice(int choice)
{
	sendbuf[0] = (char)MULTI_MENU_CHOICE;
	sendbuf[1] = (char)choice;

	com_send_data(sendbuf, 2, 1);
}

void
com_ready_to_start(void)
{
	newmenu_item m[2];
	int choice;

	m[0].type = m[1].type = NM_TYPE_MENU;
	m[0].text = TXT_YES;
	m[1].text = TXT_NO;

	choice = newmenu_do1(NULL, TXT_READY_DESCENT, 2, m, com_menu_poll, 0 );
	if (choice == 0)
	{
		// Yes
		com_send_choice(SELECTION_YES_START);
		other_menu_choice = SELECTION_STARTGAME;
		com_start_game();
	}		
	else 
	{
		com_send_choice(SELECTION_NO_START);
	}
}

void
com_process_other_menu_choice(void)
{
	if (other_menu_choice == SELECTION_STARTGAME)	
		com_ready_to_start();
	else if (other_menu_choice == SELECTION_CLOSE_LINK) 
	{
		nm_messagebox(NULL, 1, TXT_OK, TXT_CLOSED_LINK);
		com_hangup();
	}
}

#define SUBTITLE_LEN 120

void
com_main_menu(void)
{
	newmenu_item m[10];
	int menu_choice[10];
	int num_options = 0;
	int choice=0;
	int old_game_mode;
	char subtitle[SUBTITLE_LEN];
	int pcx_error;

	if (com_port_num == -1)
		com_load_settings();

	setjmp(LeaveGame);

	gr_set_current_canvas(NULL);
	pcx_error = pcx_read_bitmap(Menu_pcx_name,&grd_curcanv->cv_bitmap,grd_curcanv->cv_bitmap.bm_type,NULL);
	Assert(pcx_error == PCX_ERROR_NONE);

	com_process_mode = COM_PROCESS_MENU;

newmenu:
	num_options = 0;

	if (! ((Game_mode & GM_SERIAL) || (Game_mode & GM_MODEM)) )
	{
		// We haven't established any type of link
		ADD_ITEM(TXT_DIAL_MODEM, MENU_MODEM_CALL, KEY_D);
		ADD_ITEM(TXT_ANSWER_MODEM, MENU_MODEM_ANSWER, KEY_A);
		ADD_ITEM(TXT_NULL_MODEM, MENU_SERIAL_LINK_START, KEY_E);
		ADD_ITEM(TXT_COM_SETTINGS, MENU_SERIAL_SETUP, KEY_C);
	}
	else
	{
		ADD_ITEM(TXT_START_GAME, MENU_SERIAL_GAME_START, KEY_S);
		ADD_ITEM(TXT_SEND_MESSAGEP, MENU_SEND_MESSAGE, KEY_S);
	}
	if (Game_mode & GM_MODEM)
		ADD_ITEM(TXT_HANGUP_MODEM, MENU_MODEM_HANGUP, KEY_H);
	
	if (Game_mode & GM_SERIAL)
		ADD_ITEM(TXT_CLOSE_LINK, MENU_MODEM_HANGUP, KEY_C);

	sprintf(subtitle, "%s\n\n", TXT_SERIAL_GAME);

	if (Game_mode & GM_SERIAL)
		sprintf(subtitle+strlen(subtitle), "%s %s\n%s", TXT_SERIAL, TXT_LINK_ACTIVE, Players[OtherPlayer].callsign);
	else if (Game_mode & GM_MODEM)
		sprintf(subtitle+strlen(subtitle), "%d %s %s %s\n%s", com_baud_rate, TXT_BAUD, TXT_MODEM, TXT_LINK_ACTIVE, Players[OtherPlayer].callsign);	
	else
		sprintf(subtitle+strlen(subtitle), TXT_NOT_CONNECTED);

	multi_leave_menu = 0;


	Assert(strlen(subtitle) < SUBTITLE_LEN);

	choice = newmenu_do1(NULL, subtitle, num_options, m, com_menu_poll, 0);

	mprintf((0, "main menu choice was %d.\n", choice));

	if (choice == -1)
	{
		if (!((Game_mode & GM_SERIAL) || (Game_mode & GM_MODEM)))
		{
			com_disable();
			return;
		}
		m[0].text = TXT_YES; m[1].text = TXT_NO;
		m[0].type = m[1].type = NM_TYPE_MENU;

		choice = newmenu_do1(NULL, TXT_EXIT_WILL_CLOSE, 2, m, com_menu_poll, 0);
		if (choice == 0)
		{
			com_send_choice(SELECTION_CLOSE_LINK);
			com_hangup();
			return;
		}
		if ((choice == -2) && (other_menu_choice))
			com_process_other_menu_choice();

		goto newmenu;
	}

	if (choice == -2)
	{
		// Menu poll loop caused a re-draw
		if (other_menu_choice == SELECTION_STARTGAME)	
			com_ready_to_start();
		else if (other_menu_choice == SELECTION_CLOSE_LINK) 
		{
			nm_messagebox(NULL, 1, TXT_OK, TXT_CLOSED_LINK);
			com_hangup();
		}
			
		if (Function_mode == FMODE_GAME)
			return;	

		if (!com_port)
			Game_mode = GM_GAME_OVER;

		goto newmenu;
	}		

	if (choice > -1) 
	{
		old_game_mode=Game_mode;
		switch (menu_choice[choice])
		{
			case MENU_SERIAL_SETUP:
			 	com_param_setup();
				goto newmenu;
				break;
			case MENU_SERIAL_GAME_START:
				com_start_game();
				if (Function_mode != FMODE_GAME) 
					goto newmenu;
				break;
			case MENU_MODEM_CALL:
				modem_dialout();
				goto newmenu;
				break;
			case MENU_MODEM_ANSWER:
				modem_answer();
				goto newmenu;
				break;
			case MENU_SEND_MESSAGE:
				multi_send_message_dialog();
				if (Network_message_reciever != -1)
					multi_send_message();
				multi_sending_message = 0;
				goto newmenu;
				break;
			case MENU_SERIAL_LINK_START:
				serial_link_start();
				goto newmenu;
				break;
			case MENU_MODEM_HANGUP:
				com_hangup();
				goto newmenu;
				break;
			default: 
				Int3();
				return;
		}
	}
}

codex(code_13s, code_13e)

void com_custom_param_setup(void)
{
	// User menu for setting up custom IRQ/Base settings for a COM port

	newmenu_item mm[6];
	int loc;

	char base[10]; 
	char irq[3];
	char title[60];
	int new_irq, new_base;
	int menu_save, menu_reset;
	int mmn;

	sprintf(title, "%s%d", TXT_COM_CUSTOM_SETTINGS, com_port_num+1);
	
	if (com_port_num != com_custom_port) 
	{
		new_irq = default_irq[com_port_num];
		new_base = default_base[com_port_num];
	}
	else
	{
		new_irq = com_custom_irq;
		new_base = com_custom_base;
	}

newmenu:
	sprintf(base, "%x", new_base);
	sprintf(irq, "%d", new_irq);

	loc = 0;
	mm[loc].type = NM_TYPE_TEXT; mm[loc].text = TXT_COM_BASE; loc++;
	mm[loc].type = NM_TYPE_INPUT; mm[loc].text = base; mm[loc].text_len = 9; loc++;
	mm[loc].type = NM_TYPE_TEXT; mm[loc].text = TXT_COM_IRQ; loc++;
	mm[loc].type = NM_TYPE_INPUT; mm[loc].text = irq, mm[loc].text_len = 2; loc++;
	menu_reset = loc;
	mm[loc].type = NM_TYPE_MENU; mm[loc].text = TXT_RESET_DEFAULTS; loc++;
	menu_save = loc;
	mm[loc].type = NM_TYPE_MENU; mm[loc].text = TXT_ACCEPT; loc++;	

	mmn = newmenu_do1(NULL, title, loc, mm, NULL, menu_save);

	if (mmn == -1)
		return; // All changes lost

	new_irq = strtol(irq, NULL, 0);
	new_base = strtol(base, NULL, 16);

	if (mmn == menu_reset) 
	{
		new_irq = default_irq[com_port_num];
		new_base = default_base[com_port_num];
	}
	if (mmn == menu_save) 
	{
		if ((new_irq == default_irq[com_port_num]) && (new_base == default_base[com_port_num])) {
			com_custom_port = -1;
			mprintf((0, "Custom com settings not changed.\n"));
			return;
		}
		if ((new_irq < IRQ2) || (new_irq > IRQ7)) {
		   new_irq = default_irq[com_port_num];
			nm_messagebox(NULL, 1, TXT_OK, TXT_VALID_IRQS);
			goto newmenu;
		}

		com_custom_irq = new_irq;
		com_custom_base = new_base;
		com_custom_port = com_port_num;
		return;
	}
	goto newmenu;
}

void com_param_setup_poll(int nitems, newmenu_item *menus, int *key, int citem)
{
	nitems = nitems;
	key = key;
	citem = citem;
	
	if ((com_custom_port == -1) && menus[4].value)
	{
		menus[4].value = 0; menus[4].redraw = 1;
		return;
	}

	if (com_custom_port == -1)
		return;

	if (menus[com_custom_port].value && !menus[4].value) 
	{
		menus[4].value = 1; menus[4].redraw = 1;
	}
	else if (menus[4].value && !menus[com_custom_port].value)
	{
		menus[4].value = 0; menus[4].redraw = 1;
	}
	
}

void com_param_setup(void)
{
	int mmn;
	int was_enabled = 0;
	newmenu_item mm[12];
	char init_string[INIT_STRING_LEN+1];
	int changed = 0;
	int menu_baud, menu_custom, menu_save;
	int loc;

	strcpy (init_string, modem_init_string);

	com_type = -1;

	if (com_open)
	{
		was_enabled = 1;
		com_disable();
	}

setupmenu:	
	loc = 0;
	mm[loc].type=NM_TYPE_RADIO; mm[loc].value=(com_port_num == COM1); mm[loc].text="COM1"; mm[loc].group=0; loc++;
	mm[loc].type=NM_TYPE_RADIO; mm[loc].value=(com_port_num == COM2); mm[loc].text="COM2"; mm[loc].group=0; loc++;
	mm[loc].type=NM_TYPE_RADIO; mm[loc].value=(com_port_num == COM3); mm[loc].text="COM3"; mm[loc].group=0; loc++;
	mm[loc].type=NM_TYPE_RADIO; mm[loc].value=(com_port_num == COM4); mm[loc].text="COM4"; mm[loc].group=0; loc++;
	menu_custom = loc;
	mm[loc].type=NM_TYPE_CHECK; mm[loc].value=(com_port_num == com_custom_port); mm[loc].text=TXT_COM_CUSTOM_SETTINGS; loc++;
	mm[loc].type=NM_TYPE_TEXT; mm[loc].text = TXT_BAUD_RATE; loc++;
	menu_baud = loc;
	mm[loc].type=NM_TYPE_RADIO; mm[loc].value=(com_speed == 9600); mm[loc].text="9600"; mm[loc].group=1; loc++;
	mm[loc].type=NM_TYPE_RADIO; mm[loc].value=(com_speed == 19200); mm[loc].text="19200"; mm[loc].group=1; loc++;
	mm[loc].type=NM_TYPE_RADIO; mm[loc].value=(com_speed == 38400); mm[loc].text="38400"; mm[loc].group=1; loc++;
	mm[loc].type=NM_TYPE_TEXT; mm[loc].text = TXT_MODEM_INIT_STRING; loc++;
	mm[loc].type=NM_TYPE_INPUT; mm[loc].text_len = INIT_STRING_LEN; mm[loc].text = init_string; loc++;
	menu_save = loc;
	mm[loc].type=NM_TYPE_MENU; mm[loc].text = TXT_ACCEPT_SAVE; loc++;

	mmn = newmenu_do1(NULL, TXT_SERIAL_SETTINGS, loc, mm, com_param_setup_poll, menu_save);

	if (mmn > -1 ) {
		changed = 1;

		if (mm[0].value)
			com_port_num = COM1;
		else if (mm[1].value)
			com_port_num = COM2;
		else if (mm[2].value) 
			com_port_num = COM3;
		else 
			com_port_num = COM4;
		
		if (mmn == menu_custom)
		{
			com_custom_param_setup();
		}

		com_type = -1;
		com_type = com_type_detect();

		if (com_type == -1)
		{
			nm_messagebox(NULL, 1, TXT_OK, "%s\n%s", TXT_WARNING, TXT_NO_UART);
		}

		if ((mm[menu_baud].value) || (mmn == menu_baud)) 
			com_speed = 9600;
		else if ((mm[menu_baud+1].value) || (mmn == menu_baud+1))
			com_speed = 19200;
		else
		{
			if (com_type == 16550)
				com_speed = 38400;
			else
			{
				nm_messagebox(NULL, 1, TXT_OK, TXT_WARNING_16550);
				com_speed = 19200;
			}
		}
				
		//mprintf((0, "%s\n", init_string));

		if ((strnicmp("AT", init_string, 2)) && (strlen(init_string) < (INIT_STRING_LEN-2)))
			sprintf(modem_init_string, "AT%s", init_string);
		else
			strcpy(modem_init_string, init_string);

		if (mmn != menu_save)
			goto setupmenu;
	}
	
	if (was_enabled)
		com_enable();

	if (changed)
		com_save_settings();

}
	
codex(code_14s, code_14e)

extern int opt_cinvul;
extern int last_cinvul;

void modem_game_param_poll( int nitems, newmenu_item * menus, int * key, int citem )
{
	nitems = nitems;
	key = key;
	citem = citem;
	if ( last_cinvul != menus[opt_cinvul].value )	{
		sprintf( menus[opt_cinvul].text, "%s: %d %s", TXT_REACTOR_LIFE, menus[opt_cinvul].value*5, TXT_MINUTES_ABBREV );
		last_cinvul = menus[opt_cinvul].value;
		menus[opt_cinvul].redraw = 1;
	}		
}

// Handshaking to start a serial game, 2 players only

int com_start_game_menu(void)
{
	newmenu_item m[13];
	char level[5];
	int choice = 0;
	int opt, diff_opt, mode_opt, options_opt;
	char level_text[32];
	char srinvul[32];

#ifndef SHAREWARE
	int new_mission_num, anarchy_only = 0;

	new_mission_num = multi_choose_mission(&anarchy_only);

	if (new_mission_num < 0)
		return 0;

	strcpy(my_sync.mission_name, Mission_list[new_mission_num].filename);
#endif

	sprintf(level, "1");

	Game_mode &= ~GM_MULTI_COOP;
	Game_mode &= ~GM_MULTI_ROBOTS;
	Netgame.game_flags = 0;

	sprintf(level_text, "%s (1-%d)", TXT_LEVEL_, Last_level);
	if (Last_secret_level < -1)
		sprintf(level_text+strlen(level_text)-1, ", S1-S%d)", -Last_secret_level);
	else if (Last_secret_level == -1)
		sprintf(level_text+strlen(level_text)-1, ", S1)");

	Assert(strlen(level_text) < 32);

	// Put up menu for user choices controlling gameplay

newmenu:
	opt = 0;
	m[opt].type = NM_TYPE_TEXT; m[opt].text = level_text; opt++;
	m[opt].type = NM_TYPE_INPUT; m[opt].text_len = 4; m[opt].text = level; opt++;
#ifdef ROCKWELL_CODE
	mode_opt = 0;
#else
	m[opt].type = NM_TYPE_TEXT; m[opt].text = TXT_MODE;
	mode_opt = opt; 
	m[opt].type = NM_TYPE_RADIO; m[opt].text = TXT_ANARCHY; m[opt].value=!(Game_mode & GM_MULTI_ROBOTS); m[opt].group = 0; opt++;
	m[opt].type = NM_TYPE_RADIO; m[opt].text = TXT_ANARCHY_W_ROBOTS; m[opt].value=(!(Game_mode & GM_MULTI_COOP) && (Game_mode & GM_MULTI_ROBOTS)); m[opt].group = 0; opt++;
	m[opt].type = NM_TYPE_RADIO; m[opt].text = TXT_COOPERATIVE; m[opt].value=(Game_mode & GM_MULTI_COOP);m[opt].group = 0; opt++;
#endif
	diff_opt = opt;
	m[opt].type = NM_TYPE_SLIDER; m[opt].text = TXT_DIFFICULTY; m[opt].value = Player_default_difficulty; m[opt].min_value = 0; m[opt].max_value = (NDL-1); opt++;

#ifndef SHAREWARE
	options_opt = opt;
//	m[opt].type = NM_TYPE_CHECK; m[opt].text = TXT_SHOW_IDS; m[opt].value=0; opt++;
	m[opt].type = NM_TYPE_CHECK; m[opt].text = TXT_SHOW_ON_MAP; m[opt].value=0; opt++;

	opt_cinvul = opt;
	sprintf( srinvul, "%s: %d %s", TXT_REACTOR_LIFE, 5*control_invul_time, TXT_MINUTES_ABBREV );
	last_cinvul = control_invul_time;
	m[opt].type = NM_TYPE_SLIDER; m[opt].value=control_invul_time; m[opt].text= srinvul; m[opt].min_value=0; m[opt].max_value=15; opt++;

#endif

	Assert(opt <= 13);

	choice = newmenu_do1(NULL, TXT_SERIAL_GAME_SETUP, opt, m, modem_game_param_poll, 1);
	if (choice > -1) 
	{
#ifdef ROCKWELL_CODE
		Game_mode |= (GM_MULTI_COOP | GM_MULTI_ROBOTS);
#else
		if (m[mode_opt].value)
			Game_mode &= ~(GM_MULTI_COOP | GM_MULTI_ROBOTS);
#ifdef SHAREWARE
		else {
			nm_messagebox(NULL, 1, TXT_OK, TXT_ONLY_ANARCHY);
			goto newmenu;
		}
#else
		else if (anarchy_only) {
			nm_messagebox(NULL, 1, TXT_OK, TXT_ANARCHY_ONLY_MISSION);
			goto newmenu;
		}
		else if (m[mode_opt+1].value)
		{
			Game_mode &= ~GM_MULTI_COOP;
			Game_mode |= GM_MULTI_ROBOTS;
		}
		else
			Game_mode |= (GM_MULTI_COOP | GM_MULTI_ROBOTS);
#endif
#endif 	// ifdef ROCKWELL_CODE

//		if (m[options_opt].value)
//			Netgame.game_flags |= NETGAME_FLAG_SHOW_ID;
		if (m[options_opt].value)
			Netgame.game_flags |= NETGAME_FLAG_SHOW_MAP;
		if (!strnicmp(level, "s", 1))
			start_level_num = -atoi(level+1);
		else
			start_level_num = atoi(level);

		if ((start_level_num < Last_secret_level) || (start_level_num > Last_level) || (start_level_num == 0))
		{
			nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_LEVEL_OUT_RANGE);
			sprintf(level, "1");
			goto newmenu;
		}

		Difficulty_level = m[diff_opt].value;
		control_invul_time = m[opt_cinvul].value;
		Netgame.control_invul_time = control_invul_time*5*F1_0*60;

		return(1); // Go for game!
	}
	return 0; // No game
}

int
com_ask_to_start()
{	
	// Ask the other player if its OK to start now

	newmenu_item m[1];
	int choice;

	com_send_choice(SELECTION_STARTGAME);

	m[0].type = NM_TYPE_TEXT; m[0].text = TXT_ESC_ABORT;
menu:
	choice = newmenu_do(NULL, TXT_WAIT_FOR_OK, 1, m, com_menu_poll);

	if (choice == -1)
	{
		com_send_choice(SELECTION_STARTGAME_ABORT);
		return(0);
	}
	if (choice == -2)
	{
		if (other_menu_choice == SELECTION_YES_START)
			return(1);
		else if (other_menu_choice == SELECTION_STARTGAME)
		{
			com_send_choice(SELECTION_YES_START);
			return(1);
		}
		else 
			return(0);
	}
	goto menu;
}
		
codex(code_15s, code_15e)

void
com_start_game()
{
	// Start a serial game after being linked

	mprintf((0, "Entered com_start_game\n"));

	com_reset_game();

	if (! ( (Game_mode & GM_MODEM) || (Game_mode & GM_SERIAL) ) ) 
		return;
	
	Assert (master != -1);

	if (other_menu_choice != SELECTION_STARTGAME)
	{
		if (!com_ask_to_start())
			return;
	}

	if (master == 1) // Master chooses options
	{
		if (com_start_game_menu())
		{
			OtherPlayer = 1;
			change_playernum_to(0);
			my_sync.level_num = start_level_num;
			my_sync.difficulty = Difficulty_level;
			my_sync.game_mode = Game_mode;
			memcpy(my_sync.callsign, Players[Player_num].callsign, CALLSIGN_LEN+1);
#ifndef SHAREWARE
			my_sync.sync_time = control_invul_time*5*F1_0*60;
			my_sync.game_flags = Netgame.game_flags;
			Netgame.control_invul_time = control_invul_time*5*F1_0*60;
#endif
			com_sync(0);
		}
	}
	else // Slave
	{
		OtherPlayer = 0;
		change_playernum_to(1);
		memcpy(my_sync.callsign, Players[Player_num].callsign, CALLSIGN_LEN+1);
	
		my_sync.level_num = 1;
		
		com_sync(0);
		if (com_process_mode == COM_PROCESS_NORMAL)
		{
			Difficulty_level = other_sync.difficulty;
			start_level_num = other_sync.level_num;
			Game_mode = other_sync.game_mode;
#ifndef SHAREWARE
			Netgame.game_flags = other_sync.game_flags;
			Netgame.control_invul_time = other_sync.sync_time;
			if (!load_mission_by_name(other_sync.mission_name))
			{
				mprintf((0, "Mission not found: %s!\n", other_sync.mission_name));
				nm_messagebox(NULL, 1, TXT_OK, TXT_MISSION_NOT_FOUND);
				my_sync.sync_id = start_level_num;
				serial_sync_abort(0);
				return;
			}
#endif
		}
	}
	if (com_process_mode != COM_PROCESS_NORMAL)
		return;

	memcpy(Players[OtherPlayer].callsign, other_sync.callsign, CALLSIGN_LEN+1);
	Function_mode = FMODE_GAME;
	Game_mode &= ~GM_GAME_OVER;
	Show_kill_list = 1;
	init_player_stats_game();
	init_player_stats_level();
//	Assert(start_level_num > 0);
  	Assert((start_level_num >= Last_secret_level) && (start_level_num <= Last_level));
  	StartNewLevel(start_level_num);
}

//
// Modem control functions, dialing, answering, etc.
//

void modem_edit_phonebook(newmenu_item *m)
{
	int choice, choice2;
	newmenu_item menu[5];
	char text[2][100];
	int default_choice = 0;

	m[NUM_PHONE_NUM].text = TXT_SAVE;

	menu[0].text = TXT_NAME; menu[0].type = NM_TYPE_TEXT;
	menu[1].type = NM_TYPE_INPUT; menu[1].text = text[0]; menu[1].text_len = LEN_PHONE_NAME;
	menu[2].text = TXT_PHONE_NUM; menu[2].type = NM_TYPE_TEXT;
	menu[3].type = NM_TYPE_INPUT; menu[3].text = text[1]; menu[3].text_len = LEN_PHONE_NUM;
	menu[4].text = TXT_ACCEPT; menu[4].type = NM_TYPE_MENU;

menu:
	choice = newmenu_do1(NULL, TXT_SEL_NUMBER_EDIT, NUM_PHONE_NUM+1, m, NULL, default_choice);
	if (choice == -1)
	{
		com_load_settings();
		return;
	}
	if (choice == NUM_PHONE_NUM)
	{
		// Finished
		com_save_settings();
		return;
	}
	
	default_choice = 1;
edit:
	// Edit an entry
	strcpy(menu[1].text, phone_name[choice]);
	strcpy(menu[3].text, phone_num[choice]);

	choice2 = newmenu_do1(NULL, TXT_EDIT_PHONE_ENTRY, 5, menu, NULL, default_choice);
	if (choice2 != -1)
	{	
		strcpy(phone_name[choice], menu[1].text);
		strcpy(phone_num[choice], menu[3].text);
		sprintf(m[choice].text, "%d. %s \t", choice+1, phone_name[choice]);
		add_phone_number(m[choice].text, phone_num[choice] );
	}
	if (choice2 != 4)
	{
		default_choice += 2; if (default_choice > 4) default_choice = 4;
		goto edit;
	}

	default_choice = NUM_PHONE_NUM;
	goto menu;
}


codex(code_16s, code_16e)

void add_phone_number( char * src, char * num )
{
	char p;
	int l;
	l = strlen(num);
	if ( l<15)	{
		strcat( src, num );
		return;
	}
	p = num[15];
	num[15] = 0;
	strcat( src, num );
	num[15] = p;
	strcat( src, "..." );
}

int modem_dial_menu(void)
{
	newmenu_item m[NUM_PHONE_NUM+2];
	char menu_text[NUM_PHONE_NUM][80];
	int choice = 0;
	int i;

menu:
	for (i = 0; i < NUM_PHONE_NUM; i++)
	{
		m[i].text = menu_text[i];
		sprintf(m[i].text, "%d. %s \t", i+1, phone_name[i]);
		add_phone_number(m[i].text, phone_num[i] );
		m[i].type = NM_TYPE_MENU;
	}

	strcat(m[i-1].text, "\n");

	m[NUM_PHONE_NUM].type = NM_TYPE_MENU; 
	m[NUM_PHONE_NUM].text = TXT_MANUAL_ENTRY;
	m[NUM_PHONE_NUM+1].text = TXT_EDIT_PHONEBOOK;
	m[NUM_PHONE_NUM+1].type = NM_TYPE_MENU;

	choice = newmenu_do1(NULL, TXT_SEL_NUMBER_DIAL, NUM_PHONE_NUM+2, m, NULL, 0);
	if (choice == -1) 
		return -1; // user abort

	if (choice == NUM_PHONE_NUM+1)
	{
		// Edit phonebook
		modem_edit_phonebook(m);
		goto menu;
	}

	if (choice == NUM_PHONE_NUM)
	{
		// Manual entry
		newmenu_item m2[1];
		m2[0].type = NM_TYPE_INPUT; m2[0].text = phone_num[NUM_PHONE_NUM]; m2[0].text_len = LEN_PHONE_NUM;
		choice = newmenu_do(NULL, TXT_ENTER_NUMBER_DIAL, 1, m2, NULL);
		if (choice == -1)
			goto menu;
		else
			return NUM_PHONE_NUM;
	}

	// A phone number was chosen
	return(choice);
}

void
com_wait_for_connect(int nitems, newmenu_item *menus, int *key, int citem)
{
	int result;
	char input_buffer[81];
	int baud;
	char error_mess[5][15] = 
		{"NO DIAL TONE",
		 "BUSY",
		 "NO ANSWER",
		 "NO CARRIER",
		 "VOICE"};
	char text[100];
	int i;

	int num_error_messages = 5;

	menus = menus;
	nitems = nitems;
	citem = citem;

	
	if (GetCd(com_port))
	{
		carrier_on = 1;
	}
	else
	{
		if (carrier_on)
		{
			*key = -3;
			nm_messagebox(NULL, 1, TXT_OK, TXT_CARRIER_LOST);
			carrier_on = 0;
			return;
		}
	}

	result = HMInputLine(com_port, 500, input_buffer, 80);
	
	if (result == 0) 
		return;		// Timed out

	mprintf((0, "Modem string: '%s'\n", input_buffer));

	for (i = 0; i < num_error_messages; i++)
	{
		if (!strncmp(input_buffer, error_mess[i], strlen(error_mess[i])))
		{
			sprintf(text, "%s %s", TXT_ERR_MODEM_RETURN, input_buffer);
			nm_messagebox(NULL, 1, TXT_OK, text);
			*key = -3;
			return;
		}
	}

	if (strncmp(input_buffer, TXT_CONNECT, 7))
	{
		mprintf((0, "Non-connect message found.\n"));
		return; // some other string.  Not an error, but not a connect
	}

	sscanf(input_buffer, "CONNECT %d", &baud);

	mprintf((0, "Connect at %d baud.\n", baud));

	if (baud < 9600)
	{
		nm_messagebox(NULL, 1, TXT_OK, TXT_BAUD_GREATER_9600);
		*key = -3;
		return;
	}

	com_baud_rate = baud;
	*key = -2;
	
	return;
}

codex(code_17s, code_17e)

void
com_wait_for_ring(int nitems, newmenu_item *menus, int *key, int citem)
{
	int result;
	char input_buffer[81];

	menus = menus;
	nitems = nitems;
	citem = citem;

	result = HMInputLine(com_port, 500, input_buffer, 80);
	
	if ((result <= 0)	|| strncmp(input_buffer, TXT_RING, 4))
		return;
	
	*key = -2;

	return;
	
}

int modem_verify(void)
{
	// Is there a modem on this com port or not?

	int result;

	HMWaitForOK( 5000, NULL);

//=================================================
// This was changed by John in response to a 
// Creative voice modem not working, since this
// code doesn't wait for an OK.

//OLD	HMSendStringNoWait(com_port, "AT", -2);
//OLD	result = HMSendString(com_port, "AT");

	HMSendString(com_port, "AT" );
	result = HMSendString(com_port, "AT");
//=================================================

	if (result != 0)
		return (0);
	return(1);
}
	
void modem_dialout(void)
{
	newmenu_item m[5];
	char text[50];
	int choice;
	
	if (!serial_active)
	{
		nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_NO_SERIAL_OPT);
		return;
	}

	com_enable(); // Open COM port as configured

	if (!com_open)
		return;

//	UseRtsCts(com_port, ON); // use hardware handshaking

main:
	if ((choice = modem_dial_menu()) == -1)
		return; // user aborted

	show_boxed_message(TXT_RESET_MODEM);

	// Verify presence of modem
	if (!modem_verify())
	{
		clear_boxed_message();
		nm_messagebox(NULL, 1, TXT_OK, TXT_NO_MODEM);
		com_abort();
		return;
	}

	if (strlen(phone_num[choice]) == 0)
	{
		clear_boxed_message();
		nm_messagebox(NULL, 1, TXT_OK, TXT_NO_PHONENUM);
		goto main;
	}

	clear_boxed_message();

	sprintf(text, "%s\n%s", TXT_DIALING, phone_num[choice]);
	show_boxed_message(text);

	// Proceed with the call

	HMReset( com_port );

	HMSendString( com_port, modem_init_string );
	
	HMDial( com_port, phone_num[choice] );
	
	carrier_on = 0;

	clear_boxed_message();

	m[0].type=NM_TYPE_TEXT; m[0].text=TXT_ESC_ABORT;

//repeat:
	choice = newmenu_do(NULL, TXT_WAITING_FOR_ANS, 1, m, com_wait_for_connect);
	if (choice != -2) {
		HMSendStringNoWait(com_port, "", -2);
		com_abort();
		return;
	}

	// We are now connected to the other modem

	N_players = 2;

	master = 1;   // The person who dialed is the master of the connection
	change_playernum_to(0);

	if (!com_connect())
	{
		Game_mode |= GM_MODEM;
		digi_play_sample(SOUND_HUD_MESSAGE, F1_0);
	}
	else {
		HMSendStringNoWait(com_port, "", -2);
		com_abort();
	}
}
									 	
codex(code_18s, code_18e)

void modem_answer(void)
{
	int choice;
	newmenu_item m[3];

	if (!serial_active)
	{
		nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_NO_SERIAL_OPT);
		return;
	}

	com_enable(); // Open COM port as configured

	if (!com_open)
		return;

//	UseRtsCts(com_port, ON); // use hardware handshaking
	
	show_boxed_message(TXT_RESET_MODEM);

	// Verify presence of modem
	if (!modem_verify())
	{
		clear_boxed_message();
		nm_messagebox(NULL, 1, TXT_OK, TXT_NO_MODEM);
		com_abort();
		return;
	}

	HMReset( com_port );
	
	HMSendString( com_port, modem_init_string );

	HMSendString( com_port, "AT"); // To set the DTE rate for RING notification

	clear_boxed_message();

	m[0].type=NM_TYPE_TEXT; m[0].text=TXT_ESC_ABORT;

repeat:
	choice = newmenu_do(NULL, TXT_WAITING_FOR_CALL, 1, m, com_wait_for_ring);
	if (choice == -1) {
		HMSendStringNoWait(com_port, "", -2);
		com_abort();
		return;
	}
	if (choice != -2)	
		goto repeat;

	// Now answer the phone and wait for carrier

	HMAnswer(com_port);

	carrier_on = 0;

	choice = newmenu_do(NULL, TXT_WAITING_FOR_CARR, 1, m, com_wait_for_connect);
	if (choice != -2) {
		HMSendStringNoWait(com_port, "", -2);
		com_abort();
		return;
	}

	// We are now connected to the other modem
	
	N_players = 2;

	master = 0;
	change_playernum_to(1);

	if (!com_connect()) 
	{
		Game_mode |= GM_MODEM;
		digi_play_sample(SOUND_HUD_MESSAGE, F1_0);
	}
	else {
		HMSendStringNoWait(com_port, "", -2);
		com_abort();
	}
}

void serial_link_start(void)
{
	if (!serial_active)
	{
		nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_NO_SERIAL_OPT);
		return;
	}

	com_enable(); // Open COM port as configured

	if (!com_open)
		return;

	N_players = 2;

	synccnt = 0;

	srand(clock());
	my_sync.sync_time = rand();
	mprintf((0, "My rand set to %d.\n", my_sync.sync_time));

	if (!com_connect()) 
	{
		Game_mode |= GM_SERIAL;
		digi_play_sample(SOUND_HUD_MESSAGE, F1_0);
	} 
	else
	{
		nm_messagebox(NULL, 1, TXT_OK, "%s\n%s", TXT_ERROR, TXT_FAILED_TO_NEGOT);
	}
}

//
// Syncronization functions
//

void
serial_sync_abort(int val)
{
	// Send "I got Sync but it was no good!" packet

	sendbuf[0] = (char)MULTI_END_SYNC;
	sendbuf[1] = Player_num;
	sendbuf[2] = (char)val; // Indicated failure
#ifndef SHAREWARE
	sendbuf[3] = my_sync.sync_id;
	com_send_data(sendbuf, 4, 1);
#else
	com_send_data(sendbuf, 3, 1);
#endif
}
	
int
com_level_sync(void)
{
	// Send between-level sync stuff

	mprintf((0, "entered com_level_sync()\n"));

	Function_mode = FMODE_MENU; // Prevent the game loop from running during the menus!

	// At this point, the new level is loaded but the extra objects or players have not 
	// been removed

	my_sync.level_num = Current_level_num;
	my_sync.seg_checksum = netmisc_calc_checksum(Segments, (Highest_segment_index+1) * sizeof(segment));
	my_sync.kills[0] = kill_matrix[Player_num][0];
	my_sync.kills[1] = kill_matrix[Player_num][1];
	my_sync.proto_version = MULTI_PROTO_VERSION;
#ifndef SHAREWARE
	my_sync.killed = Players[Player_num].net_killed_total;
#endif
	srand(clock());

	if (Game_mode & GM_MULTI_COOP)
		my_sync.difficulty = Player_num;
	else
		my_sync.difficulty = rand()%MAX_NUM_NET_PLAYERS; // My starting position

	if (com_sync(Current_level_num))
	{
		com_process_mode = COM_PROCESS_MENU;
		nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_NEGOTIATION_FAIL );
		longjmp(LeaveGame, 0);
	}

	if (my_sync.level_num != other_sync.level_num)
	{
		// Fatal error
		nm_messagebox(TXT_ERROR, 1, TXT_OK, "%s %d\n%s %d", TXT_FATAL_ERROR_LEVEL, my_sync.level_num, TXT_OTHER_LEVEL, other_sync.level_num);
		longjmp(LeaveGame, 0);
	}

	if (my_sync.seg_checksum != other_sync.seg_checksum)
	{
		// Checksum failure
		mprintf((1, "My check %d, other check %d.\n", my_sync.seg_checksum, other_sync.seg_checksum));
		nm_messagebox(TXT_ERROR, 1, TXT_OK, "%s %d %s %s%s", TXT_YOUR_LEVEL, my_sync.level_num, TXT_LVL_NO_MATCH, other_sync.callsign, TXT_CHECK_VERSION);
		longjmp(LeaveGame, 0);
	}

	if (my_sync.proto_version != other_sync.proto_version)
	{
		// Version mismatch
		nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_DESCENT_NO_MATCH);
		longjmp(LeaveGame, 0);
	}

	mprintf((0, "My pos = %d, other pos = %d.\n", my_sync.difficulty, other_sync.difficulty));

	if ((other_sync.difficulty == my_sync.difficulty) && !master)
	{
		// If we chose the same position and I am the slave, choose another
		my_sync.difficulty = (my_sync.difficulty+1) % MAX_NUM_NET_PLAYERS;
	}

	Objects[Players[OtherPlayer].objnum].pos = Player_init[other_sync.difficulty].pos;
	Objects[Players[OtherPlayer].objnum].orient = Player_init[other_sync.difficulty].orient;
	obj_relink(Players[OtherPlayer].objnum,Player_init[other_sync.difficulty].segnum);
	Objects[Players[OtherPlayer].objnum].type = OBJ_PLAYER;

	Objects[Players[Player_num].objnum].pos = Player_init[my_sync.difficulty].pos;
	Objects[Players[Player_num].objnum].orient = Player_init[my_sync.difficulty].orient;
	obj_relink(Players[Player_num].objnum, Player_init[my_sync.difficulty].segnum);
	Objects[Players[Player_num].objnum].type = OBJ_PLAYER;

	SerialLastMessage = GameTime;

	kill_matrix[OtherPlayer][0] = other_sync.kills[0];
	kill_matrix[OtherPlayer][1] = other_sync.kills[1];
	Players[Player_num].net_kills_total = kill_matrix[Player_num][OtherPlayer] - kill_matrix[Player_num][Player_num];
	Players[OtherPlayer].net_kills_total = kill_matrix[OtherPlayer][Player_num] - kill_matrix[OtherPlayer][OtherPlayer];
//	Players[Player_num].net_killed_total = kill_matrix[0][Player_num] + kill_matrix[1][Player_num];
//	Players[OtherPlayer].net_killed_total = kill_matrix[0][OtherPlayer] + kill_matrix[1][OtherPlayer];
	Players[OtherPlayer].net_killed_total = other_sync.killed;
	Players[OtherPlayer].connected = Players[Player_num].connected = 1;

	Assert(N_players == 2);
	Assert(Player_num != OtherPlayer);

	gr_palette_fade_out(gr_palette, 32, 0);

	Function_mode = FMODE_GAME;
	com_process_mode = COM_PROCESS_NORMAL;
	multi_sort_kill_list();
	return(0);
}

codex(code_19s, code_19e)
	
void
com_send_end_sync(void)
{
	// Send "I got Sync" packet

	sendbuf[0] = (char)MULTI_END_SYNC;
	sendbuf[1] = Player_num;
	sendbuf[2] = 1; // Indicates success
#ifndef SHAREWARE
	sendbuf[3] = my_sync.sync_id;
	com_send_data(sendbuf, 4, 2);
#else
	com_send_data(sendbuf, 3, 2);
#endif
}

void
com_send_begin_sync(void)
{
	mprintf((0, "Sending my sync.\n"));
	com_send_data((char *)&my_sync, sizeof(com_sync_pack)-3, 1);
}

void
com_process_end_sync(byte *buf)
{
	// Process incoming end-sync packet

	if (buf[2] != 1) {
		com_process_mode = COM_PROCESS_MENU;
		return;
	}

#ifndef SHAREWARE
	if (buf[3] == my_sync.sync_id)
#endif
		other_got_sync = 1;
}

void
com_process_sync(char *buf, int len)
{
	len = len;
	switch(buf[0])
	{
		case MULTI_END_SYNC:
		{
			com_process_end_sync(buf);
			break;
		}
		case MULTI_BEGIN_SYNC:
		{
			mprintf((0, "Incoming begin_sync message.\n"));
			if (got_sync)
				break;

			memcpy(&other_sync, buf, sizeof(com_sync_pack)-3);
#ifndef SHAREWARE
			if (other_sync.sync_id != my_sync.sync_id) 
			{
				mprintf((0, "Other sync invalid id, %d != %d.\n", other_sync.sync_id, my_sync.sync_id));
			}
			else
#endif
			{			
				mprintf((0, "got other sync size %d.\n", sizeof(com_sync_pack)-3));
				got_sync = 1;
				com_send_end_sync();
			}
			break;
		}
	} // switch

	if (got_sync && other_got_sync)
	{
		// OK to start game
//		mprintf((1, "Starting game.\n"));
		got_sync = 0;
		other_got_sync = 0;
		com_process_mode = COM_PROCESS_NORMAL;
	}
}
	
void
com_send_sync(void)
{
	// Send sync information, depending on the situation

	if (!other_got_sync)
	{
		com_send_begin_sync();
	}
	if (got_sync)
	{
		com_send_end_sync();
	}
}

codex(code_20s, code_20e)

void com_sync_poll(int nitems, newmenu_item *menus, int *key, int citem)
{
	static fix t1 = 0;

	menus = menus;
	nitems = nitems;
	citem = citem;

	if (!com_open)
	{
		*key = -3;
		return;
	}

	if (timer_get_approx_seconds() > t1+F1_0)
	{
		com_send_sync();
		t1 = timer_get_approx_seconds();
	}

	Assert(com_process_mode == COM_PROCESS_SYNC);
		
	com_process_input();

	if (com_process_mode == COM_PROCESS_NORMAL)
	{
		*key = -2;
		com_send_sync();
		mprintf((0, "Sync finished.\n"));
		return;
	}
	if (com_process_mode == COM_PROCESS_MENU)
	{
		*key = -3;
		mprintf((0, "Sync denied by other side.\n"));
		return;
	}
	return;
}

int
com_sync(int id)
{
	// How to handle the end of the level and start of the next level
	// returns 0 for success or 1 for failure

	int choice;
	newmenu_item m[3];
	int pcx_error;

	mprintf((0, "Entered com_sync\n"));

	gr_set_current_canvas(NULL);
	pcx_error = pcx_read_bitmap(Menu_pcx_name, &grd_curcanv->cv_bitmap,grd_curcanv->cv_bitmap.bm_type,NULL);
	Assert(pcx_error == PCX_ERROR_NONE);

	com_process_mode = COM_PROCESS_SYNC;
	got_sync = other_got_sync = 0;

	com_flush();
	com_flush();

#ifndef SHAREWARE
	my_sync.sync_id = id;
#endif

	m[0].type=NM_TYPE_TEXT; m[0].text=TXT_ESC_ABORT;
	m[1].type = m[2].type = NM_TYPE_MENU;
	m[1].text = TXT_YES; 
	m[2].text = TXT_NO;

repeat:
	choice = newmenu_do(NULL, TXT_WAIT_OPPONENT, 1, m, com_sync_poll);

	if (choice == -1) 
	{
		choice = newmenu_do1(NULL, TXT_SURE_ABORT_SYNC, 2, m+1, com_sync_poll, 1);
		if (choice == -1)
			choice = 1;
		if (choice == 0)
			choice = -1;
	}

	if ((choice == -1) || (choice == -3)) {
		return(-1);
	}
	else if (choice != -2)
		goto repeat;

	return(0);
}

void
com_endlevel(int *secret)
{
	// What do we do between levels?

	Function_mode = FMODE_MENU;

	gr_palette_fade_out(gr_palette, 32, 0);

	my_sync.level_num = multi_goto_secret;

	if (com_sync(-3))
	{
		com_process_mode = COM_PROCESS_MENU;
		nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_NEGOTIATION_FAIL);
		longjmp(LeaveGame, 0);
	}

	com_process_mode = COM_PROCESS_ENDLEVEL;

	if ((multi_goto_secret == 1) || (other_sync.level_num == 1))
		*secret = 1;
	else
		*secret = 0;

	multi_goto_secret = 0;

	return;
}

codex(code_21s, code_21e)

#endif


