diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 334f666c..9d6aeafc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,6 +35,7 @@ set(SRB2_CORE_SOURCES m_random.c md5.c mserv.c + http-mserv.c s_sound.c screen.c sounds.c @@ -99,6 +100,7 @@ set(SRB2_CORE_HEADERS m_swap.h md5.h mserv.h + http-mserv.h p5prof.h s_sound.h screen.h diff --git a/src/Makefile b/src/Makefile index 4df44e63..6395c7ef 100644 --- a/src/Makefile +++ b/src/Makefile @@ -554,6 +554,7 @@ OBJS:=$(i_main_o) \ $(OBJDIR)/w_wad.o \ $(OBJDIR)/filesrch.o \ $(OBJDIR)/mserv.o \ + $(OBJDIR)/http-mserv.o\ $(OBJDIR)/i_tcp.o \ $(OBJDIR)/lzf.o \ $(OBJDIR)/vid_copy.o \ diff --git a/src/console.c b/src/console.c index 28958089..11a7961e 100644 --- a/src/console.c +++ b/src/console.c @@ -31,6 +31,7 @@ #include "i_video.h" #include "z_zone.h" #include "i_system.h" +#include "i_threads.h" #include "d_main.h" #include "m_menu.h" #include "filesrch.h" @@ -45,6 +46,16 @@ #define MAXHUDLINES 20 +#ifdef HAVE_THREADS +I_mutex con_mutex; + +# define Lock_state() I_lock_mutex(&con_mutex) +# define Unlock_state() I_unlock_mutex(con_mutex) +#else/*HAVE_THREADS*/ +# define Lock_state() +# define Unlock_state() +#endif/*HAVE_THREADS*/ + static boolean con_started = false; // console has been initialised boolean con_startup = false; // true at game startup, screen need refreshing static boolean con_forcepic = true; // at startup toggle console translucency when first off @@ -170,6 +181,8 @@ static void CONS_hudlines_Change(void) { INT32 i; + Lock_state(); + // Clear the currently displayed lines for (i = 0; i < con_hudlines; i++) con_hudtime[i] = 0; @@ -181,6 +194,8 @@ static void CONS_hudlines_Change(void) con_hudlines = cons_hudlines.value; + Unlock_state(); + CONS_Printf(M_GetText("Number of console HUD lines is now %d\n"), con_hudlines); } @@ -188,12 +203,16 @@ static void CONS_hudlines_Change(void) // static void CONS_Clear_f(void) { + Lock_state(); + memset(con_buffer, 0, CON_BUFFERSIZE); con_cx = 0; con_cy = con_totallines-1; con_line = &con_buffer[con_cy*con_width]; con_scrollup = 0; + + Unlock_state(); } // Choose english keymap @@ -369,20 +388,29 @@ void CON_Init(void) for (i = 0; i < NUMINPUTS; i++) bindtable[i] = NULL; + Lock_state(); + // clear all lines memset(con_buffer, 0, CON_BUFFERSIZE); // make sure it is ready for the loading screen con_width = 0; + + Unlock_state(); + CON_RecalcSize(); CON_SetupColormaps(); + Lock_state(); + //note: CON_Ticker should always execute at least once before D_Display() con_clipviewtop = -1; // -1 does not clip con_hudlines = atoi(cons_hudlines.defaultvalue); + Unlock_state(); + // setup console input filtering CON_InputInit(); @@ -391,15 +419,23 @@ void CON_Init(void) COM_AddCommand("cls", CONS_Clear_f); //COM_AddCommand("english", CONS_English_f); // set console full screen for game startup MAKE SURE VID_Init() done !!! + Lock_state(); + con_destlines = vid.height; con_curlines = vid.height; + Unlock_state(); if (!dedicated) { + Lock_state(); + con_started = true; con_startup = true; // need explicit screen refresh until we are in Doom loop consoletoggle = false; + + Unlock_state(); + CV_RegisterVar(&cons_msgtimeout); CV_RegisterVar(&cons_hudlines); CV_RegisterVar(&cons_speed); @@ -411,19 +447,27 @@ void CON_Init(void) } else { + Lock_state(); + con_started = true; con_startup = false; // need explicit screen refresh until we are in Doom loop consoletoggle = true; + + Unlock_state(); } } // Console input initialization // static void CON_InputInit(void) { + Lock_state(); + // prepare the first prompt line memset(inputlines, 0, sizeof (inputlines)); inputline = 0; input_cur = input_sel = input_len = 0; + + Unlock_state(); } //====================================================================== @@ -439,6 +483,8 @@ static void CON_RecalcSize(void) char *tmp_buffer; char *string; + Lock_state(); + switch (cv_constextsize.value) { case V_NOSCALEPATCH: @@ -476,11 +522,18 @@ static void CON_RecalcSize(void) // check for change of video width if (conw == con_width) + { + Unlock_state(); return; // didn't change + } + + Unlock_state(); tmp_buffer = Z_Malloc(CON_BUFFERSIZE, PU_STATIC, NULL); string = Z_Malloc(CON_BUFFERSIZE, PU_STATIC, NULL); // BP: it is a line but who know + Lock_state(); + oldcon_width = con_width; oldnumlines = con_totallines; oldcon_cy = con_cy; @@ -501,6 +554,8 @@ static void CON_RecalcSize(void) con_line = &con_buffer[con_cy*con_width]; con_scrollup = 0; + Unlock_state(); + // re-arrange console text buffer to keep text if (oldcon_width) // not the first time { @@ -525,7 +580,11 @@ static void CON_RecalcSize(void) static void CON_ChangeHeight(void) { - INT32 minheight = 20 * con_scalefactor; // 20 = 8+8+4 + INT32 minheight; + + Lock_state(); + + minheight = 20 * con_scalefactor; // 20 = 8+8+4 // toggle console in con_destlines = (cons_height.value*vid.height)/100; @@ -535,13 +594,19 @@ static void CON_ChangeHeight(void) con_destlines = vid.height; con_destlines &= ~0x3; // multiple of text row height + + Unlock_state(); } // Handles Console moves in/out of screen (per frame) // static void CON_MoveConsole(void) { - const fixed_t conspeed = FixedDiv(cons_speed.value*vid.fdupy, FRACUNIT); + fixed_t conspeed; + + Lock_state(); + + conspeed = FixedDiv(cons_speed.value*vid.fdupy, FRACUNIT); // instant if (!cons_speed.value) @@ -563,6 +628,8 @@ static void CON_MoveConsole(void) if (con_curlines < con_destlines) con_curlines = con_destlines; } + + Unlock_state(); } INT32 CON_ShiftChar(INT32 ch) @@ -587,27 +654,44 @@ void CON_ClearHUD(void) { INT32 i; + Lock_state(); + for (i = 0; i < con_hudlines; i++) con_hudtime[i] = 0; + + Unlock_state(); } // Force console to move out immediately // note: con_ticker will set consoleready false void CON_ToggleOff(void) { + Lock_state(); + if (!con_destlines) + { + Unlock_state(); return; + } con_destlines = 0; con_curlines = 0; CON_ClearHUD(); con_forcepic = 0; con_clipviewtop = -1; // remove console clipping of view + + Unlock_state(); } boolean CON_Ready(void) { - return consoleready; + boolean ready; + Lock_state(); + { + ready = consoleready; + } + Unlock_state(); + return ready; } // Console ticker: handles console move in/out, cursor blinking @@ -615,7 +699,11 @@ boolean CON_Ready(void) void CON_Ticker(void) { INT32 i; - INT32 minheight = 20 * con_scalefactor; // 20 = 8+8+4 + INT32 minheight; + + Lock_state(); + + minheight = 20 * con_scalefactor; // 20 = 8+8+4 // cursor blinking con_tick++; @@ -673,6 +761,8 @@ void CON_Ticker(void) if (con_hudtime[i] < 0) con_hudtime[i] = 0; } + + Unlock_state(); } // @@ -684,32 +774,51 @@ void CON_Ticker(void) static void CON_InputClear(void) { + Lock_state(); + memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS); input_cur = input_sel = input_len = 0; + + Unlock_state(); } static void CON_InputSetString(const char *c) { + Lock_state(); + memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS); strcpy(inputlines[inputline], c); input_cur = input_sel = input_len = strlen(c); + + Unlock_state(); } static void CON_InputAddString(const char *c) { size_t csize = strlen(c); + + Lock_state(); + if (input_len + csize > CON_MAXPROMPTCHARS-1) + { + Unlock_state(); return; + } if (input_cur != input_len) memmove(&inputlines[inputline][input_cur+csize], &inputlines[inputline][input_cur], input_len-input_cur); memcpy(&inputlines[inputline][input_cur], c, csize); input_len += csize; input_sel = (input_cur += csize); + + Unlock_state(); } static void CON_InputDelSelection(void) { size_t start, end, len; + + Lock_state(); + if (input_cur > input_sel) { start = input_sel; @@ -728,27 +837,39 @@ static void CON_InputDelSelection(void) input_len -= len; input_sel = input_cur = start; + + Unlock_state(); } static void CON_InputAddChar(char c) { if (input_len >= CON_MAXPROMPTCHARS-1) return; + + Lock_state(); + if (input_cur != input_len) memmove(&inputlines[inputline][input_cur+1], &inputlines[inputline][input_cur], input_len-input_cur); inputlines[inputline][input_cur++] = c; inputlines[inputline][++input_len] = 0; input_sel = input_cur; + + Unlock_state(); } static void CON_InputDelChar(void) { if (!input_cur) return; + + Lock_state(); + if (input_cur != input_len) memmove(&inputlines[inputline][input_cur-1], &inputlines[inputline][input_cur], input_len-input_cur); inputlines[inputline][--input_len] = 0; input_sel = --input_cur; + + Unlock_state(); } // @@ -1174,6 +1295,8 @@ static void CON_Print(char *msg) S_StartSound(NULL, sfx_radio); } + Lock_state(); + if (!(*msg & 0x80)) { con_line[con_cx++] = '\x80'; @@ -1234,7 +1357,10 @@ static void CON_Print(char *msg) } if (*msg == '\0') + { + Unlock_state(); return; + } // printable character for (l = 0; l < (con_width-11) && msg[l] > ' '; l++) @@ -1252,6 +1378,8 @@ static void CON_Print(char *msg) for (; l > 0; l--) con_line[con_cx++] = *(msg++); } + + Unlock_state(); } void CON_LogMessage(const char *msg) @@ -1283,6 +1411,7 @@ void CONS_Printf(const char *fmt, ...) { va_list argptr; static char *txt = NULL; + boolean startup; if (txt == NULL) txt = malloc(8192); @@ -1315,11 +1444,16 @@ void CONS_Printf(const char *fmt, ...) CON_LogMessage(txt); #endif + Lock_state(); + // make sure new text is visible con_scrollup = 0; + startup = con_startup; + + Unlock_state(); // if not in display loop, force screen update - if (con_startup) + if (startup) { #if (defined (_WINDOWS)) || (defined (__OS2__) && !defined (HAVE_SDL)) patch_t *con_backpic = W_CachePatchName("KARTKREW", PU_CACHE); @@ -1633,8 +1767,13 @@ static void CON_DrawConsole(void) // void CON_Drawer(void) { + Lock_state(); + if (!con_started || !graphics_started) + { + Unlock_state(); return; + } if (con_recalc) CON_RecalcSize(); @@ -1644,4 +1783,6 @@ void CON_Drawer(void) else if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_CUTSCENE || gamestate == GS_CREDITS || gamestate == GS_VOTING || gamestate == GS_EVALUATION || gamestate == GS_WAITINGPLAYERS) CON_DrawHudlines(); + + Unlock_state(); } diff --git a/src/console.h b/src/console.h index 11621746..ba0141a0 100644 --- a/src/console.h +++ b/src/console.h @@ -12,6 +12,7 @@ #include "d_event.h" #include "command.h" +#include "i_threads.h" #ifdef _WII void CON_InitWii(void); @@ -21,6 +22,10 @@ void CON_Init(void); boolean CON_Responder(event_t *ev); +#ifdef HAVE_THREADS +extern I_mutex con_mutex; +#endif + // set true when screen size has changed, to adapt console extern boolean con_recalc; diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 5f36a30a..6f5954c5 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -80,7 +80,7 @@ boolean server = true; // true or false but !server == client #define client (!server) boolean nodownload = false; -static boolean serverrunning = false; +boolean serverrunning = false; INT32 serverplayer = 0; char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support) @@ -1752,7 +1752,7 @@ static void CL_LoadReceivedSavegame(void) #endif #ifndef NONET -static void SendAskInfo(INT32 node, boolean viams) +static void SendAskInfo(INT32 node) { const tic_t asktime = I_GetTime(); netbuffer->packettype = PT_ASKINFO; @@ -1763,10 +1763,6 @@ static void SendAskInfo(INT32 node, boolean viams) // now allowed traffic from the host to us in, so once the MS relays // our address to the host, it'll be able to speak to us. HSendPacket(node, false, 0, sizeof (askinfo_pak)); - - // Also speak to the MS. - if (viams && node != 0 && node != BROADCASTADDR) - SendAskInfoViaMS(node, asktime); } serverelem_t serverlist[MAXSERVERLIST]; @@ -1832,13 +1828,96 @@ static void SL_InsertServer(serverinfo_pak* info, SINT8 node) M_SortServerList(); } +#ifdef HAVE_THREADS +struct Fetch_servers_ctx +{ + int room; + int id; +}; + +static void +Fetch_servers_thread (struct Fetch_servers_ctx *ctx) +{ + msg_server_t *server_list; + + server_list = GetShortServersList(ctx->room, ctx->id); + + if (server_list) + { + I_lock_mutex(&ms_QueryId_mutex); + { + if (ctx->id != ms_QueryId) + { + free(server_list); + server_list = NULL; + } + } + I_unlock_mutex(ms_QueryId_mutex); + + if (server_list) + { + I_lock_mutex(&m_menu_mutex); + { + if (m_waiting_mode == M_WAITING_SERVERS) + m_waiting_mode = M_NOT_WAITING; + } + I_unlock_mutex(m_menu_mutex); + + I_lock_mutex(&ms_ServerList_mutex); + { + ms_ServerList = server_list; + } + I_unlock_mutex(ms_ServerList_mutex); + } + } + + free(ctx); +} +#endif/*HAVE_THREADS*/ + +void CL_QueryServerList (msg_server_t *server_list) +{ + INT32 i; + + for (i = 0; server_list[i].header.buffer[0]; i++) + { + // Make sure MS version matches our own, to + // thwart nefarious servers who lie to the MS. + + /* lol bruh, that version COMES from the servers */ + //if (strcmp(version, server_list[i].version) == 0) + { + INT32 node = I_NetMakeNodewPort(server_list[i].ip, server_list[i].port); + if (node == -1) + break; // no more node free + SendAskInfo(node); + // Force close the connection so that servers can't eat + // up nodes forever if we never get a reply back from them + // (usually when they've not forwarded their ports). + // + // Don't worry, we'll get in contact with the working + // servers again when they send SERVERINFO to us later! + // + // (Note: as a side effect this probably means every + // server in the list will probably be using the same node (e.g. node 1), + // not that it matters which nodes they use when + // the connections are closed afterwards anyway) + // -- Monster Iestyn 12/11/18 + Net_CloseConnection(node|FORCECLOSE); + } + } +} + void CL_UpdateServerList(boolean internetsearch, INT32 room) { +#ifdef HAVE_THREADS + struct Fetch_servers_ctx *ctx; +#endif + SL_ClearServerList(0); if (!netgame && I_NetOpenSocket) { - MSCloseUDPSocket(); // Tidy up before wiping the slate. if (I_NetOpenSocket()) { netgame = true; @@ -1848,56 +1927,36 @@ void CL_UpdateServerList(boolean internetsearch, INT32 room) // search for local servers if (netgame) - SendAskInfo(BROADCASTADDR, false); + SendAskInfo(BROADCASTADDR); if (internetsearch) { - const msg_server_t *server_list; - INT32 i = -1; - server_list = GetShortServersList(room); +#ifdef HAVE_THREADS + ctx = malloc(sizeof *ctx); + + /* This called from M_Refresh so I don't use a mutex */ + m_waiting_mode = M_WAITING_SERVERS; + + I_lock_mutex(&ms_QueryId_mutex); + { + ctx->id = ms_QueryId; + } + I_unlock_mutex(ms_QueryId_mutex); + + ctx->room = room; + + I_spawn_thread("fetch-servers", (I_thread_fn)Fetch_servers_thread, ctx); +#else + msg_server_t *server_list; + + server_list = GetShortServersList(room, 0); + if (server_list) { - char version[8] = ""; -#if VERSION > 0 || SUBVERSION > 0 - snprintf(version, sizeof (version), "%d.%d", VERSION, SUBVERSION); -#else - strcpy(version, GetRevisionString()); + CL_QueryServerList(server_list); + free(server_list); + } #endif - version[sizeof (version) - 1] = '\0'; - - for (i = 0; server_list[i].header.buffer[0]; i++) - { - // Make sure MS version matches our own, to - // thwart nefarious servers who lie to the MS. - - if (strcmp(version, server_list[i].version) == 0) - { - INT32 node = I_NetMakeNodewPort(server_list[i].ip, server_list[i].port); - if (node == -1) - break; // no more node free - SendAskInfo(node, true); - // Force close the connection so that servers can't eat - // up nodes forever if we never get a reply back from them - // (usually when they've not forwarded their ports). - // - // Don't worry, we'll get in contact with the working - // servers again when they send SERVERINFO to us later! - // - // (Note: as a side effect this probably means every - // server in the list will probably be using the same node (e.g. node 1), - // not that it matters which nodes they use when - // the connections are closed afterwards anyway) - // -- Monster Iestyn 12/11/18 - Net_CloseConnection(node|FORCECLOSE); - } - } - } - - //no server list?(-1) or no servers?(0) - if (!i) - { - ; /// TODO: display error or warning? - } } } @@ -1978,14 +2037,13 @@ static boolean CL_FinishedFileList(void) /** Called by CL_ServerConnectionTicker * - * \param viams ??? * \param asksent The last time we asked the server to join. We re-ask every second in case our request got lost in transmit. * \return False if the connection was aborted * \sa CL_ServerConnectionTicker * \sa CL_ConnectToServer * */ -static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent) +static boolean CL_ServerConnectionSearchTicker(tic_t *asksent) { #ifndef NONET INT32 i; @@ -2049,7 +2107,7 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent) // Ask the info to the server (askinfo packet) if (*asksent + NEWTICRATE < I_GetTime()) { - SendAskInfo(servernode, viams); + SendAskInfo(servernode); *asksent = I_GetTime(); } #else @@ -2064,7 +2122,6 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent) /** Called by CL_ConnectToServer * - * \param viams ??? * \param tmpsave The name of the gamestate file??? * \param oldtic Used for knowing when to poll events and redraw * \param asksent The last time we asked the server to join. We re-ask every second in case our request got lost in transmit. @@ -2073,7 +2130,7 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent) * \sa CL_ConnectToServer * */ -static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic_t *oldtic, tic_t *asksent) +static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic_t *asksent) { boolean waitmore; INT32 i; @@ -2085,7 +2142,7 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic switch (cl_mode) { case CL_SEARCHING: - if (!CL_ServerConnectionSearchTicker(viams, asksent)) + if (!CL_ServerConnectionSearchTicker(asksent)) return false; break; @@ -2250,11 +2307,10 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic /** Use adaptive send using net_bandwidth and stat.sendbytes * - * \param viams ??? * \todo Better description... * */ -static void CL_ConnectToServer(boolean viams) +static void CL_ConnectToServer(void) { INT32 pnumnodes, nodewaited = doomcom->numnodes, i; tic_t oldtic; @@ -2326,9 +2382,9 @@ static void CL_ConnectToServer(boolean viams) { // If the connection was aborted for some reason, leave #ifndef NONET - if (!CL_ServerConnectionTicker(viams, tmpsave, &oldtic, &asksent)) + if (!CL_ServerConnectionTicker(tmpsave, &oldtic, &asksent)) #else - if (!CL_ServerConnectionTicker(viams, (char*)NULL, &oldtic, (tic_t *)NULL)) + if (!CL_ServerConnectionTicker((char*)NULL, &oldtic, (tic_t *)NULL)) #endif return; @@ -2509,9 +2565,6 @@ static void Command_ReloadBan(void) //recheck ban.txt static void Command_connect(void) { - // Assume we connect directly. - boolean viams = false; - if (COM_Argc() < 2 || *COM_Argv(1) == 0) { CONS_Printf(M_GetText( @@ -2545,9 +2598,6 @@ static void Command_connect(void) if (netgame && !stricmp(COM_Argv(1), "node")) { servernode = (SINT8)atoi(COM_Argv(2)); - - // Use MS to traverse NAT firewalls. - viams = true; } else if (netgame) { @@ -2556,7 +2606,6 @@ static void Command_connect(void) } else if (I_NetOpenSocket) { - MSCloseUDPSocket(); // Tidy up before wiping the slate. I_NetOpenSocket(); netgame = true; multiplayer = true; @@ -2585,7 +2634,7 @@ static void Command_connect(void) } botingame = false; botskin = 0; - CL_ConnectToServer(viams); + CL_ConnectToServer(); } #endif @@ -3638,7 +3687,6 @@ boolean SV_SpawnServer(void) SV_GenContext(); if (netgame && I_NetOpenSocket) { - MSCloseUDPSocket(); // Tidy up before wiping the slate. I_NetOpenSocket(); if (ms_RoomId > 0) RegisterServer(); @@ -3646,7 +3694,7 @@ boolean SV_SpawnServer(void) // non dedicated server just connect to itself if (!dedicated) - CL_ConnectToServer(false); + CL_ConnectToServer(); else doomcom->numslots = 1; } @@ -5472,7 +5520,13 @@ FILESTAMP if (nowtime > resptime) { resptime = nowtime; +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif M_Ticker(); +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif CON_Ticker(); } SV_FileSendTicker(); diff --git a/src/d_clisrv.h b/src/d_clisrv.h index e9b180df..a872b02e 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -18,6 +18,7 @@ #include "d_netcmd.h" #include "tables.h" #include "d_player.h" +#include "mserv.h" /* The 'packet version' is used to distinguish packet formats. @@ -531,6 +532,7 @@ typedef enum } kickreason_t; extern boolean server; +extern boolean serverrunning; #define client (!server) extern boolean dedicated; // For dedicated server extern UINT16 software_MAXPACKETLENGTH; @@ -577,6 +579,7 @@ void CL_RemoveSplitscreenPlayer(UINT8 p); void CL_Reset(void); void CL_ClearPlayer(INT32 playernum); void CL_RemovePlayer(INT32 playernum, INT32 reason); +void CL_QueryServerList(msg_server_t *list); void CL_UpdateServerList(boolean internetsearch, INT32 room); // Is there a game running boolean Playing(void); diff --git a/src/d_main.c b/src/d_main.c index 3eb82e23..6029c648 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -50,6 +50,7 @@ int snprintf(char *str, size_t n, const char *fmt, ...); #include "hu_stuff.h" #include "i_sound.h" #include "i_system.h" +#include "i_threads.h" #include "i_video.h" #include "m_argv.h" #include "m_menu.h" @@ -200,6 +201,8 @@ void D_ProcessEvents(void) { event_t *ev; + boolean eaten; + for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1)) { ev = &events[eventtail]; @@ -221,7 +224,17 @@ void D_ProcessEvents(void) } // Menu input - if (M_Responder(ev)) +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif + { + eaten = M_Responder(ev); + } +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif + + if (eaten) continue; // menu ate the event // Demo input: @@ -230,7 +243,17 @@ void D_ProcessEvents(void) continue; // demo ate the event // console input - if (CON_Responder(ev)) +#ifdef HAVE_THREADS + I_lock_mutex(&con_mutex); +#endif + { + eaten = CON_Responder(ev); + } +#ifdef HAVE_THREADS + I_unlock_mutex(con_mutex); +#endif + + if (eaten) continue; // ate the event G_Responder(ev); @@ -526,7 +549,13 @@ static void D_Display(void) if (gamestate != GS_TIMEATTACK) CON_Drawer(); +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif M_Drawer(); // menu is drawn even on top of everything +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif // focus lost moved to M_Drawer // diff --git a/src/d_net.h b/src/d_net.h index 30de2991..cc80fcaa 100644 --- a/src/d_net.h +++ b/src/d_net.h @@ -19,7 +19,9 @@ #define __D_NET__ // Max computers in a game -#define MAXNETNODES 64 +// 127 is probably as high as this can go, because +// SINT8 is used for nodes sometimes >:( +#define MAXNETNODES 127 #define BROADCASTADDR MAXNETNODES #define NETSPLITSCREEN // Kart's splitscreen netgame feature diff --git a/src/doomdef.h b/src/doomdef.h index 392ba760..9ee55d94 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -155,8 +155,8 @@ extern char logfilename[1024]; #else #define VERSION 1 // Game version #define SUBVERSION 2 // more precise version number -#define VERSIONSTRING "v1.2" -#define VERSIONSTRINGW L"v1.2" +#define VERSIONSTRING "v1.2 (HTTP MS)" +#define VERSIONSTRINGW L"v1.2 (HTTP MS)" // Hey! If you change this, add 1 to the MODVERSION below! Otherwise we can't force updates! // And change CMakeLists.txt, for CMake users! // AND appveyor.yml, for the build bots! diff --git a/src/f_finale.c b/src/f_finale.c index 909ca261..ade26bbe 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -24,6 +24,7 @@ #include "w_wad.h" #include "z_zone.h" #include "i_system.h" +#include "i_threads.h" #include "m_menu.h" #include "dehacked.h" #include "g_input.h" @@ -317,7 +318,13 @@ void F_IntroDrawer(void) { I_OsPolling(); I_UpdateNoBlit(); +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif M_Drawer(); // menu is drawn even on top of wipes +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001 } } diff --git a/src/f_wipe.c b/src/f_wipe.c index bb1397bc..8bcb1278 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.c @@ -22,6 +22,7 @@ #include "z_zone.h" #include "i_system.h" +#include "i_threads.h" #include "m_menu.h" #include "console.h" #include "d_main.h" @@ -379,7 +380,15 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu) I_UpdateNoBlit(); if (drawMenu) + { +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif M_Drawer(); // menu is drawn even on top of wipes +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif + } I_FinishUpdate(); // page flip or blit buffer diff --git a/src/http-mserv.c b/src/http-mserv.c new file mode 100644 index 00000000..9d05b80e --- /dev/null +++ b/src/http-mserv.c @@ -0,0 +1,675 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2020 by James R. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +// \brief HTTP based master server + +/* +Documentation available here. + + +*/ + +#include + +#include "doomdef.h" +#include "d_clisrv.h" +#include "command.h" +#include "m_argv.h" +#include "m_menu.h" +#include "mserv.h" +#include "i_tcp.h"/* for current_port */ +#include "i_threads.h" + +/* reasonable default I guess?? */ +#define DEFAULT_BUFFER_SIZE (4096) + +/* I just stop myself from making macros anymore. */ +#define Blame( ... ) \ + CONS_Printf("\x85" __VA_ARGS__) + +static void MasterServer_Debug_OnChange (void); + +consvar_t cv_masterserver_timeout = { + "masterserver_timeout", "5", CV_SAVE, CV_Unsigned, + NULL, 0, NULL, NULL, 0, 0, NULL/* C90 moment */ +}; + +consvar_t cv_masterserver_debug = { + "masterserver_debug", "Off", CV_SAVE|CV_CALL, CV_OnOff, + MasterServer_Debug_OnChange, 0, NULL, NULL, 0, 0, NULL/* C90 moment */ +}; + +consvar_t cv_masterserver_token = { + "masterserver_token", "", CV_SAVE, NULL, + NULL, 0, NULL, NULL, 0, 0, NULL/* C90 moment */ +}; + +static int hms_started; + +static char *hms_api; +#ifdef HAVE_THREADS +static I_mutex hms_api_mutex; +#endif + +static char *hms_server_token; + +struct HMS_buffer +{ + CURL *curl; + char *buffer; + int needle; + int end; +}; + +static void +Contact_error (void) +{ + CONS_Alert(CONS_ERROR, + "There was a problem contacting the master server...\n" + ); +} + +static size_t +HMS_on_read (char *s, size_t _1, size_t n, void *userdata) +{ + struct HMS_buffer *buffer; + size_t blocks; + + (void)_1; + + buffer = userdata; + + if (n >= (size_t)( buffer->end - buffer->needle )) + { + /* resize to next multiple of buffer size */ + blocks = ( n / DEFAULT_BUFFER_SIZE + 1 ); + buffer->end += ( blocks * DEFAULT_BUFFER_SIZE ); + + buffer->buffer = realloc(buffer->buffer, buffer->end); + } + + memcpy(&buffer->buffer[buffer->needle], s, n); + buffer->needle += n; + + return n; +} + +static struct HMS_buffer * +HMS_connect (const char *format, ...) +{ + va_list ap; + CURL *curl; + char *url; + char *quack_token; + size_t seek; + size_t token_length; + struct HMS_buffer *buffer; + + if (! hms_started) + { + if (curl_global_init(CURL_GLOBAL_ALL) != 0) + { + Contact_error(); + Blame("From curl_global_init.\n"); + return NULL; + } + else + { + atexit(curl_global_cleanup); + hms_started = 1; + } + } + + curl = curl_easy_init(); + + if (! curl) + { + Contact_error(); + Blame("From curl_easy_init.\n"); + return NULL; + } + + if (cv_masterserver_token.string[0]) + { + quack_token = curl_easy_escape(curl, cv_masterserver_token.string, 0); + token_length = ( sizeof "?token="-1 )+ strlen(quack_token); + } + else + { + quack_token = NULL; + token_length = 0; + } + +#ifdef HAVE_THREADS + I_lock_mutex(&hms_api_mutex); +#endif + + seek = strlen(hms_api) + 1;/* + '/' */ + + va_start (ap, format); + url = malloc(seek + vsnprintf(0, 0, format, ap) + token_length + 1); + va_end (ap); + + sprintf(url, "%s/", hms_api); + +#ifdef HAVE_THREADS + I_unlock_mutex(hms_api_mutex); +#endif + + va_start (ap, format); + seek += vsprintf(&url[seek], format, ap); + va_end (ap); + + if (quack_token) + sprintf(&url[seek], "?token=%s", quack_token); + + CONS_Printf("HMS: connecting '%s'...\n", url); + + buffer = malloc(sizeof *buffer); + buffer->curl = curl; + buffer->end = DEFAULT_BUFFER_SIZE; + buffer->buffer = malloc(buffer->end); + buffer->needle = 0; + + if (cv_masterserver_debug.value) + { + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_STDERR, logstream); + } + + if (M_CheckParm("-bindaddr") && M_IsNextParm()) + { + curl_easy_setopt(curl, CURLOPT_INTERFACE, M_GetNextParm()); + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + + curl_easy_setopt(curl, CURLOPT_TIMEOUT, cv_masterserver_timeout.value); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HMS_on_read); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, buffer); + + curl_free(quack_token); + free(url); + + return buffer; +} + +static int +HMS_do (struct HMS_buffer *buffer) +{ + CURLcode cc; + long status; + + char *p; + + cc = curl_easy_perform(buffer->curl); + + if (cc != CURLE_OK) + { + Contact_error(); + Blame( + "From curl_easy_perform: %s\n", + curl_easy_strerror(cc) + ); + return 0; + } + + buffer->buffer[buffer->needle] = '\0'; + + curl_easy_getinfo(buffer->curl, CURLINFO_RESPONSE_CODE, &status); + + if (status != 200) + { + p = strchr(buffer->buffer, '\n'); + + if (p) + *p = '\0'; + + Contact_error(); + Blame( + "Master server error %ld: %s%s\n", + status, + buffer->buffer, + ( (p) ? "" : " (malformed)" ) + ); + + return 0; + } + else + return 1; +} + +static void +HMS_end (struct HMS_buffer *buffer) +{ + curl_easy_cleanup(buffer->curl); + free(buffer->buffer); + free(buffer); +} + +int +HMS_fetch_rooms (int joining, int query_id) +{ + struct HMS_buffer *hms; + int ok; + + int doing_shit; + + char *id; + char *title; + char *room_motd; + + int id_no; + + char *p; + char *end; + + int i; + + (void)query_id; + + hms = HMS_connect("rooms"); + + if (! hms) + return 0; + + if (HMS_do(hms)) + { + doing_shit = 1; + + p = hms->buffer; + + for (i = 0; i < NUM_LIST_ROOMS && ( end = strstr(p, "\n\n\n") );) + { + *end = '\0'; + + id = strtok(p, "\n"); + title = strtok(0, "\n"); + room_motd = strtok(0, ""); + + if (id && title && room_motd) + { + id_no = atoi(id); + + /* + Don't show the 'All' room if hosting. And it's a hack like this + because I'm way too lazy to add another feature to the MS. + */ + if (joining || id_no != 0) + { +#ifdef HAVE_THREADS + I_lock_mutex(&ms_QueryId_mutex); + { + if (query_id != ms_QueryId) + doing_shit = 0; + } + I_unlock_mutex(ms_QueryId_mutex); + + if (! doing_shit) + break; +#endif + + room_list[i].header.buffer[0] = 1; + + room_list[i].id = id_no; + strlcpy(room_list[i].name, title, sizeof room_list[i].name); + strlcpy(room_list[i].motd, room_motd, sizeof room_list[i].motd); + + i++; + } + + p = ( end + 3 );/* skip the three linefeeds */ + } + else + break; + } + + if (doing_shit) + room_list[i].header.buffer[0] = 0; + + ok = 1; + + if (doing_shit) + { +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif + { + for (i = 0; room_list[i].header.buffer[0]; i++) + { + if(*room_list[i].name != '\0') + { + MP_RoomMenu[i+1].text = room_list[i].name; + roomIds[i] = room_list[i].id; + MP_RoomMenu[i+1].status = IT_STRING|IT_CALL; + } + } + } +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif + } + } + else + ok = 0; + + HMS_end(hms); + + return ok; +} + +int +HMS_register (void) +{ + struct HMS_buffer *hms; + int ok; + + char post[256]; + + char *title; + + hms = HMS_connect("rooms/%d/register", ms_RoomId); + + if (! hms) + return 0; + + title = curl_easy_escape(hms->curl, cv_servername.string, 0); + + snprintf(post, sizeof post, + "port=%d&" + "title=%s&" + "version=%d.%d", + + current_port, + + title, + + VERSION, + SUBVERSION + ); + + curl_free(title); + + curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post); + + ok = HMS_do(hms); + + if (ok) + { + hms_server_token = strdup(strtok(hms->buffer, "\n")); + } + + HMS_end(hms); + + return ok; +} + +int +HMS_unlist (void) +{ + struct HMS_buffer *hms; + int ok; + + hms = HMS_connect("servers/%s/unlist", hms_server_token); + + if (! hms) + return 0; + + curl_easy_setopt(hms->curl, CURLOPT_CUSTOMREQUEST, "POST"); + + ok = HMS_do(hms); + HMS_end(hms); + + free(hms_server_token); + + return ok; +} + +int +HMS_update (void) +{ + struct HMS_buffer *hms; + int ok; + + char post[256]; + + char *title; + + hms = HMS_connect("servers/%s/update", hms_server_token); + + if (! hms) + return 0; + + title = curl_easy_escape(hms->curl, cv_servername.string, 0); + + snprintf(post, sizeof post, + "title=%s", + title + ); + + curl_free(title); + + curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post); + + ok = HMS_do(hms); + HMS_end(hms); + + return ok; +} + +void +HMS_list_servers (void) +{ + struct HMS_buffer *hms; + + char *p; + + hms = HMS_connect("servers"); + + if (! hms) + return; + + if (HMS_do(hms)) + { + p = &hms->buffer[strlen(hms->buffer)]; + while (*--p == '\n') + ; + + CONS_Printf("%s\n", hms->buffer); + } + + HMS_end(hms); +} + +msg_server_t * +HMS_fetch_servers (msg_server_t *list, int room_number, int query_id) +{ + struct HMS_buffer *hms; + + int doing_shit; + + char local_version[9]; + + char *room; + + char *address; + char *port; + char *title; + char *version; + + char *end; + char *section_end; + char *p; + + int i; + + (void)query_id; + + if (room_number > 0) + { + hms = HMS_connect("rooms/%d/servers", room_number); + } + else + hms = HMS_connect("servers"); + + if (! hms) + return NULL; + + if (HMS_do(hms)) + { + doing_shit = 1; + + snprintf(local_version, sizeof local_version, + "%d.%d", + VERSION, + SUBVERSION + ); + + p = hms->buffer; + i = 0; + + do + { + section_end = strstr(p, "\n\n"); + + room = strtok(p, "\n"); + + p = strtok(0, ""); + + if (! p) + break; + + while (i < MAXSERVERLIST && ( end = strchr(p, '\n') )) + { + *end = '\0'; + + address = strtok(p, " "); + port = strtok(0, " "); + title = strtok(0, " "); + version = strtok(0, ""); + + if (address && port && title && version) + { +#ifdef HAVE_THREADS + I_lock_mutex(&ms_QueryId_mutex); + { + if (query_id != ms_QueryId) + doing_shit = 0; + } + I_unlock_mutex(ms_QueryId_mutex); + + if (! doing_shit) + break; +#endif + + if (strcmp(version, local_version) == 0) + { + strlcpy(list[i].ip, address, sizeof list[i].ip); + strlcpy(list[i].port, port, sizeof list[i].port); + strlcpy(list[i].name, title, sizeof list[i].name); + strlcpy(list[i].version, version, sizeof list[i].version); + + list[i].room = atoi(room); + + list[i].header.buffer[0] = 1; + + i++; + } + + if (end == section_end)/* end of list for this room */ + break; + else + p = ( end + 1 );/* skip server delimiter */ + } + else + { + section_end = 0;/* malformed so quit the parsing */ + break; + } + } + + if (! doing_shit) + break; + + p = ( section_end + 2 ); + } + while (section_end) ; + + if (doing_shit) + list[i].header.buffer[0] = 0; + } + else + list = NULL; + + HMS_end(hms); + + return list; +} + +int +HMS_compare_mod_version (char *buffer, size_t buffer_size) +{ + struct HMS_buffer *hms; + int ok; + + char *version; + char *version_name; + + hms = HMS_connect("versions/%d", MODID); + + if (! hms) + return 0; + + ok = 0; + + if (HMS_do(hms)) + { + version = strtok(hms->buffer, " "); + version_name = strtok(0, "\n"); + + if (version && version_name) + { + if (atoi(version) != MODVERSION) + { + strlcpy(buffer, version_name, buffer_size); + ok = 1; + } + else + ok = -1; + } + } + + HMS_end(hms); + + return ok; +} + +void +HMS_set_api (char *api) +{ +#ifdef HAVE_THREADS + I_lock_mutex(&hms_api_mutex); +#endif + { + free(hms_api); + hms_api = api; + } +#ifdef HAVE_THREADS + I_unlock_mutex(hms_api_mutex); +#endif +} + +static void +MasterServer_Debug_OnChange (void) +{ + /* TODO: change to 'latest-log.txt' for log files revision. */ + if (cv_masterserver_debug.value) + CONS_Printf("Master server debug messages will appear in log.txt\n"); +} diff --git a/src/i_threads.h b/src/i_threads.h new file mode 100644 index 00000000..45a3dcc3 --- /dev/null +++ b/src/i_threads.h @@ -0,0 +1,39 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2020 by James R. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file i_threads.h +/// \brief Multithreading abstraction + +#ifdef HAVE_THREADS + +#ifndef I_THREADS_H +#define I_THREADS_H + +typedef void (*I_thread_fn)(void *userdata); + +typedef void * I_mutex; +typedef void * I_cond; + +void I_start_threads (void); +void I_stop_threads (void); + +void I_spawn_thread (const char *name, I_thread_fn, void *userdata); + +/* check in your thread whether to return early */ +int I_thread_is_stopped (void); + +void I_lock_mutex (I_mutex *); +void I_unlock_mutex (I_mutex); + +void I_hold_cond (I_cond *, I_mutex); + +void I_wake_one_cond (I_cond *); +void I_wake_all_cond (I_cond *); + +#endif/*I_THREADS_H*/ +#endif/*HAVE_THREADS*/ diff --git a/src/m_menu.c b/src/m_menu.c index eb19ecef..850b8d74 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -32,6 +32,7 @@ #include "sounds.h" #include "s_sound.h" #include "i_system.h" +#include "i_threads.h" // Addfile #include "filesrch.h" @@ -118,6 +119,12 @@ typedef enum NUM_QUITMESSAGES } text_enum; +#ifdef HAVE_THREADS +I_mutex m_menu_mutex; +#endif + +M_waiting_mode_t m_waiting_mode = M_NOT_WAITING; + const char *quitmsg[NUM_QUITMESSAGES]; // Stuff for customizing the player select screen Tails 09-22-2003 @@ -1039,7 +1046,7 @@ enum FIRSTSERVERLINE }; -static menuitem_t MP_RoomMenu[] = +menuitem_t MP_RoomMenu[] = { {IT_STRING | IT_CALL, NULL, "", M_ChooseRoom, 9}, {IT_DISABLED, NULL, "", M_ChooseRoom, 18}, @@ -2896,7 +2903,6 @@ boolean M_Responder(event_t *ev) //make sure the game doesn't still think we're in a netgame. if (!Playing() && netgame && multiplayer) { - MSCloseUDPSocket(); // Clean up so we can re-open the connection later. netgame = false; multiplayer = false; } @@ -3304,6 +3310,30 @@ void M_SetupNextMenu(menu_t *menudef) { INT16 i; +#ifdef HAVE_THREADS + if (currentMenu == &MP_RoomDef || currentMenu == &MP_ConnectDef) + { + I_lock_mutex(&ms_QueryId_mutex); + { + ms_QueryId++; + } + I_unlock_mutex(ms_QueryId_mutex); + } + + if (currentMenu == &MP_ConnectDef) + { + I_lock_mutex(&ms_ServerList_mutex); + { + if (ms_ServerList) + { + free(ms_ServerList); + ms_ServerList = NULL; + } + } + I_unlock_mutex(ms_ServerList_mutex); + } +#endif/*HAVE_THREADS*/ + if (currentMenu->quitroutine) { // If you're going from a menu to itself, why are you running the quitroutine? You're not quitting it! -SH @@ -3361,6 +3391,19 @@ void M_Ticker(void) if (--vidm_testingmode == 0) setmodeneeded = vidm_previousmode + 1; } + +#ifdef HAVE_THREADS + I_lock_mutex(&ms_ServerList_mutex); + { + if (ms_ServerList) + { + CL_QueryServerList(ms_ServerList); + free(ms_ServerList); + ms_ServerList = NULL; + } + } + I_unlock_mutex(ms_ServerList_mutex); +#endif } // @@ -8327,22 +8370,65 @@ static INT32 menuRoomIndex = 0; static void M_DrawRoomMenu(void) { + static int frame = -12; + int dot_frame; + char text[4]; + const char *rmotd; + const char *waiting_message; + + int dots; + + if (m_waiting_mode) + { + dot_frame = frame / 4; + dots = dot_frame + 3; + + strcpy(text, " "); + + if (dots > 0) + { + if (dot_frame < 0) + dot_frame = 0; + + strncpy(&text[dot_frame], "...", min(dots, 3 - dot_frame)); + } + + if (++frame == 12) + frame = -12; + + currentMenu->menuitems[0].text = text; + } // use generic drawer for cursor, items and title M_DrawGenericMenu(); V_DrawString(currentMenu->x - 16, currentMenu->y, highlightflags, M_GetText("Select a room")); - M_DrawTextBox(144, 24, 20, 20); + if (m_waiting_mode == M_NOT_WAITING) + { + M_DrawTextBox(144, 24, 20, 20); - if (itemOn == 0) - rmotd = M_GetText("Don't connect to the Master Server."); - else - rmotd = room_list[itemOn-1].motd; + if (itemOn == 0) + rmotd = M_GetText("Don't connect to the Master Server."); + else + rmotd = room_list[itemOn-1].motd; - rmotd = V_WordWrap(0, 20*8, 0, rmotd); - V_DrawString(144+8, 32, V_ALLOWLOWERCASE|V_RETURN8, rmotd); + rmotd = V_WordWrap(0, 20*8, 0, rmotd); + V_DrawString(144+8, 32, V_ALLOWLOWERCASE|V_RETURN8, rmotd); + } + + if (m_waiting_mode) + { + // Display a little "please wait" message. + M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3); + if (m_waiting_mode == M_WAITING_VERSION) + waiting_message = "Checking for updates..."; + else + waiting_message = "Fetching room info..."; + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, waiting_message); + V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait."); + } } static void M_DrawConnectMenu(void) @@ -8416,6 +8502,14 @@ static void M_DrawConnectMenu(void) localservercount = serverlistcount; M_DrawGenericMenu(); + + if (m_waiting_mode) + { + // Display a little "please wait" message. + M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3); + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Searching for servers..."); + V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait."); + } } static boolean M_CancelConnect(void) @@ -8497,10 +8591,10 @@ void M_SortServerList(void) #ifndef NONET #ifdef UPDATE_ALERT -static boolean M_CheckMODVersion(void) +static boolean M_CheckMODVersion(int id) { char updatestring[500]; - const char *updatecheck = GetMODVersion(); + const char *updatecheck = GetMODVersion(id); if(updatecheck) { sprintf(updatestring, UPDATE_ALERT_STRING, VERSIONSTRING, updatecheck); @@ -8509,7 +8603,62 @@ static boolean M_CheckMODVersion(void) } else return true; } -#endif + +#ifdef HAVE_THREADS +static void +Check_new_version_thread (int *id) +{ + int hosting; + int okay; + + okay = 0; + + if (M_CheckMODVersion(*id)) + { + I_lock_mutex(&ms_QueryId_mutex); + { + okay = ( *id == ms_QueryId ); + } + I_unlock_mutex(ms_QueryId_mutex); + + if (okay) + { + I_lock_mutex(&m_menu_mutex); + { + m_waiting_mode = M_WAITING_ROOMS; + hosting = ( currentMenu->prevMenu == &MP_ServerDef ); + } + I_unlock_mutex(m_menu_mutex); + + GetRoomsList(hosting, *id); + } + } + else + { + I_lock_mutex(&ms_QueryId_mutex); + { + okay = ( *id == ms_QueryId ); + } + I_unlock_mutex(ms_QueryId_mutex); + } + + if (okay) + { + I_lock_mutex(&m_menu_mutex); + { + if (m_waiting_mode) + { + m_waiting_mode = M_NOT_WAITING; + MP_RoomMenu[0].text = ""; + } + } + I_unlock_mutex(m_menu_mutex); + } + + free(id); +} +#endif/*HAVE_THREADS*/ +#endif/*UPDATE_ALERT*/ static void M_ConnectMenu(INT32 choice) { @@ -8545,11 +8694,14 @@ static void M_ConnectMenuModChecks(INT32 choice) M_ConnectMenu(-1); } -static UINT32 roomIds[NUM_LIST_ROOMS]; +UINT32 roomIds[NUM_LIST_ROOMS]; static void M_RoomMenu(INT32 choice) { INT32 i; +#ifdef HAVE_THREADS + int *id; +#endif (void)choice; @@ -8562,34 +8714,47 @@ static void M_RoomMenu(INT32 choice) if (rendermode == render_soft) I_FinishUpdate(); // page flip or blit buffer - if (GetRoomsList(currentMenu == &MP_ServerDef) < 0) - return; - -#ifdef UPDATE_ALERT - if (!M_CheckMODVersion()) - return; -#endif - for (i = 1; i < NUM_LIST_ROOMS+1; ++i) MP_RoomMenu[i].status = IT_DISABLED; memset(roomIds, 0, sizeof(roomIds)); - for (i = 0; room_list[i].header.buffer[0]; i++) - { - if(*room_list[i].name != '\0') - { - MP_RoomMenu[i+1].text = room_list[i].name; - roomIds[i] = room_list[i].id; - MP_RoomMenu[i+1].status = IT_STRING|IT_CALL; - } - } - MP_RoomDef.prevMenu = currentMenu; M_SetupNextMenu(&MP_RoomDef); + +#ifdef UPDATE_ALERT +#ifdef HAVE_THREADS + m_waiting_mode = M_WAITING_VERSION; + MP_RoomMenu[0].text = ""; + + id = malloc(sizeof *id); + + I_lock_mutex(&ms_QueryId_mutex); + { + *id = ms_QueryId; + } + I_unlock_mutex(ms_QueryId_mutex); + + I_spawn_thread("check-new-version", + (I_thread_fn)Check_new_version_thread, id); +#else/*HAVE_THREADS*/ + if (M_CheckMODVersion(0)) + { + GetRoomsList(currentMenu->prevMenu == &MP_ServerDef, 0); + } +#endif/*HAVE_THREADS*/ +#endif/*UPDATE_ALERT*/ } static void M_ChooseRoom(INT32 choice) { +#ifdef HAVE_THREADS + I_lock_mutex(&ms_QueryId_mutex); + { + ms_QueryId++; + } + I_unlock_mutex(ms_QueryId_mutex); +#endif + if (choice == 0) ms_RoomId = -1; else diff --git a/src/m_menu.h b/src/m_menu.h index 86a84089..1ad20c77 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -17,6 +17,8 @@ #include "d_event.h" #include "command.h" +#include "i_threads.h" +#include "mserv.h" #include "r_things.h" // for SKINNAMESIZE // @@ -72,6 +74,18 @@ typedef enum } menumessagetype_t; void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtype); +typedef enum +{ + M_NOT_WAITING, + + M_WAITING_VERSION, + M_WAITING_ROOMS, + M_WAITING_SERVERS, +} +M_waiting_mode_t; + +extern M_waiting_mode_t m_waiting_mode; + // Called by linux_x/i_video_xshm.c void M_QuitResponse(INT32 ch); @@ -161,6 +175,9 @@ typedef struct menuitem_s extern menuitem_t PlayerMenu[MAXSKINS]; +extern menuitem_t MP_RoomMenu[]; +extern UINT32 roomIds[NUM_LIST_ROOMS]; + typedef struct menu_s { const char *menutitlepic; @@ -176,6 +193,10 @@ typedef struct menu_s void M_SetupNextMenu(menu_t *menudef); void M_ClearMenus(boolean callexitmenufunc); +#ifdef HAVE_THREADS +extern I_mutex m_menu_mutex; +#endif + extern menu_t *currentMenu; extern menu_t MainDef; diff --git a/src/mserv.c b/src/mserv.c index b8194aff..7311227a 100644 --- a/src/mserv.c +++ b/src/mserv.c @@ -2,246 +2,81 @@ //----------------------------------------------------------------------------- // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2018 by Sonic Team Junior. +// Copyright (C) 2020 by James R. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- /// \file mserv.c -/// \brief Commands used for communicate with the master server - -#ifdef __GNUC__ -#include -#include -#include -#endif +/// \brief Commands used to communicate with the master server #if !defined (UNDER_CE) #include #endif -#if (defined (NOMD5) || defined (NOMSERV)) && !defined (NONET) -#define NONET -#endif - -#ifndef NONET - -#ifndef NO_IPV6 -#define HAVE_IPV6 -#endif - -#if (defined (_WIN32) || defined (_WIN32_WCE)) && !defined (_XBOX) -#define RPC_NO_WINDOWS_H -#ifdef HAVE_IPV6 -#include -#else -#include // socket(),... -#endif //!HAVE_IPV6 -#else -#ifdef __OS2__ -#include -#endif // __OS2__ - -#ifdef HAVE_LWIP -#include -#include -#include -#define ioctl lwip_ioctl -#else -#include -#ifdef __APPLE_CC__ -#ifndef _BSD_SOCKLEN_T_ -#define _BSD_SOCKLEN_T_ -#endif -#endif -#include // socket(),... -#include // sockaddr_in -#ifdef _PS3 -#include -#elif !defined(_arch_dreamcast) -#include // getaddrinfo(),... -#include -#endif -#endif - -#ifdef _arch_dreamcast -#include "sdl12/SRB2DC/dchelp.h" -#endif - -#include // timeval,... (TIMEOUT) -#include -#endif // _WIN32/_WIN32_WCE - -#ifdef __OS2__ -#include -#endif // __OS2__ -#endif // !NONET - #include "doomstat.h" #include "doomdef.h" #include "command.h" -#include "i_net.h" -#include "console.h" +#include "i_threads.h" #include "mserv.h" -#include "d_net.h" -#include "i_tcp.h" -#include "i_system.h" -#include "byteptr.h" #include "m_menu.h" -#include "m_argv.h" // Alam is going to kill me <3 -#include "m_misc.h" // GetRevisionString() - -#ifdef _WIN32_WCE -#include "sdl12/SRB2CE/cehelp.h" -#endif - -#include "i_addrinfo.h" +#include "z_zone.h" #ifdef HAVE_DISCORDRPC #include "discord.h" #endif -// ================================ DEFINITIONS =============================== +static int MSId; +static int MSRegisteredId = -1; -#define PACKET_SIZE 1024 +static boolean MSRegistered; +static boolean MSInProgress; +static boolean MSUpdateAgain; +static time_t MSLastPing; -#define MS_NO_ERROR 0 -#define MS_SOCKET_ERROR -201 -#define MS_CONNECT_ERROR -203 -#define MS_WRITE_ERROR -210 -#define MS_READ_ERROR -211 -#define MS_CLOSE_ERROR -212 -#define MS_GETHOSTBYNAME_ERROR -220 -#define MS_GETHOSTNAME_ERROR -221 -#define MS_TIMEOUT_ERROR -231 +#ifdef HAVE_THREADS +static I_mutex MSMutex; +static I_cond MSCond; -// see master server code for the values -#define ADD_SERVER_MSG 101 -#define REMOVE_SERVER_MSG 103 -#define ADD_SERVERv2_MSG 104 -#define GET_SERVER_MSG 200 -#define GET_SHORT_SERVER_MSG 205 -#define ASK_SERVER_MSG 206 -#define ANSWER_ASK_SERVER_MSG 207 -#define ASK_SERVER_MSG 206 -#define ANSWER_ASK_SERVER_MSG 207 -#define GET_MOTD_MSG 208 -#define SEND_MOTD_MSG 209 -#define GET_ROOMS_MSG 210 -#define SEND_ROOMS_MSG 211 -#define GET_ROOMS_HOST_MSG 212 -#define GET_VERSION_MSG 213 -#define SEND_VERSION_MSG 214 -#define GET_BANNED_MSG 215 // Someone's been baaaaaad! -#define PING_SERVER_MSG 216 +# define Lock_state() I_lock_mutex (&MSMutex) +# define Unlock_state() I_unlock_mutex (MSMutex) +#else/*HAVE_THREADS*/ +# define Lock_state() +# define Unlock_state() +#endif/*HAVE_THREADS*/ -#define HEADER_SIZE (sizeof (INT32)*4) - -#define HEADER_MSG_POS 0 -#define IP_MSG_POS 16 -#define PORT_MSG_POS 32 -#define HOSTNAME_MSG_POS 40 - - -#if defined(_MSC_VER) -#pragma pack(1) -#endif - -/** A message to be exchanged with the master server. - */ -typedef struct -{ - INT32 id; ///< Unused? - INT32 type; ///< Type of message. - INT32 room; ///< Because everyone needs a roomie. - UINT32 length; ///< Length of the message. - char buffer[PACKET_SIZE]; ///< Actual contents of the message. -} ATTRPACK msg_t; - -#if defined(_MSC_VER) -#pragma pack() -#endif - -typedef struct Copy_CVarMS_t -{ - char ip[64]; - char port[8]; - char name[64]; -} Copy_CVarMS_s; -static Copy_CVarMS_s registered_server; -static time_t MSLastPing; - -#if defined(_MSC_VER) -#pragma pack(1) -#endif -typedef struct -{ - char ip[16]; // Big enough to hold a full address. - UINT16 port; - UINT8 padding1[2]; - tic_t time; -} ATTRPACK ms_holepunch_packet_t; -#if defined(_MSC_VER) -#pragma pack() -#endif - -// win32 or djgpp -#if defined (_WIN32) || defined (_WIN32_WCE) || defined (__DJGPP__) -#define ioctl ioctlsocket -#define close closesocket -#ifdef WATTCP -#define strerror strerror_s -#endif -#if defined (_WIN32) || defined (_WIN32_WCE) -#undef errno -#define errno h_errno // some very strange things happen when not using h_error -#endif -#ifndef AI_ADDRCONFIG -#define AI_ADDRCONFIG 0x00000400 -#endif -#endif +static void Update_parameters (void); #ifndef NONET static void Command_Listserv_f(void); #endif static void MasterServer_OnChange(void); -static void ServerName_OnChange(void); -#define DEF_PORT "28900" -consvar_t cv_masterserver = {"masterserver", "ms.srb2.org:"DEF_PORT, CV_SAVE, NULL, MasterServer_OnChange, 0, NULL, NULL, 0, 0, NULL}; -consvar_t cv_servername = {"servername", "SRB2Kart server", CV_SAVE|CV_CALL|CV_NOINIT, NULL, ServerName_OnChange, 0, NULL, NULL, 0, 0, NULL}; +static CV_PossibleValue_t masterserver_update_rate_cons_t[] = { + {2, "MIN"}, + {60, "MAX"}, + {0} +}; + +consvar_t cv_masterserver = {"masterserver", "https://mb.srb2.org/MS/0", CV_SAVE|CV_CALL, NULL, MasterServer_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_servername = {"servername", "SRB2Kart server", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Update_parameters, 0, NULL, NULL, 0, 0, NULL}; + +consvar_t cv_masterserver_update_rate = {"masterserver_update_rate", "15", CV_SAVE|CV_CALL|CV_NOINIT, masterserver_update_rate_cons_t, Update_parameters, 0, NULL, NULL, 0, 0, NULL}; INT16 ms_RoomId = -1; -static enum { MSCS_NONE, MSCS_WAITING, MSCS_REGISTERED, MSCS_FAILED } con_state = MSCS_NONE; +#ifdef HAVE_THREADS +int ms_QueryId; +I_mutex ms_QueryId_mutex; + +msg_server_t *ms_ServerList; +I_mutex ms_ServerList_mutex; +#endif -static INT32 msnode = -1; UINT16 current_port = 0; -#if (defined (_WIN32) || defined (_WIN32_WCE) || defined (_WIN32)) && !defined (NONET) -typedef SOCKET SOCKET_TYPE; -#define ERRSOCKET (SOCKET_ERROR) -#else -#if (defined (__unix__) && !defined (MSDOS)) || defined (__APPLE__) || defined (__HAIKU__) || defined (_PS3) -typedef int SOCKET_TYPE; -#else -typedef unsigned long SOCKET_TYPE; -#endif -#define ERRSOCKET (-1) -#endif - -#if (defined (WATTCP) && !defined (__libsocket_socklen_t)) || defined (_WIN32) -typedef int socklen_t; -#endif - -#ifndef NONET -static SOCKET_TYPE socket_fd = ERRSOCKET; // WINSOCK socket -static struct timeval select_timeout; -static fd_set wset; -static size_t recvfull(SOCKET_TYPE s, char *buf, size_t len, int flags); -#endif - // Room list is an external variable now. // Avoiding having to get info ten thousand times... msg_rooms_t room_list[NUM_LIST_ROOMS+1]; // +1 for easy test @@ -255,371 +90,96 @@ void AddMServCommands(void) { #ifndef NONET CV_RegisterVar(&cv_masterserver); + CV_RegisterVar(&cv_masterserver_update_rate); + CV_RegisterVar(&cv_masterserver_timeout); + CV_RegisterVar(&cv_masterserver_debug); + CV_RegisterVar(&cv_masterserver_token); CV_RegisterVar(&cv_servername); COM_AddCommand("listserv", Command_Listserv_f); #endif } -/** Closes the connection to the master server. - * - * \todo Fix for Windows? - */ -static void CloseConnection(void) +static void WarnGUI (void) { -#ifndef NONET - if (socket_fd != (SOCKET_TYPE)ERRSOCKET) - close(socket_fd); - socket_fd = ERRSOCKET; +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); #endif -} - -// -// MS_Write(): -// -static INT32 MS_Write(msg_t *msg) -{ -#ifdef NONET - (void)msg; - return MS_WRITE_ERROR; -#else - size_t len; - - if (msg->length == 0) - msg->length = (INT32)strlen(msg->buffer); - len = msg->length + HEADER_SIZE; - - msg->type = htonl(msg->type); - msg->length = htonl(msg->length); - msg->room = htonl(msg->room); - - if ((size_t)send(socket_fd, (char *)msg, (int)len, 0) != len) - return MS_WRITE_ERROR; - return 0; + M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n\nCheck the console for details.\n"), NULL, MM_NOTHING); +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); #endif } -// -// MS_Read(): -// -static INT32 MS_Read(msg_t *msg) -{ -#ifdef NONET - (void)msg; - return MS_READ_ERROR; -#else - if (recvfull(socket_fd, (char *)msg, HEADER_SIZE, 0) != HEADER_SIZE) - return MS_READ_ERROR; - - msg->type = ntohl(msg->type); - msg->length = ntohl(msg->length); - msg->room = ntohl(msg->room); - - if (!msg->length) // fix a bug in Windows 2000 - return 0; - - if (recvfull(socket_fd, (char *)msg->buffer, msg->length, 0) != msg->length) - return MS_READ_ERROR; - return 0; -#endif -} - -#ifndef NONET -/** Gets a list of game servers from the master server. - */ -static INT32 GetServersList(void) -{ - msg_t msg; - INT32 count = 0; - - msg.type = GET_SERVER_MSG; - msg.length = 0; - msg.room = 0; - if (MS_Write(&msg) < 0) - return MS_WRITE_ERROR; - - while (MS_Read(&msg) >= 0) - { - if (!msg.length) - { - if (!count) - CONS_Alert(CONS_NOTICE, M_GetText("No servers currently running.\n")); - return MS_NO_ERROR; - } - count++; - CONS_Printf("%s",msg.buffer); - } - - return MS_READ_ERROR; -} -#endif - -// -// MS_Connect() -// -static INT32 MS_Connect(const char *ip_addr, const char *str_port, INT32 async) -{ -#ifdef NONET - (void)ip_addr; - (void)str_port; - (void)async; -#else - struct my_addrinfo *ai, *runp, hints; - int gaie; - - memset (&hints, 0x00, sizeof(hints)); -#ifdef AI_ADDRCONFIG - hints.ai_flags = AI_ADDRCONFIG; -#endif - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - //I_InitTcpNetwork(); this is already done on startup in D_SRB2Main() - if (!I_InitTcpDriver()) // this is done only if not already done - return MS_SOCKET_ERROR; - - gaie = I_getaddrinfo(ip_addr, str_port, &hints, &ai); - if (gaie != 0) - return MS_GETHOSTBYNAME_ERROR; - else - runp = ai; - - while (runp != NULL) - { - socket_fd = socket(runp->ai_family, runp->ai_socktype, runp->ai_protocol); - if (socket_fd != (SOCKET_TYPE)ERRSOCKET) - { - if (async) // do asynchronous connection - { -#ifdef FIONBIO -#ifdef WATTCP - char res = 1; -#else - unsigned long res = 1; -#endif - - ioctl(socket_fd, FIONBIO, &res); -#endif - - if (connect(socket_fd, runp->ai_addr, (socklen_t)runp->ai_addrlen) == ERRSOCKET) - { -#ifdef _WIN32 // humm, on win32/win64 it doesn't work with EINPROGRESS (stupid windows) - if (WSAGetLastError() != WSAEWOULDBLOCK) -#else - if (errno != EINPROGRESS) -#endif - { - con_state = MSCS_FAILED; - CloseConnection(); - I_freeaddrinfo(ai); - return MS_CONNECT_ERROR; - } - } - con_state = MSCS_WAITING; - FD_ZERO(&wset); - FD_SET(socket_fd, &wset); - select_timeout.tv_sec = 0, select_timeout.tv_usec = 0; - I_freeaddrinfo(ai); - return 0; - } - else if (connect(socket_fd, runp->ai_addr, (socklen_t)runp->ai_addrlen) != ERRSOCKET) - { - I_freeaddrinfo(ai); - return 0; - } - } - runp = runp->ai_next; - } - I_freeaddrinfo(ai); -#endif - return MS_CONNECT_ERROR; -} - #define NUM_LIST_SERVER MAXSERVERLIST -const msg_server_t *GetShortServersList(INT32 room) +msg_server_t *GetShortServersList(INT32 room, int id) { - static msg_server_t server_list[NUM_LIST_SERVER+1]; // +1 for easy test - msg_t msg; - INT32 i; + msg_server_t *server_list; - // we must be connected to the master server before writing to it - if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0)) - { - CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n")); - M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n"), NULL, MM_NOTHING); - return NULL; - } + // +1 for easy test + server_list = malloc(( NUM_LIST_SERVER + 1 ) * sizeof *server_list); - msg.type = GET_SHORT_SERVER_MSG; - msg.length = 0; - msg.room = room; - if (MS_Write(&msg) < 0) - return NULL; - - for (i = 0; i < NUM_LIST_SERVER && MS_Read(&msg) >= 0; i++) - { - if (!msg.length) - { - server_list[i].header.buffer[0] = 0; - CloseConnection(); - return server_list; - } - M_Memcpy(&server_list[i], msg.buffer, sizeof (msg_server_t)); - server_list[i].header.buffer[0] = 1; - } - CloseConnection(); - if (i == NUM_LIST_SERVER) - { - server_list[i].header.buffer[0] = 0; + if (HMS_fetch_servers(server_list, room, id)) return server_list; - } else + { + free(server_list); + WarnGUI(); return NULL; + } } -INT32 GetRoomsList(boolean hosting) +INT32 GetRoomsList(boolean hosting, int id) { - static msg_ban_t banned_info[1]; - msg_t msg; - INT32 i; - - // we must be connected to the master server before writing to it - if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0)) - { - CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n")); - M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n"), NULL, MM_NOTHING); - return -1; - } - - if (hosting) - msg.type = GET_ROOMS_HOST_MSG; - else - msg.type = GET_ROOMS_MSG; - msg.length = 0; - msg.room = 0; - if (MS_Write(&msg) < 0) - { - room_list[0].id = 1; - strcpy(room_list[0].motd,"Master Server Offline."); - strcpy(room_list[0].name,"Offline"); - return -1; - } - - for (i = 0; i < NUM_LIST_ROOMS && MS_Read(&msg) >= 0; i++) - { - if(msg.type == GET_BANNED_MSG) - { - char banmsg[1000]; - M_Memcpy(&banned_info[0], msg.buffer, sizeof (msg_ban_t)); - if (hosting) - sprintf(banmsg, M_GetText("You have been banned from\nhosting netgames.\n\nUnder the following IP Range:\n%s - %s\n\nFor the following reason:\n%s\n\nYour ban will expire on:\n%s"),banned_info[0].ipstart,banned_info[0].ipend,banned_info[0].reason,banned_info[0].endstamp); - else - sprintf(banmsg, M_GetText("You have been banned from\njoining netgames.\n\nUnder the following IP Range:\n%s - %s\n\nFor the following reason:\n%s\n\nYour ban will expire on:\n%s"),banned_info[0].ipstart,banned_info[0].ipend,banned_info[0].reason,banned_info[0].endstamp); - M_StartMessage(banmsg, NULL, MM_NOTHING); - ms_RoomId = -1; - return -2; - } - if (!msg.length) - { - room_list[i].header.buffer[0] = 0; - CloseConnection(); - return 1; - } - M_Memcpy(&room_list[i], msg.buffer, sizeof (msg_rooms_t)); - room_list[i].header.buffer[0] = 1; - } - CloseConnection(); - if (i == NUM_LIST_ROOMS) - { - room_list[i].header.buffer[0] = 0; + if (HMS_fetch_rooms( ! hosting, id)) return 1; - } else { - room_list[0].id = 1; - strcpy(room_list[0].motd,M_GetText("Master Server Offline.")); - strcpy(room_list[0].name,M_GetText("Offline")); + WarnGUI(); return -1; } } #ifdef UPDATE_ALERT -const char *GetMODVersion(void) +char *GetMODVersion(int id) { - static msg_t msg; + char *buffer; + int c; - // we must be connected to the master server before writing to it - if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0)) + (void)id; + + buffer = malloc(16); + + c = HMS_compare_mod_version(buffer, 16); + +#ifdef HAVE_THREADS + I_lock_mutex(&ms_QueryId_mutex); { - CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n")); - M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n"), NULL, MM_NOTHING); - return NULL; + if (id != ms_QueryId) + c = -1; } + I_unlock_mutex(ms_QueryId_mutex); +#endif - msg.type = GET_VERSION_MSG; - msg.length = sizeof MODVERSION; - msg.room = MODID; // Might as well use it for something. - sprintf(msg.buffer,"%d",MODVERSION); - if (MS_Write(&msg) < 0) - { - CONS_Alert(CONS_ERROR, M_GetText("Could not send to the Master Server\n")); - M_StartMessage(M_GetText("Could not send to the Master Server\n"), NULL, MM_NOTHING); - CloseConnection(); - return NULL; - } - - if (MS_Read(&msg) < 0) - { - CONS_Alert(CONS_ERROR, M_GetText("No reply from the Master Server\n")); - M_StartMessage(M_GetText("No reply from the Master Server\n"), NULL, MM_NOTHING); - CloseConnection(); - return NULL; - } - - CloseConnection(); - - if(strcmp(msg.buffer,"NULL") != 0) - { - return msg.buffer; - } + if (c > 0) + return buffer; else + { + free(buffer); + + if (! c) + WarnGUI(); + return NULL; + } } // Console only version of the above (used before game init) void GetMODVersion_Console(void) { - static msg_t msg; + char buffer[16]; - // we must be connected to the master server before writing to it - if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0)) - { - CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n")); - return; - } - - msg.type = GET_VERSION_MSG; - msg.length = sizeof MODVERSION; - msg.room = MODID; // Might as well use it for something. - sprintf(msg.buffer,"%d",MODVERSION); - if (MS_Write(&msg) < 0) - { - CONS_Alert(CONS_ERROR, M_GetText("Could not send to the Master Server\n")); - CloseConnection(); - return; - } - - if (MS_Read(&msg) < 0) - { - CONS_Alert(CONS_ERROR, M_GetText("No reply from the Master Server\n")); - CloseConnection(); - return; - } - - CloseConnection(); - - if(strcmp(msg.buffer,"NULL") != 0) - I_Error(UPDATE_ALERT_STRING_CONSOLE, VERSIONSTRING, msg.buffer); + if (HMS_compare_mod_version(buffer, sizeof buffer) > 0) + I_Error(UPDATE_ALERT_STRING_CONSOLE, VERSIONSTRING, buffer); } #endif @@ -628,367 +188,314 @@ void GetMODVersion_Console(void) */ static void Command_Listserv_f(void) { - if (con_state == MSCS_WAITING) - { - CONS_Alert(CONS_NOTICE, M_GetText("Not yet connected to the Master Server.\n")); - return; - } - CONS_Printf(M_GetText("Retrieving server list...\n")); - if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0)) { - CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n")); - return; + HMS_list_servers(); } - - if (GetServersList()) - CONS_Alert(CONS_ERROR, M_GetText("Cannot get server list\n")); - - CloseConnection(); } #endif -FUNCMATH static const char *int2str(INT32 n) +static void +Finish_registration (void) { - INT32 i; - static char res[16]; + int registered; - res[15] = '\0'; - res[14] = (char)((char)(n%10)+'0'); - for (i = 13; (n /= 10); i--) - res[i] = (char)((char)(n%10)+'0'); + CONS_Printf("Registering this server on the master server...\n"); - return &res[i+1]; -} + registered = HMS_register(); -#ifndef NONET -static INT32 ConnectionFailed(void) -{ - con_state = MSCS_FAILED; - CONS_Alert(CONS_ERROR, M_GetText("Connection to Master Server failed\n")); - CloseConnection(); - return MS_CONNECT_ERROR; -} -#endif - -/** Tries to register the local game server on the master server. - */ -static INT32 AddToMasterServer(boolean firstadd) -{ -#ifdef NONET - (void)firstadd; -#else - static INT32 retry = 0; - int i, res; - socklen_t j; - msg_t msg; - msg_server_t *info = (msg_server_t *)msg.buffer; - INT32 room = -1; - fd_set tset; - time_t timestamp = time(NULL); - UINT32 signature, tmp; - const char *insname; - - M_Memcpy(&tset, &wset, sizeof (tset)); - res = select(255, NULL, &tset, NULL, &select_timeout); - if (res != ERRSOCKET && !res) + Lock_state(); { - if (retry++ > 30) // an about 30 second timeout + MSRegistered = registered; + MSRegisteredId = MSId; + + time(&MSLastPing); + } + Unlock_state(); + + if (registered) + CONS_Printf("Master server registration successful.\n"); +} + +static void +Finish_update (void) +{ + int registered; + int done; + + Lock_state(); + { + registered = MSRegistered; + MSUpdateAgain = false;/* this will happen anyway */ + } + Unlock_state(); + + if (registered) + { + if (HMS_update()) { - retry = 0; - CONS_Alert(CONS_ERROR, M_GetText("Master Server timed out\n")); - MSLastPing = timestamp; - return ConnectionFailed(); + Lock_state(); + { + time(&MSLastPing); + MSRegistered = true; + } + Unlock_state(); + + CONS_Printf("Updated master server listing.\n"); } - return MS_CONNECT_ERROR; + else + Finish_registration(); } - retry = 0; - /* - Somehow we can still select our old socket despite it being closed(?). - Atleast, that's what I THINK is happening. Anyway, we have to check that we - haven't open a socket, and actually open it! - */ - /*if (res == ERRSOCKET)*//* wtf? no! */ - if (socket_fd == (SOCKET_TYPE)ERRSOCKET) + else + Finish_registration(); + + Lock_state(); { - if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0)) + done = ! MSUpdateAgain; + + if (done) + MSInProgress = false; + } + Unlock_state(); + + if (! done) + Finish_update(); +} + +static void +Finish_unlist (void) +{ + int registered; + + Lock_state(); + { + registered = MSRegistered; + } + Unlock_state(); + + if (registered) + { + CONS_Printf("Removing this server from the master server...\n"); + + if (HMS_unlist()) + CONS_Printf("Server deregistration request successfully sent.\n"); + + Lock_state(); { - CONS_Alert(CONS_ERROR, M_GetText("Master Server socket error #%u: %s\n"), errno, strerror(errno)); - MSLastPing = timestamp; - return ConnectionFailed(); + MSRegistered = false; } - } + Unlock_state(); - // so, the socket is writable, but what does that mean, that the connection is - // ok, or bad... let see that! - j = (socklen_t)sizeof (i); - getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, (char *)&i, &j); - /* - This is also wrong. If getsockopt fails, i doesn't have to be set. Plus, if - it is set (which it appearantly is on linux), we check errno anyway. And in - the case that i is returned as normal, we don't even report the correct - value! So we accomplish NOTHING, except returning due to dumb luck. - If you care, fix this--I don't. -James (R.) - */ - if (i) // it was bad - { - CONS_Alert(CONS_ERROR, M_GetText("Master Server socket error #%u: %s\n"), errno, strerror(errno)); - MSLastPing = timestamp; - return ConnectionFailed(); - } - -#ifdef PARANOIA - if (ms_RoomId <= 0) - I_Error("Attmepted to host in room \"All\"!\n"); +#ifdef HAVE_THREADS + I_wake_all_cond(&MSCond); #endif - room = ms_RoomId; - - for(signature = 0, insname = cv_servername.string; *insname; signature += *insname++); - tmp = (UINT32)(signature * (size_t)&MSLastPing); - signature *= tmp; - signature &= 0xAAAAAAAA; - M_Memcpy(&info->header.signature, &signature, sizeof (UINT32)); - - strcpy(info->ip, ""); - strcpy(info->port, int2str(current_port)); - strcpy(info->name, cv_servername.string); - M_Memcpy(&info->room, & room, sizeof (INT32)); -#if VERSION > 0 || SUBVERSION > 0 - sprintf(info->version, "%d.%d", VERSION, SUBVERSION); -#else // Trunk build, send revision info - strcpy(info->version, GetRevisionString()); -#endif - strcpy(registered_server.name, cv_servername.string); - - if(firstadd) - msg.type = ADD_SERVER_MSG; - else - msg.type = PING_SERVER_MSG; - - msg.length = (UINT32)sizeof (msg_server_t); - msg.room = 0; - if (MS_Write(&msg) < 0) - { - MSLastPing = timestamp; - return ConnectionFailed(); } - if(con_state != MSCS_REGISTERED) - CONS_Printf(M_GetText("Master Server update successful.\n")); - - MSLastPing = timestamp; - con_state = MSCS_REGISTERED; - CloseConnection(); -#endif - return MS_NO_ERROR; -} - -static INT32 RemoveFromMasterSever(void) -{ - msg_t msg; - msg_server_t *info = (msg_server_t *)msg.buffer; - - strcpy(info->header.buffer, ""); - strcpy(info->ip, ""); - strcpy(info->port, int2str(current_port)); - strcpy(info->name, registered_server.name); - sprintf(info->version, "%d.%d", VERSION, SUBVERSION); - - msg.type = REMOVE_SERVER_MSG; - msg.length = (UINT32)sizeof (msg_server_t); - msg.room = 0; - if (MS_Write(&msg) < 0) - return MS_WRITE_ERROR; - - return MS_NO_ERROR; -} - -const char *GetMasterServerPort(void) -{ - const char *t = cv_masterserver.string; - - while ((*t != ':') && (*t != '\0')) - t++; - - if (*t) - return ++t; - else - return DEF_PORT; -} - -/** Gets the IP address of the master server. Actually, it seems to just - * return the hostname, instead; the lookup is done elsewhere. - * - * \return Hostname of the master server, without port number on the end. - * \todo Rename function? - */ -const char *GetMasterServerIP(void) -{ - static char str_ip[64]; - char *t = str_ip; - - if (strstr(cv_masterserver.string, "srb2.ssntails.org:28910") - || strstr(cv_masterserver.string, "srb2.servegame.org:28910") - || strstr(cv_masterserver.string, "srb2.servegame.org:28900") - ) + Lock_state(); { - // replace it with the current default one - CV_Set(&cv_masterserver, cv_masterserver.defaultvalue); + if (MSId == MSRegisteredId) + MSId++; } - - strcpy(t, cv_masterserver.string); - - while ((*t != ':') && (*t != '\0')) - t++; - *t = '\0'; - - return str_ip; + Unlock_state(); } -void MSOpenUDPSocket(void) +#ifdef HAVE_THREADS +static int * +Server_id (void) { -#ifndef NONET - if (I_NetMakeNodewPort) + int *id; + id = malloc(sizeof *id); + Lock_state(); { - // If it's already open, there's nothing to do. - if (msnode < 0) - msnode = I_NetMakeNodewPort(GetMasterServerIP(), GetMasterServerPort()); + *id = MSId; } - else -#endif - msnode = -1; + Unlock_state(); + return id; } -void MSCloseUDPSocket(void) +static int * +New_server_id (void) { - if (msnode != INT16_MAX) I_NetFreeNodenum(msnode); - msnode = -1; + int *id; + id = malloc(sizeof *id); + Lock_state(); + { + *id = ++MSId; + I_wake_all_cond(&MSCond); + } + Unlock_state(); + return id; } +static void +Register_server_thread (int *id) +{ + int same; + + Lock_state(); + { + /* wait for previous unlist to finish */ + while (*id == MSId && MSRegistered) + I_hold_cond(&MSCond, MSMutex); + + same = ( *id == MSId );/* it could have been a while */ + } + Unlock_state(); + + if (same)/* it could have been a while */ + Finish_registration(); + + free(id); +} + +static void +Update_server_thread (int *id) +{ + int same; + + Lock_state(); + { + same = ( *id == MSRegisteredId ); + } + Unlock_state(); + + if (same) + Finish_update(); + + free(id); +} + +static void +Unlist_server_thread (int *id) +{ + int same; + + Lock_state(); + { + same = ( *id == MSRegisteredId ); + } + Unlock_state(); + + if (same) + Finish_unlist(); + + free(id); +} + +static void +Change_masterserver_thread (char *api) +{ + Lock_state(); + { + while (MSRegistered) + I_hold_cond(&MSCond, MSMutex); + } + Unlock_state(); + + HMS_set_api(api); +} +#endif/*HAVE_THREADS*/ + void RegisterServer(void) { - if (con_state == MSCS_REGISTERED || con_state == MSCS_WAITING) - return; +#ifdef HAVE_THREADS + I_spawn_thread( + "register-server", + (I_thread_fn)Register_server_thread, + New_server_id() + ); +#else + Finish_registration(); +#endif +} - CONS_Printf(M_GetText("Registering this server on the Master Server...\n")); +static void UpdateServer(void) +{ +#ifdef HAVE_THREADS + I_spawn_thread( + "update-server", + (I_thread_fn)Update_server_thread, + Server_id() + ); +#else + Finish_update(); +#endif +} - strcpy(registered_server.ip, GetMasterServerIP()); - strcpy(registered_server.port, GetMasterServerPort()); +void UnregisterServer(void) +{ +#ifdef HAVE_THREADS + I_spawn_thread( + "unlist-server", + (I_thread_fn)Unlist_server_thread, + Server_id() + ); +#else + Finish_unlist(); +#endif - if (MS_Connect(registered_server.ip, registered_server.port, 1)) - { - CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n")); - return; - } - MSOpenUDPSocket(); - - // keep the TCP connection open until AddToMasterServer() is completed; #ifdef HAVE_DISCORDRPC DRPC_UpdatePresence(); #endif } +static boolean +Online (void) +{ + return ( serverrunning && ms_RoomId > 0 ); +} + static inline void SendPingToMasterServer(void) { -/* static tic_t next_time = 0; - tic_t cur_time; - char *inbuffer = (char*)netbuffer; + int ready; + time_t now; - cur_time = I_GetTime(); - if (!netgame) - UnregisterServer(); - else if (cur_time > next_time) // ping every 2 second if possible + if (Online()) { - next_time = cur_time+2*TICRATE; + time(&now); - if (con_state == MSCS_WAITING) - AddToMasterServer(); + Lock_state(); + { + ready = ( + MSRegisteredId == MSId && + ! MSInProgress && + now >= ( MSLastPing + 60 * cv_masterserver_update_rate.value ) + ); - if (con_state != MSCS_REGISTERED) - return; + if (ready) + MSInProgress = true; + } + Unlock_state(); - // cur_time is just a dummy data to send - WRITEUINT32(inbuffer, cur_time); - doomcom->datalength = sizeof (cur_time); - doomcom->remotenode = (INT16)msnode; - I_NetSend(); - } -*/ - -// Here, have a simpler MS Ping... - Cue - if(time(NULL) > (MSLastPing+(60*2)) && con_state != MSCS_NONE) - { - //CONS_Debug(DBG_NETPLAY, "%ld (current time) is greater than %d (Last Ping Time)\n", time(NULL), MSLastPing); - if(MSLastPing < 1) - AddToMasterServer(true); - else - AddToMasterServer(false); + if (ready) + UpdateServer(); } } -void SendAskInfoViaMS(INT32 node, tic_t asktime) +static void +Update_parameters (void) { - const char *address; - UINT16 port; - char *inip; - ms_holepunch_packet_t mshpp; + int registered; + int delayed; - MSOpenUDPSocket(); - - // This must be called after calling MSOpenUDPSocket, due to the - // static buffer. - address = I_GetNodeAddress(node); - - // no address? - if (!address) - return; - - // Copy the IP address into the buffer. - inip = mshpp.ip; - while(*address && *address != ':') *inip++ = *address++; - *inip = '\0'; - - // Get the port. - port = (UINT16)(*address++ ? atoi(address) : 0); - mshpp.port = SHORT(port); - - // Set the time for ping calculation. - mshpp.time = LONG(asktime); - - // Send to the MS. - M_Memcpy(netbuffer, &mshpp, sizeof(mshpp)); - doomcom->datalength = sizeof(ms_holepunch_packet_t); - doomcom->remotenode = (INT16)msnode; - I_NetSend(); -} - -void UnregisterServer(void) -{ - if (con_state != MSCS_REGISTERED) + if (Online()) { - con_state = MSCS_NONE; - CloseConnection(); - return; + Lock_state(); + { + delayed = MSInProgress; + + if (delayed)/* do another update after the current one */ + MSUpdateAgain = true; + else + registered = MSRegistered; + } + Unlock_state(); + + if (! delayed && registered) + UpdateServer(); } - con_state = MSCS_NONE; - - CONS_Printf(M_GetText("Removing this server from the Master Server...\n")); - - if (MS_Connect(registered_server.ip, registered_server.port, 0)) - { - CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n")); - return; - } - - if (RemoveFromMasterSever() < 0) - CONS_Alert(CONS_ERROR, M_GetText("Cannot remove this server from the Master Server\n")); - - CloseConnection(); - MSCloseUDPSocket(); - MSLastPing = 0; - #ifdef HAVE_DISCORDRPC DRPC_UpdatePresence(); #endif @@ -996,40 +503,40 @@ void UnregisterServer(void) void MasterClient_Ticker(void) { - if (server && ms_RoomId > 0) - SendPingToMasterServer(); + SendPingToMasterServer(); } -static void ServerName_OnChange(void) +static void +Set_api (const char *api) { - if (con_state == MSCS_REGISTERED) - AddToMasterServer(false); +#ifdef HAVE_THREADS + I_spawn_thread( + "change-masterserver", + (I_thread_fn)Change_masterserver_thread, + strdup(api) + ); +#else + HMS_set_api(strdup(api)); +#endif } static void MasterServer_OnChange(void) { UnregisterServer(); - RegisterServer(); -} -#ifndef NONET -// Like recv, but waits until we've got enough data to fill the buffer. -static size_t recvfull(SOCKET_TYPE s, char *buf, size_t len, int flags) -{ - /* Total received. */ - size_t totallen = 0; - - while(totallen < len) - { - ssize_t ret = (ssize_t)recv(s, buf + totallen, (int)(len - totallen), flags); - - /* Error. */ - if(ret == -1) - return (size_t)-1; - - totallen += ret; + /* + TODO: remove this for v2, it's just a hack + for those coming in with an old config. + */ + if ( + ! cv_masterserver.changed && + strcmp(cv_masterserver.string, "ms.srb2.org:28900") == 0 + ){ + CV_StealthSet(&cv_masterserver, cv_masterserver.defaultvalue); } - return totallen; + Set_api(cv_masterserver.string); + + if (Online()) + RegisterServer(); } -#endif diff --git a/src/mserv.h b/src/mserv.h index 8f8152a7..9269c408 100644 --- a/src/mserv.h +++ b/src/mserv.h @@ -2,6 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2018 by Sonic Team Junior. +// Copyright (C) 2020 by James R. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -13,7 +14,7 @@ #ifndef _MSERV_H_ #define _MSERV_H_ -#define MASTERSERVERS21 // MasterServer v2.1 +#include "i_threads.h" // lowered from 32 due to menu changes #define NUM_LIST_ROOMS 16 @@ -64,33 +65,47 @@ typedef struct // ================================ GLOBALS =============================== extern consvar_t cv_masterserver, cv_servername; +extern consvar_t cv_masterserver_update_rate; +extern consvar_t cv_masterserver_timeout; +extern consvar_t cv_masterserver_debug; +extern consvar_t cv_masterserver_token; // < 0 to not connect (usually -1) (offline mode) // == 0 to show all rooms, not a valid hosting room // anything else is whatever room the MS assigns to that number (online mode) extern INT16 ms_RoomId; -const char *GetMasterServerPort(void); -const char *GetMasterServerIP(void); +#ifdef HAVE_THREADS +extern int ms_QueryId; +extern I_mutex ms_QueryId_mutex; -void MSOpenUDPSocket(void); -void MSCloseUDPSocket(void); - -void SendAskInfoViaMS(INT32 node, tic_t asktime); +extern msg_server_t *ms_ServerList; +extern I_mutex ms_ServerList_mutex; +#endif void RegisterServer(void); void UnregisterServer(void); void MasterClient_Ticker(void); -const msg_server_t *GetShortServersList(INT32 room); -INT32 GetRoomsList(boolean hosting); +msg_server_t *GetShortServersList(INT32 room, int id); +INT32 GetRoomsList(boolean hosting, int id); #ifdef UPDATE_ALERT -const char *GetMODVersion(void); +char *GetMODVersion(int id); void GetMODVersion_Console(void); #endif extern msg_rooms_t room_list[NUM_LIST_ROOMS+1]; void AddMServCommands(void); +/* HTTP */ +void HMS_set_api (char *api); +int HMS_fetch_rooms (int joining, int id); +int HMS_register (void); +int HMS_unlist (void); +int HMS_update (void); +void HMS_list_servers (void); +msg_server_t * HMS_fetch_servers (msg_server_t *list, int room, int id); +int HMS_compare_mod_version (char *buffer, size_t size_of_buffer); + #endif diff --git a/src/sdl/Makefile.cfg b/src/sdl/Makefile.cfg index 58c4d086..b0c591ce 100644 --- a/src/sdl/Makefile.cfg +++ b/src/sdl/Makefile.cfg @@ -83,6 +83,11 @@ else SDL_LDFLAGS+=-lSDL2_mixer endif +ifndef NOTHREADS + OPTS+=-DHAVE_THREADS + OBJS+=$(OBJDIR)/i_threads.o +endif + ifdef SDL_TTF OPTS+=-DHAVE_TTF SDL_LDFLAGS+=-lSDL2_ttf -lfreetype -lz diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj index 69b3465a..813e31a9 100644 --- a/src/sdl/Srb2SDL-vc10.vcxproj +++ b/src/sdl/Srb2SDL-vc10.vcxproj @@ -249,6 +249,7 @@ + @@ -399,6 +400,7 @@ + diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters index 9e5b9837..f197a394 100644 --- a/src/sdl/Srb2SDL-vc10.vcxproj.filters +++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters @@ -291,6 +291,9 @@ I_Interface + + I_Interface + LUA @@ -663,6 +666,9 @@ I_Interface + + I_Interface + LUA diff --git a/src/sdl/Srb2SDL-vc9.vcproj b/src/sdl/Srb2SDL-vc9.vcproj index b21eedb8..115e17a3 100644 --- a/src/sdl/Srb2SDL-vc9.vcproj +++ b/src/sdl/Srb2SDL-vc9.vcproj @@ -2782,6 +2782,50 @@ RelativePath="..\mserv.h" > + + + + + + + + + + + + + + + + + +typedef void * (*Create_fn)(void); + +struct Link; +struct Thread; + +typedef struct Link * Link; +typedef struct Thread * Thread; + +struct Link +{ + void * data; + Link next; + Link prev; +}; + +struct Thread +{ + I_thread_fn entry; + void * userdata; + + SDL_Thread * thread; +}; + +static Link i_thread_pool; +static Link i_mutex_pool; +static Link i_cond_pool; + +static I_mutex i_thread_pool_mutex; +static I_mutex i_mutex_pool_mutex; +static I_mutex i_cond_pool_mutex; + +static SDL_atomic_t i_threads_running = {1}; + +static Link +Insert_link ( + Link * head, + Link link +){ + link->prev = NULL; + link->next = (*head); + if ((*head)) + (*head)->prev = link; + (*head) = link; + return link; +} + +static void +Free_link ( + Link * head, + Link link +){ + if (link->prev) + link->prev->next = link->next; + else + (*head) = link->next; + + if (link->next) + link->next->prev = link->prev; + + free(link->data); + free(link); +} + +static Link +New_link (void *data) +{ + Link link; + + link = malloc(sizeof *link); + + if (! link) + abort(); + + link->data = data; + + return link; +} + +static void * +Identity ( + Link * pool_anchor, + I_mutex pool_mutex, + + void ** anchor, + + Create_fn create_fn +){ + void * id; + + id = SDL_AtomicGetPtr(anchor); + + if (! id) + { + I_lock_mutex(&pool_mutex); + { + id = SDL_AtomicGetPtr(anchor); + + if (! id) + { + id = (*create_fn)(); + + if (! id) + abort(); + + Insert_link(pool_anchor, New_link(id)); + + SDL_AtomicSetPtr(anchor, id); + } + } + I_unlock_mutex(pool_mutex); + } + + return id; +} + +static int +Worker ( + Link link +){ + Thread th; + + th = link->data; + + (*th->entry)(th->userdata); + + if (SDL_AtomicGet(&i_threads_running)) + { + I_lock_mutex(&i_thread_pool_mutex); + { + if (SDL_AtomicGet(&i_threads_running)) + { + SDL_DetachThread(th->thread); + Free_link(&i_thread_pool, link); + } + } + I_unlock_mutex(i_thread_pool_mutex); + } + + return 0; +} + +void +I_spawn_thread ( + const char * name, + I_thread_fn entry, + void * userdata +){ + Link link; + Thread th; + + th = malloc(sizeof *th); + + if (! th) + abort();/* this is pretty GNU of me */ + + th->entry = entry; + th->userdata = userdata; + + I_lock_mutex(&i_thread_pool_mutex); + { + link = Insert_link(&i_thread_pool, New_link(th)); + + if (SDL_AtomicGet(&i_threads_running)) + { + th->thread = SDL_CreateThread( + (SDL_ThreadFunction)Worker, + name, + link + ); + + if (! th->thread) + abort(); + } + } + I_unlock_mutex(i_thread_pool_mutex); +} + +int +I_thread_is_stopped (void) +{ + return ( ! SDL_AtomicGet(&i_threads_running) ); +} + +void +I_start_threads (void) +{ + i_thread_pool_mutex = SDL_CreateMutex(); + i_mutex_pool_mutex = SDL_CreateMutex(); + i_cond_pool_mutex = SDL_CreateMutex(); + + if (!( + i_thread_pool_mutex && + i_mutex_pool_mutex && + i_cond_pool_mutex + )){ + abort(); + } +} + +void +I_stop_threads (void) +{ + Link link; + Link next; + + Thread th; + SDL_mutex * mutex; + SDL_cond * cond; + + if (i_threads_running.value) + { + /* rely on the good will of thread-san */ + SDL_AtomicSet(&i_threads_running, 0); + + I_lock_mutex(&i_thread_pool_mutex); + { + for ( + link = i_thread_pool; + link; + link = next + ){ + next = link->next; + th = link->data; + + SDL_WaitThread(th->thread, NULL); + + free(th); + free(link); + } + } + I_unlock_mutex(i_thread_pool_mutex); + + for ( + link = i_mutex_pool; + link; + link = next + ){ + next = link->next; + mutex = link->data; + + SDL_DestroyMutex(mutex); + + free(link); + } + + for ( + link = i_cond_pool; + link; + link = next + ){ + next = link->next; + cond = link->data; + + SDL_DestroyCond(cond); + + free(link); + } + + SDL_DestroyMutex(i_thread_pool_mutex); + SDL_DestroyMutex(i_mutex_pool_mutex); + SDL_DestroyMutex(i_cond_pool_mutex); + } +} + +void +I_lock_mutex ( + I_mutex * anchor +){ + SDL_mutex * mutex; + + mutex = Identity( + &i_mutex_pool, + i_mutex_pool_mutex, + anchor, + (Create_fn)SDL_CreateMutex + ); + + if (SDL_LockMutex(mutex) == -1) + abort(); +} + +void +I_unlock_mutex ( + I_mutex id +){ + if (SDL_UnlockMutex(id) == -1) + abort(); +} + +void +I_hold_cond ( + I_cond * cond_anchor, + I_mutex mutex_id +){ + SDL_cond * cond; + + cond = Identity( + &i_cond_pool, + i_cond_pool_mutex, + cond_anchor, + (Create_fn)SDL_CreateCond + ); + + if (SDL_CondWait(cond, mutex_id) == -1) + abort(); +} + +void +I_wake_one_cond ( + I_cond * anchor +){ + SDL_cond * cond; + + cond = Identity( + &i_cond_pool, + i_cond_pool_mutex, + anchor, + (Create_fn)SDL_CreateCond + ); + + if (SDL_CondSignal(cond) == -1) + abort(); +} + +void +I_wake_all_cond ( + I_cond * anchor +){ + SDL_cond * cond; + + cond = Identity( + &i_cond_pool, + i_cond_pool_mutex, + anchor, + (Create_fn)SDL_CreateCond + ); + + if (SDL_CondBroadcast(cond) == -1) + abort(); +}