Add password checking system

Co-authored-by: toaster <rollerorbital@gmail.com>
This commit is contained in:
James R 2024-03-22 01:04:20 -07:00
parent a0dcde98c1
commit acbb7eb463
11 changed files with 340 additions and 0 deletions

View file

@ -43,6 +43,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
m_memcpy.c
m_misc.cpp
m_perfstats.c
m_pw.cpp
m_pw_hash.c
m_random.c
m_queue.c

View file

@ -91,6 +91,7 @@
#include "k_credits.h"
#include "r_debug.hpp"
#include "k_director.h"
#include "m_pw.h"
#ifdef HWRENDER
#include "hardware/hw_main.h" // 3D View Rendering
@ -1746,6 +1747,8 @@ void D_SRB2Main(void)
CON_SetLoadingProgress(LOADED_PWAD);
M_PasswordInit();
//---------------------------------------------------- READY SCREEN
// we need to check for dedicated before initialization of some subsystems

View file

@ -469,6 +469,7 @@ void D_RegisterServerCommands(void)
#ifdef DEVELOP
COM_AddDebugCommand("fastforward", Command_FastForward);
COM_AddDebugCommand("crypt", Command_Crypt_f);
#endif
K_RegisterMidVoteCVars();

View file

@ -10,6 +10,8 @@
/// \file deh_soc.c
/// \brief Load SOC file and change tables and text
#include "modp_b64/modp_b64.h"
#include "doomdef.h"
#include "d_main.h" // for srb2home
#include "g_game.h"
@ -42,6 +44,7 @@
#endif
#include "m_cond.h"
#include "m_pw_hash.h"
#include "dehacked.h"
#include "deh_soc.h"
@ -2589,6 +2592,27 @@ static void readcondition(UINT16 set, UINT32 id, char *word2)
stringvar = Z_StrDup(spos);
}
else if (fastcmp(params[0], "PASSWORD"))
{
size_t slen = strlen(spos);
EXTENDEDPARAMCHECK(spos, 1);
ty = UC_PASSWORD;
if (slen > modp_b64_encode_len(M_PW_BUF_SIZE)-1)
{
deh_warning("Password hash is invalid");
return;
}
stringvar = Z_Malloc(modp_b64_decode_len(slen), PU_STATIC, NULL);
if (modp_b64_decode(stringvar, spos, slen) != M_PW_BUF_SIZE)
{
deh_warning("Password hash is invalid");
Z_Free(stringvar);
return;
}
}
if (ty != UC_NONE)
goto setcondition;

View file

@ -88,6 +88,9 @@ void Command_Goto_f(void);
void Command_Angle_f(void);
void Command_RespawnAt_f(void);
void Command_GotoSkybox_f(void);
#ifdef DEVELOP
void Command_Crypt_f(void);
#endif
#ifdef _DEBUG
void Command_CauseCfail_f(void);
#endif

View file

@ -1608,6 +1608,8 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
return false;
case UC_TUTORIALSKIP:
return (gamedata->finishedtutorialchallenge == true);
case UC_PASSWORD:
return (cn->stringvar == NULL);
case UC_SPRAYCAN:
{
@ -2475,6 +2477,8 @@ static const char *M_GetConditionString(condition_t *cn)
return NULL;
case UC_TUTORIALSKIP:
return "successfully skip the Tutorial";
case UC_PASSWORD:
return "enter a secret password";
case UC_SPRAYCAN:
{

View file

@ -67,6 +67,8 @@ typedef enum
UC_CRASH, // Hee ho !
UC_TUTORIALSKIP, // Complete the Tutorial Challenge
UC_PASSWORD, // Type in something funny
UC_SPRAYCAN, // Grab a spraycan
UC_PRISONEGGCD, // Grab a CD from a Prison Egg

225
src/m_pw.cpp Normal file
View file

@ -0,0 +1,225 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Vivian "toastergrl" Grannell.
// Copyright (C) 2024 by James Robert Roman.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include <algorithm>
#include <array>
#include <cctype>
#include <fstream>
#include <future>
#include <stdexcept>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "modp_b64/modp_b64.h"
#include "cxxutil.hpp"
#include "command.h"
#include "d_main.h"
#include "doomdef.h"
#include "doomstat.h"
#include "doomtype.h"
#include "g_game.h"
#include "k_menu.h"
#include "m_cheat.h"
#include "m_cond.h"
#include "m_pw.h"
#include "m_pw_hash.h"
#include "s_sound.h"
#include "sounds.h"
#include "stun.h" // csprng
#include "z_zone.h"
namespace
{
struct Pw
{
Pw(void (*cb)(), const char *encoded_hash) : cb_(cb), hash_(decode_hash(encoded_hash)) {}
void (*cb_)();
const std::array<UINT8, M_PW_BUF_SIZE> hash_;
private:
static std::array<UINT8, M_PW_BUF_SIZE> decode_hash(std::string encoded)
{
std::array<UINT8, M_PW_BUF_SIZE> decoded;
if (modp::b64_decode(encoded).size() != decoded.size())
throw std::invalid_argument("hash is incorrectly sized");
std::copy(encoded.begin(), encoded.end(), decoded.begin());
return decoded;
}
};
std::vector<Pw> passwords;
// m_cond.c
template <typename F>
void iter_conditions(F&& f)
{
UINT32 i, j;
conditionset_t *c;
condition_t *cn;
for (i = 0; i < MAXCONDITIONSETS; ++i)
{
c = &conditionSets[i];
if (!c->numconditions || gamedata->achieved[i])
continue;
for (j = 0; j < c->numconditions; ++j)
{
cn = &c->condition[j];
if (cn->type != UC_PASSWORD)
continue;
if (cn->stringvar == NULL)
continue;
f(cn);
}
}
}
}; // namespace
try_password_e M_TryPassword(const char *password, boolean conditions)
{
using var = std::variant<std::monostate, condition_t*, Pw*>;
// Normalize input casing
std::string key = password;
strlwr(key.data());
auto worker = [&key](const UINT8* hash, var result)
{
if (M_HashCompare(hash, key.c_str()))
result = std::monostate {}; // fail state
return result;
};
// Because hashing is time consuming, do the work in parallel.
std::vector<std::future<var>> jobs;
auto add_job = [&](auto&&... args)
{
jobs.push_back(std::move(std::async(std::launch::async, worker, args...)));
};
for (Pw& pw : passwords)
add_job(pw.hash_.data(), &pw);
// Only consider challenges passwords as needed.
if (conditions)
iter_conditions([&](condition_t* cn) { add_job((const UINT8*)cn->stringvar, cn); });
var result;
for (auto& job : jobs)
{
SRB2_ASSERT(job.valid());
// Wait for every thread to finish, then retrieve the last matched password (if any).
if (var n = job.get(); !std::holds_alternative<std::monostate>(n))
result = n;
}
try_password_e return_code = M_PW_INVALID;
if (!std::holds_alternative<std::monostate>(result))
{
// Evaluate the password's function.
auto visitor = srb2::Overload {
[&](condition_t* cn)
{
// Remove the password for this session.
Z_Free(cn->stringvar);
cn->stringvar = NULL;
return_code = M_PW_CHALLENGES;
},
[&](Pw* pw)
{
pw->cb_();
return_code = M_PW_EXTRAS;
},
[](std::monostate) {},
};
std::visit(visitor, result);
}
return return_code;
}
#ifdef DEVELOP
void Command_Crypt_f(void)
{
if (COM_Argc() == 1)
{
CONS_Printf(
"crypt <password>: generate a password hash\n"
"crypt -i <file>: generate multiple hashes by reading from file\n"
);
return;
}
auto gen = [](char *input)
{
UINT8 bin[M_PW_BUF_SIZE];
UINT8* salt = &bin[M_PW_HASH_SIZE];
csprng(salt, M_PW_SALT_SIZE); // randomize salt
strlwr(input);
M_HashPassword(bin, input, salt);
CONS_Printf("%s %s\n", input, modp::b64_encode((const char*)bin, M_PW_BUF_SIZE).c_str());
};
if (!stricmp(COM_Argv(1), "-i"))
{
if (COM_Argc() != 3)
{
CONS_Printf("crypt: missing file argument\n");
return;
}
std::ifstream file{va(pandf, srb2home, COM_Argv(2))};
if (!file.is_open())
{
CONS_Printf("crypt: file error\n");
return;
}
for (std::string line; std::getline(file, line);)
{
// remove comments
std::size_t p = line.find("#");
if (p == line.npos)
p = line.size();
// remove trailing whitespace
while (p > 0 && std::isspace(line[p - 1]))
p--;
line.erase(p);
// ignore empty or completely filtered lines
if (!line.empty())
gen(line.data());
}
return;
}
gen(COM_Args());
}
#endif
void M_PasswordInit(void)
{
}

34
src/m_pw.h Normal file
View file

@ -0,0 +1,34 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2024 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef m_pw_H
#define m_pw_H
#include "doomtype.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum
{
M_PW_INVALID,
M_PW_EXTRAS,
M_PW_CHALLENGES,
}
try_password_e;
void M_PasswordInit(void);
try_password_e M_TryPassword(const char *password, boolean challenges);
#ifdef __cplusplus
} // extern "C"
#endif
#endif/*m_pw_H*/

View file

@ -7,6 +7,7 @@
#include "../s_sound.h"
#include "../f_finale.h"
#include "../k_credits.h"
#include "../m_pw.h"
static void M_Credits(INT32 choice)
{
@ -170,6 +171,31 @@ void M_ExtrasTick(void)
extrasmenu.textx = 160;
extrasmenu.texty = 50;
}
if (menutyping.active == false && cv_dummyextraspassword.string[0] != '\0')
{
switch (M_TryPassword(cv_dummyextraspassword.string, true))
{
case M_PW_CHALLENGES:
if (M_UpdateUnlockablesAndExtraEmblems(true, true))
{
M_Challenges(0);
}
break;
case M_PW_EXTRAS:
if (menuactive == true)
{
M_InitExtras(-1);
}
break;
default:
break;
}
CV_StealthSet(&cv_dummyextraspassword, "");
}
}
boolean M_ExtrasInputs(INT32 ch)

View file

@ -15,6 +15,7 @@
#include "../r_main.h"
#include "../m_easing.h"
#include "../g_input.h"
#include "../m_pw.h"
#include <forward_list>
@ -851,6 +852,22 @@ void M_GonerTick(void)
if (menutyping.active || menumessage.active || P_AutoPause())
return;
if (cv_dummyextraspassword.string[0] != '\0')
{
// Challenges are not interpreted at this stage.
// See M_ExtraTick for the full behaviour.
if (M_TryPassword(cv_dummyextraspassword.string, false) != M_PW_EXTRAS)
{
goner_delay = 0;
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE,
"Aha! Nice try. You're tricky enough WITHOUT admin access, thank you.");
M_GonerHidePassword();
}
CV_StealthSet(&cv_dummyextraspassword, "");
}
if (goner_typewriter.textDone)
{
if (!LinesOutput.empty())