mirror of
https://git.do.srb2.org/KartKrew/Kart-Public.git
synced 2024-12-27 04:41:23 +00:00
Merge branch 'join-passwords' into 'next'
join passwords See merge request KartKrew/Kart-Public!104
This commit is contained in:
commit
f4318aed58
15 changed files with 537 additions and 85 deletions
|
@ -1254,7 +1254,8 @@ found:
|
||||||
|
|
||||||
var->string = var->zstring = Z_StrDup(valstr);
|
var->string = var->zstring = Z_StrDup(valstr);
|
||||||
|
|
||||||
if (override)
|
if (var->flags & CV_PASSWORD); // Don't change value for password field
|
||||||
|
else if (override)
|
||||||
var->value = overrideval;
|
var->value = overrideval;
|
||||||
else if (var->flags & CV_FLOAT)
|
else if (var->flags & CV_FLOAT)
|
||||||
{
|
{
|
||||||
|
|
|
@ -95,7 +95,8 @@ typedef enum
|
||||||
CV_HIDEN = 1024, // variable is not part of the cvar list so cannot be accessed by the console
|
CV_HIDEN = 1024, // variable is not part of the cvar list so cannot be accessed by the console
|
||||||
// can only be set when we have the pointer to it
|
// can only be set when we have the pointer to it
|
||||||
// used on menus
|
// used on menus
|
||||||
CV_CHEAT = 2048 // Don't let this be used in multiplayer unless cheats are on.
|
CV_CHEAT = 2048, // Don't let this be used in multiplayer unless cheats are on.
|
||||||
|
CV_PASSWORD = 4096 // Password field
|
||||||
} cvflags_t;
|
} cvflags_t;
|
||||||
|
|
||||||
typedef struct CV_PossibleValue_s
|
typedef struct CV_PossibleValue_s
|
||||||
|
|
|
@ -544,6 +544,22 @@ static void CON_MoveConsole(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
INT32 CON_ShiftChar(INT32 ch)
|
||||||
|
{
|
||||||
|
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
|
||||||
|
{
|
||||||
|
if (shiftdown ^ capslock)
|
||||||
|
ch = shiftxform[ch];
|
||||||
|
}
|
||||||
|
else // if we're holding shift we should still shift non letter symbols
|
||||||
|
{
|
||||||
|
if (shiftdown)
|
||||||
|
ch = shiftxform[ch];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ch;
|
||||||
|
}
|
||||||
|
|
||||||
// Clear time of console heads up messages
|
// Clear time of console heads up messages
|
||||||
//
|
//
|
||||||
void CON_ClearHUD(void)
|
void CON_ClearHUD(void)
|
||||||
|
@ -1084,16 +1100,6 @@ boolean CON_Responder(event_t *ev)
|
||||||
else if (key == KEY_KPADSLASH)
|
else if (key == KEY_KPADSLASH)
|
||||||
key = '/';
|
key = '/';
|
||||||
|
|
||||||
// capslock
|
|
||||||
if (key == KEY_CAPSLOCK) // it's a toggle.
|
|
||||||
{
|
|
||||||
if (capslock)
|
|
||||||
capslock = false;
|
|
||||||
else
|
|
||||||
capslock = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// same capslock code as hu_stuff.c's HU_responder. Check there for details.
|
// same capslock code as hu_stuff.c's HU_responder. Check there for details.
|
||||||
if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z'))
|
if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z'))
|
||||||
{
|
{
|
||||||
|
|
|
@ -44,6 +44,8 @@ extern UINT8 *yellowmap, *purplemap, *greenmap, *bluemap, *graymap, *redmap, *or
|
||||||
// Console bg color (auto updated to match)
|
// Console bg color (auto updated to match)
|
||||||
extern UINT8 *consolebgmap;
|
extern UINT8 *consolebgmap;
|
||||||
|
|
||||||
|
INT32 CON_ShiftChar(INT32 ch);
|
||||||
|
|
||||||
void CON_SetupBackColormap(void);
|
void CON_SetupBackColormap(void);
|
||||||
void CON_ClearHUD(void); // clear heads up messages
|
void CON_ClearHUD(void); // clear heads up messages
|
||||||
|
|
||||||
|
|
286
src/d_clisrv.c
286
src/d_clisrv.c
|
@ -22,6 +22,7 @@
|
||||||
#include "i_video.h"
|
#include "i_video.h"
|
||||||
#include "d_net.h"
|
#include "d_net.h"
|
||||||
#include "d_main.h"
|
#include "d_main.h"
|
||||||
|
#include "d_event.h"
|
||||||
#include "g_game.h"
|
#include "g_game.h"
|
||||||
#include "hu_stuff.h"
|
#include "hu_stuff.h"
|
||||||
#include "keys.h"
|
#include "keys.h"
|
||||||
|
@ -1126,12 +1127,22 @@ typedef enum
|
||||||
CL_DOWNLOADSAVEGAME,
|
CL_DOWNLOADSAVEGAME,
|
||||||
#endif
|
#endif
|
||||||
CL_CONNECTED,
|
CL_CONNECTED,
|
||||||
CL_ABORTED
|
CL_ABORTED,
|
||||||
|
CL_ASKDOWNLOADFILES,
|
||||||
|
CL_WAITDOWNLOADFILESRESPONSE,
|
||||||
|
CL_CHALLENGE
|
||||||
} cl_mode_t;
|
} cl_mode_t;
|
||||||
|
|
||||||
static void GetPackets(void);
|
static void GetPackets(void);
|
||||||
|
|
||||||
static cl_mode_t cl_mode = CL_SEARCHING;
|
static cl_mode_t cl_mode = CL_SEARCHING;
|
||||||
|
static boolean cl_needsdownload = false;
|
||||||
|
|
||||||
|
static UINT8 cl_challengenum = 0;
|
||||||
|
static UINT8 cl_challengequestion[MD5_LEN+1];
|
||||||
|
static char cl_challengepassword[65];
|
||||||
|
static UINT8 cl_challengeanswer[MD5_LEN+1];
|
||||||
|
static UINT8 cl_challengeattempted = 0;
|
||||||
|
|
||||||
// Player name send/load
|
// Player name send/load
|
||||||
|
|
||||||
|
@ -1168,6 +1179,8 @@ static void CV_LoadPlayerNames(UINT8 **p)
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CLIENT_LOADINGSCREEN
|
#ifdef CLIENT_LOADINGSCREEN
|
||||||
|
static UINT32 SL_SearchServer(INT32 node);
|
||||||
|
|
||||||
//
|
//
|
||||||
// CL_DrawConnectionStatus
|
// CL_DrawConnectionStatus
|
||||||
//
|
//
|
||||||
|
@ -1191,11 +1204,42 @@ static inline void CL_DrawConnectionStatus(void)
|
||||||
// 15 pal entries total.
|
// 15 pal entries total.
|
||||||
const char *cltext;
|
const char *cltext;
|
||||||
|
|
||||||
|
if (cl_mode != CL_CHALLENGE)
|
||||||
for (i = 0; i < 16; ++i)
|
for (i = 0; i < 16; ++i)
|
||||||
V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-24, 16, 8, palstart + ((animtime - i) & 15));
|
V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-24, 16, 8, palstart + ((animtime - i) & 15));
|
||||||
|
|
||||||
switch (cl_mode)
|
switch (cl_mode)
|
||||||
{
|
{
|
||||||
|
case CL_CHALLENGE:
|
||||||
|
{
|
||||||
|
char asterisks[33];
|
||||||
|
size_t sl = min(32, strlen(cl_challengepassword));
|
||||||
|
UINT32 i;
|
||||||
|
|
||||||
|
memset(asterisks, '*', sl);
|
||||||
|
memset(asterisks+sl, 0, 33-sl);
|
||||||
|
|
||||||
|
V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_MONOSPACE|V_ALLOWLOWERCASE, asterisks);
|
||||||
|
V_DrawFixedPatch((BASEVIDWIDTH/2) << FRACBITS, (BASEVIDHEIGHT/2) << FRACBITS, FRACUNIT, 0, W_CachePatchName("BSRVLOCK", PU_CACHE), NULL);
|
||||||
|
|
||||||
|
i = SL_SearchServer(servernode);
|
||||||
|
|
||||||
|
if (i == -1)
|
||||||
|
{
|
||||||
|
M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT/2-8, 32, 1);
|
||||||
|
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, V_REDMAP, M_GetText("This server is password protected."));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT/2-8, 32, 3);
|
||||||
|
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, V_REDMAP, M_GetText("This server,"));
|
||||||
|
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2+8, V_ALLOWLOWERCASE, serverlist[i].info.servername);
|
||||||
|
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2+16, V_REDMAP, M_GetText("is password protected."));
|
||||||
|
}
|
||||||
|
|
||||||
|
cltext = M_GetText(cl_challengeattempted ? "Incorrect password. Please try again." : "Please enter the server password.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
#ifdef JOININGAME
|
#ifdef JOININGAME
|
||||||
case CL_DOWNLOADSAVEGAME:
|
case CL_DOWNLOADSAVEGAME:
|
||||||
if (lastfilenum != -1)
|
if (lastfilenum != -1)
|
||||||
|
@ -1215,6 +1259,9 @@ static inline void CL_DrawConnectionStatus(void)
|
||||||
case CL_WAITJOINRESPONSE:
|
case CL_WAITJOINRESPONSE:
|
||||||
cltext = M_GetText("Requesting to join...");
|
cltext = M_GetText("Requesting to join...");
|
||||||
break;
|
break;
|
||||||
|
case CL_ASKDOWNLOADFILES:
|
||||||
|
case CL_WAITDOWNLOADFILESRESPONSE:
|
||||||
|
cltext = M_GetText("Waiting to download files...");
|
||||||
default:
|
default:
|
||||||
cltext = M_GetText("Connecting to server...");
|
cltext = M_GetText("Connecting to server...");
|
||||||
break;
|
break;
|
||||||
|
@ -1292,6 +1339,9 @@ static boolean CL_SendJoin(void)
|
||||||
netbuffer->u.clientcfg.localplayers = localplayers;
|
netbuffer->u.clientcfg.localplayers = localplayers;
|
||||||
netbuffer->u.clientcfg.version = VERSION;
|
netbuffer->u.clientcfg.version = VERSION;
|
||||||
netbuffer->u.clientcfg.subversion = SUBVERSION;
|
netbuffer->u.clientcfg.subversion = SUBVERSION;
|
||||||
|
netbuffer->u.clientcfg.needsdownload = cl_needsdownload;
|
||||||
|
netbuffer->u.clientcfg.challengenum = cl_challengenum;
|
||||||
|
memcpy(netbuffer->u.clientcfg.challengeanswer, cl_challengeanswer, MD5_LEN);
|
||||||
|
|
||||||
return HSendPacket(servernode, true, 0, sizeof (clientconfig_pak));
|
return HSendPacket(servernode, true, 0, sizeof (clientconfig_pak));
|
||||||
}
|
}
|
||||||
|
@ -1312,7 +1362,13 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
|
||||||
netbuffer->u.serverinfo.gametype = (UINT8)(G_BattleGametype() ? VANILLA_GT_MATCH : VANILLA_GT_RACE); // SRB2Kart: Vanilla's gametype constants for MS support
|
netbuffer->u.serverinfo.gametype = (UINT8)(G_BattleGametype() ? VANILLA_GT_MATCH : VANILLA_GT_RACE); // SRB2Kart: Vanilla's gametype constants for MS support
|
||||||
netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame;
|
netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame;
|
||||||
netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled();
|
netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled();
|
||||||
netbuffer->u.serverinfo.isdedicated = (UINT8)dedicated;
|
|
||||||
|
netbuffer->u.serverinfo.kartvars = (UINT8) (
|
||||||
|
(cv_kartspeed.value & SV_SPEEDMASK) |
|
||||||
|
(dedicated ? SV_DEDICATED : 0) |
|
||||||
|
(D_IsJoinPasswordOn() ? SV_PASSWORD : 0)
|
||||||
|
);
|
||||||
|
|
||||||
strncpy(netbuffer->u.serverinfo.servername, cv_servername.string,
|
strncpy(netbuffer->u.serverinfo.servername, cv_servername.string,
|
||||||
MAXSERVERNAME);
|
MAXSERVERNAME);
|
||||||
strncpy(netbuffer->u.serverinfo.mapname, G_BuildMapName(gamemap), 7);
|
strncpy(netbuffer->u.serverinfo.mapname, G_BuildMapName(gamemap), 7);
|
||||||
|
@ -1872,7 +1928,7 @@ void CL_UpdateServerList(boolean internetsearch, INT32 room)
|
||||||
/** Called by CL_ServerConnectionTicker
|
/** Called by CL_ServerConnectionTicker
|
||||||
*
|
*
|
||||||
* \param viams ???
|
* \param viams ???
|
||||||
* \param asksent ???
|
* \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
|
* \return False if the connection was aborted
|
||||||
* \sa CL_ServerConnectionTicker
|
* \sa CL_ServerConnectionTicker
|
||||||
* \sa CL_ConnectToServer
|
* \sa CL_ConnectToServer
|
||||||
|
@ -1965,9 +2021,12 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
|
||||||
), NULL, MM_NOTHING);
|
), NULL, MM_NOTHING);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cl_mode = CL_ASKDOWNLOADFILES;
|
||||||
|
|
||||||
// no problem if can't send packet, we will retry later
|
// no problem if can't send packet, we will retry later
|
||||||
if (CL_SendRequestFile())
|
//if (CL_SendRequestFile())
|
||||||
cl_mode = CL_DOWNLOADFILES;
|
// cl_mode = CL_DOWNLOADFILES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1997,7 +2056,7 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
|
||||||
* \param viams ???
|
* \param viams ???
|
||||||
* \param tmpsave The name of the gamestate file???
|
* \param tmpsave The name of the gamestate file???
|
||||||
* \param oldtic Used for knowing when to poll events and redraw
|
* \param oldtic Used for knowing when to poll events and redraw
|
||||||
* \param asksent ???
|
* \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
|
* \return False if the connection was aborted
|
||||||
* \sa CL_ServerConnectionSearchTicker
|
* \sa CL_ServerConnectionSearchTicker
|
||||||
* \sa CL_ConnectToServer
|
* \sa CL_ConnectToServer
|
||||||
|
@ -2035,6 +2094,7 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
|
||||||
/* FALLTHRU */
|
/* FALLTHRU */
|
||||||
|
|
||||||
case CL_ASKJOIN:
|
case CL_ASKJOIN:
|
||||||
|
cl_needsdownload = false;
|
||||||
CL_LoadServerFiles();
|
CL_LoadServerFiles();
|
||||||
#ifdef JOININGAME
|
#ifdef JOININGAME
|
||||||
// prepare structures to save the file
|
// prepare structures to save the file
|
||||||
|
@ -2043,9 +2103,23 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
|
||||||
CL_PrepareDownloadSaveGame(tmpsave);
|
CL_PrepareDownloadSaveGame(tmpsave);
|
||||||
#endif
|
#endif
|
||||||
if (CL_SendJoin())
|
if (CL_SendJoin())
|
||||||
|
{
|
||||||
|
*asksent = I_GetTime();
|
||||||
cl_mode = CL_WAITJOINRESPONSE;
|
cl_mode = CL_WAITJOINRESPONSE;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CL_ASKDOWNLOADFILES:
|
||||||
|
cl_needsdownload = true;
|
||||||
|
|
||||||
|
if (CL_SendJoin())
|
||||||
|
{
|
||||||
|
*asksent = I_GetTime();
|
||||||
|
cl_mode = CL_WAITDOWNLOADFILESRESPONSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOININGAME
|
#ifdef JOININGAME
|
||||||
case CL_DOWNLOADSAVEGAME:
|
case CL_DOWNLOADSAVEGAME:
|
||||||
// At this state, the first (and only) needed file is the gamestate
|
// At this state, the first (and only) needed file is the gamestate
|
||||||
|
@ -2059,7 +2133,19 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
case CL_CHALLENGE:
|
||||||
|
(*asksent) = I_GetTime() - NEWTICRATE; // Send password immediately upon entering
|
||||||
|
break;
|
||||||
|
|
||||||
case CL_WAITJOINRESPONSE:
|
case CL_WAITJOINRESPONSE:
|
||||||
|
case CL_WAITDOWNLOADFILESRESPONSE:
|
||||||
|
if (*asksent + NEWTICRATE < I_GetTime() && CL_SendJoin())
|
||||||
|
{
|
||||||
|
*asksent = I_GetTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case CL_CONNECTED:
|
case CL_CONNECTED:
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -2077,20 +2163,10 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
|
||||||
// Call it only once by tic
|
// Call it only once by tic
|
||||||
if (*oldtic != I_GetTime())
|
if (*oldtic != I_GetTime())
|
||||||
{
|
{
|
||||||
INT32 key;
|
|
||||||
|
|
||||||
I_OsPolling();
|
I_OsPolling();
|
||||||
key = I_GetKey();
|
D_ProcessEvents();
|
||||||
// Only ESC and non-keyboard keys abort connection
|
if (gamestate != GS_WAITINGPLAYERS)
|
||||||
if (key == KEY_ESCAPE || key >= KEY_MOUSE1)
|
|
||||||
{
|
|
||||||
CONS_Printf(M_GetText("Network game synchronization aborted.\n"));
|
|
||||||
// M_StartMessage(M_GetText("Network game synchronization aborted.\n\nPress ESC\n"), NULL, MM_NOTHING);
|
|
||||||
D_QuitNetGame();
|
|
||||||
CL_Reset();
|
|
||||||
D_StartTitle();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
// why are these here? this is for servers, we're a client
|
// why are these here? this is for servers, we're a client
|
||||||
//if (key == 's' && server)
|
//if (key == 's' && server)
|
||||||
|
@ -2119,6 +2195,71 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean CL_Responder(event_t *ev)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
INT32 ch;
|
||||||
|
|
||||||
|
if (!(client && cl_mode != CL_CONNECTED && cl_mode != CL_ABORTED))
|
||||||
|
return false; // Don't do anything outside of the connection screen
|
||||||
|
|
||||||
|
if (ev->type != ev_keydown)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ch = (INT32)ev->data1;
|
||||||
|
|
||||||
|
// Only ESC and non-keyboard keys abort connection
|
||||||
|
if (ch == KEY_ESCAPE || ch >= KEY_MOUSE1)
|
||||||
|
{
|
||||||
|
CONS_Printf(M_GetText("Network game synchronization aborted.\n"));
|
||||||
|
//M_StartMessage(M_GetText("Network game synchronization aborted.\n\nPress ESC\n"), NULL, MM_NOTHING);
|
||||||
|
D_QuitNetGame();
|
||||||
|
CL_Reset();
|
||||||
|
D_StartTitle();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cl_mode != CL_CHALLENGE)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && hu_font[ch-HU_FONTSTART])
|
||||||
|
|| ch == ' ') // Allow spaces, of course
|
||||||
|
{
|
||||||
|
len = strlen(cl_challengepassword);
|
||||||
|
if (len < 64)
|
||||||
|
{
|
||||||
|
cl_challengepassword[len+1] = 0;
|
||||||
|
cl_challengepassword[len] = CON_ShiftChar(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
cl_challengeattempted = 0;
|
||||||
|
}
|
||||||
|
else if (ch == KEY_BACKSPACE)
|
||||||
|
{
|
||||||
|
len = strlen(cl_challengepassword);
|
||||||
|
|
||||||
|
if (len > 0)
|
||||||
|
cl_challengepassword[len-1] = 0;
|
||||||
|
|
||||||
|
cl_challengeattempted = 0;
|
||||||
|
}
|
||||||
|
else if (ch == KEY_ENTER)
|
||||||
|
{
|
||||||
|
netgame = true;
|
||||||
|
multiplayer = true;
|
||||||
|
|
||||||
|
#ifndef NONET
|
||||||
|
SL_ClearServerList(servernode);
|
||||||
|
#endif
|
||||||
|
cl_mode = CL_SEARCHING;
|
||||||
|
|
||||||
|
D_ComputeChallengeAnswer(cl_challengequestion, cl_challengepassword, cl_challengeanswer);
|
||||||
|
cl_challengeattempted = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/** Use adaptive send using net_bandwidth and stat.sendbytes
|
/** Use adaptive send using net_bandwidth and stat.sendbytes
|
||||||
*
|
*
|
||||||
* \param viams ???
|
* \param viams ???
|
||||||
|
@ -2139,6 +2280,7 @@ static void CL_ConnectToServer(boolean viams)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
cl_mode = CL_SEARCHING;
|
cl_mode = CL_SEARCHING;
|
||||||
|
cl_challengenum = 0;
|
||||||
|
|
||||||
#ifdef CLIENT_LOADINGSCREEN
|
#ifdef CLIENT_LOADINGSCREEN
|
||||||
lastfilenum = -1;
|
lastfilenum = -1;
|
||||||
|
@ -2196,6 +2338,8 @@ static void CL_ConnectToServer(boolean viams)
|
||||||
SL_ClearServerList(servernode);
|
SL_ClearServerList(servernode);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
cl_challengeattempted = 0;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// If the connection was aborted for some reason, leave
|
// If the connection was aborted for some reason, leave
|
||||||
|
@ -3142,6 +3286,9 @@ void D_ClientServerInit(void)
|
||||||
gametic = 0;
|
gametic = 0;
|
||||||
localgametic = 0;
|
localgametic = 0;
|
||||||
|
|
||||||
|
memset(cl_challengequestion, 0x00, MD5_LEN+1);
|
||||||
|
memset(cl_challengeanswer, 0x00, MD5_LEN+1);
|
||||||
|
|
||||||
// do not send anything before the real begin
|
// do not send anything before the real begin
|
||||||
SV_StopServer();
|
SV_StopServer();
|
||||||
SV_ResetServer();
|
SV_ResetServer();
|
||||||
|
@ -3631,6 +3778,33 @@ static void HandleConnect(SINT8 node)
|
||||||
boolean newnode = false;
|
boolean newnode = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (node != servernode && !nodeingame[node] && D_IsJoinPasswordOn())
|
||||||
|
{
|
||||||
|
// Ensure node sent the correct password challenge
|
||||||
|
boolean passed = false;
|
||||||
|
|
||||||
|
if (netbuffer->u.clientcfg.challengenum && D_VerifyJoinPasswordChallenge(netbuffer->u.clientcfg.challengenum, netbuffer->u.clientcfg.challengeanswer))
|
||||||
|
passed = true;
|
||||||
|
|
||||||
|
if (!passed)
|
||||||
|
{
|
||||||
|
D_MakeJoinPasswordChallenge(&netbuffer->u.joinchallenge.challengenum, netbuffer->u.joinchallenge.question);
|
||||||
|
|
||||||
|
netbuffer->packettype = PT_JOINCHALLENGE;
|
||||||
|
HSendPacket(node, true, 0, sizeof(joinchallenge_pak));
|
||||||
|
Net_CloseConnection(node);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (netbuffer->u.clientcfg.needsdownload)
|
||||||
|
{
|
||||||
|
netbuffer->packettype = PT_DOWNLOADFILESOKAY;
|
||||||
|
HSendPacket(node, true, 0, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// client authorised to join
|
// client authorised to join
|
||||||
nodewaiting[node] = (UINT8)(netbuffer->u.clientcfg.localplayers - playerpernode[node]);
|
nodewaiting[node] = (UINT8)(netbuffer->u.clientcfg.localplayers - playerpernode[node]);
|
||||||
if (!nodeingame[node])
|
if (!nodeingame[node])
|
||||||
|
@ -3639,6 +3813,7 @@ static void HandleConnect(SINT8 node)
|
||||||
#ifndef NONET
|
#ifndef NONET
|
||||||
newnode = true;
|
newnode = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SV_AddNode(node);
|
SV_AddNode(node);
|
||||||
|
|
||||||
/// \note Wait what???
|
/// \note Wait what???
|
||||||
|
@ -3794,6 +3969,43 @@ static void HandlePacketFromAwayNode(SINT8 node)
|
||||||
Net_CloseConnection(node);
|
Net_CloseConnection(node);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PT_JOINCHALLENGE:
|
||||||
|
if (server && serverrunning)
|
||||||
|
{ // But wait I thought I'm the server?
|
||||||
|
Net_CloseConnection(node);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SERVERONLY
|
||||||
|
if (cl_mode == CL_WAITJOINRESPONSE || cl_mode == CL_WAITDOWNLOADFILESRESPONSE)
|
||||||
|
{
|
||||||
|
cl_challengenum = netbuffer->u.joinchallenge.challengenum;
|
||||||
|
memcpy(cl_challengequestion, netbuffer->u.joinchallenge.question, 16);
|
||||||
|
|
||||||
|
Net_CloseConnection(node|FORCECLOSE); // Don't need to stay connected while challenging
|
||||||
|
|
||||||
|
cl_mode = CL_CHALLENGE;
|
||||||
|
|
||||||
|
switch (cl_challengeattempted)
|
||||||
|
{
|
||||||
|
case 2:
|
||||||
|
// We already sent a correct password, so throw it back up again.
|
||||||
|
D_ComputeChallengeAnswer(cl_challengequestion, cl_challengepassword, cl_challengeanswer);
|
||||||
|
cl_mode = CL_ASKJOIN;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
// We entered the wrong password!
|
||||||
|
S_StartSound(NULL, sfx_s26d);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// First entry to the password screen.
|
||||||
|
S_StartSound(NULL, sfx_s224);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case PT_SERVERREFUSE: // Negative response of client join request
|
case PT_SERVERREFUSE: // Negative response of client join request
|
||||||
if (server && serverrunning)
|
if (server && serverrunning)
|
||||||
{ // But wait I thought I'm the server?
|
{ // But wait I thought I'm the server?
|
||||||
|
@ -3822,6 +4034,41 @@ static void HandlePacketFromAwayNode(SINT8 node)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PT_DOWNLOADFILESOKAY:
|
||||||
|
if (server && serverrunning)
|
||||||
|
{ // But wait I thought I'm the server?
|
||||||
|
Net_CloseConnection(node);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SERVERONLY
|
||||||
|
|
||||||
|
// This should've already been checked, but just to be safe...
|
||||||
|
if (!CL_CheckDownloadable())
|
||||||
|
{
|
||||||
|
D_QuitNetGame();
|
||||||
|
CL_Reset();
|
||||||
|
D_StartTitle();
|
||||||
|
M_StartMessage(M_GetText(
|
||||||
|
"You cannot connect to this server\n"
|
||||||
|
"because you cannot download the files\n"
|
||||||
|
"that you are missing from the server.\n\n"
|
||||||
|
"See the console or log file for\n"
|
||||||
|
"more details.\n\n"
|
||||||
|
"Press ESC\n"
|
||||||
|
), NULL, MM_NOTHING);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cl_challengeattempted == 1) // Successful password noise.
|
||||||
|
S_StartSound(NULL, sfx_s221);
|
||||||
|
|
||||||
|
cl_challengeattempted = 2;
|
||||||
|
CONS_Printf("trying to download\n");
|
||||||
|
if (CL_SendRequestFile())
|
||||||
|
cl_mode = CL_DOWNLOADFILES;
|
||||||
|
break;
|
||||||
|
|
||||||
case PT_SERVERCFG: // Positive response of client join request
|
case PT_SERVERCFG: // Positive response of client join request
|
||||||
{
|
{
|
||||||
INT32 j;
|
INT32 j;
|
||||||
|
@ -3837,6 +4084,9 @@ static void HandlePacketFromAwayNode(SINT8 node)
|
||||||
if (cl_mode != CL_WAITJOINRESPONSE)
|
if (cl_mode != CL_WAITJOINRESPONSE)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
if (cl_challengeattempted == 1) // Successful password noise.
|
||||||
|
S_StartSound(NULL, sfx_s221);
|
||||||
|
|
||||||
if (client)
|
if (client)
|
||||||
{
|
{
|
||||||
maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
|
maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
|
||||||
|
|
|
@ -13,11 +13,14 @@
|
||||||
#ifndef __D_CLISRV__
|
#ifndef __D_CLISRV__
|
||||||
#define __D_CLISRV__
|
#define __D_CLISRV__
|
||||||
|
|
||||||
|
#include "d_event.h"
|
||||||
#include "d_ticcmd.h"
|
#include "d_ticcmd.h"
|
||||||
#include "d_netcmd.h"
|
#include "d_netcmd.h"
|
||||||
#include "tables.h"
|
#include "tables.h"
|
||||||
#include "d_player.h"
|
#include "d_player.h"
|
||||||
|
|
||||||
|
#include "md5.h"
|
||||||
|
|
||||||
// Network play related stuff.
|
// Network play related stuff.
|
||||||
// There is a data struct that stores network
|
// There is a data struct that stores network
|
||||||
// communication related stuff, and another
|
// communication related stuff, and another
|
||||||
|
@ -73,6 +76,9 @@ typedef enum
|
||||||
PT_CLIENT4MIS,
|
PT_CLIENT4MIS,
|
||||||
PT_BASICKEEPALIVE,// Keep the network alive during wipes, as tics aren't advanced and NetUpdate isn't called
|
PT_BASICKEEPALIVE,// Keep the network alive during wipes, as tics aren't advanced and NetUpdate isn't called
|
||||||
|
|
||||||
|
PT_JOINCHALLENGE, // You must give a password to joinnnnn
|
||||||
|
PT_DOWNLOADFILESOKAY, // You can download files from the server....
|
||||||
|
|
||||||
PT_CANFAIL, // This is kind of a priority. Anything bigger than CANFAIL
|
PT_CANFAIL, // This is kind of a priority. Anything bigger than CANFAIL
|
||||||
// allows HSendPacket(*, true, *, *) to return false.
|
// allows HSendPacket(*, true, *, *) to return false.
|
||||||
// In addition, this packet can't occupy all the available slots.
|
// In addition, this packet can't occupy all the available slots.
|
||||||
|
@ -353,9 +359,21 @@ typedef struct
|
||||||
UINT8 version; // Different versions don't work
|
UINT8 version; // Different versions don't work
|
||||||
UINT8 subversion; // Contains build version
|
UINT8 subversion; // Contains build version
|
||||||
UINT8 localplayers;
|
UINT8 localplayers;
|
||||||
UINT8 mode;
|
UINT8 needsdownload;
|
||||||
|
UINT8 challengenum; // Non-zero if trying to join with a password attempt
|
||||||
|
UINT8 challengeanswer[MD5_LEN]; // Join challenge
|
||||||
} ATTRPACK clientconfig_pak;
|
} ATTRPACK clientconfig_pak;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
UINT8 challengenum; // Number to send back in join attempt
|
||||||
|
UINT8 question[MD5_LEN]; // Challenge data to be manipulated and answered with
|
||||||
|
} ATTRPACK joinchallenge_pak;
|
||||||
|
|
||||||
|
#define SV_SPEEDMASK 0x03
|
||||||
|
#define SV_DEDICATED 0x40
|
||||||
|
#define SV_PASSWORD 0x80
|
||||||
|
|
||||||
#define MAXSERVERNAME 32
|
#define MAXSERVERNAME 32
|
||||||
#define MAXFILENEEDED 915
|
#define MAXFILENEEDED 915
|
||||||
// This packet is too large
|
// This packet is too large
|
||||||
|
@ -368,7 +386,7 @@ typedef struct
|
||||||
UINT8 gametype;
|
UINT8 gametype;
|
||||||
UINT8 modifiedgame;
|
UINT8 modifiedgame;
|
||||||
UINT8 cheatsenabled;
|
UINT8 cheatsenabled;
|
||||||
UINT8 isdedicated;
|
UINT8 kartvars; // Previously isdedicated, now appropriated for our own nefarious purposes
|
||||||
UINT8 fileneedednum;
|
UINT8 fileneedednum;
|
||||||
SINT8 adminplayer;
|
SINT8 adminplayer;
|
||||||
tic_t time;
|
tic_t time;
|
||||||
|
@ -447,7 +465,8 @@ typedef struct
|
||||||
UINT8 resynchgot; //
|
UINT8 resynchgot; //
|
||||||
UINT8 textcmd[MAXTEXTCMD+1]; // 66049 bytes (wut??? 64k??? More like 257 bytes...)
|
UINT8 textcmd[MAXTEXTCMD+1]; // 66049 bytes (wut??? 64k??? More like 257 bytes...)
|
||||||
filetx_pak filetxpak; // 139 bytes
|
filetx_pak filetxpak; // 139 bytes
|
||||||
clientconfig_pak clientcfg; // 136 bytes
|
clientconfig_pak clientcfg; // 153 bytes
|
||||||
|
joinchallenge_pak joinchallenge; // 17 bytes
|
||||||
serverinfo_pak serverinfo; // 1024 bytes
|
serverinfo_pak serverinfo; // 1024 bytes
|
||||||
serverrefuse_pak serverrefuse; // 65025 bytes (somehow I feel like those values are garbage...)
|
serverrefuse_pak serverrefuse; // 65025 bytes (somehow I feel like those values are garbage...)
|
||||||
askinfo_pak askinfo; // 61 bytes
|
askinfo_pak askinfo; // 61 bytes
|
||||||
|
@ -555,6 +574,7 @@ void CL_RemoveSplitscreenPlayer(UINT8 p);
|
||||||
void CL_Reset(void);
|
void CL_Reset(void);
|
||||||
void CL_ClearPlayer(INT32 playernum);
|
void CL_ClearPlayer(INT32 playernum);
|
||||||
void CL_UpdateServerList(boolean internetsearch, INT32 room);
|
void CL_UpdateServerList(boolean internetsearch, INT32 room);
|
||||||
|
boolean CL_Responder(event_t *ev);
|
||||||
// Is there a game running
|
// Is there a game running
|
||||||
boolean Playing(void);
|
boolean Playing(void);
|
||||||
|
|
||||||
|
|
|
@ -203,6 +203,8 @@ static inline void D_ModifierKeyResponder(event_t *ev)
|
||||||
case KEY_RCTRL: ctrldown |= 0x2; return;
|
case KEY_RCTRL: ctrldown |= 0x2; return;
|
||||||
case KEY_LALT: altdown |= 0x1; return;
|
case KEY_LALT: altdown |= 0x1; return;
|
||||||
case KEY_RALT: altdown |= 0x2; return;
|
case KEY_RALT: altdown |= 0x2; return;
|
||||||
|
case KEY_CAPSLOCK: capslock = !capslock; return;
|
||||||
|
|
||||||
default: return;
|
default: return;
|
||||||
}
|
}
|
||||||
else if (ev->type == ev_keyup) switch (ev->data1)
|
else if (ev->type == ev_keyup) switch (ev->data1)
|
||||||
|
@ -236,6 +238,9 @@ void D_ProcessEvents(void)
|
||||||
if (M_ScreenshotResponder(ev))
|
if (M_ScreenshotResponder(ev))
|
||||||
continue; // ate the event
|
continue; // ate the event
|
||||||
|
|
||||||
|
if (CL_Responder(ev))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (gameaction == ga_nothing && gamestate == GS_TITLESCREEN)
|
if (gameaction == ga_nothing && gamestate == GS_TITLESCREEN)
|
||||||
{
|
{
|
||||||
if (cht_Responder(ev))
|
if (cht_Responder(ev))
|
||||||
|
|
15
src/d_net.c
15
src/d_net.c
|
@ -821,10 +821,6 @@ static const char *packettypename[NUMPACKETTYPE] =
|
||||||
"CLIENTMIS",
|
"CLIENTMIS",
|
||||||
"CLIENT2CMD",
|
"CLIENT2CMD",
|
||||||
"CLIENT2MIS",
|
"CLIENT2MIS",
|
||||||
"CLIENT3CMD",
|
|
||||||
"CLIENT3MIS",
|
|
||||||
"CLIENT4CMD",
|
|
||||||
"CLIENT4MIS",
|
|
||||||
"NODEKEEPALIVE",
|
"NODEKEEPALIVE",
|
||||||
"NODEKEEPALIVEMIS",
|
"NODEKEEPALIVEMIS",
|
||||||
"SERVERTICS",
|
"SERVERTICS",
|
||||||
|
@ -841,6 +837,15 @@ static const char *packettypename[NUMPACKETTYPE] =
|
||||||
"RESYNCHEND",
|
"RESYNCHEND",
|
||||||
"RESYNCHGET",
|
"RESYNCHGET",
|
||||||
|
|
||||||
|
"CLIENT3CMD",
|
||||||
|
"CLIENT3MIS",
|
||||||
|
"CLIENT4CMD",
|
||||||
|
"CLIENT4MIS",
|
||||||
|
"BASICKEEPALIVE",
|
||||||
|
|
||||||
|
"JOINCHALLENGE",
|
||||||
|
"DOWNLOADFILESOKAY",
|
||||||
|
|
||||||
"FILEFRAGMENT",
|
"FILEFRAGMENT",
|
||||||
"TEXTCMD",
|
"TEXTCMD",
|
||||||
"TEXTCMD2",
|
"TEXTCMD2",
|
||||||
|
@ -868,7 +873,7 @@ static void DebugPrintpacket(const char *header)
|
||||||
break;
|
break;
|
||||||
case PT_CLIENTJOIN:
|
case PT_CLIENTJOIN:
|
||||||
fprintf(debugfile, " number %d mode %d\n", netbuffer->u.clientcfg.localplayers,
|
fprintf(debugfile, " number %d mode %d\n", netbuffer->u.clientcfg.localplayers,
|
||||||
netbuffer->u.clientcfg.mode);
|
netbuffer->u.clientcfg.needsdownload);
|
||||||
break;
|
break;
|
||||||
case PT_SERVERTICS:
|
case PT_SERVERTICS:
|
||||||
{
|
{
|
||||||
|
|
145
src/d_netcmd.c
145
src/d_netcmd.c
|
@ -169,6 +169,7 @@ static void Got_Verification(UINT8 **cp, INT32 playernum);
|
||||||
static void Got_Removal(UINT8 **cp, INT32 playernum);
|
static void Got_Removal(UINT8 **cp, INT32 playernum);
|
||||||
static void Command_Verify_f(void);
|
static void Command_Verify_f(void);
|
||||||
static void Command_RemoveAdmin_f(void);
|
static void Command_RemoveAdmin_f(void);
|
||||||
|
static void Command_ChangeJoinPassword_f(void);
|
||||||
static void Command_MotD_f(void);
|
static void Command_MotD_f(void);
|
||||||
static void Got_MotD_f(UINT8 **cp, INT32 playernum);
|
static void Got_MotD_f(UINT8 **cp, INT32 playernum);
|
||||||
|
|
||||||
|
@ -534,6 +535,8 @@ void D_RegisterServerCommands(void)
|
||||||
RegisterNetXCmd(XD_PICKVOTE, Got_PickVotecmd);
|
RegisterNetXCmd(XD_PICKVOTE, Got_PickVotecmd);
|
||||||
|
|
||||||
// Remote Administration
|
// Remote Administration
|
||||||
|
CV_RegisterVar(&cv_dummyjoinpassword);
|
||||||
|
COM_AddCommand("joinpassword", Command_ChangeJoinPassword_f);
|
||||||
COM_AddCommand("password", Command_Changepassword_f);
|
COM_AddCommand("password", Command_Changepassword_f);
|
||||||
RegisterNetXCmd(XD_LOGIN, Got_Login);
|
RegisterNetXCmd(XD_LOGIN, Got_Login);
|
||||||
COM_AddCommand("login", Command_Login_f); // useful in dedicated to kick off remote admin
|
COM_AddCommand("login", Command_Login_f); // useful in dedicated to kick off remote admin
|
||||||
|
@ -3427,6 +3430,7 @@ static void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt,
|
||||||
|
|
||||||
if (len > 256-sl)
|
if (len > 256-sl)
|
||||||
len = 256-sl;
|
len = 256-sl;
|
||||||
|
|
||||||
memcpy(tmpbuf, buffer, len);
|
memcpy(tmpbuf, buffer, len);
|
||||||
memmove(&tmpbuf[len], salt, sl);
|
memmove(&tmpbuf[len], salt, sl);
|
||||||
//strcpy(&tmpbuf[len], salt);
|
//strcpy(&tmpbuf[len], salt);
|
||||||
|
@ -3440,7 +3444,7 @@ static void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt,
|
||||||
}
|
}
|
||||||
|
|
||||||
#define BASESALT "basepasswordstorage"
|
#define BASESALT "basepasswordstorage"
|
||||||
static UINT8 adminpassmd5[16];
|
static UINT8 adminpassmd5[MD5_LEN];
|
||||||
static boolean adminpasswordset = false;
|
static boolean adminpasswordset = false;
|
||||||
|
|
||||||
void D_SetPassword(const char *pw)
|
void D_SetPassword(const char *pw)
|
||||||
|
@ -3479,7 +3483,7 @@ static void Command_Login_f(void)
|
||||||
// If we have no MD5 support then completely disable XD_LOGIN responses for security.
|
// If we have no MD5 support then completely disable XD_LOGIN responses for security.
|
||||||
CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n");
|
CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n");
|
||||||
#else
|
#else
|
||||||
XBOXSTATIC UINT8 finalmd5[16];
|
XBOXSTATIC UINT8 finalmd5[MD5_LEN];
|
||||||
const char *pw;
|
const char *pw;
|
||||||
|
|
||||||
if (!netgame)
|
if (!netgame)
|
||||||
|
@ -3502,11 +3506,11 @@ static void Command_Login_f(void)
|
||||||
D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &finalmd5);
|
D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &finalmd5);
|
||||||
|
|
||||||
// Do the final pass to get the comparison the server will come up with
|
// Do the final pass to get the comparison the server will come up with
|
||||||
D_MD5PasswordPass(finalmd5, 16, va("PNUM%02d", consoleplayer), &finalmd5);
|
D_MD5PasswordPass(finalmd5, MD5_LEN, va("PNUM%02d", consoleplayer), &finalmd5);
|
||||||
|
|
||||||
CONS_Printf(M_GetText("Sending login... (Notice only given if password is correct.)\n"));
|
CONS_Printf(M_GetText("Sending login... (Notice only given if password is correct.)\n"));
|
||||||
|
|
||||||
SendNetXCmd(XD_LOGIN, finalmd5, 16);
|
SendNetXCmd(XD_LOGIN, finalmd5, MD5_LEN);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3517,9 +3521,9 @@ static void Got_Login(UINT8 **cp, INT32 playernum)
|
||||||
(void)cp;
|
(void)cp;
|
||||||
(void)playernum;
|
(void)playernum;
|
||||||
#else
|
#else
|
||||||
UINT8 sentmd5[16], finalmd5[16];
|
UINT8 sentmd5[MD5_LEN], finalmd5[MD5_LEN];
|
||||||
|
|
||||||
READMEM(*cp, sentmd5, 16);
|
READMEM(*cp, sentmd5, MD5_LEN);
|
||||||
|
|
||||||
if (client)
|
if (client)
|
||||||
return;
|
return;
|
||||||
|
@ -3531,9 +3535,9 @@ static void Got_Login(UINT8 **cp, INT32 playernum)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the final pass to compare with the sent md5
|
// Do the final pass to compare with the sent md5
|
||||||
D_MD5PasswordPass(adminpassmd5, 16, va("PNUM%02d", playernum), &finalmd5);
|
D_MD5PasswordPass(adminpassmd5, MD5_LEN, va("PNUM%02d", playernum), &finalmd5);
|
||||||
|
|
||||||
if (!memcmp(sentmd5, finalmd5, 16))
|
if (!memcmp(sentmd5, finalmd5, MD5_LEN))
|
||||||
{
|
{
|
||||||
CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[playernum]);
|
CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[playernum]);
|
||||||
COM_BufInsertText(va("promote %d\n", playernum)); // do this immediately
|
COM_BufInsertText(va("promote %d\n", playernum)); // do this immediately
|
||||||
|
@ -3702,6 +3706,131 @@ static void Got_Removal(UINT8 **cp, INT32 playernum)
|
||||||
CONS_Printf(M_GetText("You are no longer a server administrator.\n"));
|
CONS_Printf(M_GetText("You are no longer a server administrator.\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Join password stuff
|
||||||
|
consvar_t cv_dummyjoinpassword = {"dummyjoinpassword", "", CV_HIDEN|CV_NOSHOWHELP|CV_PASSWORD, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
|
||||||
|
|
||||||
|
#define NUMJOINCHALLENGES 32
|
||||||
|
static UINT8 joinpassmd5[MD5_LEN+1];
|
||||||
|
boolean joinpasswordset = false;
|
||||||
|
static UINT8 joinpasschallenges[NUMJOINCHALLENGES][MD5_LEN];
|
||||||
|
static tic_t joinpasschallengeson[NUMJOINCHALLENGES];
|
||||||
|
|
||||||
|
boolean D_IsJoinPasswordOn(void)
|
||||||
|
{
|
||||||
|
return joinpasswordset;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void GetChallengeAnswer(UINT8 *question, UINT8 *passwordmd5, UINT8 *answer)
|
||||||
|
{
|
||||||
|
D_MD5PasswordPass(question, MD5_LEN, (char *) passwordmd5, answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void D_ComputeChallengeAnswer(UINT8 *question, const char *pw, UINT8 *answer)
|
||||||
|
{
|
||||||
|
static UINT8 passwordmd5[MD5_LEN+1];
|
||||||
|
|
||||||
|
memset(passwordmd5, 0x00, MD5_LEN+1);
|
||||||
|
D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &passwordmd5);
|
||||||
|
GetChallengeAnswer(question, passwordmd5, answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void D_SetJoinPassword(const char *pw)
|
||||||
|
{
|
||||||
|
memset(joinpassmd5, 0x00, MD5_LEN+1);
|
||||||
|
D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &joinpassmd5);
|
||||||
|
joinpasswordset = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean D_VerifyJoinPasswordChallenge(UINT8 num, UINT8 *answer)
|
||||||
|
{
|
||||||
|
boolean passed = false;
|
||||||
|
|
||||||
|
num %= NUMJOINCHALLENGES;
|
||||||
|
|
||||||
|
//@TODO use a constant-time memcmp....
|
||||||
|
if (joinpasschallengeson[num] > 0 && memcmp(answer, joinpasschallenges[num], MD5_LEN) == 0)
|
||||||
|
passed = true;
|
||||||
|
|
||||||
|
// Wipe and reset the challenge so that it can't be tried against again, as a small measure against brute-force attacks.
|
||||||
|
memset(joinpasschallenges[num], 0x00, MD5_LEN);
|
||||||
|
joinpasschallengeson[num] = 0;
|
||||||
|
|
||||||
|
return passed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void D_MakeJoinPasswordChallenge(UINT8 *num, UINT8 *question)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < NUMJOINCHALLENGES; i++)
|
||||||
|
{
|
||||||
|
(*num) = M_RandomKey(NUMJOINCHALLENGES);
|
||||||
|
|
||||||
|
if (joinpasschallengeson[(*num)] == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (joinpasschallengeson[(*num)] > 0)
|
||||||
|
{
|
||||||
|
// Ugh, all challenges are (probably) taken. Let's find the oldest one and overwrite it.
|
||||||
|
tic_t oldesttic = INT32_MAX;
|
||||||
|
|
||||||
|
for (i = 0; i < NUMJOINCHALLENGES; i++)
|
||||||
|
{
|
||||||
|
if (joinpasschallengeson[i] < oldesttic)
|
||||||
|
{
|
||||||
|
(*num) = i;
|
||||||
|
oldesttic = joinpasschallengeson[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
joinpasschallengeson[(*num)] = I_GetTime();
|
||||||
|
|
||||||
|
memset(question, 0x00, MD5_LEN);
|
||||||
|
for (i = 0; i < MD5_LEN; i++)
|
||||||
|
question[i] = M_RandomByte();
|
||||||
|
|
||||||
|
// Store the answer in memory. What was the question again?
|
||||||
|
GetChallengeAnswer(question, joinpassmd5, joinpasschallenges[(*num)]);
|
||||||
|
|
||||||
|
// This ensures that num is always non-zero and will be valid when used for the answer
|
||||||
|
if ((*num) == 0)
|
||||||
|
(*num) = NUMJOINCHALLENGES;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote Administration
|
||||||
|
static void Command_ChangeJoinPassword_f(void)
|
||||||
|
{
|
||||||
|
#ifdef NOMD5
|
||||||
|
// If we have no MD5 support then completely disable XD_LOGIN responses for security.
|
||||||
|
CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n");
|
||||||
|
#else
|
||||||
|
if (client) // cannot change remotely
|
||||||
|
{
|
||||||
|
CONS_Printf(M_GetText("Only the server can use this.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (COM_Argc() != 2)
|
||||||
|
{
|
||||||
|
CONS_Printf(M_GetText("joinpassword <password>: set a password to join the server\nUse -remove to disable the password.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(COM_Argv(1), "-remove") == 0)
|
||||||
|
{
|
||||||
|
joinpasswordset = false;
|
||||||
|
CONS_Printf(M_GetText("Join password removed.\n"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
D_SetJoinPassword(COM_Argv(1));
|
||||||
|
CONS_Printf(M_GetText("Join password set.\n"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
static void Command_MotD_f(void)
|
static void Command_MotD_f(void)
|
||||||
{
|
{
|
||||||
size_t i, j;
|
size_t i, j;
|
||||||
|
|
|
@ -248,6 +248,14 @@ void RemoveAdminPlayer(INT32 playernum);
|
||||||
void ItemFinder_OnChange(void);
|
void ItemFinder_OnChange(void);
|
||||||
void D_SetPassword(const char *pw);
|
void D_SetPassword(const char *pw);
|
||||||
|
|
||||||
|
extern consvar_t cv_dummyjoinpassword;
|
||||||
|
extern boolean joinpasswordset;
|
||||||
|
boolean D_IsJoinPasswordOn(void);
|
||||||
|
void D_ComputeChallengeAnswer(UINT8 *question, const char *pw, UINT8 *answer);
|
||||||
|
void D_SetJoinPassword(const char *pw);
|
||||||
|
boolean D_VerifyJoinPasswordChallenge(UINT8 num, UINT8 *answer);
|
||||||
|
void D_MakeJoinPasswordChallenge(UINT8 *num, UINT8 *question);
|
||||||
|
|
||||||
// used for the player setup menu
|
// used for the player setup menu
|
||||||
UINT8 CanChangeSkin(INT32 playernum);
|
UINT8 CanChangeSkin(INT32 playernum);
|
||||||
|
|
||||||
|
|
|
@ -1225,8 +1225,6 @@ static INT16 typelines = 1; // number of drawfill lines we need when drawing the
|
||||||
//
|
//
|
||||||
boolean HU_Responder(event_t *ev)
|
boolean HU_Responder(event_t *ev)
|
||||||
{
|
{
|
||||||
INT32 c=0;
|
|
||||||
|
|
||||||
if (ev->type != ev_keydown)
|
if (ev->type != ev_keydown)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -1252,18 +1250,6 @@ boolean HU_Responder(event_t *ev)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
c = (INT32)ev->data1;
|
|
||||||
|
|
||||||
// capslock (now handled outside of chat on so that it works everytime......)
|
|
||||||
if (c && c == KEY_CAPSLOCK) // it's a toggle.
|
|
||||||
{
|
|
||||||
if (capslock)
|
|
||||||
capslock = false;
|
|
||||||
else
|
|
||||||
capslock = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef NONET
|
#ifndef NONET
|
||||||
if (!chat_on)
|
if (!chat_on)
|
||||||
{
|
{
|
||||||
|
@ -1291,6 +1277,7 @@ boolean HU_Responder(event_t *ev)
|
||||||
}
|
}
|
||||||
else // if chat_on
|
else // if chat_on
|
||||||
{
|
{
|
||||||
|
INT32 c = (INT32)ev->data1;
|
||||||
|
|
||||||
// Ignore modifier keys
|
// Ignore modifier keys
|
||||||
// Note that we do this here so users can still set
|
// Note that we do this here so users can still set
|
||||||
|
@ -1306,20 +1293,7 @@ boolean HU_Responder(event_t *ev)
|
||||||
&& ev->data1 != gamecontrol[gc_talkkey][1]))
|
&& ev->data1 != gamecontrol[gc_talkkey][1]))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
c = (INT32)ev->data1;
|
c = CON_ShiftChar(c);
|
||||||
|
|
||||||
// I know this looks very messy but this works. If it ain't broke, don't fix it!
|
|
||||||
// shift LETTERS to uppercase if we have capslock or are holding shift
|
|
||||||
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
|
|
||||||
{
|
|
||||||
if (shiftdown ^ capslock)
|
|
||||||
c = shiftxform[c];
|
|
||||||
}
|
|
||||||
else // if we're holding shift we should still shift non letter symbols
|
|
||||||
{
|
|
||||||
if (shiftdown)
|
|
||||||
c = shiftxform[c];
|
|
||||||
}
|
|
||||||
|
|
||||||
// pasting. pasting is cool. chat is a bit limited, though :(
|
// pasting. pasting is cool. chat is a bit limited, though :(
|
||||||
if (((c == 'v' || c == 'V') && ctrldown) && !CHAT_MUTE)
|
if (((c == 'v' || c == 'V') && ctrldown) && !CHAT_MUTE)
|
||||||
|
|
59
src/m_menu.c
59
src/m_menu.c
|
@ -948,9 +948,10 @@ static menuitem_t MP_MainMenu[] =
|
||||||
|
|
||||||
static menuitem_t MP_ServerMenu[] =
|
static menuitem_t MP_ServerMenu[] =
|
||||||
{
|
{
|
||||||
{IT_STRING|IT_CVAR, NULL, "Max. Player Count", &cv_maxplayers, 10},
|
{IT_STRING|IT_CVAR, NULL, "Max. Player Count", &cv_maxplayers, 0},
|
||||||
{IT_STRING|IT_CALL, NULL, "Room...", M_RoomMenu, 20},
|
{IT_STRING|IT_CALL, NULL, "Room...", M_RoomMenu, 10},
|
||||||
{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Server Name", &cv_servername, 30},
|
{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Server Name", &cv_servername, 20},
|
||||||
|
{IT_STRING|IT_CVAR|IT_CV_PASSWORD, NULL, "Password", &cv_dummyjoinpassword, 44},
|
||||||
|
|
||||||
{IT_STRING|IT_CVAR, NULL, "Game Type", &cv_newgametype, 68},
|
{IT_STRING|IT_CVAR, NULL, "Game Type", &cv_newgametype, 68},
|
||||||
{IT_STRING|IT_CVAR, NULL, "Level", &cv_nextmap, 78},
|
{IT_STRING|IT_CVAR, NULL, "Level", &cv_nextmap, 78},
|
||||||
|
@ -2383,6 +2384,9 @@ static void M_NextOpt(void)
|
||||||
{
|
{
|
||||||
INT16 oldItemOn = itemOn; // prevent infinite loop
|
INT16 oldItemOn = itemOn; // prevent infinite loop
|
||||||
|
|
||||||
|
if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_PASSWORD)
|
||||||
|
((consvar_t *)currentMenu->menuitems[itemOn].itemaction)->value = 0;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (itemOn + 1 > currentMenu->numitems - 1)
|
if (itemOn + 1 > currentMenu->numitems - 1)
|
||||||
|
@ -2396,6 +2400,9 @@ static void M_PrevOpt(void)
|
||||||
{
|
{
|
||||||
INT16 oldItemOn = itemOn; // prevent infinite loop
|
INT16 oldItemOn = itemOn; // prevent infinite loop
|
||||||
|
|
||||||
|
if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_PASSWORD)
|
||||||
|
((consvar_t *)currentMenu->menuitems[itemOn].itemaction)->value = 0;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (!itemOn)
|
if (!itemOn)
|
||||||
|
@ -2682,8 +2689,11 @@ boolean M_Responder(event_t *ev)
|
||||||
// BP: one of the more big hack i have never made
|
// BP: one of the more big hack i have never made
|
||||||
if (routine && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR)
|
if (routine && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR)
|
||||||
{
|
{
|
||||||
if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_STRING)
|
if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_STRING || (currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_PASSWORD)
|
||||||
{
|
{
|
||||||
|
if (ch == KEY_TAB && (currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_PASSWORD)
|
||||||
|
((consvar_t *)currentMenu->menuitems[itemOn].itemaction)->value ^= 1;
|
||||||
|
|
||||||
if (shiftdown && ch >= 32 && ch <= 127)
|
if (shiftdown && ch >= 32 && ch <= 127)
|
||||||
ch = shiftxform[ch];
|
ch = shiftxform[ch];
|
||||||
if (M_ChangeStringCvar(ch))
|
if (M_ChangeStringCvar(ch))
|
||||||
|
@ -3557,6 +3567,8 @@ static void M_DrawGenericMenu(void)
|
||||||
case IT_CVAR:
|
case IT_CVAR:
|
||||||
{
|
{
|
||||||
consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
|
consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
|
||||||
|
char asterisks[MAXSTRINGLENGTH+1];
|
||||||
|
size_t sl;
|
||||||
switch (currentMenu->menuitems[i].status & IT_CVARTYPE)
|
switch (currentMenu->menuitems[i].status & IT_CVARTYPE)
|
||||||
{
|
{
|
||||||
case IT_CV_SLIDER:
|
case IT_CV_SLIDER:
|
||||||
|
@ -3564,6 +3576,27 @@ static void M_DrawGenericMenu(void)
|
||||||
case IT_CV_NOPRINT: // color use this
|
case IT_CV_NOPRINT: // color use this
|
||||||
case IT_CV_INVISSLIDER: // monitor toggles use this
|
case IT_CV_INVISSLIDER: // monitor toggles use this
|
||||||
break;
|
break;
|
||||||
|
case IT_CV_PASSWORD:
|
||||||
|
if (i == itemOn)
|
||||||
|
{
|
||||||
|
V_DrawRightAlignedThinString(x + MAXSTRINGLENGTH*8 + 10, y, V_ALLOWLOWERCASE, va(M_GetText("Tab: %s password"), cv->value ? "hide" : "show"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cv->value || i != itemOn)
|
||||||
|
{
|
||||||
|
sl = strlen(cv->string);
|
||||||
|
memset(asterisks, '*', sl);
|
||||||
|
memset(asterisks + sl, 0, MAXSTRINGLENGTH+1-sl);
|
||||||
|
|
||||||
|
M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
|
||||||
|
V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, asterisks);
|
||||||
|
if (skullAnimCounter < 4 && i == itemOn)
|
||||||
|
V_DrawCharacter(x + 8 + V_StringWidth(asterisks, 0), y + 12,
|
||||||
|
'_' | 0x80, false);
|
||||||
|
y += 16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* fallthru */
|
||||||
case IT_CV_STRING:
|
case IT_CV_STRING:
|
||||||
M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
|
M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
|
||||||
V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, cv->string);
|
V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, cv->string);
|
||||||
|
@ -7241,6 +7274,7 @@ static void M_DrawConnectMenu(void)
|
||||||
{
|
{
|
||||||
UINT16 i, j;
|
UINT16 i, j;
|
||||||
const char *gt = "Unknown";
|
const char *gt = "Unknown";
|
||||||
|
const char *spd = "";
|
||||||
INT32 numPages = (serverlistcount+(SERVERS_PER_PAGE-1))/SERVERS_PER_PAGE;
|
INT32 numPages = (serverlistcount+(SERVERS_PER_PAGE-1))/SERVERS_PER_PAGE;
|
||||||
|
|
||||||
for (i = FIRSTSERVERLINE; i < min(localservercount, SERVERS_PER_PAGE)+FIRSTSERVERLINE; i++)
|
for (i = FIRSTSERVERLINE; i < min(localservercount, SERVERS_PER_PAGE)+FIRSTSERVERLINE; i++)
|
||||||
|
@ -7294,7 +7328,17 @@ static void M_DrawConnectMenu(void)
|
||||||
V_DrawSmallString(currentMenu->x+46,S_LINEY(i)+8, globalflags,
|
V_DrawSmallString(currentMenu->x+46,S_LINEY(i)+8, globalflags,
|
||||||
va("Players: %02d/%02d", serverlist[slindex].info.numberofplayer, serverlist[slindex].info.maxplayer));
|
va("Players: %02d/%02d", serverlist[slindex].info.numberofplayer, serverlist[slindex].info.maxplayer));
|
||||||
|
|
||||||
V_DrawSmallString(currentMenu->x+112, S_LINEY(i)+8, globalflags, va("Gametype: %s", gt));
|
V_DrawSmallString(currentMenu->x+112, S_LINEY(i)+8, globalflags, gt);
|
||||||
|
|
||||||
|
if (serverlist[slindex].info.gametype == GT_RACE)
|
||||||
|
{
|
||||||
|
spd = kartspeed_cons_t[serverlist[slindex].info.kartvars & SV_SPEEDMASK].strvalue;
|
||||||
|
|
||||||
|
V_DrawSmallString(currentMenu->x+132, S_LINEY(i)+8, globalflags, va("(%s Speed)", spd));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverlist[slindex].info.kartvars & SV_PASSWORD)
|
||||||
|
V_DrawFixedPatch((currentMenu->x - 9) << FRACBITS, (S_LINEY(i)) << FRACBITS, FRACUNIT, globalflags & (~V_ALLOWLOWERCASE), W_CachePatchName("SERVLOCK", PU_CACHE), NULL);
|
||||||
|
|
||||||
MP_ConnectMenu[i+FIRSTSERVERLINE].status = IT_STRING | IT_CALL;
|
MP_ConnectMenu[i+FIRSTSERVERLINE].status = IT_STRING | IT_CALL;
|
||||||
}
|
}
|
||||||
|
@ -7529,6 +7573,11 @@ static void M_StartServer(INT32 choice)
|
||||||
// Still need to reset devmode
|
// Still need to reset devmode
|
||||||
cv_debug = 0;
|
cv_debug = 0;
|
||||||
|
|
||||||
|
if (strlen(cv_dummyjoinpassword.string) > 0)
|
||||||
|
D_SetJoinPassword(cv_dummyjoinpassword.string);
|
||||||
|
else
|
||||||
|
joinpasswordset = false;
|
||||||
|
|
||||||
if (demoplayback)
|
if (demoplayback)
|
||||||
G_StopDemo();
|
G_StopDemo();
|
||||||
if (metalrecording)
|
if (metalrecording)
|
||||||
|
|
|
@ -104,6 +104,7 @@ boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt);
|
||||||
#define IT_CV_NOPRINT 1536
|
#define IT_CV_NOPRINT 1536
|
||||||
#define IT_CV_NOMOD 2048
|
#define IT_CV_NOMOD 2048
|
||||||
#define IT_CV_INVISSLIDER 2560
|
#define IT_CV_INVISSLIDER 2560
|
||||||
|
#define IT_CV_PASSWORD 3072
|
||||||
|
|
||||||
//call/submenu specific
|
//call/submenu specific
|
||||||
// There used to be a lot more here but ...
|
// There used to be a lot more here but ...
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
# include <limits.h>
|
# include <limits.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define MD5_LEN 16
|
||||||
|
|
||||||
/* The following contortions are an attempt to use the C preprocessor
|
/* The following contortions are an attempt to use the C preprocessor
|
||||||
to determine an unsigned integral type that is 32 bits wide. An
|
to determine an unsigned integral type that is 32 bits wide. An
|
||||||
alternative approach is to use autoconf's AC_CHECK_SIZEOF macro, but
|
alternative approach is to use autoconf's AC_CHECK_SIZEOF macro, but
|
||||||
|
|
|
@ -1556,7 +1556,6 @@ void *W_CachePatchName(const char *name, INT32 tag)
|
||||||
return W_CachePatchNum(num, tag);
|
return W_CachePatchNum(num, tag);
|
||||||
}
|
}
|
||||||
#ifndef NOMD5
|
#ifndef NOMD5
|
||||||
#define MD5_LEN 16
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prints an MD5 string into a human-readable textual format.
|
* Prints an MD5 string into a human-readable textual format.
|
||||||
|
|
Loading…
Reference in a new issue