From e5085ba1d94ad8be3b0cc15e9d3307a35d0d5c32 Mon Sep 17 00:00:00 2001 From: Daniel Collins Date: Wed, 25 Nov 2015 23:22:31 +0000 Subject: [PATCH] Implemented basic DirectPlay test suite. --- Makefile | 9 +- readme.dev.txt | 2 + tests/50-dplay.t | 416 ++++++++++++++++++++++++++++ tests/config.pm | 5 + tests/lib/IPXWrapper/Tool/DPTool.pm | 176 ++++++++++++ tools/dptool.c | 316 +++++++++++++++++++++ 6 files changed, 920 insertions(+), 4 deletions(-) create mode 100644 tests/50-dplay.t create mode 100644 tests/lib/IPXWrapper/Tool/DPTool.pm create mode 100644 tools/dptool.c diff --git a/Makefile b/Makefile index bc00158..96653f6 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/readme.dev.txt b/readme.dev.txt index 62f4e21..c2b8953 100644 --- a/readme.dev.txt +++ b/readme.dev.txt @@ -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: diff --git a/tests/50-dplay.t b/tests/50-dplay.t new file mode 100644 index 0000000..f06be9d --- /dev/null +++ b/tests/50-dplay.t @@ -0,0 +1,416 @@ +# IPXWrapper test suite +# Copyright (C) 2015 Daniel Collins +# +# 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; diff --git a/tests/config.pm b/tests/config.pm index 89621e2..9cbd24b 100644 --- a/tests/config.pm +++ b/tests/config.pm @@ -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"; diff --git a/tests/lib/IPXWrapper/Tool/DPTool.pm b/tests/lib/IPXWrapper/Tool/DPTool.pm new file mode 100644 index 0000000..ec84418 --- /dev/null +++ b/tests/lib/IPXWrapper/Tool/DPTool.pm @@ -0,0 +1,176 @@ +# IPXWrapper test suite +# Copyright (C) 2015 Daniel Collins +# +# 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; diff --git a/tools/dptool.c b/tools/dptool.c new file mode 100644 index 0000000..eb1a944 --- /dev/null +++ b/tools/dptool.c @@ -0,0 +1,316 @@ +/* IPXWrapper test tools + * Copyright (C) 2015 Daniel Collins + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +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)(©_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; +}