static void fs2netd_store_stats_results()
{
	char str[512];

	memset(str, 0, sizeof(str));

	multi_display_chat_msg(XSTR("<PXO stats store process complete>", 1001), 0, 0);
	ml_string( XSTR("<PXO stats store process complete>", 1001) );

	if (Multi_debrief_stats_accept_code != 1) {
		sprintf(str, XSTR("<PXO stats store failed for player %s>", 1002), Net_player->m_player->callsign);
		multi_display_chat_msg(str, 0, 0);
		ml_string(str);
	}
}
// attempt to kick a player. return success or fail
void multi_kick_player(int player_index, int ban, int reason)
{	
	// only the standalone should be able to kick the host of the game
	if(!(Game_mode & GM_STANDALONE_SERVER) && ((Net_players[player_index].flags & NETINFO_FLAG_GAME_HOST) || (Net_players[player_index].flags & NETINFO_FLAG_AM_MASTER))){
		nprintf(("Network","Cannot kick the host or server of a game!\n"));
	} else {
		// if we're the master, then delete the guy
		if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
			// if we're supposed to ban him, add his address to the banned list
			if(ban){
				multi_kick_add_ban(&Net_players[player_index].p_info.addr);
			}

			// mark him as having been kicked
			Net_players[player_index].flags |= NETINFO_FLAG_KICKED;

			// set his kick timestamp and send him a leave game packet
			Net_players[player_index].s_info.kick_timestamp = timestamp(MULTI_KICK_RESPONSE_TIME);
			Net_players[player_index].s_info.kick_reason = reason;
			send_leave_game_packet(Net_players[player_index].player_id, reason, &Net_players[player_index]);							

			// tell everyone else that he was kicked
			send_leave_game_packet(Net_players[player_index].player_id, reason);
			
			// wait until he either shuts his connection down or he times out)
			// add the string to the chatbox and the hud (always safe - if it is not inited, nothing bad will happen)			
			char str[512];
			memset(str, 0, 512);
			sprintf(str, XSTR("<kicking %s ...>", 1501), Net_players[player_index].m_player->callsign);
			multi_display_chat_msg(str, player_index, 0);							 
		}
		// otherwise, we should send the packet indicating that this guy should be kicked
		else {
			send_player_kick_packet(player_index, ban, reason);
		}
	}
}
// general quit function, with optional notification, error, and winsock error codes
int multi_quit_game(int prompt, int notify_code, int err_code, int wsa_error)
{
	int ret_val,quit_already;

	// check for reentrancy
	if(Multi_quit_game){
		return 0;
	}

	// if we're not connected or have not net-player
	if((Net_player == NULL) || !(Net_player->flags & NETINFO_FLAG_CONNECTED)){
		return 1;
	}

	// reentrancy
	Multi_quit_game = 1;

	quit_already = 0;

	// reset my control info so that I don't continually do whacky stuff.  This is ugly
	//player_control_reset_ci( &Player->ci );
	if ( Game_mode & GM_IN_MISSION ) {
		memset(&Player->ci, 0, sizeof(Player->ci) );
		Player->ci.afterburner_stop = 1;
		physics_read_flying_controls( &Player_obj->orient, &Player_obj->phys_info, &(Player->ci), flFrametime);
	}

	// CASE 1 - response to a user request
	// if there is no associated notification or error code, don't override the prompt argument
	if((err_code == -1) && (notify_code == -1)){
		// if we're the server and we're already waiting for clients to leave, don't do anything
		if((Net_player->flags & NETINFO_FLAG_AM_MASTER) && Multi_endgame_server_waiting){
			Multi_quit_game = 0;
			return 0;
		}

		// if we're the client and we're already waiting to leave, don't do anythin
		if(!(Net_player->flags & NETINFO_FLAG_AM_MASTER) && Multi_endgame_client_waiting){
			Multi_quit_game = 0;
			return 0;
		}

		// see if we should be prompting the host for confirmation
		if((prompt==PROMPT_HOST || prompt==PROMPT_ALL) && (Net_player->flags & NETINFO_FLAG_GAME_HOST)){
			int p_flags;

			p_flags = PF_USE_AFFIRMATIVE_ICON | PF_USE_NEGATIVE_ICON | PF_BODY_BIG;
			if ( Game_mode & GM_IN_MISSION )
				p_flags |= PF_RUN_STATE;

			ret_val = popup(p_flags,2,POPUP_CANCEL,POPUP_OK,XSTR("Warning - quitting will end the game for all players!",647));

			// check for host cancel
			if((ret_val == 0) || (ret_val == -1)){
				Multi_quit_game = 0;
				return 0;
			}

			// set this so that under certain circumstances, we don't call the popup below us as well
			quit_already = 1;
		}

		// see if we should be prompting the client for confirmation
		if((prompt==PROMPT_CLIENT || prompt==PROMPT_ALL) && !quit_already){
			ret_val = popup(PF_USE_AFFIRMATIVE_ICON | PF_USE_NEGATIVE_ICON | PF_BODY_BIG,2,POPUP_NO,POPUP_YES,XSTR("Are you sure you want to quit?",648));

			// check for host cancel
			if((ret_val == 0) || (ret_val == -1)){
				Multi_quit_game = 0;
				return 0;
			}
			quit_already = 1;
		}

		// if i'm the server of the game, tell all clients that i'm leaving, then wait
		if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
			send_netgame_end_error_packet(MULTI_END_NOTIFY_SERVER_LEFT,MULTI_END_ERROR_NONE);

			// set the waiting flag and the waiting timestamp
			Multi_endgame_server_waiting = 1;
			Multi_endgame_server_wait_stamp = MULTI_ENDGAME_SERVER_WAIT;
		}
		// if i'm the client, quit now
		else {
			multi_endgame_cleanup();
		}
	}
	// CASE 2 - response to an error code or packet from the server
	// this is the case where we're being forced to quit the game because of some error or other notification
	else {				
		// if i'm the server, send a packet to the clients telling them that I'm leaving and why
		if((Net_player->flags & NETINFO_FLAG_AM_MASTER) && !Multi_endgame_server_waiting){
			// if we're in the debrief state, mark down that the server has left the game
			if(((gameseq_get_state() == GS_STATE_DEBRIEF) || (gameseq_get_state() == GS_STATE_MULTI_DOGFIGHT_DEBRIEF)) && !(Game_mode & GM_STANDALONE_SERVER)){
				multi_debrief_server_left();

				// add a message to the chatbox
				multi_display_chat_msg(XSTR("<Team captains have left>",649),0,0);				
				
				// set ourselves to be "not quitting"
				Multi_quit_game = 0;

				// tell the users, the game has ended
				send_netgame_end_error_packet(notify_code,err_code);				
				return 0;
			}

			send_netgame_end_error_packet(notify_code,err_code);			

			// store the globals 
			Multi_endgame_notify_code = notify_code;
			Multi_endgame_error_code = err_code;
			Multi_endgame_wsa_error = wsa_error;

			// by setting this, multi_endgame_process() will know to check and see if it is ok for us to leave
			Multi_endgame_server_waiting = 1;				
			Multi_endgame_server_wait_stamp = MULTI_ENDGAME_SERVER_WAIT;
		}
		// if i'm the client, set the error codes and leave the game now
		else if(!Multi_endgame_client_waiting){
			// if we're in the debrief state, mark down that the server has left the game
			if((gameseq_get_state() == GS_STATE_DEBRIEF) || (gameseq_get_state() == GS_STATE_MULTI_DOGFIGHT_DEBRIEF)){
				multi_debrief_server_left();

				// add a message to the chatbox
				multi_display_chat_msg(XSTR("<The server has ended the game>",650),0,0);

				// shut our reliable socket to the server down
				psnet_rel_close_socket(&Net_player->reliable_socket);
				Net_player->reliable_socket = INVALID_SOCKET;

				// remove our do-notworking flag
				Net_player->flags &= ~(NETINFO_FLAG_DO_NETWORKING);
				
				Multi_quit_game = 0;
				return 0;
			}

			Multi_endgame_notify_code = notify_code;
			Multi_endgame_error_code = err_code;
			Multi_endgame_wsa_error = wsa_error;

			// by setting this, multi_endgame_process() will know to check and see if it is ok for us to leave
			Multi_endgame_client_waiting = 1;			
		}
	}		

	// unset the reentrancy flag
	Multi_quit_game = 0;

	return 1;
}
static void fs2netd_handle_messages()
{
	int buffer_size = 0, buffer_offset = 0;
	int bytes_read = 0;
	char tbuf[256];
	char buffer[8192];
	ubyte pid = 0;
	int itemp;

	while ( FS2NetD_DataReady() && (bytes_read < (int)sizeof(buffer)) ) {
		int read_size = FS2NetD_GetData(buffer+bytes_read, sizeof(buffer)-bytes_read);

		if (read_size <= 0) {
			break;
		}

		bytes_read += read_size;

		Sleep(20);
	}

	if ( (bytes_read == 0) || (bytes_read < BASE_PACKET_SIZE) ) {
		return;
	}

	Last_activity = timer_get_seconds();

	buffer_offset = 0;

	while (buffer_offset+BASE_PACKET_SIZE <= bytes_read) {
		PXO_GET_DATA( pid );
		PXO_GET_INT( buffer_size );

		// packet has more data than our buffer received
		if (buffer_offset+buffer_size-BASE_PACKET_SIZE > bytes_read) {
			break;
		}

		// processing time!
		switch (pid) {
			case PCKT_PING: {
				PXO_GET_INT( itemp );

			//	ml_printf("FS2NetD received PING");

				FS2NetD_Pong(itemp);

				break;
			}

			case PCKT_PONG: {
				PXO_GET_INT( itemp );

				ml_printf("FS2NetD PONG: %d ms", timer_get_milliseconds() - itemp);

				// reset timestamp, since the ping was successful
				Ping_timestamp = -1;

				break;
			}

			case PCKT_NETOWRK_WALL: {
				PXO_GET_STRING( tbuf );
				ml_printf("FS2NetD WALL received MSG: %s", tbuf);

				switch (Netgame.game_state) {
					case NETGAME_STATE_FORMING:
					case NETGAME_STATE_BRIEFING:
					case NETGAME_STATE_MISSION_SYNC:
					case NETGAME_STATE_DEBRIEF:
						multi_display_chat_msg(tbuf, 0, 0);
						break;

					case NETGAME_STATE_IN_MISSION: // gotta make it paused
						//multi_pause_request(1); 
						//send_game_chat_packet(Net_player, str, MULTI_MSG_ALL, NULL);
						HUD_printf(tbuf);
						break;

					default:
						// do-nothing
						break;
				}

				break;
			}

			case PCKT_CHAT_CHAN_COUNT_REPLY: {
				PXO_GET_STRING( tbuf );
				PXO_GET_INT( itemp );

				if ( (itemp < 0) || (itemp > USHRT_MAX) ) {
					itemp = 0;
				}

				multi_pxo_channel_count_update(tbuf, itemp);

				break;
			}

			case PCKT_VALID_SID_REPLY: {
				ubyte login_status = 0;

				PXO_GET_DATA( login_status );

				if (login_status != 1) {
					ml_printf("FS2NetD IDENT: Got invalid login check!");
					fs2netd_reset_connection();
				}

				break;
			}

			case PCKT_DUP_LOGIN_REPLY: {
				ubyte dupe_status = 0;

				PXO_GET_DATA( dupe_status );

				Duplicate_login_detected = (dupe_status != 0);

				break;
			}

			case PCKT_SLIST_REPLY: {
				int numServers = 0;
				int svr_flags;
				ushort svr_port;
				char svr_ip[16];
				active_game ag;

				PXO_GET_USHORT( numServers );

				if (numServers == 0) {
					break;
				}

				for (int i = 0; i < numServers; i++) {
					PXO_GET_INT( svr_flags );
					PXO_GET_USHORT( svr_port );
					PXO_GET_STRING( svr_ip );

					if ( !psnet_is_valid_ip_string(svr_ip) ) {
						ml_printf("FS2NetD SLIST: Invalid ip string (%s)!", svr_ip);
					} else {
						memset( &ag, 0, sizeof(active_game) );

						ag.server_addr.type = NET_TCP;
						ag.server_addr.port = (short) svr_port;

						if (ag.server_addr.port <= 0) {
							ag.server_addr.port = DEFAULT_GAME_PORT;
						}

						psnet_string_to_addr(&ag.server_addr, svr_ip);

						// query this server
						send_server_query(&ag.server_addr);
					}
				}

				break;
			}

			default: {
				break;
			}
		}
	}
}
void fs2netd_store_stats()
{
	if ( !Logged_in ) {
		return;
	}

	ml_string("Sending stats to server");

	// default to not saving the stats
	Multi_debrief_stats_accept_code = 0;

	if (Duplicate_login_detected) {
		Duplicate_login_detected = false;
		multi_display_chat_msg( XSTR("<Duplicate login detected - stats have been tossed>", 1582), 0, 0 );
		ml_string( XSTR("<Duplicate login detected - stats have been tossed>", 1583) );
		fs2netd_store_stats_results();
		return;
	}

	if ( game_hacked_data() ) {
		multi_display_chat_msg( XSTR("<Hacked tables detected - stats have been tossed>", 1584), 0, 0 );
		popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("You are playing with a hacked tables, your stats will not be saved", 1585) );
		fs2netd_store_stats_results();
		return;
	}

	if ( (multi_num_players() <= 1) && (Multi_num_players_at_start <= 1) ) {
		multi_display_chat_msg(XSTR("<Not enough players were present at game start or end, stats will not be saved>", 1048), 0, 0);
		ml_string( XSTR("<Not enough players were present at game start or end, stats will not be saved>", 1048) );
		fs2netd_store_stats_results();
		return;
	}

/*
	// if any players have hacked info
	for(int idx = 0; idx < MAX_PLAYERS; idx++) {
		if ( MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && (Net_players[idx].flags & NETINFO_FLAG_HAXOR) ) {
			multi_display_chat_msg( XSTR("<Connected player has hacked info - tossing invalid stats>", -1), 0, 0 );
			return;
		}
	}
*/
	if ( !fs2netd_check_mission(Netgame.mission_name) ) {
		multi_display_chat_msg(XSTR("<Server detected a non PXO validated mission. Stats will not be saved>", 1049), 0, 0);
		popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("This is not a PXO validated mission, your stats will not be saved", 1050));
		fs2netd_store_stats_results();
		return;
	}

	int spd_ret = fs2netd_send_player();

	switch (spd_ret) { // 0 = pilot updated, 1  = invalid pilot, 2 = invalid (expired?) sid
		case -1:
			ml_string("<stats have been tossed - server error>");
			break;

		case 0:
			ml_string( XSTR("<stats have been accepted>", 850) );
			Multi_debrief_stats_accept_code = 1;
			break;

		case 1:
			ml_string("<stats have been tossed - pilot error>");
			break;

		case 2:
			// we should never get here with the new code
			Int3();
			ml_string("<stats have been tossed - invalid tracker id>");
			break;

		default:
			multi_display_chat_msg( XSTR("Unknown Stats Store Request Reply", 1586), 0, 0 );
			break;
	}

	fs2netd_store_stats_results();
}
void fs2netd_debrief_init()
{
	if ( !(Game_mode & GM_MULTIPLAYER) ) {
		return;
	}

	if ( !Om_tracker_flag ) {
		return;
	}

	if ( !Is_connected ) {
		return;
	}


	uint CurrentMissionChsum;
	bool mValidStatus = false;

	cf_chksum_long(Netgame.mission_name, &CurrentMissionChsum);

	mValidStatus = FS2NetD_CheckSingleMission(Netgame.mission_name, CurrentMissionChsum);

	if ( ((multi_num_players() > 1) || (Multi_num_players_at_start > 1)) && !game_hacked_data() && mValidStatus ) {
		// verify that we are logged in before doing anything else
		fs2netd_login();

		int spd_ret = FS2NetD_SendPlayerData(PXO_SID, Players[Player_num].callsign, Multi_tracker_login, &Players[Player_num]);

		switch (spd_ret) { // 0 = pilot updated, 1  = invalid pilot, 2 = invalid (expired?) sid
			case -1:
				multi_display_chat_msg( XSTR("<Did not receive response from server within timeout period>", -1), 0, 0 );
				multi_display_chat_msg( XSTR("<Your stats may not have been stored>", -1), 0, 0 );
				multi_display_chat_msg( XSTR("<This is not a critical error>", -1), 0, 0 );
				Multi_debrief_stats_accept_code = 1;
				break;

			case 0:
				multi_display_chat_msg( XSTR("<stats have been accepted>", 850), 0, 0 );
				Multi_debrief_stats_accept_code = 1;
				break;

			case 1:
				multi_display_chat_msg( XSTR("<stats have been tossed>", 850), 0, 0 );
				multi_display_chat_msg( XSTR("WARNING: Your pilot was invalid, this is a serious error, possible data corruption", -1), 0, 0 );
				Multi_debrief_stats_accept_code = 0;
				break;

			case 2:
				// we really shouldn't be here with the new code, but handle it just in case
				Int3();
			
				fs2netd_login();

				if (PXO_SID != -1) {
					if ( !FS2NetD_SendPlayerData(PXO_SID, Players[Player_num].callsign, Multi_tracker_login, &Players[Player_num]) ) {
						multi_display_chat_msg( XSTR("<stats have been accepted>", 850), 0, 0 );
						Multi_debrief_stats_accept_code = 1;
						break;
					 }
				}

				multi_display_chat_msg( XSTR("<stats have been tossed>", 851), 0, 0 );
				Multi_debrief_stats_accept_code = 0;
				break;

			default:
				multi_display_chat_msg( XSTR("Unknown Stats Store Request Reply", -1), 0, 0 );
				break;
		}
	} else {
		multi_display_chat_msg( XSTR("<stats have been tossed>", 851), 0, 0 );
		Multi_debrief_stats_accept_code = 0;
	}
}
void fs2netd_do_frame()
{
	int rc, buffer_size, buffer_offset;
	char buffer[300], str[256];
	ubyte pid = 0;
	int itemp;
	static fix NextPing = -1, NextHeartBeat = -1;
	static fix GotPong = -1;
	bool reset = false;

	// don't bother with this if we aren't on FS2NetD
	if ( !Om_tracker_flag ) {
		return;
	}

	if ( !(Game_mode & GM_MULTIPLAYER) ) {
		return;
	}

	// not connected to server
	if ( !Is_connected ) {
		return;
	}

	// in a previous processing loop, so don't do a frame until that has completed
	if ( In_process ) {
		return;
	}


	// if we didn't get a PONG within 4 minutes the server connection must have dropped
	if ( (GotPong != -1) && ((NextPing - GotPong) > (240 * F1_0)) ) {
		ml_printf("FS2NetD WARNING:  Lost connection to server!");
		FS2NetD_Disconnect();

		Is_connected = false;
		Logged_in = false;
		PXO_SID = -1;

		NextHeartBeat = -1;
		NextPing = -1;
		GotPong = -1;

		// try to reinit the server connection
		fs2netd_login();

		// make sure that we are good to go
		if ( !Is_connected ) {
			if (!Is_standalone) {
				gamesnd_play_iface(SND_GENERAL_FAIL);
				popup(PF_USE_AFFIRMATIVE_ICON | PF_TITLE_BIG | PF_TITLE_RED, 1, POPUP_OK, "ERROR:\nLost connection to the FS2NetD server!");
			}
	
			return;
		} else {
			ml_printf("FS2NetD NOTICE:  Connection to server has been reestablished!");
		}
	}

	// send out ping every 60 seconds
	if ( (NextPing == -1) || (timer_get_fixed_seconds() >= NextPing) ) {
		// if we have seen a long period of time between pings then reset the pong time too
		if ( (timer_get_fixed_seconds() - NextPing) > (120 * F1_0) ) {
			reset = true;
		}

		NextPing = timer_get_fixed_seconds() + (60 * F1_0);

		// we go ahead and set the initial GotPong here, even though we haven't gotten a pong yet
		if ( (GotPong == -1) || reset ) {
			GotPong = NextPing;
			reset = false;
		}

		FS2NetD_Ping();

		ml_printf("FS2NetD sent PING");
	}

	// send out server heartbeat every 2 minutes, unless forced to by an update
	if ( (Net_player->flags & NETINFO_FLAG_AM_MASTER) && (Multi_create_force_heartbeat || (NextHeartBeat == -1) || (timer_get_fixed_seconds() >= NextHeartBeat)) ) {
		Multi_create_force_heartbeat = 0;
		NextHeartBeat = timer_get_fixed_seconds() + (120 * F1_0);

		FS2NetD_SendHeartBeat(Netgame.name, Netgame.mission_name, Netgame.title, Netgame.type_flags, Netgame.server_addr.port, multi_num_players());

		ml_printf("FS2NetD sent HeartBeat");
	}

	// Check for GWall messages - ping replies, etc
	if ( (rc = FS2NetD_GetData(buffer, sizeof(buffer))) != -1 ) {
		int rc_total = rc;
		buffer_offset = 0;

		while (rc_total > buffer_offset) {
			// make sure we have enough data to try and process
			if (rc_total < BASE_PACKET_SIZE) {
				break;
			}

			PXO_GET_DATA( pid );
			PXO_GET_INT( buffer_size );

			while ( (rc_total < buffer_size) && ((sizeof(buffer) - rc_total) > 0) ) {
				if ( (rc = FS2NetD_GetData(buffer+rc_total, sizeof(buffer) - rc_total)) != -1 ) {
					rc_total += rc;
				} else {
					break;
				}
			}

			if (buffer_size <= 0) {
				break;
			}

			// we don't have the full packet, so bail
			if (rc_total < buffer_size) {
				break;
			}

			// processing time!
			switch (pid) {
				case PCKT_PING: {
					PXO_GET_INT( itemp );

					ml_printf("FS2NetD received PING");

					FS2NetD_Pong(itemp);
					break;
				}

				case PCKT_PONG: {
					PXO_GET_INT( itemp );

					ml_printf("FS2NetD received PONG: %d ms", timer_get_milliseconds() - itemp);

					GotPong = timer_get_fixed_seconds();
					break;
				}

				case PCKT_NETOWRK_WALL: {
					PXO_GET_STRING( str );
					ml_printf("FS2NetD WALL received MSG: %s", str);
	
					switch (Netgame.game_state) {
						case NETGAME_STATE_FORMING:
						case NETGAME_STATE_BRIEFING:
						case NETGAME_STATE_MISSION_SYNC:
						case NETGAME_STATE_DEBRIEF:
							multi_display_chat_msg(str, 0, 0);
							break;

						/* -- Won't Happen - multi_do_frame() is not called during paused state 
							  so the game will not even receive the data during it
						case NETGAME_STATE_PAUSED: // EASY!
							send_game_chat_packet(Net_player, str, MULTI_MSG_ALL, NULL);
							break;
						*/

						case NETGAME_STATE_IN_MISSION: // gotta make it paused
							//multi_pause_request(1); 
							//send_game_chat_packet(Net_player, str, MULTI_MSG_ALL, NULL);
							HUD_printf(str);
							break;

						default:
							// do-nothing
							break;
					}

					break;
				}

				default: {
					ml_printf("Unexpected FS2NetD Packet - PID = %x", pid);
					break;
				}
			}

			buffer_offset += (buffer_size - BASE_PACKET_SIZE);
		}
	}
}