2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on the
12 #include "osregistry.h"
13 #include "multi_options.h"
14 #include "gamesequence.h"
18 #include "stand_server.h"
19 #include "multi_pmsg.h"
20 #include "multi_endgame.h"
21 #include "multimsgs.h"
22 #include "multiutil.h"
23 #include "freespace.h"
24 #include "missiongoals.h"
26 #include "multi_kick.h"
27 #include "multi_fstracker.h"
28 #include "osregistry.h"
30 #include <libwebsockets.h>
38 std::string debug_txt;
39 std::string popup_title;
40 std::string popup_field1;
41 std::string popup_field2;
43 std::string mission_name;
44 std::string mission_time;
45 std::string mission_goals;
46 std::string netgame_info;
50 static std_state Standalone_state;
52 static std::list<std::string> Standalone_send_buf;
54 static std::string Standalone_pinfo_active_player;
57 #define STANDALONE_MAX_BAN 50
58 static std::vector<std::string> Standalone_ban_list;
60 #define STD_STATS_UPDATE_TIME 500 // ms between updating player stats
61 #define STD_NG_UPDATE_TIME 1500 // ms between updating netgame information
62 #define STD_PING_UPDATE_TIME 1000 // ms between updating pings
63 #define STD_FPS_UPDATE_TIME 250 // ms between fps updates
65 static int Standalone_stats_stamp = -1;
66 static int Standalone_ng_stamp = -1;
67 static int Standalone_ping_stamp = -1;
68 static int Standalone_fps_stamp = -1;
70 static lws_context *stand_context = NULL;
72 static int startup_reset_stamp;
73 static struct lws *active_wsi = NULL;
76 static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
78 bool try_reuse = false;
81 case LWS_CALLBACK_HTTP: {
83 lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL);
89 int ret = lws_serve_http_file(wsi, "./standalone.html", "text/html", NULL, 0);
91 if ( (ret < 0) || ((ret > 0) && lws_http_transaction_completed(wsi)) ) {
92 // error or can't reuse connection, close the socket
99 case LWS_CALLBACK_HTTP_BODY_COMPLETION: {
100 lws_return_http_status(wsi, HTTP_STATUS_OK, NULL);
106 case LWS_CALLBACK_HTTP_FILE_COMPLETION: {
117 if (lws_http_transaction_completed(wsi)) {
125 static int callback_standalone(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
127 #define MAX_BUF_SIZE 1024
128 unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + MAX_BUF_SIZE + LWS_SEND_BUFFER_POST_PADDING];
129 unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
134 case LWS_CALLBACK_ESTABLISHED: {
135 if ( timestamp_elapsed(startup_reset_stamp) ) {
136 std_reset_standalone_gui();
144 case LWS_CALLBACK_CLOSED: {
150 case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: {
158 case LWS_CALLBACK_SERVER_WRITEABLE: {
159 while ( !Standalone_send_buf.empty() ) {
160 size = SDL_strlcpy((char *)p, Standalone_send_buf.front().c_str(), MAX_BUF_SIZE);
162 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
165 lwsl_err("ERROR sending buffer!\n");
166 lws_close_reason(wsi, LWS_CLOSE_STATUS_UNEXPECTED_CONDITION, (unsigned char *)"write error", 11);
171 Standalone_send_buf.pop_front();
173 if ( lws_send_pipe_choked(wsi) ) {
174 lws_callback_on_writable(wsi);
183 case LWS_CALLBACK_RECEIVE: {
184 if (in != NULL && len > 0) {
185 const char *msg = (const char *)in;
188 if ( !SDL_strcmp(msg, "shutdown") ) {
189 gameseq_post_event(GS_EVENT_QUIT_GAME);
190 lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY, (unsigned char *)"shutdown", 8);
195 if ( !SDL_strcmp(msg, "reset") ) {
196 multi_quit_game(PROMPT_NONE);
197 std_reset_standalone_gui();
205 if ( !SDL_strncmp(msg+2, "name ", 5) ) {
206 SDL_strlcpy(Netgame.name, msg+7, SDL_arraysize(Netgame.name));
207 SDL_strlcpy(Multi_options_g.std_pname, Netgame.name, SDL_arraysize(Multi_options_g.std_pname));
208 } else if ( !SDL_strncmp(msg+2, "pass ", 5) ) {
209 SDL_strlcpy(Multi_options_g.std_passwd, msg+7, SDL_arraysize(Multi_options_g.std_passwd));
210 } else if ( !SDL_strncmp(msg+2, "kick ", 5) ) {
213 for (int i = 0; i < MAX_PLAYERS; i++) {
214 if ( MULTI_CONNECTED(Net_players[i]) ) {
215 psnet_addr_to_string(ip_string, SDL_arraysize(ip_string), &Net_players[i].p_info.addr);
217 if ( !SDL_strcmp(msg+7, ip_string) ) {
218 multi_kick_player(i, 0);
228 else if (mtype == 'M') {
230 if ( !SDL_strncmp(msg+2, "fps ", 4) ) {
231 int fps = SDL_atoi(msg+6);
233 Multi_options_g.std_framecap = fps;
238 else if (mtype == 'P') {
240 if ( !SDL_strncmp(msg+2, "info ", 5) ) {
243 for (i = 0; i < MAX_PLAYERS; i++) {
244 net_player *np = &Net_players[i];
246 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
247 if ( !SDL_strcmp(msg+7, np->player->callsign) ) {
248 Standalone_pinfo_active_player = msg+7;
249 std_pinfo_display_player_info(np);
256 if (i == MAX_PLAYERS) {
257 Standalone_pinfo_active_player.clear();
263 else if (mtype == 'G') {
265 if ( !SDL_strncmp(msg+2, "smsg ", 5) ) {
268 SDL_strlcpy(txt, msg+7, SDL_arraysize(txt));
270 if (SDL_strlen(txt) > 0) {
271 send_game_chat_packet(Net_player, txt, MULTI_MSG_ALL, NULL);
273 std_add_chat_text(txt, MY_NET_PLAYER_NUM, 1);
275 } else if ( !SDL_strcmp(msg+2, "mrefresh") ) {
276 if (MULTI_IS_TRACKER_GAME) {
277 cf_delete(MULTI_VALID_MISSION_FILE, CF_TYPE_DATA);
279 multi_update_valid_missions();
296 static struct lws_protocols stand_protocols[] = {
318 static void std_lws_logger(int level, const char *line)
320 if (level & (LLL_WARN|LLL_ERR)) {
321 mprintf(("STD: %s", line));
322 } else if (level & LLL_NOTICE) {
323 nprintf(("lws", "STD: %s", line));
329 static void std_add_ws_message(const char *id, const char *val)
333 // if no client, and startup stamp elapsed, then don't add more messages
334 if ( (active_wsi == NULL) && timestamp_elapsed(startup_reset_stamp) ) {
344 Standalone_send_buf.push_back(msg);
347 void std_deinit_standalone()
350 lws_cancel_service(stand_context);
351 lws_context_destroy(stand_context);
352 stand_context = NULL;
356 void std_init_standalone()
358 struct lws_context_creation_info info;
366 // basic security measure for admin interface
367 // "1" bind to loopback iface only *default*
368 // "0" bind to any/all
369 // any other value should be ip or iface name to bind
370 // (invalid values will trigger error)
372 const char *stand_iface = os_config_read_string("Network", "RestrictStandAdmin", "1");
374 if ( !SDL_strcmp(stand_iface, "1") ) {
375 info.iface = "127.0.0.1";
376 } else if ( !SDL_strcmp(stand_iface, "0") ) {
379 info.iface = stand_iface;
382 info.port = Multi_options_g.port;
384 info.protocols = stand_protocols;
389 lws_set_log_level(LLL_ERR|LLL_WARN|LLL_NOTICE, std_lws_logger);
391 stand_context = lws_create_context(&info);
393 if (stand_context == NULL) {
394 Error(LOCATION, "Unable to initialize standalone server!");
397 atexit(std_deinit_standalone);
399 // turn off all sound and music
400 Cmdline_freespace_no_sound = 1;
401 Cmdline_freespace_no_music = 1;
404 SDL_snprintf(title, SDL_arraysize(title), "%s %d.%02d.%02d", XSTR("FreeSpace Standalone", 935), FS_VERSION_MAJOR, FS_VERSION_MINOR, FS_VERSION_BUILD);
405 Standalone_state.title = title;
407 // connections > 5 sec after startup should get gui reset
408 startup_reset_stamp = timestamp(5000);
410 std_reset_standalone_gui();
412 std_multi_update_netgame_info_controls();
415 static void std_update_ping_all()
417 std::string ping_upd;
420 for (int i = 0, idx = 0; i < MAX_PLAYERS; i++) {
421 net_player *np = &Net_players[i];
423 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
424 if (np->s_info.ping.ping_avg > -1) {
425 if (np->s_info.ping.ping_avg >= 1000) {
426 SDL_snprintf(ping_str, SDL_arraysize(ping_str), "%s", XSTR("> 1 sec", 914));
428 SDL_snprintf(ping_str, SDL_arraysize(ping_str), "%d%s", np->s_info.ping.ping_avg, XSTR(" ms", 915));
434 // append separator if not first
436 ping_upd.append(",");
439 ping_upd.append(ping_str);
443 if ( !ping_upd.empty() ) {
444 std_add_ws_message("S:ping ", ping_upd.c_str());
448 static void std_update_connections()
450 std::string conn_str;
453 conn_str.reserve(1024);
455 for (int i = 0, idx = 0; i < MAX_PLAYERS; i++) {
456 net_player *np = &Net_players[i];
458 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
459 // append seperator if not first
461 conn_str.append(";");
464 conn_str.append(np->player->callsign);
465 conn_str.append(",");
467 psnet_addr_to_string(ip_address, SDL_arraysize(ip_address), &np->p_info.addr);
468 conn_str.append(ip_address);
472 SDL_assert(conn_str.length() < 1024);
474 if ( !conn_str.empty() ) {
475 std_add_ws_message("S:conn ", conn_str.c_str());
479 void std_do_gui_frame()
481 // maybe update selected player stats
482 if ( ((Standalone_stats_stamp == -1) || timestamp_elapsed(Standalone_stats_stamp)) && !Standalone_pinfo_active_player.empty() ) {
483 Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
485 for (int i = 0; i < MAX_PLAYERS; i++) {
486 net_player *np = &Net_players[i];
488 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
489 if ( !SDL_strcmp(Standalone_pinfo_active_player.c_str(), np->player->callsign) ) {
490 std_pinfo_display_player_info(np);
498 // maybe update netgame info
499 if ( (Standalone_ng_stamp == -1) || timestamp_elapsed(Standalone_ng_stamp) ) {
500 Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
502 std_multi_update_netgame_info_controls();
505 // update connection ping times
506 if ( ((Standalone_ping_stamp == -1) || timestamp_elapsed(Standalone_ping_stamp)) ) {
507 Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
509 std_update_ping_all();
512 if ( !Standalone_send_buf.empty() ) {
513 lws_callback_on_writable_all_protocol(stand_context, &stand_protocols[1]);
516 lws_service(stand_context, 0);
519 void std_debug_set_standalone_state_string(const char *str)
521 Standalone_state.debug_txt = str;
523 std_add_ws_message("D:", str);
526 void std_connect_set_gamename(const char *name)
529 // if a permanent name exists, use that instead of the default
530 if ( SDL_strlen(Multi_options_g.std_pname) ) {
531 SDL_strlcpy(Netgame.name, Multi_options_g.std_pname, SDL_arraysize(Netgame.name));
533 SDL_strlcpy(Netgame.name, XSTR("Standalone Server", 916), SDL_arraysize(Netgame.name));
536 SDL_strlcpy(Netgame.name, name, SDL_arraysize(Netgame.name));
539 std_add_ws_message("S:name ", Netgame.name);
542 int std_connect_set_connect_count()
546 for (int i = 0; i < MAX_PLAYERS; i++) {
547 if (MULTI_CONNECTED(Net_players[i]) && (Net_player != &Net_players[i]) ) {
555 void std_add_player(net_player *p)
557 std_update_connections();
559 // check to see if this guy is the host
560 std_connect_set_host_connect_status();
563 int std_remove_player(net_player *p)
567 std_update_connections();
569 // update the host connect count
570 std_connect_set_host_connect_status();
572 // update the currently connected players
573 count = std_connect_set_connect_count();
576 multi_quit_game(PROMPT_NONE);
583 void std_update_player_ping(net_player *p)
587 void std_pinfo_display_player_info(net_player *p)
595 pinfo.append(Ship_info[p->p_info.ship_class].name);
599 if (p->s_info.ping.ping_avg > 1000) {
600 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%s", XSTR("> 1 sec", 914));
602 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d%s", p->s_info.ping.ping_avg, XSTR(" ms", 915));
605 pinfo.append(sml_ping);
608 scoring_struct *ptr = &p->player->stats;
611 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_fired);
612 pinfo.append(sml_ping);
614 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_hit);
615 pinfo.append(sml_ping);
617 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_bonehead_hits);
618 pinfo.append(sml_ping);
620 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_fired ? (int)(100.0f * ((float)ptr->p_shots_hit / (float)ptr->p_shots_fired)) : 0);
621 pinfo.append(sml_ping);
623 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_fired ? (int)(100.0f * ((float)ptr->p_bonehead_hits / (float)ptr->p_shots_fired)) : 0);
624 pinfo.append(sml_ping);
626 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_fired);
627 pinfo.append(sml_ping);
629 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_hit);
630 pinfo.append(sml_ping);
632 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_bonehead_hits);
633 pinfo.append(sml_ping);
635 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_fired ? (int)(100.0f * ((float)ptr->s_shots_hit / (float)ptr->s_shots_fired)) : 0);
636 pinfo.append(sml_ping);
638 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_fired ? (int)(100.0f * ((float)ptr->s_bonehead_hits / (float)ptr->s_shots_fired)) : 0);
639 pinfo.append(sml_ping);
641 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->assists);
642 pinfo.append(sml_ping);
643 pinfo.append(";"); // <- end of block
646 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_fired);
647 pinfo.append(sml_ping);
649 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_hit);
650 pinfo.append(sml_ping);
652 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_bonehead_hits);
653 pinfo.append(sml_ping);
655 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_fired ? (int)(100.0f * ((float)ptr->mp_shots_hit / (float)ptr->mp_shots_fired)) : 0);
656 pinfo.append(sml_ping);
658 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_fired ? (int)(100.0f * ((float)ptr->mp_bonehead_hits / (float)ptr->mp_shots_fired)) : 0);
659 pinfo.append(sml_ping);
661 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_fired);
662 pinfo.append(sml_ping);
664 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_hit);
665 pinfo.append(sml_ping);
667 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_bonehead_hits);
668 pinfo.append(sml_ping);
670 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_fired ? (int)(100.0f * ((float)ptr->ms_shots_hit / (float)ptr->ms_shots_fired)) : 0);
671 pinfo.append(sml_ping);
673 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_fired ? (int)(100.0f * ((float)ptr->ms_bonehead_hits / (float)ptr->ms_shots_fired)) : 0);
674 pinfo.append(sml_ping);
676 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->m_assists);
677 pinfo.append(sml_ping);
679 std_add_ws_message("P:info ", pinfo.c_str());
682 void std_add_chat_text(const char *text, int player_index, int add_id)
687 if ( (player_index < 0) || (player_index >= MAX_PLAYERS) ) {
691 // format the chat text nicely
693 if ( MULTI_STANDALONE(Net_players[player_index]) ) {
694 SDL_snprintf(id, SDL_arraysize(id), XSTR("<SERVER> %s", 924), "");
696 SDL_snprintf(id, SDL_arraysize(id), "%s: ", Net_players[player_index].player->callsign);
705 std_add_ws_message("G:mesg ", msg.c_str());
708 void std_reset_timestamps()
710 // reset the stats update stamp
711 Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
713 // reset the netgame controls update timestamp
714 Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
716 // reset the ping update stamp
717 Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
719 // reset fps update stamp
720 Standalone_fps_stamp = timestamp(STD_FPS_UPDATE_TIME);
723 void std_add_ban(const char *name)
725 if ( (name == NULL) || !SDL_strlen(name) ) {
729 if (Standalone_ban_list.size() >= STANDALONE_MAX_BAN) {
733 Standalone_ban_list.push_back(name);
736 int std_player_is_banned(const char *name)
738 if ( Standalone_ban_list.empty() ) {
742 for (size_t i = 0; i < Standalone_ban_list.size(); i++) {
743 if ( !SDL_strcasecmp(name, Standalone_ban_list[i].c_str()) ) {
751 int std_is_host_passwd()
753 return (SDL_strlen(Multi_options_g.std_passwd) > 0) ? 1 : 0;
756 void std_multi_set_standalone_mission_name(const char *mission_name)
758 Standalone_state.mission_name = mission_name;
760 std_add_ws_message("M:name ", mission_name);
763 void std_multi_set_standalone_missiontime(float mission_time)
767 fix m_time = fl2f(mission_time);
769 // format the time string and set the text
770 game_format_time(m_time, timestr, SDL_arraysize(timestr));
771 SDL_snprintf(txt, SDL_arraysize(txt), "%s : %.1f", timestr, mission_time);
773 Standalone_state.mission_time = txt;
775 std_add_ws_message("M:time ", txt);
778 void std_multi_update_netgame_info_controls()
782 SDL_snprintf(nginfo, SDL_arraysize(nginfo), "%d,%d,%d,%d", Netgame.max_players, Netgame.options.max_observers, Netgame.security, Netgame.respawn);
784 Standalone_state.netgame_info = nginfo;
786 std_add_ws_message("M:info ", nginfo);
789 void std_set_standalone_fps(float fps)
791 if ( (Standalone_fps_stamp == -1) || timestamp_elapsed(Standalone_fps_stamp) ) {
792 Standalone_fps_stamp = timestamp(STD_FPS_UPDATE_TIME);
794 SDL_snprintf(Standalone_state.rfps, SDL_arraysize(Standalone_state.rfps), "%.1f", fps);
796 std_add_ws_message("M:rfps ", Standalone_state.rfps);
800 void std_multi_setup_goal_tree()
803 std::string secondary;
807 Standalone_state.mission_goals.clear();
809 for (int i = 0; i < Num_goals; i++) {
810 switch (Mission_goals[i].satisfied) {
816 case GOAL_COMPLETE: {
821 case GOAL_INCOMPLETE:
828 switch (Mission_goals[i].type & GOAL_TYPE_MASK) {
830 primary.append(status);
831 primary.append(Mission_goals[i].name);
837 case SECONDARY_GOAL: {
838 secondary.append(status);
839 secondary.append(Mission_goals[i].name);
840 secondary.append(",");
846 bonus.append(status);
847 bonus.append(Mission_goals[i].name);
858 if ( primary.empty() ) {
859 Standalone_state.mission_goals.append("i none");
861 Standalone_state.mission_goals.append(primary.substr(0, primary.size()-1));
864 Standalone_state.mission_goals.append(";");
866 if ( secondary.empty() ) {
867 Standalone_state.mission_goals.append("i none");
869 Standalone_state.mission_goals.append(secondary.substr(0, secondary.size()-1));
872 Standalone_state.mission_goals.append(";");
874 if ( bonus.empty() ) {
875 Standalone_state.mission_goals.append("i none");
877 Standalone_state.mission_goals.append(bonus.substr(0, bonus.size()-1));
880 std_add_ws_message("M:goal ", Standalone_state.mission_goals.c_str());
883 void std_multi_add_goals()
885 std_multi_setup_goal_tree();
888 void std_multi_update_goals()
890 std_multi_setup_goal_tree();
893 void std_reset_standalone_gui()
895 Standalone_send_buf.clear();
897 std_add_ws_message("reset", NULL);
899 std_add_ws_message("T: ", Standalone_state.title.c_str());
901 std_add_ws_message("S:name ", Netgame.name);
902 std_add_ws_message("S:pass ", Multi_options_g.std_passwd);
904 std_update_connections();
905 std_set_standalone_fps(0.0f);
906 std_multi_set_standalone_missiontime(0.0f);
907 std_multi_update_netgame_info_controls();
908 std_reset_timestamps();
910 Standalone_pinfo_active_player.clear();
914 void std_create_gen_dialog(const char *title)
916 Standalone_state.popup_title = title;
918 Standalone_state.popup_field1 = "";
919 Standalone_state.popup_field2 = "";
922 void std_destroy_gen_dialog()
924 std_add_ws_message("popup ", NULL);
927 void std_gen_set_text(const char *str, int field_num)
929 std::string popup_str;
933 Standalone_state.popup_title = str;
937 Standalone_state.popup_field1 = str;
941 Standalone_state.popup_field2 = str;
948 popup_str.append(Standalone_state.popup_title);
949 popup_str.append(";");
950 popup_str.append(Standalone_state.popup_field1);
951 popup_str.append(";");
952 popup_str.append(Standalone_state.popup_field2);
954 std_add_ws_message("popup ", popup_str.c_str());
956 // force ws write since do_frame() may not happen until popup is done
957 lws_callback_on_writable_all_protocol(stand_context, &stand_protocols[1]);
958 lws_service(stand_context, 0);
961 void std_tracker_notify_login_fail()
966 void std_tracker_login()
968 if ( !Multi_options_g.pxo ) {
972 multi_fs_tracker_init();
974 if ( !multi_fs_tracker_inited() ) {
975 std_tracker_notify_login_fail();
979 multi_fs_tracker_login_freespace();
982 void std_connect_set_host_connect_status()