rocksndiamonds-3.3.1.0
[rocksndiamonds.git] / src / network.c
1 /***********************************************************
2 * Rocks'n'Diamonds -- McDuffin Strikes Back!               *
3 *----------------------------------------------------------*
4 * (c) 1995-2006 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * network.c                                                *
12 ***********************************************************/
13
14 #include "libgame/platform.h"
15
16 #if defined(NETWORK_AVALIABLE)
17
18 #include <signal.h>
19 #include <sys/time.h>
20
21 #if defined(TARGET_SDL)
22 #include "main.h"
23 #else
24 #include <sys/wait.h>
25 #include <sys/socket.h>
26 #include <netinet/in.h>
27 #include <netinet/tcp.h>
28 #include <arpa/inet.h>
29 #include <netdb.h>
30 #endif
31
32 #include "libgame/libgame.h"
33
34 #include "network.h"
35 #include "netserv.h"
36 #include "game.h"
37 #include "tape.h"
38 #include "files.h"
39 #include "tools.h"
40 #include "screens.h"
41
42 struct NetworkClientPlayerInfo
43 {
44   byte nr;
45   char name[MAX_PLAYER_NAME_LEN + 1];
46   struct NetworkClientPlayerInfo *next;
47 };
48
49 static struct NetworkClientPlayerInfo first_player =
50 {
51   0,
52   EMPTY_PLAYER_NAME,
53   NULL
54 };
55
56 /* server stuff */
57
58 #if defined(TARGET_SDL)
59 static TCPsocket sfd;           /* server socket */
60 static SDLNet_SocketSet rfds;   /* socket set */
61 #else
62 static int sfd;                 /* server socket */
63 #endif
64
65 static byte realbuffer[512];
66 static byte readbuffer[MAX_BUFFER_SIZE], writbuffer[MAX_BUFFER_SIZE];
67 static byte *buffer = realbuffer + 4;
68 static int nread = 0, nwrite = 0;
69 static boolean stop_network_game = FALSE;
70
71 static void SendBufferToServer(int size)
72 {
73   if (!options.network)
74     return;
75
76   realbuffer[0] = realbuffer[1] = realbuffer[2] = 0;
77   realbuffer[3] = (byte)size;
78   buffer[0] = 0;
79
80   if (nwrite + 4 + size >= MAX_BUFFER_SIZE)
81     Error(ERR_EXIT, "internal error: network send buffer overflow");
82
83   memcpy(writbuffer + nwrite, realbuffer, 4 + size);
84   nwrite += 4 + size;
85
86   /* directly send the buffer to the network server */
87 #if defined(TARGET_SDL)
88   SDLNet_TCP_Send(sfd, writbuffer, nwrite);
89 #else
90   if (write(sfd, writbuffer, nwrite) == -1)
91     Error(ERR_WARN, "write() failed; %s", strerror(errno));
92 #endif
93   nwrite = 0;
94 }
95
96 struct NetworkClientPlayerInfo *getNetworkPlayer(int player_nr)
97 {
98   struct NetworkClientPlayerInfo *player = NULL;
99
100   for (player = &first_player; player; player = player->next)
101     if (player->nr == player_nr)
102       break;
103
104   if (player == NULL)   /* should not happen */
105     Error(ERR_EXIT, "protocol error: reference to non-existing player %d",
106           player_nr);
107
108   return player;
109 }
110
111 char *getNetworkPlayerName(int player_nr)
112 {
113   struct NetworkClientPlayerInfo *player;
114
115   if (player_nr == 0)
116     return("the network game server");
117   else if (player_nr == first_player.nr)
118     return("you");
119   else
120     for (player = &first_player; player; player = player->next)
121       if (player->nr == player_nr && player->name && strlen(player->name))
122         return(player->name);
123
124   return(EMPTY_PLAYER_NAME);
125 }
126
127 static void StartNetworkServer(int port)
128 {
129 #if defined(TARGET_SDL)
130   static int p;
131
132   p = port;
133   server_thread = SDL_CreateThread(NetworkServerThread, &p);
134   network_server = TRUE;
135
136 #else
137
138   switch (fork())
139   {
140     case 0:
141       NetworkServer(port, options.serveronly);
142
143       /* never reached */
144       exit(0);
145
146     case -1:
147       Error(ERR_WARN,
148             "cannot create network server process - no network playing");
149       options.network = FALSE;
150       return;
151
152     default:
153       /* we are parent process -- resume normal operation */
154       return;
155   }
156 #endif
157 }
158
159 #if defined(TARGET_SDL)
160 boolean ConnectToServer(char *hostname, int port)
161 {
162   IPaddress ip;
163   int i;
164
165   if (port == 0)
166     port = DEFAULT_SERVER_PORT;
167
168   rfds = SDLNet_AllocSocketSet(1);
169
170   if (hostname)
171   {
172     SDLNet_ResolveHost(&ip, hostname, port);
173     if (ip.host == INADDR_NONE)
174       Error(ERR_EXIT, "cannot locate host '%s'", hostname);
175   }
176   else
177   {
178     SDLNet_Write32(0x7f000001, &ip.host);       /* 127.0.0.1 */
179     SDLNet_Write16(port, &ip.port);
180   }
181
182   sfd = SDLNet_TCP_Open(&ip);
183
184   if (sfd)
185   {
186     SDLNet_TCP_AddSocket(rfds, sfd);
187     return TRUE;
188   }
189   else
190   {
191     printf("SDLNet_TCP_Open(): %s\n", SDLNet_GetError());
192   }
193
194   if (hostname)                 /* connect to specified server failed */
195     return FALSE;
196
197   printf("No rocksndiamonds server on localhost -- starting up one ...\n");
198   StartNetworkServer(port);
199
200   /* wait for server to start up and try connecting several times */
201   for (i = 0; i < 6; i++)
202   {
203     Delay(500);                 /* wait 500 ms == 0.5 seconds */
204
205     if ((sfd = SDLNet_TCP_Open(&ip)))           /* connected */
206     {
207       SDLNet_TCP_AddSocket(rfds, sfd);
208       return TRUE;
209     }
210   }
211
212   /* when reaching this point, connect to newly started server has failed */
213   return FALSE;
214 }
215
216 #else
217
218 boolean ConnectToServer(char *hostname, int port)
219 {
220   struct sockaddr_in s;
221   struct protoent *tcpproto;
222   int on = 1, i;
223
224   if (hostname)
225   {
226     if ((s.sin_addr.s_addr = inet_addr(hostname)) == -1)
227     {
228       struct hostent *host;
229
230       if ((host = gethostbyname(hostname)) == NULL)
231         Error(ERR_EXIT, "cannot locate host '%s'", hostname);
232
233       s.sin_addr = *(struct in_addr *)(host->h_addr_list[0]);
234     }
235   }
236   else
237     s.sin_addr.s_addr = inet_addr("127.0.0.1");         /* localhost */
238
239   if (port == 0)
240     port = DEFAULT_SERVER_PORT;
241
242   s.sin_port = htons(port);
243   s.sin_family = AF_INET;
244
245   sfd = socket(PF_INET, SOCK_STREAM, 0);
246   if (sfd < 0)
247     Error(ERR_EXIT, "out of file descriptors");
248
249   if ((tcpproto = getprotobyname("tcp")) != NULL)
250     setsockopt(sfd, tcpproto->p_proto, TCP_NODELAY, (char *)&on, sizeof(int));
251
252   if (connect(sfd, (struct sockaddr *)&s, sizeof(s)) == 0)      /* connected */
253     return TRUE;
254
255   if (hostname) /* connect to specified server failed */
256     return FALSE;
257
258   printf("No rocksndiamonds server on localhost -- starting up one ...\n");
259   StartNetworkServer(port);
260
261   /* wait for server to start up and try connecting several times */
262   for (i = 0; i < 6; i++)
263   {
264     Delay(500);         /* wait 500 ms == 0.5 seconds */
265     close(sfd);
266
267     sfd = socket(PF_INET, SOCK_STREAM, 0);
268     if (sfd < 0)
269       Error(ERR_EXIT, "out of file descriptors");
270
271     setsockopt(sfd, tcpproto->p_proto, TCP_NODELAY, (char *)&on, sizeof(int));
272
273     if (connect(sfd, (struct sockaddr *)&s, sizeof(s)) >= 0)    /* connected */
274       return TRUE;
275   }
276
277   /* when reaching this point, connect to newly started server has failed */
278   return FALSE;
279 }
280 #endif  /* defined(TARGET_SDL) */
281
282 void SendToServer_PlayerName(char *player_name)
283 {
284   int len_player_name = strlen(player_name);
285
286   buffer[1] = OP_PLAYER_NAME;
287   memcpy(&buffer[2], player_name, len_player_name);
288   SendBufferToServer(2 + len_player_name);
289   Error(ERR_NETWORK_CLIENT, "you set your player name to \"%s\"", player_name);
290 }
291
292 void SendToServer_ProtocolVersion()
293 {
294   buffer[1] = OP_PROTOCOL_VERSION;
295   buffer[2] = PROTOCOL_VERSION_1;
296   buffer[3] = PROTOCOL_VERSION_2;
297   buffer[4] = PROTOCOL_VERSION_3;
298
299   SendBufferToServer(5);
300 }
301
302 void SendToServer_NrWanted(int nr_wanted)
303 {
304   buffer[1] = OP_NUMBER_WANTED;
305   buffer[2] = nr_wanted;
306
307   SendBufferToServer(3);
308 }
309
310 void SendToServer_StartPlaying()
311 {
312   unsigned int new_random_seed = InitRND(level.random_seed);
313
314   int dummy = 0;                /* !!! HAS NO MEANING ANYMORE !!! */
315                                 /* the name of the level must be enough */
316
317   buffer[1] = OP_START_PLAYING;
318   buffer[2] = (byte)(level_nr >> 8);
319   buffer[3] = (byte)(level_nr & 0xff);
320   buffer[4] = (byte)(dummy >> 8);
321   buffer[5] = (byte)(dummy & 0xff);
322
323   buffer[6] = (unsigned char)((new_random_seed >> 24) & 0xff);
324   buffer[7] = (unsigned char)((new_random_seed >> 16) & 0xff);
325   buffer[8] = (unsigned char)((new_random_seed >>  8) & 0xff);
326   buffer[9] = (unsigned char)((new_random_seed >>  0) & 0xff);
327
328   strcpy((char *)&buffer[10], leveldir_current->identifier);
329
330   SendBufferToServer(10 + strlen(leveldir_current->identifier) + 1);
331 }
332
333 void SendToServer_PausePlaying()
334 {
335   buffer[1] = OP_PAUSE_PLAYING;
336
337   SendBufferToServer(2);
338 }
339
340 void SendToServer_ContinuePlaying()
341 {
342   buffer[1] = OP_CONTINUE_PLAYING;
343
344   SendBufferToServer(2);
345 }
346
347 void SendToServer_StopPlaying(int cause_for_stopping)
348 {
349   buffer[1] = OP_STOP_PLAYING;
350   buffer[2] = cause_for_stopping;
351
352   SendBufferToServer(3);
353 }
354
355 void SendToServer_MovePlayer(byte player_action)
356 {
357   buffer[1] = OP_MOVE_PLAYER;
358   buffer[2] = player_action;
359
360   SendBufferToServer(3);
361 }
362
363 static void Handle_OP_BAD_PROTOCOL_VERSION()
364 {
365   Error(ERR_WARN, "protocol version mismatch");
366   Error(ERR_EXIT, "server expects %d.%d.x instead of %d.%d.%d",
367         buffer[2], buffer[3],
368         PROTOCOL_VERSION_1, PROTOCOL_VERSION_2, PROTOCOL_VERSION_3);
369 }
370
371 static void Handle_OP_YOUR_NUMBER()
372 {
373   int new_client_nr = buffer[2];
374   int new_index_nr = new_client_nr - 1;
375   struct PlayerInfo *old_local_player = local_player;
376   struct PlayerInfo *new_local_player = &stored_player[new_index_nr];
377
378   printf("OP_YOUR_NUMBER: %d\n", buffer[0]);
379   first_player.nr = new_client_nr;
380
381   if (old_local_player != new_local_player)
382   {
383     /* copy existing player settings and change to new player */
384
385     *new_local_player = *old_local_player;
386     old_local_player->connected = FALSE;
387     local_player = new_local_player;
388   }
389
390   if (first_player.nr > MAX_PLAYERS)
391     Error(ERR_EXIT, "sorry, more than %d players not allowed", MAX_PLAYERS);
392
393   Error(ERR_NETWORK_CLIENT, "you get client # %d", new_client_nr);
394 }
395
396 static void Handle_OP_NUMBER_WANTED()
397 {
398   int client_nr_wanted = buffer[2];
399   int old_client_nr = buffer[0];
400   int new_client_nr = buffer[3];
401   int old_index_nr = old_client_nr - 1;
402   int new_index_nr = new_client_nr - 1;
403   int index_nr_wanted = client_nr_wanted - 1;
404   struct PlayerInfo *old_player = &stored_player[old_index_nr];
405   struct PlayerInfo *new_player = &stored_player[new_index_nr];
406
407   printf("OP_NUMBER_WANTED: %d\n", buffer[0]);
408
409   if (new_client_nr == client_nr_wanted)        /* switching succeeded */
410   {
411     struct NetworkClientPlayerInfo *player;
412
413     if (old_client_nr != client_nr_wanted)      /* client's nr has changed */
414       Error(ERR_NETWORK_CLIENT, "client %d switches to # %d",
415             old_client_nr, new_client_nr);
416     else if (old_client_nr == first_player.nr)  /* local player keeps his nr */
417       Error(ERR_NETWORK_CLIENT, "keeping client # %d", new_client_nr);
418
419     if (old_client_nr != new_client_nr)
420     {
421       /* copy existing player settings and change to new player */
422
423       *new_player = *old_player;
424       old_player->connected = FALSE;
425     }
426
427     player = getNetworkPlayer(old_client_nr);
428     player->nr = new_client_nr;
429
430     if (old_player == local_player)             /* local player switched */
431       local_player = new_player;
432   }
433   else if (old_client_nr == first_player.nr)    /* failed -- local player? */
434   {
435     char request[100];
436
437     sprintf(request, "Sorry ! Player %d already exists ! You are player %d !",
438             index_nr_wanted + 1, new_index_nr + 1);
439
440     Request(request, REQ_CONFIRM);
441
442     Error(ERR_NETWORK_CLIENT, "cannot switch -- you keep client # %d",
443           new_client_nr);
444   }
445 }
446
447 static void Handle_OP_PLAYER_NAME(unsigned int len)
448 {
449   struct NetworkClientPlayerInfo *player;
450   int player_nr = (int)buffer[0];
451
452   printf("OP_PLAYER_NAME: %d\n", player_nr);
453   player = getNetworkPlayer(player_nr);
454   buffer[len] = 0;
455   Error(ERR_NETWORK_CLIENT, "client %d calls itself \"%s\"",
456         buffer[0], &buffer[2]);
457   strncpy(player->name, (char *)&buffer[2], MAX_PLAYER_NAME_LEN);
458 }
459
460 static void Handle_OP_PLAYER_CONNECTED()
461 {
462   struct NetworkClientPlayerInfo *player, *last_player = NULL;
463   int new_client_nr = (int)buffer[0];
464   int new_index_nr = new_client_nr - 1;
465
466   printf("OP_PLAYER_CONNECTED: %d\n", new_client_nr);
467   Error(ERR_NETWORK_CLIENT, "new client %d connected", new_client_nr);
468
469   for (player = &first_player; player; player = player->next)
470   {
471     if (player->nr == new_client_nr)
472       Error(ERR_EXIT, "multiplayer server sent duplicate player id");
473
474     last_player = player;
475   }
476
477   last_player->next = player =
478     checked_malloc(sizeof(struct NetworkClientPlayerInfo));
479   player->nr = new_client_nr;
480   player->name[0] = '\0';
481   player->next = NULL;
482
483   stored_player[new_index_nr].connected = TRUE;
484 }
485
486 static void Handle_OP_PLAYER_DISCONNECTED()
487 {
488   struct NetworkClientPlayerInfo *player, *player_disconnected;
489   int player_nr = (int)buffer[0];
490
491   printf("OP_PLAYER_DISCONNECTED: %d\n", player_nr);
492   player_disconnected = getNetworkPlayer(player_nr);
493   Error(ERR_NETWORK_CLIENT, "client %d (%s) disconnected",
494         player_nr, getNetworkPlayerName(buffer[0]));
495
496   for (player = &first_player; player; player = player->next)
497     if (player->next == player_disconnected)
498       player->next = player_disconnected->next;
499   free(player_disconnected);
500 }
501
502 static void Handle_OP_START_PLAYING()
503 {
504   LevelDirTree *new_leveldir;
505   int new_level_nr;
506   unsigned int new_random_seed;
507   char *new_leveldir_identifier;
508
509   new_level_nr = (buffer[2] << 8) + buffer[3];
510   new_random_seed =
511     (buffer[6] << 24) | (buffer[7] << 16) | (buffer[8] << 8) | (buffer[9]);
512   new_leveldir_identifier = (char *)&buffer[10];
513
514   new_leveldir = getTreeInfoFromIdentifier(leveldir_first,
515                                            new_leveldir_identifier);
516   if (new_leveldir == NULL)
517   {
518     Error(ERR_WARN, "no such level identifier: '%s'", new_leveldir_identifier);
519
520     new_leveldir = leveldir_first;
521     Error(ERR_WARN, "using default level set: '%s'", new_leveldir->identifier);
522   }
523
524   printf("OP_START_PLAYING: %d\n", buffer[0]);
525   Error(ERR_NETWORK_CLIENT,
526         "client %d starts game [level %d from level identifier '%s']\n",
527         buffer[0], new_level_nr, new_leveldir->identifier);
528
529   leveldir_current = new_leveldir;
530   level_nr = new_level_nr;
531
532   TapeErase();
533   LoadTape(level_nr);
534   LoadLevel(level_nr);
535
536   StartGameActions(FALSE, setup.autorecord, new_random_seed);
537 }
538
539 static void Handle_OP_PAUSE_PLAYING()
540 {
541   printf("OP_PAUSE_PLAYING: %d\n", buffer[0]);
542   Error(ERR_NETWORK_CLIENT, "client %d pauses game", buffer[0]);
543
544   tape.pausing = TRUE;
545   DrawVideoDisplay(VIDEO_STATE_PAUSE_ON,0);
546 }
547
548 static void Handle_OP_CONTINUE_PLAYING()
549 {
550   printf("OP_CONTINUE_PLAYING: %d\n", buffer[0]);
551   Error(ERR_NETWORK_CLIENT, "client %d continues game", buffer[0]);
552
553   tape.pausing = FALSE;
554   DrawVideoDisplay(VIDEO_STATE_PAUSE_OFF,0);
555 }
556
557 static void Handle_OP_STOP_PLAYING()
558 {
559   printf("OP_STOP_PLAYING: %d [%d]\n", buffer[0], buffer[2]);
560   Error(ERR_NETWORK_CLIENT, "client %d stops game [%d]", buffer[0], buffer[2]);
561
562   if (game_status == GAME_MODE_PLAYING)
563   {
564     if (buffer[2] == NETWORK_STOP_BY_PLAYER)
565       Request("Network game stopped by player!", REQ_CONFIRM);
566     else if (buffer[2] == NETWORK_STOP_BY_ERROR)
567       Request("Network game stopped due to internal error!", REQ_CONFIRM);
568     else
569       Request("Network game stopped !", REQ_CONFIRM);
570   }
571
572   game_status = GAME_MODE_MAIN;
573   DrawMainMenu();
574 }
575
576 static void Handle_OP_MOVE_PLAYER(unsigned int len)
577 {
578   int server_frame_counter;
579   int i;
580
581   if (!network_playing)
582     return;
583
584   server_frame_counter =
585     (buffer[2] << 24) | (buffer[3] << 16) | (buffer[4] << 8) | (buffer[5]);
586
587 #if 0
588   Error(ERR_NETWORK_CLIENT, "receiving server frame counter value %d [%d]",
589         server_frame_counter, FrameCounter);
590 #endif
591
592   if (server_frame_counter != FrameCounter)
593   {
594     Error(ERR_INFO, "client and servers frame counters out of sync");
595     Error(ERR_INFO, "frame counter of client is %d", FrameCounter);
596     Error(ERR_INFO, "frame counter of server is %d", server_frame_counter);
597     Error(ERR_INFO, "this should not happen -- please debug");
598
599     stop_network_game = TRUE;
600
601     return;
602   }
603
604   /* copy valid player actions */
605   for (i = 0; i < MAX_PLAYERS; i++)
606     stored_player[i].effective_action =
607       (i < len - 6 && stored_player[i].active ? buffer[6 + i] : 0);
608
609   network_player_action_received = TRUE;
610 }
611
612 static void HandleNetworkingMessages()
613 {
614   unsigned int message_length;
615
616   stop_network_game = FALSE;
617
618   while (nread >= 4 && nread >= 4 + readbuffer[3])
619   {
620     message_length = readbuffer[3];
621     if (readbuffer[0] || readbuffer[1] || readbuffer[2])
622       Error(ERR_EXIT, "wrong network server line length");
623
624     memcpy(buffer, &readbuffer[4], message_length);
625     nread -= 4 + message_length;
626     memmove(readbuffer, readbuffer + 4 + message_length, nread);
627
628     switch (buffer[1])
629     {
630       case OP_BAD_PROTOCOL_VERSION:
631         Handle_OP_BAD_PROTOCOL_VERSION();
632         break;
633
634       case OP_YOUR_NUMBER:
635         Handle_OP_YOUR_NUMBER();
636         break;
637
638       case OP_NUMBER_WANTED:
639         Handle_OP_NUMBER_WANTED();
640         break;
641
642       case OP_PLAYER_NAME:
643         Handle_OP_PLAYER_NAME(message_length);
644         break;
645
646       case OP_PLAYER_CONNECTED:
647         Handle_OP_PLAYER_CONNECTED();
648         break;
649       
650       case OP_PLAYER_DISCONNECTED:
651         Handle_OP_PLAYER_DISCONNECTED();
652         break;
653
654       case OP_START_PLAYING:
655         Handle_OP_START_PLAYING();
656         break;
657
658       case OP_PAUSE_PLAYING:
659         Handle_OP_PAUSE_PLAYING();
660         break;
661
662       case OP_CONTINUE_PLAYING:
663         Handle_OP_CONTINUE_PLAYING();
664         break;
665
666       case OP_STOP_PLAYING:
667         Handle_OP_STOP_PLAYING();
668         break;
669
670       case OP_MOVE_PLAYER:
671         Handle_OP_MOVE_PLAYER(message_length);
672         break;
673
674       case OP_BROADCAST_MESSAGE:
675         printf("OP_BROADCAST_MESSAGE: %d\n", buffer[0]);
676         Error(ERR_NETWORK_CLIENT, "client %d sends message", buffer[0]);
677         break;
678     }
679   }
680
681   fflush(stdout);
682
683   /* in case of internal error, stop network game */
684   if (stop_network_game)
685     SendToServer_StopPlaying(NETWORK_STOP_BY_ERROR);
686 }
687
688 /* TODO */
689
690 void HandleNetworking()
691 {
692 #if !defined(TARGET_SDL)
693   static struct timeval tv = { 0, 0 };
694   fd_set rfds;
695 #endif
696   int r = 0;
697
698   do
699   {
700 #if defined(TARGET_SDL)
701     if ((r = SDLNet_CheckSockets(rfds, 1)) < 0)
702       Error(ERR_EXIT, "HandleNetworking(): SDLNet_CheckSockets() failed");
703
704 #else
705
706     FD_ZERO(&rfds);
707     FD_SET(sfd, &rfds);
708
709     r = select(sfd + 1, &rfds, NULL, NULL, &tv);
710
711     if (r < 0 && errno != EINTR)
712       Error(ERR_EXIT, "HandleNetworking(): select() failed");
713
714     if (r < 0)
715       FD_ZERO(&rfds);
716 #endif
717
718 #if defined(TARGET_SDL)
719     if (r > 0)
720 #else
721     if (FD_ISSET(sfd, &rfds))
722 #endif
723     {
724 #if defined(TARGET_SDL)
725       r = SDLNet_TCP_Recv(sfd, readbuffer + nread, 1);
726 #else
727       r = read(sfd, readbuffer + nread, MAX_BUFFER_SIZE - nread);
728 #endif
729
730       if (r < 0)
731         Error(ERR_EXIT, "error reading from network server");
732
733       if (r == 0)
734         Error(ERR_EXIT, "connection to network server lost");
735
736       nread += r;
737
738       HandleNetworkingMessages();
739     }
740   }
741   while (r > 0);
742 }
743
744 #endif /* PLATFORM_UNIX */