mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
687 lines
17 KiB
C++
687 lines
17 KiB
C++
// DR. ROBOTNIK'S RING RACERS
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 2024 by "Lat'".
|
|
// Copyright (C) 2024 by AJ "Tyron" Martinez.
|
|
// Copyright (C) 2024 by Kart Krew.
|
|
//
|
|
// 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 k_profiles.c
|
|
/// \brief implements methods for profiles etc.
|
|
|
|
#include <algorithm>
|
|
|
|
#include <fmt/format.h>
|
|
|
|
#include "io/streams.hpp"
|
|
#include "doomtype.h"
|
|
#include "d_main.h" // pandf
|
|
#include "byteptr.h" // READ/WRITE macros
|
|
#include "p_saveg.h" // savebuffer_t
|
|
#include "m_misc.h" //FIL_WriteFile()
|
|
#include "k_profiles.h"
|
|
#include "z_zone.h"
|
|
#include "r_skins.h"
|
|
#include "monocypher/monocypher.h"
|
|
#include "stun.h"
|
|
#include "k_color.h"
|
|
#include "command.h"
|
|
|
|
extern "C" consvar_t cv_dummyprofilefov, cv_fov[MAXSPLITSCREENPLAYERS];
|
|
|
|
CV_PossibleValue_t lastprofile_cons_t[] = {{-1, "MIN"}, {MAXPROFILES, "MAX"}, {0, NULL}};
|
|
|
|
// List of all the profiles.
|
|
static profile_t *profilesList[MAXPROFILES+1]; // +1 because we're gonna add a default "GUEST' profile.
|
|
static UINT8 numprofiles = 0; // # of loaded profiles
|
|
|
|
INT32 PR_GetNumProfiles(void)
|
|
{
|
|
return numprofiles;
|
|
}
|
|
|
|
static void PR_GenerateProfileKeys(profile_t *newprofile)
|
|
{
|
|
static uint8_t seed[32];
|
|
csprng(seed, 32);
|
|
crypto_eddsa_key_pair(newprofile->secret_key, newprofile->public_key, seed);
|
|
}
|
|
|
|
profile_t* PR_MakeProfile(
|
|
const char *prname,
|
|
const char *pname,
|
|
const char *sname, const UINT16 col,
|
|
const char *fname, const UINT16 fcol,
|
|
INT32 controlarray[num_gamecontrols][MAXINPUTMAPPING],
|
|
boolean guest)
|
|
{
|
|
profile_t *newprofile = static_cast<profile_t*>(Z_Calloc(sizeof(profile_t), PU_STATIC, NULL));
|
|
|
|
newprofile->version = PROFILEVER;
|
|
|
|
memset(newprofile->secret_key, 0, sizeof(newprofile->secret_key));
|
|
memset(newprofile->public_key, 0, sizeof(newprofile->public_key));
|
|
|
|
if (!guest)
|
|
{
|
|
PR_GenerateProfileKeys(newprofile);
|
|
}
|
|
|
|
strcpy(newprofile->profilename, prname);
|
|
newprofile->profilename[sizeof newprofile->profilename - 1] = '\0';
|
|
|
|
strcpy(newprofile->skinname, sname);
|
|
strcpy(newprofile->playername, pname);
|
|
newprofile->color = col;
|
|
|
|
strcpy(newprofile->follower, fname);
|
|
newprofile->followercolor = fcol;
|
|
newprofile->kickstartaccel = false;
|
|
newprofile->autoroulette = false;
|
|
newprofile->litesteer = true;
|
|
newprofile->rumble = true;
|
|
newprofile->fov = atoi(cv_dummyprofilefov.defaultvalue);
|
|
|
|
// Copy from gamecontrol directly as we'll be setting controls up directly in the profile.
|
|
memcpy(newprofile->controls, controlarray, sizeof(newprofile->controls));
|
|
|
|
newprofile->wins = 0;
|
|
newprofile->rounds = 0;
|
|
|
|
return newprofile;
|
|
}
|
|
|
|
profile_t* PR_MakeProfileFromPlayer(const char *prname, const char *pname, const char *sname, const UINT16 col, const char *fname, UINT16 fcol, UINT8 pnum)
|
|
{
|
|
// Generate profile using the player's gamecontrol, as we set them directly when making profiles from menus.
|
|
profile_t *newprofile = PR_MakeProfile(prname, pname, sname, col, fname, fcol, gamecontrol[pnum], false);
|
|
|
|
// Player bound cvars:
|
|
newprofile->kickstartaccel = cv_kickstartaccel[pnum].value;
|
|
newprofile->autoroulette = cv_autoroulette[pnum].value;
|
|
newprofile->litesteer = cv_litesteer[pnum].value;
|
|
newprofile->rumble = cv_rumble[pnum].value;
|
|
newprofile->fov = cv_fov[pnum].value / FRACUNIT;
|
|
|
|
return newprofile;
|
|
}
|
|
|
|
boolean PR_AddProfile(profile_t *p)
|
|
{
|
|
if (numprofiles < MAXPROFILES+1)
|
|
{
|
|
profilesList[numprofiles] = p;
|
|
numprofiles++;
|
|
|
|
CONS_Printf("Profile '%s' added\n", p->profilename);
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
profile_t* PR_GetProfile(INT32 num)
|
|
{
|
|
if (num < numprofiles)
|
|
return profilesList[num];
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
boolean PR_DeleteProfile(INT32 num)
|
|
{
|
|
UINT8 i;
|
|
profile_t* sacrifice;
|
|
|
|
if (num <= 0 || num > numprofiles)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
sacrifice = profilesList[num];
|
|
|
|
// If we're deleting inbetween profiles, move everything.
|
|
if (num < numprofiles)
|
|
{
|
|
for (i = num; i < numprofiles-1; i++)
|
|
{
|
|
profilesList[i] = profilesList[i+1];
|
|
}
|
|
|
|
// Make sure to move cv_lastprofile (and title/current profile) values as well!
|
|
for (i = 0; i < MAXSPLITSCREENPLAYERS+2; i++)
|
|
{
|
|
consvar_t *cv;
|
|
|
|
if (i < MAXSPLITSCREENPLAYERS)
|
|
cv = &cv_lastprofile[i];
|
|
else if (i == MAXSPLITSCREENPLAYERS)
|
|
cv = &cv_ttlprofilen;
|
|
else
|
|
cv = &cv_currprofile;
|
|
|
|
if (cv->value < num)
|
|
{
|
|
// Not affected.
|
|
continue;
|
|
}
|
|
|
|
if (cv->value > num)
|
|
{
|
|
// Shift our lastprofile number down to match the new order.
|
|
CV_StealthSetValue(cv, cv->value-1);
|
|
continue;
|
|
}
|
|
|
|
if (cv != &cv_currprofile)
|
|
{
|
|
// There's no hope for it. If we were on the deleted profile, default back to guest.
|
|
CV_StealthSetValue(cv, PROFILE_GUEST);
|
|
continue;
|
|
}
|
|
|
|
// Oh boy, now we're really in for it.
|
|
CV_StealthSetValue(cv, -1);
|
|
}
|
|
}
|
|
|
|
// In any case, delete the last profile as well.
|
|
profilesList[numprofiles] = NULL;
|
|
numprofiles--;
|
|
|
|
PR_SaveProfiles();
|
|
|
|
// Finally, clear up our memory!
|
|
Z_Free(sacrifice);
|
|
|
|
return true;
|
|
}
|
|
|
|
void PR_InitNewProfile(void)
|
|
{
|
|
char pname[PROFILENAMELEN+1] = "PRF";
|
|
profile_t *dprofile;
|
|
UINT8 usenum = numprofiles-1;
|
|
UINT8 i;
|
|
boolean nameok = false;
|
|
|
|
pname[4] = '\0';
|
|
|
|
// When deleting profile, it's possible to do some pretty wacko stuff that would lead a new fresh profile to share the same name as another profile we have never changed the name of.
|
|
// This could become an infinite loop if MAXPROFILES >= 26.
|
|
while (!nameok)
|
|
{
|
|
pname[3] = 'A'+usenum;
|
|
|
|
for (i = 0; i < numprofiles; i++)
|
|
{
|
|
profile_t *pr = PR_GetProfile(i);
|
|
if (!strcmp(pr->profilename, pname))
|
|
{
|
|
usenum++;
|
|
if (pname[3] == 'Z')
|
|
usenum = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
// if we got here, then it means the name is okay!
|
|
if (i == numprofiles-1)
|
|
nameok = true;
|
|
}
|
|
}
|
|
|
|
dprofile = PR_MakeProfile(
|
|
pname,
|
|
PROFILEDEFAULTPNAME,
|
|
PROFILEDEFAULTSKIN, PROFILEDEFAULTCOLOR,
|
|
PROFILEDEFAULTFOLLOWER, PROFILEDEFAULTFOLLOWERCOLOR,
|
|
gamecontroldefault,
|
|
false
|
|
);
|
|
PR_AddProfile(dprofile);
|
|
}
|
|
|
|
void PR_SaveProfiles(void)
|
|
{
|
|
namespace fs = std::filesystem;
|
|
using json = nlohmann::json;
|
|
using namespace srb2;
|
|
namespace io = srb2::io;
|
|
|
|
if (profilesList[PROFILE_GUEST] == NULL)
|
|
{
|
|
// Profiles have not been loaded yet, don't overwrite with garbage.
|
|
return;
|
|
}
|
|
|
|
ProfilesJson ng{};
|
|
|
|
for (size_t i = 1; i < numprofiles; i++)
|
|
{
|
|
ProfileJson jsonprof;
|
|
profile_t* cprof = profilesList[i];
|
|
|
|
jsonprof.version = PROFILEVER;
|
|
jsonprof.profilename = std::string(cprof->profilename);
|
|
std::copy(std::begin(cprof->public_key), std::end(cprof->public_key), std::begin(jsonprof.publickey));
|
|
std::copy(std::begin(cprof->secret_key), std::end(cprof->secret_key), std::begin(jsonprof.secretkey));
|
|
jsonprof.playername = std::string(cprof->playername);
|
|
jsonprof.skinname = std::string(cprof->skinname);
|
|
jsonprof.colorname = std::string(skincolors[cprof->color].name);
|
|
jsonprof.followername = std::string(cprof->follower);
|
|
if (cprof->followercolor == FOLLOWERCOLOR_MATCH)
|
|
{
|
|
jsonprof.followercolorname = "Match";
|
|
}
|
|
else if (cprof->followercolor == FOLLOWERCOLOR_OPPOSITE)
|
|
{
|
|
jsonprof.followercolorname = "Opposite";
|
|
}
|
|
else if (cprof->followercolor == SKINCOLOR_NONE)
|
|
{
|
|
jsonprof.followercolorname = "Default";
|
|
}
|
|
else if (cprof->followercolor >= numskincolors)
|
|
{
|
|
jsonprof.followercolorname = std::string();
|
|
}
|
|
else
|
|
{
|
|
jsonprof.followercolorname = std::string(skincolors[cprof->followercolor].name);
|
|
}
|
|
jsonprof.records.wins = cprof->wins;
|
|
jsonprof.records.rounds = cprof->rounds;
|
|
jsonprof.preferences.kickstartaccel = cprof->kickstartaccel;
|
|
jsonprof.preferences.autoroulette = cprof->autoroulette;
|
|
jsonprof.preferences.litesteer = cprof->litesteer;
|
|
jsonprof.preferences.rumble = cprof->rumble;
|
|
jsonprof.preferences.fov = cprof->fov;
|
|
|
|
for (size_t j = 0; j < num_gamecontrols; j++)
|
|
{
|
|
for (size_t k = 0; k < MAXINPUTMAPPING; k++)
|
|
{
|
|
jsonprof.controls[j][k] = cprof->controls[j][k];
|
|
}
|
|
}
|
|
|
|
ng.profiles.emplace_back(std::move(jsonprof));
|
|
}
|
|
|
|
std::vector<uint8_t> ubjson = json::to_ubjson(ng);
|
|
|
|
std::string realpath = fmt::format("{}/{}", srb2home, PROFILESFILE);
|
|
std::string tmppath = fmt::format("{}.tmp", realpath);
|
|
|
|
try
|
|
{
|
|
io::FileStream file {tmppath, io::FileStreamMode::kWrite};
|
|
io::BufferedOutputStream<io::FileStream> bos {std::move(file)};
|
|
|
|
io::write(static_cast<uint32_t>(0x52494E47), bos, io::Endian::kBE); // "RING"
|
|
io::write(static_cast<uint32_t>(0x5052464C), bos, io::Endian::kBE); // "PRFL"
|
|
io::write(static_cast<uint8_t>(0), bos); // reserved1
|
|
io::write(static_cast<uint8_t>(0), bos); // reserved2
|
|
io::write(static_cast<uint8_t>(0), bos); // reserved3
|
|
io::write(static_cast<uint8_t>(0), bos); // reserved4
|
|
io::write_exact(bos, tcb::as_bytes(tcb::make_span(ubjson)));
|
|
bos.flush();
|
|
file = bos.stream();
|
|
file.close();
|
|
|
|
fs::rename(tmppath, realpath);
|
|
}
|
|
catch (...)
|
|
{
|
|
I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder?");
|
|
}
|
|
}
|
|
|
|
void PR_LoadProfiles(void)
|
|
{
|
|
namespace fs = std::filesystem;
|
|
using namespace srb2;
|
|
namespace io = srb2::io;
|
|
using json = nlohmann::json;
|
|
|
|
profile_t *dprofile = PR_MakeProfile(
|
|
PROFILEDEFAULTNAME,
|
|
PROFILEDEFAULTPNAME,
|
|
PROFILEDEFAULTSKIN, PROFILEDEFAULTCOLOR,
|
|
PROFILEDEFAULTFOLLOWER, PROFILEDEFAULTFOLLOWERCOLOR,
|
|
gamecontroldefault,
|
|
true
|
|
);
|
|
|
|
std::string datapath {fmt::format("{}/{}", srb2home, PROFILESFILE)};
|
|
|
|
io::BufferedInputStream<io::FileStream> bis;
|
|
try
|
|
{
|
|
io::FileStream file {datapath, io::FileStreamMode::kRead};
|
|
bis = io::BufferedInputStream(std::move(file));
|
|
}
|
|
catch (const io::FileStreamException& ex)
|
|
{
|
|
PR_AddProfile(dprofile);
|
|
return;
|
|
}
|
|
|
|
ProfilesJson js;
|
|
try
|
|
{
|
|
uint32_t magic1;
|
|
uint32_t magic2;
|
|
uint8_t reserved1;
|
|
uint8_t reserved2;
|
|
uint8_t reserved3;
|
|
uint8_t reserved4;
|
|
magic1 = io::read_uint32(bis, io::Endian::kBE);
|
|
magic2 = io::read_uint32(bis, io::Endian::kBE);
|
|
reserved1 = io::read_uint8(bis);
|
|
reserved2 = io::read_uint8(bis);
|
|
reserved3 = io::read_uint8(bis);
|
|
reserved4 = io::read_uint8(bis);
|
|
|
|
if (magic1 != 0x52494E47 || magic2 != 0x5052464C || reserved1 != 0 || reserved2 != 0 || reserved3 != 0 || reserved4 != 0)
|
|
{
|
|
throw std::domain_error("Header is incompatible");
|
|
}
|
|
|
|
std::vector<std::byte> remainder = io::read_to_vec(bis);
|
|
// safety: std::byte repr is always uint8_t 1-byte aligned
|
|
tcb::span<uint8_t> remainder_as_u8 = tcb::span((uint8_t*)remainder.data(), remainder.size());
|
|
json parsed = json::from_ubjson(remainder_as_u8);
|
|
js = parsed.template get<ProfilesJson>();
|
|
}
|
|
catch (...)
|
|
{
|
|
I_Error("Profiles file is corrupt");
|
|
return;
|
|
}
|
|
|
|
numprofiles = js.profiles.size() + 1; // 1 for guest
|
|
if (numprofiles > MAXPROFILES+1)
|
|
{
|
|
numprofiles = MAXPROFILES+1;
|
|
}
|
|
|
|
for (size_t i = 1; i < numprofiles; i++)
|
|
{
|
|
auto& jsprof = js.profiles[i - 1];
|
|
profile_t* newprof = static_cast<profile_t*>(Z_Calloc(sizeof(profile_t), PU_STATIC, NULL));
|
|
profilesList[i] = newprof;
|
|
|
|
newprof->version = jsprof.version;
|
|
strlcpy(newprof->profilename, jsprof.profilename.c_str(), sizeof(newprof->profilename));
|
|
memcpy(newprof->public_key, jsprof.publickey.data(), sizeof(newprof->public_key));
|
|
memcpy(newprof->secret_key, jsprof.secretkey.data(), sizeof(newprof->secret_key));
|
|
|
|
strlcpy(newprof->playername, jsprof.playername.c_str(), sizeof(newprof->playername));
|
|
strlcpy(newprof->skinname, jsprof.skinname.c_str(), sizeof(newprof->skinname));
|
|
newprof->color = PROFILEDEFAULTCOLOR;
|
|
for (size_t c = 0; c < numskincolors; c++)
|
|
{
|
|
if (jsprof.colorname == skincolors[c].name && K_ColorUsable(static_cast<skincolornum_t>(c), false, false))
|
|
{
|
|
newprof->color = c;
|
|
break;
|
|
}
|
|
}
|
|
|
|
strlcpy(newprof->follower, jsprof.followername.c_str(), sizeof(newprof->follower));
|
|
newprof->followercolor = PROFILEDEFAULTFOLLOWERCOLOR;
|
|
if (jsprof.followercolorname == "Match")
|
|
{
|
|
newprof->followercolor = FOLLOWERCOLOR_MATCH;
|
|
}
|
|
else if (jsprof.followercolorname == "Opposite")
|
|
{
|
|
newprof->followercolor = FOLLOWERCOLOR_OPPOSITE;
|
|
}
|
|
else if (jsprof.followercolorname == "Default")
|
|
{
|
|
newprof->followercolor = SKINCOLOR_NONE;
|
|
}
|
|
else if (!jsprof.followercolorname.empty())
|
|
{
|
|
for (size_t c = 0; c < numskincolors; c++)
|
|
{
|
|
if (jsprof.followercolorname == skincolors[c].name && K_ColorUsable(static_cast<skincolornum_t>(c), false, false))
|
|
{
|
|
newprof->followercolor = c;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
newprof->wins = jsprof.records.wins;
|
|
newprof->rounds = jsprof.records.rounds;
|
|
newprof->kickstartaccel = jsprof.preferences.kickstartaccel;
|
|
newprof->autoroulette = jsprof.preferences.autoroulette;
|
|
newprof->litesteer = jsprof.preferences.litesteer;
|
|
newprof->rumble = jsprof.preferences.rumble;
|
|
newprof->fov = jsprof.preferences.fov;
|
|
|
|
try
|
|
{
|
|
for (size_t j = 0; j < num_gamecontrols; j++)
|
|
{
|
|
for (size_t k = 0; k < MAXINPUTMAPPING; k++)
|
|
{
|
|
newprof->controls[j][k] = jsprof.controls.at(j).at(k);
|
|
}
|
|
}
|
|
}
|
|
catch (const std::out_of_range& ex)
|
|
{
|
|
I_Error("Profile '%s' controls are corrupt", jsprof.playername.c_str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
profilesList[PROFILE_GUEST] = dprofile;
|
|
}
|
|
|
|
static void PR_ApplyProfile_Appearance(profile_t *p, UINT8 playernum)
|
|
{
|
|
CV_StealthSet(&cv_skin[playernum], p->skinname);
|
|
CV_StealthSetValue(&cv_playercolor[playernum], p->color);
|
|
CV_StealthSet(&cv_playername[playernum], p->playername);
|
|
|
|
// Followers
|
|
CV_StealthSet(&cv_follower[playernum], p->follower);
|
|
CV_StealthSetValue(&cv_followercolor[playernum], p->followercolor);
|
|
}
|
|
|
|
static void PR_ApplyProfile_Settings(profile_t *p, UINT8 playernum)
|
|
{
|
|
// toggles
|
|
CV_StealthSetValue(&cv_kickstartaccel[playernum], p->kickstartaccel);
|
|
CV_StealthSetValue(&cv_autoroulette[playernum], p->autoroulette);
|
|
CV_StealthSetValue(&cv_litesteer[playernum], p->litesteer);
|
|
CV_StealthSetValue(&cv_rumble[playernum], p->rumble);
|
|
CV_StealthSetValue(&cv_fov[playernum], p->fov);
|
|
|
|
// set controls...
|
|
G_ApplyControlScheme(playernum, p->controls);
|
|
}
|
|
|
|
static void PR_ApplyProfile_Memory(UINT8 profilenum, UINT8 playernum)
|
|
{
|
|
// set memory cvar
|
|
CV_StealthSetValue(&cv_lastprofile[playernum], profilenum);
|
|
|
|
// If we're doing this on P1, also change current profile.
|
|
if (playernum == 0)
|
|
{
|
|
CV_StealthSetValue(&cv_currprofile, profilenum);
|
|
}
|
|
}
|
|
|
|
void PR_ApplyProfile(UINT8 profilenum, UINT8 playernum)
|
|
{
|
|
profile_t *p = PR_GetProfile(profilenum);
|
|
|
|
// this CAN happen!!
|
|
if (dedicated || p == NULL)
|
|
{
|
|
if (!dedicated)
|
|
CONS_Printf("Profile '%d' could not be loaded as it does not exist. Guest Profile will be loaded instead.\n", profilenum);
|
|
profilenum = 0; // make sure to set this so that the cvar is set properly.
|
|
p = PR_GetProfile(profilenum);
|
|
}
|
|
|
|
PR_ApplyProfile_Appearance(p, playernum);
|
|
PR_ApplyProfile_Settings(p, playernum);
|
|
PR_ApplyProfile_Memory(profilenum, playernum);
|
|
}
|
|
|
|
void PR_ApplyProfileLight(UINT8 profilenum, UINT8 playernum)
|
|
{
|
|
profile_t *p = PR_GetProfile(profilenum);
|
|
|
|
// this CAN happen!!
|
|
if (p == NULL)
|
|
{
|
|
// no need to be as loud...
|
|
profilenum = 0; // make sure to set this so that the cvar is set properly.
|
|
p = PR_GetProfile(profilenum);
|
|
}
|
|
|
|
PR_ApplyProfile_Appearance(p, playernum);
|
|
}
|
|
|
|
void PR_ApplyProfilePretend(UINT8 profilenum, UINT8 playernum)
|
|
{
|
|
profile_t *p = PR_GetProfile(profilenum);
|
|
|
|
// this CAN happen!!
|
|
if (dedicated || p == NULL)
|
|
{
|
|
if (!dedicated)
|
|
CONS_Printf("Profile '%d' could not be loaded as it does not exist. Guest Profile will be loaded instead.\n", profilenum);
|
|
profilenum = 0; // make sure to set this so that the cvar is set properly.
|
|
p = PR_GetProfile(profilenum);
|
|
}
|
|
|
|
PR_ApplyProfile_Memory(profilenum, playernum);
|
|
}
|
|
|
|
UINT8 PR_GetProfileNum(profile_t *p)
|
|
{
|
|
UINT8 i;
|
|
for (i = 0; i < MAXPROFILES+1; i++)
|
|
{
|
|
profile_t *comp = PR_GetProfile(i);
|
|
if (comp == p)
|
|
return i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
SINT8 PR_ProfileUsedBy(profile_t *p)
|
|
{
|
|
UINT8 i;
|
|
UINT8 prn = PR_GetProfileNum(p);
|
|
|
|
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
|
|
{
|
|
if (prn == cv_lastprofile[i].value)
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
profile_t *PR_GetPlayerProfile(player_t *player)
|
|
{
|
|
const UINT8 playerNum = (player - players);
|
|
UINT8 i;
|
|
|
|
if (demo.playback)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i <= splitscreen; i++)
|
|
{
|
|
if (playerNum == g_localplayers[i])
|
|
{
|
|
return PR_GetProfile(cv_lastprofile[i].value);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
profile_t *PR_GetLocalPlayerProfile(INT32 player)
|
|
{
|
|
if (player >= MAXSPLITSCREENPLAYERS)
|
|
return NULL;
|
|
return PR_GetProfile(cv_lastprofile[player].value);
|
|
}
|
|
|
|
boolean PR_IsLocalPlayerGuest(INT32 player)
|
|
{
|
|
return !(cv_lastprofile[player].value);
|
|
}
|
|
|
|
static char rrid_buf[256];
|
|
|
|
char *GetPrettyRRID(const unsigned char *bin, boolean brief)
|
|
{
|
|
size_t i;
|
|
size_t len = PUBKEYLENGTH;
|
|
|
|
if (brief)
|
|
len = 8;
|
|
|
|
if (bin == NULL || len == 0)
|
|
return NULL;
|
|
|
|
for (i=0; i<len; i++)
|
|
{
|
|
rrid_buf[i*2] = "0123456789ABCDEF"[bin[i] >> 4];
|
|
rrid_buf[i*2+1] = "0123456789ABCDEF"[bin[i] & 0x0F];
|
|
}
|
|
|
|
rrid_buf[len*2] = '\0';
|
|
|
|
return rrid_buf;
|
|
}
|
|
|
|
unsigned char *FromPrettyRRID(unsigned char *bin, const char *text)
|
|
{
|
|
size_t i;
|
|
size_t len = PUBKEYLENGTH * 2;
|
|
|
|
if (strlen(text) != len)
|
|
return NULL;
|
|
|
|
for (i = 0; i < len; i += 2)
|
|
{
|
|
char byt[3] = { text[i], text[i+1], '\0' };
|
|
char *p;
|
|
|
|
bin[i/2] = strtol(byt, &p, 16);
|
|
|
|
if (*p) // input is not hexadecimal
|
|
return NULL;
|
|
}
|
|
|
|
return bin;
|
|
}
|
|
|
|
|
|
static char allZero[PUBKEYLENGTH];
|
|
|
|
boolean PR_IsKeyGuest(uint8_t *key)
|
|
{
|
|
//memset(allZero, 0, PUBKEYLENGTH); -- not required, allZero is 0's
|
|
return (memcmp(key, allZero, PUBKEYLENGTH) == 0);
|
|
}
|