]> icculus.org git repositories - btb/d2x.git/blob - arch/win32/ipx_mcast4.c
add level component saving functions which use PhysicsFS (didn't commit properly...
[btb/d2x.git] / arch / win32 / ipx_mcast4.c
1 /* $Id: ipx_mcast4.c,v 1.3 2003-11-26 12:26:25 btb Exp $ */
2
3 /*
4  *
5  * "ipx driver" for IPv4 multicasting
6  *
7  */
8
9
10 #ifdef HAVE_CONFIG_H
11 #include <conf.h>
12 #endif
13
14 #include <stdio.h>
15 #include <string.h>
16 #include <stdarg.h>
17 #ifndef _MSC_VER
18 #include <unistd.h>
19 #endif
20 #include <stdlib.h>
21 #include <ctype.h>
22
23 #include <winsock2.h>
24 #include <ws2tcpip.h>
25
26 #include "ipx_mcast4.h"
27 #include "args.h"
28 #include "error.h"
29 #include "../../main/multi.h"
30 #include "../../main/newmenu.h"
31
32 //#define IPX_MCAST4_DEBUG
33
34 extern unsigned char ipx_MyAddress[10];
35
36 #define UDP_BASEPORT 28342
37 #define PORTSHIFT_TOLERANCE 0x100
38 #define MAX_PACKETSIZE 8192
39
40 /* OUR port. Can be changed by "@X[+=]..." argument (X is the shift value)
41  */
42 static int baseport=UDP_BASEPORT;
43
44 static struct in_addr game_addr;    // The game's multicast address
45
46 #define MSGHDR "IPX_mcast4: "
47
48 #ifdef __GNUC__
49 #ifdef IPX_MCAST4_DEBUG
50 static void msg(const char *fmt, ...)
51 {
52         va_list ap;
53
54         fputs(MSGHDR, stdout);
55         va_start(ap, fmt);
56         vprintf(fmt, ap);
57         va_end(ap);
58         putchar('\n');
59 }
60 #else
61 #define msg(m...)
62 #endif
63 #else
64 # ifdef IPX_MCAST4_DEBUG
65 #  define msg printf
66 # else
67 #  define msg
68 # endif
69 #endif
70
71 #ifdef __GNUC__
72 #define FAIL(m...) do{ nm_messagebox("Error", 1, "Ok", ##m); return -1; } while (0)
73 #else
74 # define FAIL() do{ nm_messagebox("Error", 1, "Ok", "IPX_mcast4 failure"); return -1; } while (0)
75 #endif
76
77 #ifdef IPX_MCAST4_DEBUG
78 /* Dump raw form of IP address/port by fancy output to user
79  */
80 static void dumpraddr(unsigned char *a)
81 {
82         short port;
83
84         printf("[%u.%u.%u.%u]", a[0], a[1], a[2], a[3]);
85         port=(signed short)ntohs(*(unsigned short *)(a+4));
86         if (port) printf(":%+d",port);
87 }
88
89 /* Like dumpraddr() but for structure "sockaddr_in"
90  */
91 static void dumpaddr(struct sockaddr_in *sin)
92 {
93         unsigned short ports;
94         unsigned char qhbuf[8];
95
96         memcpy(qhbuf + 0, &sin->sin_addr, 4);
97         ports = htons(((short)ntohs(sin->sin_port)) - UDP_BASEPORT);
98         memcpy(qhbuf + 4, &ports, 2);
99         dumpraddr(qhbuf);
100 }
101 #endif
102
103 // The multicast address for Descent 2 game announcements.
104 // TODO: Pick a better address for this
105 #define DESCENT2_ANNOUNCE_ADDR inet_addr("239.255.1.2")
106
107 /* Open the socket and subscribe to the multicast session */
108 static int ipx_mcast4_OpenSocket(ipx_socket_t *sk, int port)
109 {
110         u_char loop;
111         struct ip_mreq mreq;
112         struct sockaddr_in sin;
113         int ttl = 128;
114
115         if((sk->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
116         {
117                 sk->fd = -1;
118                 FAIL("socket() creation failed on port %d: %m", port);
119         }
120
121         // Bind to the port
122         sin.sin_family = AF_INET;
123         sin.sin_addr.s_addr = htonl(INADDR_ANY);
124         sin.sin_port = htons(baseport);
125         if (bind(sk->fd, (struct sockaddr *)&sin, sizeof(sin)))
126         {
127                 if (closesocket(sk->fd))
128                         msg("close() failed during error recovery: %m");
129                 sk->fd = -1;
130                 FAIL("bind() to UDP port %d failed: %m", baseport);
131         }
132
133         // Set the TTL so the packets can get out of the local network.
134         if(setsockopt(sk->fd, IPPROTO_IP, IP_MULTICAST_TTL, (const void*)&ttl, sizeof(ttl)) < 0)
135                 FAIL("setsockopt() failed to set TTL to 128");
136
137         // Disable multicast loopback
138         loop = 0;
139         if(setsockopt(sk->fd, IPPROTO_IP, IP_MULTICAST_LOOP, (const void*)&loop, sizeof(loop)) < 0)
140                 FAIL("setsockopt() failed to disable multicast loopback: %m");
141
142         // Subscribe to the game announcement address
143         memset(&mreq, 0, sizeof(mreq));
144         mreq.imr_multiaddr.s_addr = DESCENT2_ANNOUNCE_ADDR;
145         mreq.imr_interface.s_addr = INADDR_ANY;
146         if(setsockopt(sk->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const void*)&mreq, sizeof(mreq)) < 0)
147                 FAIL("setsockopt() failed to subscribe to the game announcement multicast group");
148
149         // We're not subscribed to a game address yet
150         game_addr.s_addr = 0;
151
152         sk->socket = port;
153         return 0;
154 }
155
156 static void ipx_mcast4_CloseSocket(ipx_socket_t *sk)
157 {
158         if(closesocket(sk->fd) < 0)
159                 msg("Close failed");
160         sk->fd = -1;
161 }
162
163 static int ipx_mcast4_SendPacket(ipx_socket_t *sk, IPXPacket_t *IPXHeader, u_char *data, int dataLen)
164 {
165         struct sockaddr_in toaddr;
166         int i;
167
168         msg("SendPacket enter, dataLen=%d", dataLen);
169
170         if(dataLen < 0 || dataLen > MAX_PACKETSIZE)
171                 return -1;
172
173         toaddr.sin_family = AF_INET;
174         memcpy(&toaddr.sin_addr, IPXHeader->Destination.Node + 0, 4);
175         //toaddr.sin_port = htons(((short)ntohs(*(unsigned short *)(IPXHeader->Destination.Node + 4))) + UDP_BASEPORT);
176         // For now, just use the same port for everything
177         toaddr.sin_port = htons(UDP_BASEPORT);
178
179         // If it's the broadcast address, then we want to send it to the
180         // GAME ANNOUNCEMENT address.
181         // Data to be sent to the GAME has the destination already set by
182         // ipx_mcast4_SendGamePacket
183         if(toaddr.sin_addr.s_addr == INADDR_BROADCAST)
184                 toaddr.sin_addr.s_addr = DESCENT2_ANNOUNCE_ADDR;
185
186 #ifdef IPX_MCAST4_DEBUG
187         printf(MSGHDR "sendto((%d),Node=[4] %02X %02X,Socket=%02X %02X,s_port=%u,",
188                dataLen,
189                IPXHeader->Destination.Node[4], IPXHeader->Destination.Node[5],
190                IPXHeader->Destination.Socket[0], IPXHeader->Destination.Socket[1],
191                ntohs(toaddr.sin_port));
192         dumpaddr(&toaddr);
193         puts(").");
194 #endif
195
196         i = sendto(sk->fd, data, dataLen, 0, (struct sockaddr *)&toaddr, sizeof(toaddr));
197         return i;
198 }
199
200 static int ipx_mcast4_ReceivePacket(ipx_socket_t *sk, char *outbuf, int outbufsize, struct ipx_recv_data *rd)
201 {
202         int size;
203         struct sockaddr_in fromaddr;
204         int fromaddrsize = sizeof(fromaddr);
205
206         if((size = recvfrom(sk->fd, outbuf, outbufsize, 0, (struct sockaddr*)&fromaddr, &fromaddrsize)) < 0)
207                 return -1;
208
209 #ifdef IPX_MCAST4_DEBUG
210         printf(MSGHDR "Got packet from ");
211         dumpaddr(&fromaddr);
212         puts("");
213 #endif
214
215         // We have the packet, now fill out the receive data.
216         memset(rd, 0, sizeof(*rd));
217         memcpy(rd->src_node, &fromaddr.sin_addr, 4);
218         // TODO: Include the port like in ipx_udp.c
219         rd->pkt_type = 0;
220
221         return size;
222 }
223
224 /* Handle the netgame aux data
225  * Byte 0 is the protocol version number.
226  * Bytes 1-4 are the IPv4 multicast session to join, in network byte order.
227  */
228 static int ipx_mcast4_HandleNetgameAuxData(ipx_socket_t *sk, const u_char buf[NETGAME_AUX_SIZE])
229 {
230         // Extract the multicast session and subscribe to it.  We should
231         // now be getting packets intended for the players of this game.
232
233         // Note that we stay subscribed to the game announcement session,
234         // so we can reply to game info requests
235         struct ip_mreq mreq;
236         int ttl = 128;
237
238         // Check the protocol version
239         if(buf[0] != IPX_MCAST4_VERSION)
240         {
241                 FAIL("mcast4 protocol\nversion mismatch!\nGame version is %02x,\nour version is %02x", buf[0], IPX_MCAST4_VERSION);
242         }
243
244         // Get the multicast session
245         memcpy(&game_addr, buf + 1, sizeof(game_addr));
246
247 #ifdef IPX_MCAST4_DEBUG
248         {
249                 struct sockaddr_in tmpaddr;
250                 tmpaddr.sin_addr = game_addr;
251                 tmpaddr.sin_port = 0;
252
253                 printf("Handling netgame aux data: Subscribing to ");
254                 dumpaddr(&tmpaddr);
255                 puts("");
256         }
257 #endif
258
259         // Set the TTL so the packets can get out of the local network.
260         if(setsockopt(sk->fd, IPPROTO_IP, IP_MULTICAST_TTL, (const void*)&ttl, sizeof(ttl)) < 0)
261                 FAIL("setsockopt() failed to set TTL to 128");
262
263         memset(&mreq, 0, sizeof(mreq));
264         mreq.imr_multiaddr = game_addr;
265         mreq.imr_interface.s_addr = INADDR_ANY;
266
267         if(setsockopt(sk->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const void*)&mreq, sizeof(mreq)) < 0)
268                 FAIL("setsockopt() failed to subscribe to the game group");
269
270         return 0;
271 }
272
273 /* Create the netgame aux data.
274  * Byte 0 is the protcol version number.
275  * Bytes 1-4 hold the IPv4 multicast session for the game.
276  */
277 static void ipx_mcast4_InitNetgameAuxData(ipx_socket_t *sk, u_char buf[NETGAME_AUX_SIZE])
278 {
279         char addr[16];
280         Assert(game_addr.s_addr == 0);
281
282         // The first byte is the version number
283         buf[0] = IPX_MCAST4_VERSION;
284
285         // Generate a random session
286         // game_addr = inet_makeaddr(239*256 + 255, d_rand() % 0xFFFF);
287         sprintf(addr, "%i.%i.%i.%i", 239, 255, d_rand() % 0xFF, d_rand() % 0xFF);
288         game_addr.s_addr = inet_addr(addr);
289         memcpy(buf + 1, &game_addr, sizeof(game_addr));
290
291         // Since we're obviously the hosting machine, subscribe to this address
292         ipx_mcast4_HandleNetgameAuxData(sk, buf);
293 }
294
295 static void ipx_mcast4_HandleLeaveGame(ipx_socket_t *sk)
296 {
297         // We left the game, so unsubscribe from its multicast session
298         struct ip_mreq mreq;
299
300         Assert(game_addr.s_addr != 0);
301
302 #ifdef IPX_MCAST4_DEBUG
303         printf("Unsubscribing from game's multicast group: ");
304         dumpraddr(&game_addr.s_addr);
305         printf("\n");
306 #endif
307
308         mreq.imr_multiaddr = game_addr;
309         mreq.imr_interface.s_addr = INADDR_ANY;
310         if(setsockopt(sk->fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const void*)&mreq, sizeof(mreq)) < 0)
311                 msg("setsockopt() failed unsubscribing from previous group!");
312         game_addr.s_addr = 0;
313 }
314
315 // Send a packet to every member of the game.  We can just multicast it here.
316 static int ipx_mcast4_SendGamePacket(ipx_socket_t *sk, ubyte *data, int dataLen)
317 {
318         struct sockaddr_in toaddr;
319         int i;
320
321         memset(&toaddr, 0, sizeof(toaddr));
322         toaddr.sin_family = AF_INET;
323         toaddr.sin_addr = game_addr;
324         toaddr.sin_port = htons(UDP_BASEPORT);
325
326         msg("ipx_mcast4_SendGamePacket");
327
328         i = sendto(sk->fd, data, dataLen, 0, (struct sockaddr *)&toaddr, sizeof(toaddr));
329
330         return i;
331 }
332
333 // Pull this in from ipx_udp.c since it's the same for us.
334 extern int ipx_udp_GetMyAddress();
335
336 struct ipx_driver ipx_mcast4 = {
337         ipx_udp_GetMyAddress,
338         ipx_mcast4_OpenSocket,
339         ipx_mcast4_CloseSocket,
340         ipx_mcast4_SendPacket,
341         ipx_mcast4_ReceivePacket,
342         ipx_general_PacketReady,
343         ipx_mcast4_InitNetgameAuxData,
344         ipx_mcast4_HandleNetgameAuxData,
345         ipx_mcast4_HandleLeaveGame,
346         ipx_mcast4_SendGamePacket
347 };