コード例 #1
0
ファイル: dppv.c プロジェクト: jleclanche/darkdust-ctp2
/*--------------------------------------------------------------------------
 Delete a particular player from the table.
--------------------------------------------------------------------------*/
dp_result_t pv_deletePlayer(pv_t *pv, int player)
{
	int i;
	pv_peer_t *peer;
	if (!pv) return dp_RES_BUG;

	DPRINT(("pv_deletePlayer(dp, %d)\n", player));

	/* Get this player's table. */
	peer = (pv_peer_t *)assoctab_subscript(pv->peers, player);
	if (!peer) {
		DPRINT(("pv_deletePlayer: no table for player %d\n", player));
		return dp_RES_EMPTY;
	}

	/* Free all memory pointed to by the variables in peer->vars */
	for (i=0; i < peer->vars->n_used; i++) {
		pv_var_t *pvar;
		assoctab_item_t *pe;
		pe = assoctab_getkey(peer->vars, i);
		if (!pe)
			break;	/* horrible error */
		pvar = (pv_var_t *) &pe->value;

		/* Destroy their values */
		if (pvar->buf) dp_FREE(pvar->buf);
	}
	if (peer->incoming.buf) dp_FREE(peer->incoming.buf);

	/* Destroy it, and remove it from the table. */
	assoctab_destroy(peer->vars);
	if (assoctab_subscript_delete(pv->peers, player))
		return dp_RES_BUG;
	return dp_RES_OK;
}
コード例 #2
0
ファイル: trumpdll.c プロジェクト: jleclanche/darkdust-ctp2
/*-----------------------------------------------------------------------
 Given a local port number, a remote ip address, and a remote port number,
 return a handle that can be used to send an address to that address.

 If 0 is given for the local port number, picks an unused one.  Not suitable
 for a handle that will be used to listen for incoming service requests on
 a standard port number.

 If 0 is given for the remote port number, return a handle that can be used
 for receiving packets sent from any port at the remote host.

 If 255.255.255.255 is given for the remote address, return a handle
 that can be used for sending broadcast packets, or for receiving without
 regard to source address.

 If no handle exists for that address,
	 If insert is non-zero,
	  allocate one.
	 else
          return trump_HDL_NONE
 Multiple calls with the same arg will return the same result.
 There is no upper bound on handles.
 Returns trump_HDL_NONE on failure.
-----------------------------------------------------------------------*/
trump_hdl_t trump_adr2hdl(ip_adr_t remoteadr, short localport, short remoteport, int insert)
{
	// Search peer tab.  If not found, append it.
	trump_hdl_t h;
	ip_adr_t *peer;
	int i;
	unsigned int lport;
	OSStatus	err;

#if 0
	DPRINT(("trump_adr2hdl: adr "));
	printAdr(remoteadr);
	DPRINT((", dest_port %d, localport %d, insert %d\n", remoteport, localport, insert));
#endif

	// Look for existing address/handle
	for (i=0; i<peertab->n_used; i++) {
		assoctab_item_t *pe = assoctab_getkey(peertab, i);
		if (!pe)
			DPRINT(("trump_adr2hdl: couldn't find entry\n"));
		else {
			peer = (ip_adr_t *)pe->value;
			//DPRINT(("trump_adr2hdl: pe->value %p, *pe->value %x, remoteadr %x\n",
				//pe->value, *peer, remoteadr));
			if (memcmp(peer, &remoteadr, sizeof(ip_adr_t)) == 0) {
				//DPRINT(("trump_adr2hdl: found match, hdl %d\n", pe->key));
				return pe->key;
			}
		}
	}
	if (!insert) return trump_HDL_NONE;

	// Open new handle for this address
	h = tcpabi_udp_open(remoteadr, remoteport, localport, UDP_OPEN_NORMAL, &lport);
	if (h == trump_HDL_NONE)
		return h;

	// Save address
	peer = (ip_adr_t *)assoctab_subscript_grow(peertab, h);
	if (!peer) {
		// Abort! Out of memory!
		return trump_HDL_NONE;
	}
	dprint_peertab(peertab);
	//DPRINT(("trump_adr2hdl: saving %x at %p\n", remoteadr, peer));
	*peer = remoteadr;
	//dprint_peertab(peertab);
	//DPRINT(("trump_adr2hdl: udp_open successful. localport %d\n", lport));
	return h;
}
コード例 #3
0
ファイル: bhttp.c プロジェクト: jleclanche/darkdust-ctp2
/*------------------------------------------------------------------------
 Destroy an instance of the bhttp module.
------------------------------------------------------------------------*/
void bhttp_destroy(bhttp_t *bhttp)
{
	int i;

	if (!bhttp)
		return;
	close(bhttp->sockin);
	for (i = 0; i < bhttp->conns->n_used; i++) {
		assoctab_item_t *pi = assoctab_getkey(bhttp->conns, i);
		close(pi->key);
	}
	assoctab_destroy(bhttp->conns);
	memset(bhttp, 0, sizeof(bhttp_t));
	free(bhttp);
}
コード例 #4
0
ファイル: sbdserv.c プロジェクト: BygoneWorlds/anet
/*------------------------------------------------------------------------
 Destroy an instance of the sbdserv module.
------------------------------------------------------------------------*/
void sbdserv_destroy(sbdserv_t *sbdserv)
{
	int i;

	if (!sbdserv)
		return;
	close(sbdserv->sockin);
	for (i = 0; i < sbdserv->conns->n_used; i++) {
		assoctab_item_t *pi = assoctab_getkey(sbdserv->conns, i);
		close(pi->key);
	}	
	assoctab_destroy(sbdserv->conns);
	memset(sbdserv, 0, sizeof(sbdserv_t));
	free(sbdserv);
}
コード例 #5
0
ファイル: trumpdll.c プロジェクト: jleclanche/darkdust-ctp2
/*-----------------------------------------------------------------------
 Dump the player table to the log file.
-----------------------------------------------------------------------*/
static void dprint_peertab(assoctab_t *p)
{
#if defined(DPRNT) || defined(DEBUG)
	int i;
	for (i=0; i < p->n_used; i++) {
		assoctab_item_t	*ip			 = assoctab_getkey(p, i);
		trump_hdl_t		h			 = (trump_hdl_t) ip->key;
		ip_adr_t		*pAdr		 = (ip_adr_t *)   ip->value;
		DPRINT(("p[%d] = ", h));
		printAdr(*pAdr);
		DPRINT((", at %p\n", pAdr));
	}
#else
	(void)p;
#endif
}
コード例 #6
0
ファイル: dppv.c プロジェクト: jleclanche/darkdust-ctp2
/*--------------------------------------------------------------------------
 Destroy a pv_t.
--------------------------------------------------------------------------*/
void pv_destroy(pv_t *pv)
{
	int i;
	if (!pv) return;

	/* For all players.  Step backwards to avoid having to move in delete. */
	for (i=pv->peers->n_used-1; i>=0; i--) {
		assoctab_item_t *pe;
		pe = assoctab_getkey(pv->peers, i);
		if (!pe)
			break;	/* horrible error */

		/* Delete this player */
		pv_deletePlayer(pv, pe->key);
	}
	/* Destroy the main table */
	assoctab_destroy(pv->peers);
}
コード例 #7
0
ファイル: dppv.c プロジェクト: jleclanche/darkdust-ctp2
/*-----------------------------------------------------------------------
 Save the state of a pv_peer_t to disk along with who it's for.
-----------------------------------------------------------------------*/
static dp_result_t pv_peer_freeze(pv_peer_t *peer, dpid_t id, FILE *fp)
{
	int i;
	short dirty = peer->dirty;
	unsigned long n_vars;
	long sig = 0x12340009;
	dp_result_t err;

	DPRINT(("pv_peer_freeze:\n"));
	n_vars = (unsigned long) peer->vars->n_used;
	/* Header */
	if ((fwrite(&sig, sizeof(sig), 1, fp) != 1)
	||  (fwrite(&dirty, sizeof(dirty), 1, fp) != 1)
	||  (fwrite(&id, sizeof(id), 1, fp) != 1)
	||  (fwrite(&n_vars, sizeof(n_vars), 1, fp) != 1))
		return dp_RES_FULL;

	DPRINT(("pv_peer_freeze: peer %d, n_vars %d\n", id, n_vars));

	/* Body */
	for (i=0; i < peer->vars->n_used; i++) {
		assoctab_item_t *pe;
		pv_var_t *pvar;
		pe = assoctab_getkey(peer->vars, i);
		if (!pe)
			break;	/* horrible error */
		pvar = (pv_var_t *)&pe->value;
		DPRINT(("pv_peer_freeze: peer %d, var %d = %d, len %d\n",
			id, pe->key, pvar->key, pvar->len));
		err = pv_var_freeze(pvar, fp);
		if (err != dp_RES_OK)
			return err;
	}
	DPRINT(("pv_peer_freeze: freezing incoming\n"));
	err = pv_var_freeze(&peer->incoming, fp);
	if (err != dp_RES_OK)
		return err;

	/* No trailer */
	return dp_RES_OK;
}
コード例 #8
0
ファイル: trumpdll.c プロジェクト: jleclanche/darkdust-ctp2
int
commTerm(
	commTermReq_t *		req,		// Request (or NULL)
	commTermResp_t *	resp)		// Response (or NULL)
{
	commTermReq_t	reqDummy;
	commTermResp_t	respDummy;

	DPRINT(("@TRUMP commTerm(): "));

	if (req == NULL)
		req = (commTermReq_t *)memset(&reqDummy, 0, sizeof(*req));
	if (resp == NULL)
		resp = &respDummy;

	if (peertab) {
		int i;
		for (i=0; i<peertab->n_used; i++) {
			assoctab_item_t *pe = assoctab_getkey(peertab, i);
			if (!pe)
				DPRINT(("commTerm: couldn't find entry\n"));
			else if (pe->key != trump_HDL_ME) {
				DPRINT(("commTerm: closing handle %d\n", pe->key));
				tcpabi_udp_close(pe->key, UDP_CLOSE_NORMAL);
			} else {
				DPRINT(("commTerm: not closing handle %d\n", pe->key));
			}
		}
		assoctab_destroy(peertab);
	}
	free(scratch_flat);
	resp->status = comm_STATUS_OK;
	DPRINT(("commTerm: success\n"));

	//	unbind our endpoint and close open transport

	ShutDownUDP();

	return TRUE;
}
コード例 #9
0
ファイル: dppv.c プロジェクト: jleclanche/darkdust-ctp2
/*-----------------------------------------------------------------------
 Save the state of a pv_t to disk.
-----------------------------------------------------------------------*/
dp_result_t pv_freeze(pv_t *pv, FILE *fp)
{
	int i;
	unsigned long n_peers;
	pv_t temp;
	DPRINT(("pv_freeze:\n"));

	if (!pv) {
		DPRINT(("pv_freeze: pv null\n"));
		return dp_RES_BUG;
	}

	temp = *pv;

	/* Header */
	fwrite(pv_SIG_START, sizeof(pv_SIG_START), 1, fp);
	temp.peers = (assoctab_t *)0x12345679;		/* use as signature */
	temp.dp = NULL;
	n_peers = pv->peers->n_used;
	if ((fwrite(&temp, sizeof(pv_t), 1, fp) != 1)
	||  (fwrite(&n_peers, sizeof(n_peers), 1, fp) != 1))
		return dp_RES_FULL;

	/* Body */
	for (i=0; i<pv->peers->n_used; i++) {
		assoctab_item_t *pe;
		pe = assoctab_getkey(pv->peers, i);
		if (!pe)
			break;	/* horrible error */

		/* save this player */
		pv_peer_freeze((pv_peer_t *)&pe->value, pe->key, fp);
	}

	/* Trailer */
	fwrite(pv_SIG_END, sizeof(pv_SIG_END), 1, fp);

	return dp_RES_OK;
}
コード例 #10
0
ファイル: bhttp.c プロジェクト: jleclanche/darkdust-ctp2
/*------------------------------------------------------------------------
 Handle any new connections, data, or handle state changes.
 rfds and wfds should be the same fd_set*'s that were passed to select
 as it's second and third parameters.
 nsocks is the return value of select.

 Call this once after each select, regardless of whether select times out.

 Returns 0 upon success.
 Returns -1 on error; detailed error message is printed to log file if
 this is a debug build.
------------------------------------------------------------------------*/
int bhttp_poll(bhttp_t *bhttp, fd_set *rfds, fd_set *wfds, int nsocks)
{
	clock_t now;
	int sock;
	int i;

	if (!bhttp || !bhttp->conns)
		return -1;
	bhttp_assertValid(bhttp);
	assert(nsocks >= 0);
	bhttp->cursock = 0;	/* FIXME: should be min(socks) */
	bhttp->nsocks = nsocks;
	if (FD_ISSET(bhttp->sockin, rfds) && (bhttp->nsocks > 0)) {
		int newsock;
		struct sockaddr_in client_addr;
		int len = sizeof(struct sockaddr_in);

		newsock = accept(bhttp->sockin, (struct sockaddr *)&client_addr,&len);
		if (newsock == -1) {
			DPRINT(("bhttp_poll: accept error:%d on sock:%d\n", errno, bhttp->sockin));
		} else {
			int flags;

#ifdef VERBOSE
			DPRINT(("bhttp_poll: accepting connection from %s on sock:%d\n", inet_ntoa(client_addr.sin_addr), newsock));
#endif
			flags = fcntl(newsock, F_GETFL, 0);
			if ((-1 == flags)
			||  (-1 == fcntl(newsock, F_SETFL, (flags | O_NONBLOCK)))) {
				DPRINT(("bhttp_poll: fcntl error:%d on sock:%d\n", errno, newsock));
				close(newsock);
				return -1;
			} else if (-1 == bhttp_handleOpened(bhttp, newsock)) {
				DPRINT(("bhttp_poll: can't add h:%d\n", newsock));
				close(newsock);
				return -1;
			}
		}
		bhttp->nsocks--;
	}

	while ((bhttp->cursock <= bhttp->sockmax) && (bhttp->nsocks > 0)) {
		int used = FALSE;
		int len;
		int h = bhttp->cursock;
		bhttp_conn_t *pc = NULL;

		if (FD_ISSET(h, rfds)
		&&  FD_ISSET(h, &bhttp->rfds)) {

			pc = (bhttp_conn_t *)assoctab_subscript(bhttp->conns, h);
			if (!pc) {
				DPRINT(("bhttp_poll: no such handle h:%d\n", h));
				return -1;
			}
			len = bhttp_readData(pc, h);
			if (len == -1) {
				DPRINT(("bhttp_readData error\n"));
				bhttp_handleClosed(bhttp, h);
				close(h);
				/* silently fails */
			} else if (len > 0) {
				char buf[1024];
				int buflen;
				bhttp_url2buf_result_t urlResult;
#ifdef VERBOSE
				DPRINT(("bhttp_poll: h:%d finished receiving data\n", h));
#endif
				memset(&urlResult, 0, sizeof(urlResult));
				buflen = bhttp->url2buf_cb(bhttp->url2buf_context, pc->purl, buf, sizeof(buf), &urlResult);
				buf[buflen] = 0;
				/* Finished reading http header, formulate http response */
				bhttp_formatOutputBuffer(bhttp, h, urlResult.httpResultCode, urlResult.mimeType, buf);
				FD_SET(h, &bhttp->wfds);
			} else {
#ifdef VERBOSE
				DPRINT(("bhttp_poll: h:%d waiting for more data\n", h));
#endif
			}
			used = TRUE;
		}
		if (FD_ISSET(h, wfds)
		&&  FD_ISSET(h, &bhttp->wfds)) {
			if (!pc) pc = (bhttp_conn_t *)assoctab_subscript(bhttp->conns, h);
			if (!pc) {
				DPRINT(("bhttp_poll: no such handle h:%d\n", h));
				return -1;
			}
			len = bhttp_writeData(pc, h);
			if (len == -1) {
				DPRINT(("bhttp_writeData error\n"));
				bhttp_handleClosed(bhttp, h);
				close(h);
				/* silently fails */
			} else if (len > 0) {
#ifdef VERBOSE
				DPRINT(("bhttp_poll: h:%d finished sending data\n", h));
#endif
				bhttp_handleClosed(bhttp, h);
				if (-1 == close(h)) {
					DPRINT(("bhttp_poll: close error:%d\n", errno));
				}
			} else {
#ifdef VERBOSE
				DPRINT(("bhttp_poll: h:%d waiting for more data\n", h));
#endif
			}
			used = TRUE;
		}
		if (used)
			bhttp->nsocks--;
		bhttp->cursock++;
	}

	now = eclock();
	if ((long)(now - bhttp->t_last_poll) < 10 * ECLOCKS_PER_SEC)
		return 0;	/* don't poll more than once every 10s */
	bhttp->t_last_poll = now;
#ifdef VERBOSE
	DPRINT(("bhttp_poll: t:%d checking %d conns for timeouts\n", now, bhttp->conns->n_used));
#endif

	/* Iterate through handles, deleting those that have timed out,
	 * in reverse order, since deleting will change indices of later entries.
	 */
	for (i = bhttp->conns->n_used - 1; i >= 0; i--) {
		assoctab_item_t *pi;
		bhttp_conn_t *pc;
		int h;
 		pi = assoctab_getkey(bhttp->conns, i);
		if (!pi) {
			DPRINT(("bhttp_poll: assoctab_getkey(%d) returns NULL?\n", i));
			return -1;
		}
		h = pi->key;
		pc = (bhttp_conn_t *)pi->value;
		if (!pc) {
			DPRINT(("bhttp_poll: conns[%d] == NULL?\n", i));
			return -1;
		}
		if ((long)(now - pc->t_connect) > bhttp_CONN_TIMEOUT * ECLOCKS_PER_SEC) {
			DPRINT(("bhttp_poll: h:%d timed out\n", h));
			bhttp_handleClosed(bhttp, h);
			if (-1 == close(h)) {
				DPRINT(("bhttp_poll: close(%d) error:%d\n", h, errno));
				return -1;
			}
		}
	}
	return 0;
}
コード例 #11
0
ファイル: sbdserv.c プロジェクト: BygoneWorlds/anet
/*------------------------------------------------------------------------
 Handle any new connections, data, or handle state changes.
 rfds should be the same fd_set* that was passed to select as it's 
 second parameter.

 After a select (and regardless of whether select times out), call this
 repeatedly until it returns no data or an error. 

 Returns the length of data received if any, and returns the data in buf.
 		The caller should call sbdserv_poll again, since multiple handles
		might have data.
 Returns 0 if no data is available now.
 Returns -1 on error.
------------------------------------------------------------------------*/
int sbdserv_poll(sbdserv_t *sbdserv, fd_set *rfds, char *buf)
{
	clock_t now;
	int sock;
	int i;

	if (!sbdserv || !sbdserv->conns || !buf)
		return -1;
	sbdserv_assertValid(sbdserv);
	if (FD_ISSET(sbdserv->sockin, rfds) && (sbdserv->nsocks > 0)) {
		int newsock;
		struct sockaddr_in client_addr;
		int len = sizeof(struct sockaddr_in);

		newsock = accept(sbdserv->sockin, (struct sockaddr *)&client_addr,&len);
		if (newsock == -1) {
			DPRINT(("sbdserv_poll: accept error:%d on sock:%d\n", errno, sbdserv->sockin));
		} else {
			int flags;

#ifdef VERBOSE
			DPRINT(("sbdserv_poll: accepting connection from %s on sock:%d\n", inet_ntoa(client_addr.sin_addr), newsock));
#endif
			flags = fcntl(newsock, F_GETFL, 0);
			if ((-1 == flags)
			||  (-1 == fcntl(newsock, F_SETFL, (flags | O_NONBLOCK)))) {
				DPRINT(("sbdserv_poll: fcntl error:%d on sock:%d\n", errno, newsock));
				close(newsock);
				return -1;
			} else if (-1 == sbdserv_handleOpened(sbdserv, newsock)) {
				DPRINT(("sbdserv_poll: can't add h:%d\n", newsock));
				close(newsock);
				return -1;
			}
		}
		sbdserv->nsocks--;
	}

	while ((sbdserv->cursock <= sbdserv->sockmax) && (sbdserv->nsocks > 0)) {
		if (FD_ISSET(sbdserv->cursock, rfds)
		&&  FD_ISSET(sbdserv->cursock, &sbdserv->fds)) {
			char *bufp = NULL;
			int len;

			len = sbdserv_readData(sbdserv, sbdserv->cursock, &bufp);
			if (len == -1) {
				DPRINT(("sbdserv_readData error\n"));
				sbdserv_handleClosed(sbdserv, sbdserv->cursock);
				close(sbdserv->cursock);
				/* silently fails */
			} else if (len > 0) {
#ifdef VERBOSE
				DPRINT(("sbdserv_poll: h:%d finished sending data\n", sbdserv->cursock)); 
#endif
				/* must return copy, since closing will destroy original */
				memcpy(buf, bufp, len);
				sbdserv_handleClosed(sbdserv, sbdserv->cursock);
				if (-1 == close(sbdserv->cursock)) {
					DPRINT(("sbdserv_poll: close error:%d\n", errno));
				}
				sbdserv->nsocks--;
				sbdserv->cursock++;
				return len;
			} else {
#ifdef VERBOSE
				DPRINT(("sbdserv_poll: h:%d waiting for more data\n", sbdserv->cursock));
#endif
			}
			sbdserv->nsocks--;
		}
		sbdserv->cursock++;
	}

	now = eclock();
	if ((long)(now - sbdserv->t_last_poll) < 10 * ECLOCKS_PER_SEC)
		return 0;	/* don't poll more than once every 10s */
	sbdserv->t_last_poll = now;
#ifdef VERBOSE
	DPRINT(("sbdserv_poll: t:%d checking %d conns for timeouts\n", now, sbdserv->conns->n_used));
#endif

	/* Iterate through handles, deleting those that have timed out,
	 * in reverse order, since deleting will change indices of later entries.
	 */
	for (i = sbdserv->conns->n_used - 1; i >= 0; i--) {
		assoctab_item_t *pi;
		sbdserv_conn_t *pc;
		int h;
 		pi = assoctab_getkey(sbdserv->conns, i);
		if (!pi) {
			DPRINT(("sbdserv_poll: assoctab_getkey(%d) returns NULL?\n", i));
			return -1;
		}
		h = pi->key;
		pc = (sbdserv_conn_t *)pi->value;	
		if (!pc) {
			DPRINT(("sbdserv_poll: conns[%d] == NULL?\n", i));
			return -1;
		}
		if ((long)(now - pc->t_connect) > sbdserv_CONN_TIMEOUT * ECLOCKS_PER_SEC) {
			DPRINT(("sbdserv_poll: h:%d timed out\n", h));
			sbdserv_handleClosed(sbdserv, h);
			if (-1 == close(h)) {
				DPRINT(("sbdserv_poll: close(%d) error:%d\n", h, errno));
				return -1;
			}
		}
	}
	return 0;
}
コード例 #12
0
ファイル: dppv.c プロジェクト: jleclanche/darkdust-ctp2
dp_result_t pv_update(pv_t *pv, dpid_t owner)
{
	size_t len, hdrlen;
	pv_peer_t *peer;
	assoctab_item_t *pe;
	pv_var_t *pvar;
	dp_result_t err;
	playerHdl_t errHdl;
	time_t interval;
	int i, j;
	char buf[dpio_MAXLEN_RELIABLE];

	if (!pv) {
		DPRINT(("pv_update: pv null\n"));
		return dp_RES_BUG;
	}

	/* Wait 'til previous transmission has had time to get sent. */
	if ((long)(pv->dp->now - pv->next_send) < 0) return dp_RES_OK;
	/* Set default next-check-time if no transmission this time. */
	pv->next_send = pv->dp->now + pv->dp->clocksPerSec / 8;

	/* To propagate the given id's variables, need to get access to them */
	peer = (pv_peer_t *)assoctab_subscript(pv->peers, owner);
	if (!peer) {
		/*DPRINT(("pv_update: no variables for player %d\n", owner)); */
		return dp_RES_OK;
	}
	/*DPRINT(("pv_update: peer->dirty is %d for player %d\n", peer->dirty, owner));*/

	/* If it's time to start a new cycle, do it. */
	if (pv->cur_key_index == -1) {
		if (pv->new_ndests > 0) {
			/* New hosts have been added since last cycle. */
			/* Start a host update cycle. */

			/* Copy new host list. */
			pv->cur_ndests = pv->new_ndests;
			memcpy(pv->cur_dests, pv->new_dests, pv->new_ndests * sizeof(pv->new_dests[0]));
			/* Reset new host list. */
			pv->new_ndests = 0;

			/* Fill key list with all public keys for this player. */
			for (i=j=0; i<peer->vars->n_used && j<dp_PLAYERDATA_NKEYS_MAX; i++) {
				pe = assoctab_getkey(peer->vars, i);
				if (!pe) break;	/* horrible error */
				pvar = (pv_var_t *) &pe->value;
				if (!(pvar->flags & dp_PLAYERDATA_FLAG_NOFLOOD)) {
					pv->cur_keys[j++] = pe->key;
				}
			}
			/* Early exit if no public variables. */
			if (j == 0) return dp_RES_OK;
			pv->cur_nkeys = j;
			DPRINT(("pv_update: starting new host update cycle. nkeys %d, ndests %d\n", pv->cur_nkeys, pv->cur_ndests));
			/* Trigger start. */
			pv->cur_key_index = 0;
			pv->cur_offset = 0;
		} else if (peer->dirty > 0) {
			/* Varible values have changed since last cycle. */
			/* Start a variable update cycle. */
			peer->dirty = FALSE;
			DPRINT(("pv_update: Clearing peer->dirty for player %d\n", owner));

			/* Set host list to 'all other hosts in game'. */
			pv->cur_ndests = dp_getBroadcastHdls(pv->dp, pv->cur_dests);

			/* Fill key list with all dirty variables.  Mark them clean. */
			for (i=j=0; i<peer->vars->n_used && j<dp_PLAYERDATA_NKEYS_MAX; i++) {
				pe = assoctab_getkey(peer->vars, i);
				if (!pe) break;	/* horrible error */
				pvar = (pv_var_t *) &pe->value;
				if ((pvar->flags & dp_PLAYERDATA_FLAG_DIRTY)
				&&  !(pvar->flags & dp_PLAYERDATA_FLAG_NOFLOOD)) {
					pv->cur_keys[j++] = pe->key;
					pvar->flags &= ~dp_PLAYERDATA_FLAG_DIRTY;
				}
			}
			/* Early exit if no other hosts in game (after clearing dirty!). */
			if (pv->cur_ndests <= 0) return dp_RES_OK;
			/* Early exit if no dirty public variables. */
			if (j == 0) return dp_RES_OK;
			pv->cur_nkeys = j;
			DPRINT(("pv_update: starting new variable update cycle. nkeys %d, ndests %d\n", pv->cur_nkeys, pv->cur_ndests));
			/* Trigger start. */
			pv->cur_key_index = 0;
			pv->cur_offset = 0;
		}
	}

	/* Are we in the middle of an update? */
	if (pv->cur_key_index == -1) return dp_RES_OK;	/* No. */

	/* Get a pointer to the variable we're working on.  Make sure it's
	 * still there, still clean, and still needs data transferred.
	 */
	do {
		pvar = (pv_var_t *)assoctab_subscript(peer->vars, pv->cur_keys[pv->cur_key_index]);
		/* Has it been deleted or changed or finished? */
		if (!pvar
		|| (pvar->flags & dp_PLAYERDATA_FLAG_DIRTY)
		|| (pvar->len <= pv->cur_offset)) {
			DPRINT(("pv_update: pvar %p, flags %x; advancing to next var\n", pvar, pvar?pvar->flags:0x666));
			/* Yes.  Just advance to the next variable. */
			pv->cur_key_index++;
			pv->cur_offset = 0;
			if (pv->cur_key_index >= pv->cur_nkeys) {
				/* That was the last variable.  The cycle is over. */
				pv->cur_key_index = -1;
				DPRINT(("pv_update: cycle over\n"));
				return dp_RES_OK;
			}
			pvar = NULL;
		}
	} while (!pvar);

	/* Send the current chunk of the current variable. */
	if (pv->cur_offset == 0) {
		dp_packetType_t *tag = (dp_packetType_t *)buf;
		pv_playerData_initial_packet_t *body = (pv_playerData_initial_packet_t *)(buf + sizeof(dp_packetType_t));
		void *payload = ((char *)body) + sizeof(pv_playerData_initial_packet_t);

		/* Send an initial chunk. */
		len = pvar->len;
		if (len > pv_PLAYERDATA_INITIAL_MAXLEN)
			len = pv_PLAYERDATA_INITIAL_MAXLEN;

		*tag = pv_PLAYERDATA_INITIAL_PACKET_ID;
		body->len = pvar->len;
		body->id = owner;
		body->flags = (short) pvar->flags;
		body->key = pvar->key;
		body->crc = pvar->crc;
		/* call dpSwapPvUpdateInitial to byte swap body */
		dpSwapPvUpdateInitial(body);
		memcpy(payload, pvar->buf, len);

		hdrlen = sizeof(dp_packetType_t)+sizeof(pv_playerData_initial_packet_t);
	} else {
		dp_packetType_t *tag = (dp_packetType_t *)buf;
		char *pastTag = buf + sizeof(dp_packetType_t);
		pv_playerData_body_packet_t *body = (pv_playerData_body_packet_t *)pastTag;
		void *payload = ((char *)body) + sizeof(pv_playerData_body_packet_t);
		len = pvar->len - pv->cur_offset;
		if (len > pv_PLAYERDATA_BODY_MAXLEN)
			len = pv_PLAYERDATA_BODY_MAXLEN;
#ifndef __MWERKS__
		assert(len>0);
#endif
		*tag = pv_PLAYERDATA_BODY_PACKET_ID;
		body->id = owner;
		/* call dpSwapPvUpdate to byte swap body */
		dpSwapPvUpdate(body);
		memcpy(payload, (char *)(pvar->buf)+pv->cur_offset, len);
		hdrlen = sizeof(dp_packetType_t)+sizeof(pv_playerData_body_packet_t);
	}

	err = dpio_put_reliable(pv->dp->dpio, pv->cur_dests, pv->cur_ndests,
		buf, len + hdrlen, &errHdl);

	/* Estimate the bandwidth used by this send. */
	interval = (pv->dp->clocksPerSec * (len + hdrlen)) / pv_BYTES_PER_SEC;
	pv->next_send = pv->dp->now + interval;
	DPRINT(("pv_update: Sent key %d off %d len %d to %d hosts; result %d, errHdl %d; next send in %d tix.\n",
		pv->cur_keys[pv->cur_key_index],
		pv->cur_offset, len, pv->cur_ndests, err, errHdl, interval));

	if (err == dp_RES_OK)
		pv->cur_offset += len;
	else if (err != dp_RES_FULL)
		DPRINT(("pv_update: serious send problem: dpio_put_reliable returned %d\n", err));

	return err;
}