diff --git a/Makefile b/Makefile index d3e8ec0..484550a 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,8 @@ TEST_CXXFLAGS := $(CXXFLAGS) -I./googletest/include/ all: dpnet.dll -dpnet.dll: src/dpnet.o src/dpnet.def src/DirectPlay8Address.o src/DirectPlay8Peer.o - $(CXX) $(CXXFLAGS) -Wl,--enable-stdcall-fixup -shared -o $@ $^ -ldxguid -static-libstdc++ -static-libgcc +dpnet.dll: src/dpnet.o src/dpnet.def src/DirectPlay8Address.o src/DirectPlay8Peer.o src/network.o + $(CXX) $(CXXFLAGS) -Wl,--enable-stdcall-fixup -shared -o $@ $^ -ldxguid -lws2_32 -static-libstdc++ -static-libgcc tests/DirectPlay8Address.exe: tests/DirectPlay8Address.o src/DirectPlay8Address.o googletest/src/gtest-all.o googletest/src/gtest_main.o $(CXX) $(TEST_CXXFLAGS) -o $@ $^ -ldxguid -lole32 -static-libstdc++ -static-libgcc diff --git a/src/DirectPlay8Peer.cpp b/src/DirectPlay8Peer.cpp index ed2a91a..edc58da 100644 --- a/src/DirectPlay8Peer.cpp +++ b/src/DirectPlay8Peer.cpp @@ -1,10 +1,14 @@ +#include #include #include #include +#include #include #include +#include "DirectPlay8Address.hpp" #include "DirectPlay8Peer.hpp" +#include "network.hpp" #define UNIMPLEMENTED(fmt, ...) \ fprintf(stderr, "Unimplemented method: " fmt "\n", ## __VA_ARGS__); \ @@ -12,11 +16,17 @@ DirectPlay8Peer::DirectPlay8Peer(std::atomic *global_refcount): global_refcount(global_refcount), - local_refcount(0) + local_refcount(0), + state(STATE_DISCONNECTED), + udp_socket(-1), + listener_socket(-1), + discovery_socket(-1) { AddRef(); } +DirectPlay8Peer::~DirectPlay8Peer() {} + HRESULT DirectPlay8Peer::QueryInterface(REFIID riid, void **ppvObject) { if(riid == IID_IDirectPlay8Peer || riid == IID_IUnknown) @@ -61,7 +71,10 @@ ULONG DirectPlay8Peer::Release(void) HRESULT DirectPlay8Peer::Initialize(PVOID CONST pvUserContext, CONST PFNDPNMESSAGEHANDLER pfn, CONST DWORD dwFlags) { - UNIMPLEMENTED("DirectPlay8Peer::Initialize"); + message_handler = pfn; + message_handler_ctx = pvUserContext; + + return S_OK; } HRESULT DirectPlay8Peer::EnumServiceProviders(CONST GUID* CONST pguidServiceProvider, CONST GUID* CONST pguidApplication, DPN_SERVICE_PROVIDER_INFO* CONST pSPInfoBuffer, DWORD* CONST pcbEnumData, DWORD* CONST pcReturned, CONST DWORD dwFlags) @@ -91,17 +104,192 @@ HRESULT DirectPlay8Peer::GetSendQueueInfo(CONST DPNID dpnid, DWORD* CONST pdwNum HRESULT DirectPlay8Peer::Host(CONST DPN_APPLICATION_DESC* CONST pdnAppDesc, IDirectPlay8Address **CONST prgpDeviceInfo, CONST DWORD cDeviceInfo, CONST DPN_SECURITY_DESC* CONST pdnSecurity, CONST DPN_SECURITY_CREDENTIALS* CONST pdnCredentials, void* CONST pvPlayerContext, CONST DWORD dwFlags) { - UNIMPLEMENTED("DirectPlay8Peer::Host"); + if(state != STATE_DISCONNECTED) + { + return DPNERR_ALREADYCONNECTED; + } + + if(pdnAppDesc->dwSize != sizeof(DPN_APPLICATION_DESC)) + { + return DPNERR_INVALIDPARAM; + } + + if(pdnAppDesc->dwFlags & DPNSESSION_CLIENT_SERVER) + { + return DPNERR_INVALIDPARAM; + } + + if(pdnAppDesc->dwFlags & DPNSESSION_MIGRATE_HOST) + { + /* Not supported yet. */ + } + + application_guid = pdnAppDesc->guidApplication; + max_players = pdnAppDesc->dwMaxPlayers; + session_name = pdnAppDesc->pwszSessionName; + + if(pdnAppDesc->dwFlags & DPNSESSION_REQUIREPASSWORD) + { + password = pdnAppDesc->pwszPassword; + } + else{ + password.clear(); + } + + application_data.clear(); + + if(pdnAppDesc->pvApplicationReservedData != NULL && pdnAppDesc->dwApplicationReservedDataSize > 0) + { + application_data.insert(application_data.begin(), + (unsigned char*)(pdnAppDesc->pvApplicationReservedData), + (unsigned char*)(pdnAppDesc->pvApplicationReservedData) + pdnAppDesc->dwApplicationReservedDataSize); + } + + uint32_t ipaddr = htonl(INADDR_ANY); + uint16_t port = 0; + + for(DWORD i = 0; i < cDeviceInfo; ++i) + { + DirectPlay8Address *addr = (DirectPlay8Address*)(prgpDeviceInfo[i]); + + DWORD addr_port_value; + DWORD addr_port_size = sizeof(addr_port_value); + DWORD addr_port_type; + + if(addr->GetComponentByName(DPNA_KEY_PORT, &addr_port_value, &addr_port_size, &addr_port_type) == S_OK + && addr_port_type == DPNA_DATATYPE_DWORD) + { + if(port != 0 && port != addr_port_value) + { + /* Multiple ports specified, don't support this yet. */ + return DPNERR_INVALIDPARAM; + } + else{ + port = addr_port_value; + } + } + } + + if(port == 0) + { + port = DEFAULT_HOST_PORT; + } + + udp_socket = create_udp_socket (ipaddr, port); + listener_socket = create_listener_socket(ipaddr, port); + + if(udp_socket == -1 || listener_socket == -1) + { + return DPNERR_GENERIC; + } + + if(!(pdnAppDesc->dwFlags & DPNSESSION_NODPNSVR)) + { + discovery_socket = create_discovery_socket(); + } + + state = STATE_HOSTING; + + return S_OK; } HRESULT DirectPlay8Peer::GetApplicationDesc(DPN_APPLICATION_DESC* CONST pAppDescBuffer, DWORD* CONST pcbDataSize, CONST DWORD dwFlags) { - UNIMPLEMENTED("DirectPlay8Peer::GetApplicationDesc"); + if(state == STATE_DISCONNECTED) + { + return DPNERR_NOCONNECTION; + } + + DWORD required_size = sizeof(DPN_APPLICATION_DESC) + + (password.length() + !password.empty()) * sizeof(wchar_t) + + application_data.size(); + + if(pAppDescBuffer != NULL && *pcbDataSize >= required_size) + { + unsigned char *extra_at = (unsigned char*)(pAppDescBuffer); + + pAppDescBuffer->dwSize = sizeof(*pAppDescBuffer); + pAppDescBuffer->dwFlags = 0; + pAppDescBuffer->guidInstance = instance_guid; + pAppDescBuffer->guidApplication = application_guid; + pAppDescBuffer->dwMaxPlayers = max_players; + pAppDescBuffer->dwCurrentPlayers = peers.size() + 1; + + if(!password.empty()) + { + wcscpy((wchar_t*)(extra_at), password.c_str()); + + pAppDescBuffer->dwFlags |= DPNSESSION_REQUIREPASSWORD; + pAppDescBuffer->pwszPassword = (wchar_t*)(extra_at); + + extra_at += (password.length() + 1) * sizeof(wchar_t); + } + else{ + pAppDescBuffer->pwszPassword = NULL; + } + + pAppDescBuffer->pvReservedData = NULL; + pAppDescBuffer->dwReservedDataSize = 0; + + if(!application_data.empty()) + { + memcpy(extra_at, application_data.data(), application_data.size()); + + pAppDescBuffer->pvApplicationReservedData = extra_at; + pAppDescBuffer->dwApplicationReservedDataSize = application_data.size(); + + extra_at += application_data.size(); + } + else{ + pAppDescBuffer->pvApplicationReservedData = NULL; + pAppDescBuffer->dwApplicationReservedDataSize = 0; + } + + return S_OK; + } + else{ + *pcbDataSize = sizeof(*pAppDescBuffer); + return DPNERR_BUFFERTOOSMALL; + } } HRESULT DirectPlay8Peer::SetApplicationDesc(CONST DPN_APPLICATION_DESC* CONST pad, CONST DWORD dwFlags) { - UNIMPLEMENTED("DirectPlay8Peer::SetApplicationDesc"); + if(state == STATE_HOSTING) + { + if(pad->dwMaxPlayers > 0 && pad->dwMaxPlayers <= peers.size()) + { + /* Can't set dwMaxPlayers below current player count. */ + return DPNERR_INVALIDPARAM; + } + + max_players = pad->dwMaxPlayers; + session_name = pad->pwszSessionName; + + if(pad->dwFlags & DPNSESSION_REQUIREPASSWORD) + { + password = pad->pwszPassword; + } + else{ + password.clear(); + } + + application_data.clear(); + + if(pad->pvApplicationReservedData != NULL && pad->dwApplicationReservedDataSize > 0) + { + application_data.insert(application_data.begin(), + (unsigned char*)(pad->pvApplicationReservedData), + (unsigned char*)(pad->pvApplicationReservedData) + pad->dwApplicationReservedDataSize); + } + + /* TODO: Notify peers */ + + return S_OK; + } + else{ + return DPNERR_NOTHOST; + } } HRESULT DirectPlay8Peer::CreateGroup(CONST DPN_GROUP_INFO* CONST pdpnGroupInfo, void* CONST pvGroupContext, void* CONST pvAsyncContext, DPNHANDLE* CONST phAsyncHandle, CONST DWORD dwFlags) @@ -144,9 +332,9 @@ HRESULT DirectPlay8Peer::EnumGroupMembers(CONST DPNID dpnid, DPNID* CONST prgdpn UNIMPLEMENTED("DirectPlay8Peer::EnumGroupMembers"); } -HRESULT DirectPlay8Peer::SetPeerInfo(CONST DPN_PLAYER_INFO* CONST pdpnPlayerInfo,PVOID CONST pvAsyncContext, DPNHANDLE* CONST phAsyncHandle, CONST DWORD dwFlags) +HRESULT DirectPlay8Peer::SetPeerInfo(CONST DPN_PLAYER_INFO* CONST pdpnPlayerInfo, PVOID CONST pvAsyncContext, DPNHANDLE* CONST phAsyncHandle, CONST DWORD dwFlags) { - UNIMPLEMENTED("DirectPlay8Peer::SetPeerInfo"); + UNIMPLEMENTED("DirectPlay8Peer::SetPeerInfo(%p, %p, %p, %u)", pdpnPlayerInfo, pvAsyncContext, phAsyncHandle, (unsigned)(dwFlags)); } HRESULT DirectPlay8Peer::GetPeerInfo(CONST DPNID dpnid, DPN_PLAYER_INFO* CONST pdpnPlayerInfo, DWORD* CONST pdwSize, CONST DWORD dwFlags) diff --git a/src/DirectPlay8Peer.hpp b/src/DirectPlay8Peer.hpp index 2e218ff..3e13bcb 100644 --- a/src/DirectPlay8Peer.hpp +++ b/src/DirectPlay8Peer.hpp @@ -3,7 +3,9 @@ #include #include +#include #include +#include class DirectPlay8Peer: public IDirectPlay8Peer { @@ -11,9 +13,43 @@ class DirectPlay8Peer: public IDirectPlay8Peer std::atomic * const global_refcount; ULONG local_refcount; + PFNDPNMESSAGEHANDLER message_handler; + PVOID message_handler_ctx; + + enum { + STATE_DISCONNECTED, + STATE_HOSTING, + STATE_CONNECTED, + } state; + + GUID instance_guid; + GUID application_guid; + DWORD max_players; + std::wstring session_name; + std::wstring password; + std::vector application_data; + + int udp_socket; /* UDP socket, used for general send/recv operations. */ + int listener_socket; /* TCP listener socket. */ + int discovery_socket; /* Discovery UDP sockets, RECIEVES broadcasts only. */ + + struct Player + { + /* This is the TCP socket to the peer, we may have connected to it, or it + * may have connected to us depending who joined the session first, that + * doesn't really matter. + */ + int sock; + + uint32_t ip; /* IPv4 address, network byte order. */ + uint16_t port; /* Port, host byte order. */ + }; + + std::map peers; + public: DirectPlay8Peer(std::atomic *global_refcount); - virtual ~DirectPlay8Peer() {} + virtual ~DirectPlay8Peer(); /* IUnknown */ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override; diff --git a/src/network.cpp b/src/network.cpp new file mode 100644 index 0000000..dae0d25 --- /dev/null +++ b/src/network.cpp @@ -0,0 +1,91 @@ +#include +#include +#include + +#include "network.hpp" + +int create_udp_socket(uint32_t ipaddr, uint16_t port) +{ + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if(sock == -1) + { + return -1; + } + + BOOL broadcast = TRUE; + if(setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)(&broadcast), sizeof(BOOL)) == -1) + { + closesocket(sock); + return -1; + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = ipaddr; + addr.sin_port = htons(port); + + if(bind(sock, (struct sockaddr*)(&addr), sizeof(addr)) == -1) + { + closesocket(sock); + return -1; + } + + return sock; +} + +int create_listener_socket(uint32_t ipaddr, uint16_t port) +{ + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if(sock == -1) + { + return -1; + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = ipaddr; + addr.sin_port = htons(port); + + if(bind(sock, (struct sockaddr*)(&addr), sizeof(addr)) == -1) + { + closesocket(sock); + return -1; + } + + if(listen(sock, LISTEN_QUEUE_SIZE) == -1) + { + closesocket(sock); + return -1; + } + + return sock; +} + +int create_discovery_socket() +{ + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if(sock == -1) + { + return -1; + } + + BOOL reuse = TRUE; + if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)(&reuse), sizeof(BOOL)) == -1) + { + closesocket(sock); + return -1; + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(DISCOVERY_PORT); + + if(bind(sock, (struct sockaddr*)(&addr), sizeof(addr)) == -1) + { + closesocket(sock); + return -1; + } + + return sock; +} diff --git a/src/network.hpp b/src/network.hpp new file mode 100644 index 0000000..31cfdc1 --- /dev/null +++ b/src/network.hpp @@ -0,0 +1,14 @@ +#ifndef DPLITE_NETWORK_HPP +#define DPLITE_NETWORK_HPP + +#include + +#define DISCOVERY_PORT 6073 +#define DEFAULT_HOST_PORT 6072 +#define LISTEN_QUEUE_SIZE 16 + +int create_udp_socket(uint32_t ipaddr, uint16_t port); +int create_listener_socket(uint32_t ipaddr, uint16_t port); +int create_discovery_socket(); + +#endif /* !DPLITE_NETWORK_HPP */