1
0
mirror of https://github.com/solemnwarning/ipxwrapper synced 2024-12-30 16:45:37 +01:00

Implemented basic DirectPlay test suite.

This commit is contained in:
Daniel Collins 2015-11-25 23:22:31 +00:00
parent 91c39a769f
commit e5085ba1d9
6 changed files with 920 additions and 4 deletions

View File

@ -52,7 +52,8 @@ BIN_FILES := $(shell cat manifest.bin.txt)
SRC_FILES := $(shell cat manifest.src.txt)
TOOLS := tools/socket.exe tools/list-interfaces.exe tools/bind.exe tools/ipx-send.exe \
tools/ipx-recv.exe tools/spx-server.exe tools/spx-client.exe tools/ipx-isr.exe
tools/ipx-recv.exe tools/spx-server.exe tools/spx-client.exe tools/ipx-isr.exe \
tools/dptool.exe
# DLLs to copy to the tests directory before running the test suite.
@ -76,11 +77,11 @@ dist: all
zip -r ipxwrapper-$(VERSION)-src.zip ipxwrapper-$(VERSION)-src/
rm -r ipxwrapper-$(VERSION)-src/
tools: $(TOOLS) tests/addr.exe ipxwrapper.dll wsock32.dll
cp ipxwrapper.dll wsock32.dll tools/
tools: $(TOOLS) tests/addr.exe ipxwrapper.dll wsock32.dll dpwsockx.dll
cp ipxwrapper.dll wsock32.dll dpwsockx.dll tools/
tools/%.exe: tools/%.c tools/tools.h src/addr.o
$(CC) $(CFLAGS) -I./src/ -o $@ $< src/addr.o -lwsock32
$(CC) $(CFLAGS) -I./src/ -o $@ $< src/addr.o -lwsock32 -lole32 -lrpcrt4
tests/addr.exe: tests/addr.o tests/tap/basic.o src/addr.o
$(CC) $(CFLAGS) -I./ -o $@ $^ -lwsock32

View File

@ -18,6 +18,8 @@ Running the test suite
The test suite requires a Linux system and a Windows system, connected by two Ethernet networks with fixed IPv4 addresses on each. You must run `make tools` before attempting to run the test suite.
Running the DirectPlay tests requires an additional two Windows systems, these have the same requirements as the first, except they only need network adapters on the first network.
The Linux system:
* Must have the following Perl modules installed:

416
tests/50-dplay.t Normal file
View File

@ -0,0 +1,416 @@
# IPXWrapper test suite
# Copyright (C) 2015 Daniel Collins <solemnwarning@solemnwarning.net>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
use strict;
use warnings;
# Number of seconds to wait for things to propagate.
use constant PROP_TIME => 3;
use Test::Spec;
use FindBin;
use lib "$FindBin::Bin/lib/";
use IPXWrapper::Tool::DPTool;
require "$FindBin::Bin/config.pm";
our ($remote_ip_a, $remote_b_ip, $remote_c_ip);
# Nicked from dplay.h
use constant DPID_ALLPLAYERS => 0;
describe "A single DirectPlay client" => sub
{
it "can find a published session" => sub
{
my $host = IPXWrapper::Tool::DPTool->new($remote_b_ip);
my $s_guid = $host->create_session("catarrhous");
my $client = IPXWrapper::Tool::DPTool->new($remote_ip_a);
my %sessions = $client->list_sessions();
cmp_deeply(\%sessions, {
$s_guid => "catarrhous",
});
};
it "can find concurrently published sessions" => sub
{
my $host_a = IPXWrapper::Tool::DPTool->new($remote_b_ip);
my $sa_guid = $host_a->create_session("sturt");
my $host_b = IPXWrapper::Tool::DPTool->new($remote_c_ip);
my $sb_guid = $host_b->create_session("duodena");
my $client = IPXWrapper::Tool::DPTool->new($remote_ip_a);
my %sessions = $client->list_sessions();
cmp_deeply(\%sessions, {
$sa_guid => "sturt",
$sb_guid => "duodena",
});
};
my $single_client_common_init = sub
{
my $host = IPXWrapper::Tool::DPTool->new($remote_b_ip);
my $s_guid = $host->create_session("chaya");
my $client = IPXWrapper::Tool::DPTool->new($remote_ip_a);
$client->list_sessions();
$client->join_session($s_guid);
return ($host, $client);
};
it "can join a published session" => sub
{
my ($host, $client) = $single_client_common_init->();
pass(); # We didn't die!
};
it "can see players on the host" => sub
{
my ($host, $client) = $single_client_common_init->();
my $host_player = $host->create_player("host");
my %client_players = $client->list_players();
cmp_deeply(\%client_players, {
$host_player => "host",
});
};
it "can create players visible to the host" => sub
{
my ($host, $client) = $single_client_common_init->();
my $client_player = $client->create_player("client");
my %host_players = $host->list_players();
cmp_deeply(\%host_players, {
$client_player => "client",
});
};
it "can receive a message from the host" => sub
{
my ($host, $client) = $single_client_common_init->();
my $host_player = $host->create_player("host");
my $client_player = $client->create_player("client");
$host->send_message($host_player, $client_player, "foreordainment");
sleep(PROP_TIME);
$client->exit();
$host->exit();
my @host_messages = $host->messages();
my @client_messages = $client->messages();
cmp_deeply(\@host_messages, []);
cmp_deeply(\@client_messages, [
{
from => $host_player,
to => $client_player,
message => "foreordainment",
},
]);
};
it "can send messages to the host" => sub
{
my ($host, $client) = $single_client_common_init->();
my $host_player = $host->create_player("host");
my $client_player = $client->create_player("client");
$client->send_message($client_player, $host_player, "iskenderun");
sleep(PROP_TIME);
$client->exit();
$host->exit();
my @host_messages = $host->messages();
my @client_messages = $client->messages();
cmp_deeply(\@host_messages, [
{
from => $client_player,
to => $host_player,
message => "iskenderun",
},
]);
cmp_deeply(\@client_messages, []);
};
};
describe "Concurrent DirectPlay clients" => sub
{
my $multi_client_common_init = sub
{
my $host = IPXWrapper::Tool::DPTool->new($remote_ip_a);
my $s_guid = $host->create_session("meninges");
my $client_a = IPXWrapper::Tool::DPTool->new($remote_b_ip);
$client_a->list_sessions();
$client_a->join_session($s_guid);
my $client_b = IPXWrapper::Tool::DPTool->new($remote_c_ip);
$client_b->list_sessions();
$client_b->join_session($s_guid);
return ($host, $client_a, $client_b);
};
they "can join a published session" => sub
{
my ($host, $client_a, $client_b) = $multi_client_common_init->();
pass(); # Didn't die!
};
they "can see players on the host" => sub
{
my ($host, $client_a, $client_b) = $multi_client_common_init->();
my $host_player = $host->create_player("host");
sleep(PROP_TIME);
my %ca_players = $client_a->list_players();
my %cb_players = $client_b->list_players();
cmp_deeply(\%ca_players, { $host_player => "host" });
cmp_deeply(\%cb_players, { $host_player => "host" });
};
they "can create players visible to the host" => sub
{
my ($host, $client_a, $client_b) = $multi_client_common_init->();
my $ca_player = $client_a->create_player("client_a");
my $cb_player = $client_b->create_player("client_b");
sleep(PROP_TIME);
my %host_players = $host->list_players();
cmp_deeply(\%host_players, {
$ca_player => "client_a",
$cb_player => "client_b",
});
};
they "can create players visible to each other" => sub
{
my ($host, $client_a, $client_b) = $multi_client_common_init->();
my $ca_player = $client_a->create_player("client_a");
my $cb_player = $client_b->create_player("client_b");
sleep(PROP_TIME);
my %ca_players = $client_a->list_players();
my %cb_players = $client_b->list_players();
my %expect_players = (
$ca_player => "client_a",
$cb_player => "client_b",
);
cmp_deeply(\%ca_players, \%expect_players);
cmp_deeply(\%cb_players, \%expect_players);
};
they "can receive messages from the host" => sub
{
my ($host, $client_a, $client_b) = $multi_client_common_init->();
my $host_player = $host->create_player("host");
my $ca_player = $client_a->create_player("client_a");
my $cb_player = $client_b->create_player("client_b");
$host->send_message($host_player, $ca_player, "myrmecophilous");
$host->send_message($host_player, $cb_player, "indigestibly");
sleep(PROP_TIME);
$client_b->exit();
$client_a->exit();
$host->exit();
my @host_messages = $host->messages();
my @ca_messages = $client_a->messages();
my @cb_messages = $client_b->messages();
cmp_deeply(\@host_messages, []);
cmp_deeply(\@ca_messages, [
{
from => $host_player,
to => $ca_player,
message => "myrmecophilous",
},
]);
cmp_deeply(\@cb_messages, [
{
from => $host_player,
to => $cb_player,
message => "indigestibly",
},
]);
};
they "can send messages to the host" => sub
{
my ($host, $client_a, $client_b) = $multi_client_common_init->();
my $host_player = $host->create_player("host");
my $ca_player = $client_a->create_player("client_a");
my $cb_player = $client_b->create_player("client_b");
$client_a->send_message($ca_player, $host_player, "unvivid");
sleep(PROP_TIME);
$client_b->send_message($cb_player, $host_player, "matrilateral");
sleep(PROP_TIME);
$client_b->exit();
$client_a->exit();
$host->exit();
my @host_messages = $host->messages();
my @ca_messages = $client_a->messages();
my @cb_messages = $client_b->messages();
cmp_deeply(\@host_messages, [
{
from => $ca_player,
to => $host_player,
message => "unvivid",
},
{
from => $cb_player,
to => $host_player,
message => "matrilateral",
},
]);
cmp_deeply(\@ca_messages, []);
cmp_deeply(\@cb_messages, []);
};
they "can send messages to each other" => sub
{
my ($host, $client_a, $client_b) = $multi_client_common_init->();
my $host_player = $host->create_player("host");
my $ca_player = $client_a->create_player("client_a");
my $cb_player = $client_b->create_player("client_b");
$client_a->send_message($ca_player, $cb_player, "postpuberty");
$client_b->send_message($cb_player, $ca_player, "veridic");
sleep(PROP_TIME);
$client_b->exit();
$client_a->exit();
$host->exit();
my @host_messages = $host->messages();
my @ca_messages = $client_a->messages();
my @cb_messages = $client_b->messages();
cmp_deeply(\@host_messages, []);
cmp_deeply(\@ca_messages, [
{
from => $cb_player,
to => $ca_player,
message => "veridic",
},
]);
cmp_deeply(\@cb_messages, [
{
from => $ca_player,
to => $cb_player,
message => "postpuberty",
},
]);
};
they "can send a message to all players" => sub
{
my ($host, $client_a, $client_b) = $multi_client_common_init->();
my $host_player = $host->create_player("host");
my $ca_player = $client_a->create_player("client_a");
my $cb_player = $client_b->create_player("client_b");
$client_a->send_message($ca_player, DPID_ALLPLAYERS, "kendrew");
sleep(PROP_TIME);
$client_b->send_message($cb_player, DPID_ALLPLAYERS, "derivation");
sleep(PROP_TIME);
$client_b->exit();
$client_a->exit();
$host->exit();
my @host_messages = $host->messages();
my @ca_messages = $client_a->messages();
my @cb_messages = $client_b->messages();
cmp_deeply(\@host_messages, [
{
from => $ca_player,
to => $host_player,
message => "kendrew",
},
{
from => $cb_player,
to => $host_player,
message => "derivation",
},
]);
cmp_deeply(\@ca_messages, [
{
from => $cb_player,
to => $ca_player,
message => "derivation",
},
]);
cmp_deeply(\@cb_messages, [
{
from => $ca_player,
to => $cb_player,
message => "kendrew",
},
]);
};
};
runtests unless caller;

View File

@ -19,6 +19,11 @@ our $remote_ip_a = "172.16.1.21";
our $remote_mac_b = "08:00:27:43:47:5C";
our $remote_ip_b = "172.16.2.21";
# IP addresses of additional nodes used for testing DirectPlay.
our $remote_b_ip = "172.16.1.22";
our $remote_c_ip = "172.16.1.23";
# Network broadcast IPs.
our $net_a_bcast = "172.16.1.255";

View File

@ -0,0 +1,176 @@
# IPXWrapper test suite
# Copyright (C) 2015 Daniel Collins <solemnwarning@solemnwarning.net>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
use strict;
use warnings;
package IPXWrapper::Tool::DPTool;
use Carp;
use IPC::Open3;
use POSIX qw(:signal_h);
use Test::Spec;
sub new
{
my ($class, $host_ip) = @_;
my @command = ("ssh", $host_ip, "Z:\\tools\\dptool.exe");
note(join(" ", @command));
# No need for error checking here - open3 throws on failure.
my $pid = open3(my $in, my $out, undef, @command);
my $self = bless({
pid => $pid,
in => $in,
out => $out,
messages => [],
}, $class);
my $output = "";
while(defined(my $line = <$out>))
{
$output .= $line;
$line =~ s/[\r\n]//g;
if($line eq "ready")
{
return $self;
}
}
die("Unexpected output from dptool.exe:\n$output");
}
sub DESTROY
{
my ($self) = @_;
kill(SIGKILL, $self->{pid});
waitpid($self->{pid}, 0);
}
sub _do_cmd
{
my ($self, $cmd, $expect) = @_;
my $stdin = $self->{in};
print {$stdin} join(" ", @$cmd), "\n";
my $out = $self->{out};
my $output = "";
while(defined(my $line = <$out>))
{
$line =~ s/\r//g;
if($line =~ m/^ready$/m)
{
last;
}
if($line =~ m/^message (\d+) (\d+) (.*)$/)
{
push(@{ $self->{messages} }, {
from => $1,
to => $2,
message => $3,
});
}
else{
$output .= $line;
}
}
if($output !~ $expect)
{
confess("Unexpected output from dptool.exe: $output");
}
return $output;
}
sub create_session
{
my ($self, $session_name) = @_;
my $output = $self->_do_cmd([ "create_session", $session_name ], qr/^session_guid \S+$/);
my ($session_guid) = ($output =~ m/ (\S+)$/);
return $session_guid;
}
sub list_sessions
{
my ($self) = @_;
my $output = $self->_do_cmd([ "list_sessions" ], qr/^(?:session \S+ \S*\n)*$/);
my %sessions = map { m/(\S+) (\S*)$/; ($1 => $2) } ($output =~ m/(.+)/g);
return %sessions;
}
sub join_session
{
my ($self, $session_guid) = @_;
$self->_do_cmd([ "join_session", $session_guid ], qr/^$/);
}
sub create_player
{
my ($self, $player_longname) = @_;
my $output = $self->_do_cmd([ "create_player", $player_longname ], qr/^player_id [0-9]+$/);
my ($player_id) = ($output =~ m/([0-9]+)/);
return $player_id;
}
sub list_players
{
my ($self) = @_;
my $output = $self->_do_cmd([ "list_players" ], qr/^(?:player [0-9]+ (\S+)\n)*$/);
my %sessions = map { m/(\S+) (\S*)$/ } ($output =~ m/(.+)/g);
return %sessions;
}
sub send_message
{
my ($self, $player_from, $player_to, $message) = @_;
$self->_do_cmd([ "send_message", $player_from, $player_to, $message ], qr/^$/);
}
sub exit
{
my ($self) = @_;
$self->_do_cmd([ "exit" ], qr/^$/);
}
sub messages
{
return @{ (shift)->{messages} };
}
1;

316
tools/dptool.c Normal file
View File

@ -0,0 +1,316 @@
/* IPXWrapper test tools
* Copyright (C) 2015 Daniel Collins <solemnwarning@solemnwarning.net>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#define INITGUID
#include <dplay.h>
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdlib.h>
#include <assert.h>
DEFINE_GUID(TEST_APP_GUID, 0x38bef9eb, 0x6ad3, 0x4fc9, 0x97, 0xff, 0xfc, 0x3c, 0x7a, 0x55, 0x3b, 0x29);
static HANDLE exit_event = NULL;
#define assert_dp_call(name, expr) \
{ \
HRESULT err = (expr); \
if(err != DP_OK) \
{ \
fprintf(stderr, \
__FILE__ ":%u: " name ": %u\n", \
(unsigned int)(__LINE__), \
(unsigned int)(err)); \
exit(1); \
} \
}
static void lock_printf(const char *fmt, ...)
{
static CRITICAL_SECTION cs;
static bool cs_init = false;
if(!cs_init)
{
InitializeCriticalSection(&cs);
cs_init = true;
}
EnterCriticalSection(&cs);
va_list argv;
va_start(argv, fmt);
vprintf(fmt, argv);
va_end(argv);
LeaveCriticalSection(&cs);
}
static BOOL FAR PASCAL copy_ipx_conn(
LPCGUID *lpguidSP,
LPVOID lpConnection,
DWORD dwConnectionSize,
LPCDPNAME lpName,
DWORD dwFlags,
LPVOID lpContext)
{
if(memcmp(lpguidSP, &DPSPGUID_IPX, sizeof(DPSPGUID_IPX)) == 0)
{
assert(*(void**)(lpContext) = malloc(dwConnectionSize));
memcpy(*(void**)(lpContext), lpConnection, dwConnectionSize);
}
return TRUE;
}
static BOOL FAR PASCAL list_sessions_cb(
LPCDPSESSIONDESC2 lpThisSD,
LPDWORD lpdwTimeout,
DWORD dwFlags,
LPVOID lpContext)
{
if(lpThisSD)
{
unsigned char *uuid_str;
assert(UuidToString((UUID*)&(lpThisSD->guidInstance), &uuid_str) == RPC_S_OK);
lock_printf("session %s %s\n", uuid_str, lpThisSD->lpszSessionNameA);
RpcStringFree(&uuid_str);
return TRUE;
}
else{
/* Last session detected in this pass, return FALSE to make the
* invoking IDirectPlayX_EnumConnections call return rather than
* searching forever.
*/
return FALSE;
}
}
static BOOL FAR PASCAL list_players_cb(
DPID dpId,
DWORD dwPlayerType,
LPCDPNAME lpName,
DWORD dwFlags,
LPVOID lpContext)
{
lock_printf("player %u %s\n", (unsigned int)(dpId), lpName->lpszLongName);
return TRUE;
}
static DWORD WINAPI recv_thread_main(LPVOID lpParameter)
{
IDirectPlay4 *dp = (IDirectPlay4*)(lpParameter);
DWORD bufsize = 0;
char *buf = NULL;
while(1)
{
DPID player_from, player_to;
HRESULT err = IDirectPlayX_Receive(dp, &player_from, &player_to, 0, buf, &bufsize);
if(WaitForSingleObject(exit_event, 0) == WAIT_OBJECT_0)
{
/* We are exiting */
return 0;
}
if(err == DPERR_BUFFERTOOSMALL)
{
assert(buf = realloc(buf, bufsize));
continue;
}
else if(err == DPERR_NOMESSAGES)
{
/* non-blocking I/O... ugh */
Sleep(50);
continue;
}
else if(err != DP_OK)
{
fprintf(stderr, "IDirectPlay4::Receive: %u\n", (unsigned int)(err));
exit(1);
}
if(player_from != DPID_SYSMSG)
{
lock_printf("message %u %u %s\n",
(unsigned int)(player_from),
(unsigned int)(player_to),
buf);
}
}
}
int main(int argc, char **argv)
{
setbuf(stdout, NULL);
setbuf(stderr, NULL);
exit_event = CreateEvent(NULL, TRUE, FALSE, NULL);
assert(exit_event);
{
HRESULT err = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(err != S_OK)
{
fprintf(stderr, "CoInitializeEx: %u\n", (unsigned int)(err));
return 1;
}
}
IDirectPlay4 *dp;
assert_dp_call("CoCreateInstance",
CoCreateInstance(
&CLSID_DirectPlay, NULL, CLSCTX_INPROC_SERVER, &IID_IDirectPlay4A, (void**)(&dp)));
{
void *conn = NULL;
assert_dp_call("IDirectPlay4::EnumConnections",
IDirectPlayX_EnumConnections(
dp, NULL, (LPDPENUMCONNECTIONSCALLBACK)(&copy_ipx_conn), &conn, 0));
assert_dp_call("IDirectPlay4::InitializeConnection",
IDirectPlayX_InitializeConnection(dp, conn, 0));
free(conn);
}
HANDLE recv_thread = CreateThread(NULL, 0, &recv_thread_main, dp, 0, NULL);
assert(recv_thread);
char line[1024];
while(lock_printf("ready\n"), fgets(line, sizeof(line), stdin))
{
char *cmd = strtok(line, " \n");
if(strcmp(cmd, "create_session") == 0)
{
char *session_name = strtok(NULL, " \n");
DPSESSIONDESC2 session;
memset(&session, 0, sizeof(session));
session.dwSize = sizeof(session);
session.guidApplication = TEST_APP_GUID;
session.lpszSessionNameA = session_name;
assert_dp_call("IDirectPlay4::Open",
IDirectPlayX_Open(dp, &session, DPOPEN_CREATE));
DWORD bufsize = 0;
/* Need to call GetSessionDesc() to get the session
* GUID as Open() doesn't fill it in.
*/
assert(IDirectPlayX_GetSessionDesc(dp, NULL, &bufsize) == DPERR_BUFFERTOOSMALL);
DPSESSIONDESC2 *sd = malloc(bufsize);
assert(sd);
assert_dp_call("IDirectPlay4::GetSessionDesc",
IDirectPlayX_GetSessionDesc(dp, sd, &bufsize));
unsigned char *uuid_str;
assert(UuidToString((UUID*)&(sd->guidInstance), &uuid_str) == RPC_S_OK);
lock_printf("session_guid %s\n", uuid_str);
RpcStringFree(&uuid_str);
free(sd);
}
else if(strcmp(cmd, "list_sessions") == 0)
{
DPSESSIONDESC2 session;
memset(&session, 0, sizeof(session));
session.dwSize = sizeof(session);
session.guidApplication = TEST_APP_GUID;
assert_dp_call("IDirectPlay4::EnumSessions",
IDirectPlayX_EnumSessions(dp, &session, 3000, &list_sessions_cb, NULL, 0));
}
else if(strcmp(cmd, "join_session") == 0)
{
char *session_guid = strtok(NULL, " \n");
DPSESSIONDESC2 session;
memset(&session, 0, sizeof(session));
session.dwSize = sizeof(session);
UuidFromString((RPC_CSTR)(session_guid), &(session.guidInstance));
assert_dp_call("IDirectPlay4::Open",
IDirectPlayX_Open(dp, &session, DPOPEN_JOIN));
}
else if(strcmp(cmd, "create_player") == 0)
{
DPID player_id;
DPNAME name;
memset(&name, 0, sizeof(name));
name.dwSize = sizeof(name);
name.lpszLongNameA = strtok(NULL, " \n");
assert_dp_call("IDirectPlay4::CreatePlayer",
IDirectPlayX_CreatePlayer(dp, &player_id, &name, NULL, NULL, 0, 0));
lock_printf("player_id %u\n", (unsigned int)(player_id));
}
else if(strcmp(cmd, "list_players") == 0)
{
IDirectPlayX_EnumPlayers(dp, NULL, &list_players_cb, NULL, DPENUMPLAYERS_ALL);
}
else if(strcmp(cmd, "send_message") == 0)
{
DPID player_from = strtoul(strtok(NULL, " \n"), NULL, 10);
DPID player_to = strtoul(strtok(NULL, " \n"), NULL, 10);
char *message = strtok(NULL, " \n");
assert_dp_call("IDirectPlay4::Send",
IDirectPlayX_Send(dp, player_from, player_to, 0, message, strlen(message) + 1));
}
else if(strcmp(cmd, "exit") == 0)
{
break;
}
}
SetEvent(exit_event);
WaitForSingleObject(recv_thread, INFINITE);
CloseHandle(recv_thread);
CloseHandle(exit_event);
IDirectPlayX_Release(dp);
CoUninitialize();
return 0;
}