1 /**
2 ** Server.cc - Server functions for Exult (NOT ExultStudio).
3 **
4 ** Written: 5/2/2001 - JSF
5 **/
6
7 /*
8 Copyright (C) 2000-2013 The Exult Team
9
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 */
24
25 #ifdef HAVE_CONFIG_H
26 # include <config.h> // All the ifdefs aren't useful if we don't do this
27 #endif
28
29 // only if compiled with "exult studio support"
30 #ifdef USE_EXULTSTUDIO
31
32 #include <unistd.h>
33 #include <fcntl.h>
34
35 #if HAVE_SYS_TYPES_H
36 #include <sys/types.h>
37 #endif
38
39 #if HAVE_SYS_TIME_H
40 #include <sys/time.h>
41 #endif
42
43 #if HAVE_SYS_SOCKET_H
44 #include <sys/socket.h>
45 #endif
46
47 #include <cstdio>
48 #include <cstdlib>
49
50 #if HAVE_NETDB_H
51 #include <netdb.h>
52 #endif
53
54 #ifndef _WIN32
55 #include <sys/un.h>
56 #endif
57
58 #include "server.h"
59 #include "servemsg.h"
60 #include "utils.h"
61 #include "egg.h"
62 #include "barge.h"
63 #include "actors.h"
64 #include "gamewin.h"
65 #include "gamemap.h"
66 #include "chunkter.h"
67 #include "cheat.h"
68 #include "objserial.h"
69 #include "effects.h"
70 #include "Gump_manager.h"
71
72 #ifdef USECODE_DEBUGGER
73 #include "debugserver.h"
74 #endif
75
76 #ifdef _WIN32
77 #include "servewin32.h"
78 #include "cheat.h"
79 #endif
80
81 #include <SDL_timer.h>
82 #include <SDL_events.h>
83
84 using std::cout;
85 using std::cerr;
86 using std::endl;
87 /*
88 * Sockets, etc.
89 */
90 int listen_socket = -1; // Listen here for map-editor.
91 int client_socket = -1; // Socket to the map-editor.
92 int highest_fd = -1; // Largest fd + 1.
93
94 /*
95 * Set the 'highest_fd' value to 1 + <largest fd>.
96 */
Set_highest_fd()97 inline void Set_highest_fd(
98 ) {
99 highest_fd = -1; // Figure highest to listen to.
100 if (listen_socket > highest_fd)
101 highest_fd = listen_socket;
102 if (client_socket > highest_fd)
103 highest_fd = client_socket;
104 highest_fd++; // Select wants 1+highest.
105 }
106
107 /*
108 * Initialize server for map-editing. Call this AFTER xfd has been set.
109 */
110
Server_init()111 void Server_init(
112 ) {
113 // Get location of socket file.
114 std::string servename = get_system_path(EXULT_SERVER);
115 #ifndef _WIN32
116 // Make sure it isn't there.
117 unlink(servename.c_str());
118 #ifdef HAVE_GETADDRINFOX
119 // Don't use the old deprecated network API
120 int r;
121 struct addrinfo hints, *ai;
122
123 memset(&hints, 0, sizeof(hints));
124 hints.ai_flags = AI_PASSIVE;
125 r = getaddrinfo(0,
126 servename.c_str(),
127 &hints,
128 &ai);
129 if (r != 0) {
130 cerr << "getaddrinfo(): " << gai_strerror(r) << endl;
131 return;
132 }
133
134 listen_socket = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
135 #else
136 // Deprecated
137 listen_socket = socket(PF_UNIX, SOCK_STREAM, 0);
138 #endif
139 if (listen_socket < 0)
140 perror("Failed to open map-editor socket");
141 else {
142 #ifdef HAVE_GETADDRINFOX
143 if (bind(listen_socket, ai->ai_addr, ai->ai_addrlen) == -1 ||
144 listen(listen_socket, 1) == -1)
145 #else
146 struct sockaddr_un addr;
147 addr.sun_family = AF_UNIX;
148 strcpy(addr.sun_path, servename.c_str());
149 if (bind(listen_socket, reinterpret_cast<struct sockaddr *>(&addr),
150 sizeof(addr.sun_family) + strlen(addr.sun_path) + 1) == -1 ||
151 listen(listen_socket, 1) == -1)
152 #endif
153 {
154 perror("Bind or listen on socket failed");
155 close(listen_socket);
156 listen_socket = -1;
157 } else { // Set to be non-blocking.
158 cout << "Listening on socket " << listen_socket
159 << endl;
160 fcntl(listen_socket, F_SETFL,
161 fcntl(listen_socket, F_GETFL) | O_NONBLOCK);
162 }
163 #ifdef HAVE_GETADDRINFOX
164 freeaddrinfo(ai);
165 #endif
166 }
167 Set_highest_fd();
168 #else
169
170 listen_socket = client_socket = -1;
171 if (Exult_server::create_pipe(servename.c_str())) listen_socket = 1;
172
173 #endif
174 }
175
176 /*
177 * Close the server.
178 */
179
Server_close()180 void Server_close(
181 ) {
182 #ifdef _WIN32
183 Exult_server::close_pipe();
184 listen_socket = client_socket = -1;
185 #else
186 // unlink socket file+++++++
187 std::string servename = get_system_path(EXULT_SERVER);
188 unlink(servename.c_str());
189 #endif
190 }
191
192
193 /*
194 * A message from a client is available, so handle it.
195 */
196
Handle_client_message(int & fd)197 static void Handle_client_message(
198 int &fd // Socket to client. May be closed.
199 ) {
200 unsigned char data[Exult_server::maxlength];
201 Exult_server::Msg_type id;
202 int datalen = Exult_server::Receive_data(fd, id, data, sizeof(data));
203 if (datalen < 0)
204 return;
205 const unsigned char *ptr = &data[0];
206 Game_window *gwin = Game_window::get_instance();
207 auto unhandled_message = [](Exult_server::Msg_type id) {
208 cout << "Unhandled message received from Exult Studio (id = "
209 << id << ")" << endl;
210 };
211 switch (id) {
212 case Exult_server::obj:
213 Game_object::update_from_studio(&data[0], datalen);
214 break;
215 case Exult_server::barge:
216 Barge_object::update_from_studio(&data[0], datalen);
217 break;
218 case Exult_server::egg:
219 Egg_object::update_from_studio(&data[0], datalen);
220 break;
221 case Exult_server::container:
222 Container_game_object::update_from_studio(&data[0], datalen);
223 break;
224 case Exult_server::npc:
225 Actor::update_from_studio(&data[0], datalen);
226 break;
227 case Exult_server::info:
228 Game_info_out(client_socket, Exult_server::version,
229 cheat.get_edit_lift(),
230 gwin->skip_lift,
231 cheat.in_map_editor(),
232 cheat.show_tile_grid(),
233 gwin->was_map_modified(),
234 static_cast<int>(cheat.get_edit_mode()));
235 break;
236 case Exult_server::write_map:
237 gwin->write_map(); // Send feedback?+++++
238 break;
239 case Exult_server::read_map:
240 gwin->read_map();
241 break;
242 case Exult_server::map_editing_mode: {
243 int onoff = Read2(ptr);
244 if ((onoff != 0) != cheat.in_map_editor())
245 cheat.toggle_map_editor();
246 break;
247 }
248 case Exult_server::tile_grid: {
249 int onoff = Read2(ptr);
250 if ((onoff != 0) != cheat.show_tile_grid())
251 cheat.toggle_tile_grid();
252 break;
253 }
254 case Exult_server::edit_lift: {
255 int lift = Read2(ptr);
256 cheat.set_edit_lift(lift);
257 break;
258 }
259 case Exult_server::reload_usecode:
260 gwin->reload_usecode();
261 break;
262 case Exult_server::locate_terrain: {
263 int tnum = Read2(ptr);
264 int cx = Read2s(ptr);
265 int cy = Read2s(ptr);
266 bool up = *ptr++ != 0;
267 bool okay = gwin->get_map()->locate_terrain(tnum, cx, cy, up);
268 unsigned char *wptr = &data[2];
269 // Set back reply.
270 Write2(wptr, cx);
271 Write2(wptr, cy);
272 wptr++; // Skip 'up' flag.
273 *wptr++ = okay ? 1 : 0;
274 Exult_server::Send_data(client_socket,
275 Exult_server::locate_terrain, data, wptr - data);
276 break;
277 }
278 case Exult_server::swap_terrain: {
279 int tnum = Read2(ptr);
280 bool okay = Game_map::swap_terrains(tnum);
281 unsigned char *wptr = &data[2];
282 *wptr++ = okay ? 1 : 0;
283 Exult_server::Send_data(client_socket,
284 Exult_server::swap_terrain, data, wptr - data);
285 break;
286 }
287 case Exult_server::insert_terrain: {
288 int tnum = Read2s(ptr);
289 bool dup = *ptr++ != 0;
290 bool okay = Game_map::insert_terrain(tnum, dup);
291 unsigned char *wptr = &data[3];
292 *wptr++ = okay ? 1 : 0;
293 Exult_server::Send_data(client_socket,
294 Exult_server::insert_terrain, data, wptr - data);
295 break;
296 }
297 case Exult_server::delete_terrain: {
298 int tnum = Read2s(ptr);
299 bool okay = Game_map::delete_terrain(tnum);
300 unsigned char *wptr = &data[2];
301 *wptr++ = okay ? 1 : 0;
302 Exult_server::Send_data(client_socket,
303 Exult_server::delete_terrain, data, wptr - data);
304 break;
305 }
306 case Exult_server::send_terrain: {
307 // Send back #, total, 512-bytes data.
308 int tnum = Read2s(ptr);
309 unsigned char *wptr = &data[2];
310 Write2(wptr, gwin->get_map()->get_num_chunk_terrains());
311 Chunk_terrain *ter = Game_map::get_terrain(tnum);
312 // Serialize it.
313 wptr += ter->write_flats(wptr, Game_map::is_v2_chunks());
314 Exult_server::Send_data(client_socket,
315 Exult_server::send_terrain, data, wptr - data);
316 break;
317 }
318 case Exult_server::terrain_editing_mode: {
319 // 1=on, 0=off, -1=undo.
320 int onoff = Read2s(ptr);
321 // skip_lift==0 <==> terrain-editing.
322 gwin->skip_lift = onoff == 1 ? 0 : 256;
323 static const char *msgs[3] = {"Terrain-Editing Aborted",
324 "Terrain-Editing Done",
325 "Terrain-Editing Enabled"
326 };
327 if (onoff == 0) // End/commit.
328 Game_map::commit_terrain_edits();
329 else if (onoff == -1)
330 Game_map::abort_terrain_edits();
331 if (onoff >= -1 && onoff <= 1)
332 gwin->get_effects()->center_text(msgs[onoff + 1]);
333 gwin->set_all_dirty();
334 break;
335 }
336 case Exult_server::set_edit_shape: {
337 int shnum = Read2s(ptr);
338 int frnum = Read2s(ptr);
339 cheat.set_edit_shape(shnum, frnum);
340 break;
341 }
342 case Exult_server::view_pos: {
343 int tx = Read4(ptr);
344 if (tx == -1) { // This is a query?
345 gwin->send_location();
346 break;
347 }
348 int ty = Read4(ptr);
349 // +++Later int txs = Read4(ptr);
350 // int tys = Read4(ptr);
351 // int scale = Read4(ptr);
352 // Only set if chunk changed.
353 if (tx / c_tiles_per_chunk !=
354 gwin->get_scrolltx() / c_tiles_per_chunk ||
355 ty / c_tiles_per_chunk !=
356 gwin->get_scrollty() / c_tiles_per_chunk) {
357 gwin->set_scrolls(tx, ty);
358 gwin->set_all_dirty();
359 }
360 break;
361 }
362 case Exult_server::set_edit_mode: {
363 int md = Read2(ptr);
364 if (md >= 0 && md <= 4)
365 cheat.set_edit_mode(static_cast<Cheat::Map_editor_mode>(md));
366 break;
367 }
368 case Exult_server::hide_lift: {
369 int lift = Read2(ptr);
370 gwin->skip_lift = lift;
371 gwin->set_all_dirty();
372 break;
373 }
374 case Exult_server::reload_shapes:
375 Shape_manager::get_instance()->reload_shapes(Read2(ptr));
376 break;
377 case Exult_server::unused_shapes: {
378 // Send back shapes not used in game.
379 unsigned char data[Exult_server::maxlength];
380 size_t sz = 1024u / 8; // Gets bits for unused shapes.
381 sz = sz > sizeof(data) ? sizeof(data) : sz;
382 gwin->get_map()->find_unused_shapes(data, sz);
383 Exult_server::Send_data(client_socket,
384 Exult_server::unused_shapes, data, sz);
385 break;
386 }
387 case Exult_server::locate_shape: {
388 int shnum = Read2(ptr);
389 int frnum = Read2s(ptr);
390 int qual = Read2s(ptr);
391 bool up = *ptr++ != 0;
392 bool okay = gwin->locate_shape(shnum, up, frnum, qual);
393 unsigned char *wptr = &data[6]; // Send back reply.
394 wptr++; // Skip 'up' flag.
395 *wptr++ = okay ? 1 : 0;
396 Exult_server::Send_data(client_socket,
397 Exult_server::locate_shape, data, wptr - data);
398 break;
399 }
400 case Exult_server::cut: // Cut/copy.
401 cheat.cut(*ptr != 0);
402 break;
403 case Exult_server::paste:
404 cheat.paste();
405 break;
406 case Exult_server::npc_unused: {
407 unsigned char *wptr = &data[0];
408 Write2(wptr, gwin->get_num_npcs());
409 Write2(wptr, gwin->get_unused_npc());
410 Exult_server::Send_data(client_socket,
411 Exult_server::npc_unused, data, wptr - data);
412 break;
413 }
414 case Exult_server::npc_info: {
415 int npcnum = Read2(ptr);
416 Actor *npc = gwin->get_npc(npcnum);
417 unsigned char *wptr = &data[2];
418 if (npc) {
419 Write2(wptr, npc->get_shapenum());
420 *wptr++ = npc->is_unused();
421 std::string nm = npc->get_npc_name();
422 strcpy(reinterpret_cast<char *>(wptr), nm.c_str());
423 // Point past ending nullptr.
424 wptr += strlen(reinterpret_cast<char *>(wptr)) + 1;
425 } else
426 Write2(wptr, static_cast<unsigned short>(-1));
427 Exult_server::Send_data(client_socket,
428 Exult_server::npc_info, data, wptr - data);
429 break;
430 }
431 case Exult_server::locate_npc:
432 gwin->locate_npc(Read2(ptr));
433 break;
434 case Exult_server::edit_npc: {
435 int npcnum = Read2(ptr);
436 Actor *npc = gwin->get_npc(npcnum);
437 if (npc)
438 npc->edit();
439 break;
440 }
441 case Exult_server::edit_selected: {
442 unsigned char basic = *ptr;
443 const Game_object_shared_vector &sel = cheat.get_selected();
444 if (!sel.empty()) {
445 if (basic) // Basic obj. props?
446 sel.back()->Game_object::edit();
447 else
448 sel.back()->edit();
449 }
450 break;
451 }
452 case Exult_server::set_edit_chunknum:
453 cheat.set_edit_chunknum(Read2s(ptr));
454 break;
455 case Exult_server::game_pos: {
456 Tile_coord pos = gwin->get_main_actor()->get_tile();
457 unsigned char *wptr = &data[0];
458 Write2(wptr, pos.tx);
459 Write2(wptr, pos.ty);
460 Write2(wptr, pos.tz);
461 Exult_server::Send_data(client_socket,
462 Exult_server::game_pos, data, wptr - data);
463 break;
464 }
465 case Exult_server::goto_map: {
466 char msg[80];
467 Tile_coord pos = gwin->get_main_actor()->get_tile();
468 int num = Read2(ptr);
469 gwin->teleport_party(pos, true, num);
470 sprintf(msg, "Map #%02x", num);
471 gwin->get_effects()->center_text(msg);
472 break;
473 }
474 case Exult_server::cont_show_gump: {
475 Serial_in io(ptr);
476 uintptr addr;
477 io << addr;
478 auto *p = reinterpret_cast<Game_object *>(addr);
479 auto *obj = p->as_container();
480 if (!obj) {
481 cout << "Error decoding container data" << endl;
482 break;
483 }
484 cout << "Displaying container's gump" << endl;
485 Actor *act = obj->as_actor();
486 if (act)
487 act->show_inventory();
488 else {
489 // Avoid all frame-usecode and force-usecode subtleties.
490 int gump = ShapeID::get_info(obj->get_shapenum()).get_gump_shape();
491 if (gump >= 0) {
492 Gump_manager *gump_man =
493 Game_window::get_instance()->get_gump_man();
494 gump_man->add_gump(obj, gump);
495 } else
496 cerr << "The selected container has no gump!" << endl;
497 }
498 break;
499 }
500 case Exult_server::reload_shapes_info:
501 Shape_manager::get_instance()->reload_shape_info();
502 break;
503 case Exult_server::usecode_debugging:
504 #ifdef USECODE_DEBUGGER
505 Handle_debug_message(&data[0], datalen);
506 #else
507 unhandled_message(id);
508 #endif
509 break;
510 default:
511 unhandled_message(id);
512 }
513 }
514
515 /*
516 * Delay for a fraction of a second, or until there's data available.
517 * If a server request comes, it's handled here.
518 */
519
520 #ifndef _WIN32
521 #define DELAY_TOTAL_MS 10
522 #define DELAY_SINGLE_MS 1
523 #endif
524
Server_delay(Message_handler handle_message)525 void Server_delay(
526 Message_handler handle_message
527 ) {
528 #ifndef _WIN32
529 Uint32 expiration = DELAY_TOTAL_MS + SDL_GetTicks();
530 for (;;) {
531 SDL_PumpEvents();
532 if ((SDL_PeepEvents(nullptr, 0, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) != 0) ||
533 (static_cast<Sint32>(SDL_GetTicks()) >= static_cast<Sint32>(expiration))) return;
534
535 fd_set rfds;
536 struct timeval timer;
537 timer.tv_sec = 0;
538 timer.tv_usec = DELAY_SINGLE_MS * 1000; // Try 1/1000 second.
539 FD_ZERO(&rfds);
540 if (listen_socket >= 0)
541 FD_SET(listen_socket, &rfds);
542 if (client_socket >= 0)
543 FD_SET(client_socket, &rfds);
544 // Wait for timeout or event.
545 if (select(highest_fd, &rfds, nullptr, nullptr, &timer) > 0) {
546 // Something's come in.
547 if (listen_socket >= 0 && FD_ISSET(listen_socket, &rfds)) {
548 // New client connection.
549 // For now, just one at a time.
550 if (client_socket >= 0)
551 close(client_socket);
552 client_socket = accept(listen_socket, nullptr, nullptr);
553 cout << "Accept returned client_socket = " <<
554 client_socket << endl;
555 // Non-blocking.
556 fcntl(client_socket, F_SETFL,
557 fcntl(client_socket, F_GETFL) | O_NONBLOCK);
558 Set_highest_fd();
559 }
560 if (client_socket >= 0 && FD_ISSET(client_socket, &rfds)) {
561 handle_message(client_socket);
562 // Client gone?
563 if (client_socket == -1)
564 Set_highest_fd();
565 }
566 }
567 }
568 #else
569 if (listen_socket == -1) return;
570
571 if (client_socket == -1) {
572 // Only do this in map edit mode
573 if (!cheat.in_map_editor()) return;
574
575 std::string servename = get_system_path("<GAMEDAT>");
576 if (!Exult_server::try_connect_to_client(servename.c_str())) return;
577 else client_socket = 1;
578 std::cout << "Connected to client" << endl;
579 }
580
581 while (Exult_server::peek_pipe() > 0)
582 handle_message(client_socket);
583
584 if (Exult_server::is_broken()) {
585 std::cout << "Client disconnected." << endl;
586 client_socket = -1;
587 }
588 #endif
589
590 }
591
Server_delay()592 void Server_delay() {
593 Server_delay(Handle_client_message);
594 }
595
596 #endif
597