mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-06 00:32:25 +00:00
Login Updates for TOB (#5068)
This commit is contained in:
parent
9c8107ce96
commit
0ada77f340
@ -219,7 +219,7 @@ const char* EQ::versions::MobVersionName(MobVersion mob_version)
|
||||
case MobVersion::OfflineRoF2:
|
||||
return "Offline RoF2";
|
||||
case MobVersion::OfflineTOB:
|
||||
return "Offline Steam Latest";
|
||||
return "Offline TOB";
|
||||
default:
|
||||
return "Invalid Version";
|
||||
};
|
||||
@ -505,7 +505,7 @@ uint32 EQ::expansions::ConvertExpansionToExpansionBit(Expansion expansion)
|
||||
return bitLS;
|
||||
case Expansion::TOB:
|
||||
return bitTOB;
|
||||
|
||||
|
||||
default:
|
||||
return bitEverQuest;
|
||||
}
|
||||
|
||||
@ -302,20 +302,13 @@ void Client::SendPlayResponse(EQApplicationPacket *outapp)
|
||||
|
||||
void Client::GenerateRandomLoginKey()
|
||||
{
|
||||
m_key.clear();
|
||||
int count = 0;
|
||||
while (count < 10) {
|
||||
static const char key_selection[] =
|
||||
{
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', '0', '1', '2', '3', '4', '5',
|
||||
'6', '7', '8', '9'
|
||||
};
|
||||
static constexpr std::string_view key_selection = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||
constexpr size_t key_length = 10;
|
||||
|
||||
m_key.append((const char *) &key_selection[m_random.Int(0, 35)], 1);
|
||||
count++;
|
||||
m_key.clear();
|
||||
m_key.reserve(key_length);
|
||||
for (size_t i = 0; i < key_length; ++i) {
|
||||
m_key += key_selection[m_random.Int(0, key_selection.size() - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
@ -362,55 +355,30 @@ void Client::SendFailedLogin()
|
||||
m_stored_username.clear();
|
||||
m_stored_password.clear();
|
||||
|
||||
if (m_client_version == cv_tob) {
|
||||
// unencrypted
|
||||
LoginBaseMessage h{};
|
||||
h.sequence = m_login_base_message.sequence; // login (3)
|
||||
h.encrypt_type = m_login_base_message.encrypt_type;
|
||||
// unencrypted
|
||||
LoginBaseMessage h{};
|
||||
h.sequence = m_login_base_message.sequence;
|
||||
h.encrypt_type = m_login_base_message.encrypt_type;
|
||||
h.unk3 = m_login_base_message.unk3;
|
||||
|
||||
// encrypted
|
||||
PlayerLoginReplyTOB r{};
|
||||
r.base_reply.success = false;
|
||||
r.base_reply.error_str_id = 105; // Error - The username and/or password were not valid
|
||||
PlayerLoginReply r = m_client_version == cv_tob ? PlayerLoginReply(PlayerLoginReplyTOB{}) : PlayerLoginReply(PlayerLoginReplyOld{});
|
||||
r.set_error_code(LS::ErrStr::ERROR_INVALID_CREDS);
|
||||
// We don't care what key we send, just that it exists and is 10 characters so that we do not shift
|
||||
r.set_key("InvalidKey");
|
||||
|
||||
char encrypted_buffer[80] = { 0 };
|
||||
auto rc = eqcrypt_block((const char*)&r, sizeof(r), encrypted_buffer, 1);
|
||||
if (rc == nullptr) {
|
||||
LogDebug("Failed to encrypt eqcrypt block for failed login");
|
||||
}
|
||||
|
||||
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
|
||||
EQApplicationPacket outapp(OP_LoginAccepted, outsize);
|
||||
outapp.WriteData(&h, sizeof(h));
|
||||
outapp.WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
|
||||
|
||||
m_connection->QueuePacket(&outapp);
|
||||
char encrypted_buffer[80] = { 0 };
|
||||
auto rc = eqcrypt_block(r.data(), r.size(), encrypted_buffer, 1);
|
||||
if (rc == nullptr) {
|
||||
LogDebug("Failed to encrypt eqcrypt block for failed login");
|
||||
}
|
||||
else {
|
||||
// unencrypted
|
||||
LoginBaseMessage h{};
|
||||
h.sequence = m_login_base_message.sequence; // login (3)
|
||||
h.encrypt_type = m_login_base_message.encrypt_type;
|
||||
|
||||
// encrypted
|
||||
PlayerLoginReply r{};
|
||||
r.base_reply.success = false;
|
||||
r.base_reply.error_str_id = 105; // Error - The username and/or password were not valid
|
||||
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
|
||||
EQApplicationPacket outapp(OP_LoginAccepted, outsize);
|
||||
outapp.WriteData(&h, sizeof(h));
|
||||
outapp.WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
|
||||
|
||||
char encrypted_buffer[80] = { 0 };
|
||||
auto rc = eqcrypt_block((const char*)&r, sizeof(r), encrypted_buffer, 1);
|
||||
if (rc == nullptr) {
|
||||
LogDebug("Failed to encrypt eqcrypt block for failed login");
|
||||
}
|
||||
m_connection->QueuePacket(&outapp);
|
||||
|
||||
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
|
||||
EQApplicationPacket outapp(OP_LoginAccepted, outsize);
|
||||
outapp.WriteData(&h, sizeof(h));
|
||||
outapp.WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
|
||||
|
||||
m_connection->QueuePacket(&outapp);
|
||||
}
|
||||
|
||||
m_client_status = cs_failed_to_login;
|
||||
}
|
||||
|
||||
@ -496,91 +464,44 @@ void Client::DoSuccessfulLogin(LoginAccountsRepository::LoginAccounts &a)
|
||||
m_account_name = a.account_name;
|
||||
m_loginserver_name = a.source_loginserver;
|
||||
|
||||
if (m_client_version == cv_tob) {
|
||||
// unencrypted
|
||||
LoginBaseMessage h{};
|
||||
h.sequence = m_login_base_message.sequence;
|
||||
h.compressed = false;
|
||||
h.encrypt_type = m_login_base_message.encrypt_type;
|
||||
h.unk3 = m_login_base_message.unk3;
|
||||
// unencrypted
|
||||
LoginBaseMessage h{};
|
||||
h.sequence = m_login_base_message.sequence;
|
||||
h.compressed = false;
|
||||
h.encrypt_type = m_login_base_message.encrypt_type;
|
||||
h.unk3 = m_login_base_message.unk3;
|
||||
|
||||
// not serializing any of the variable length strings so just use struct directly
|
||||
PlayerLoginReplyTOB r{};
|
||||
r.base_reply.success = true;
|
||||
r.base_reply.error_str_id = 101; // No Error
|
||||
r.unk1 = 0;
|
||||
r.unk2 = 0;
|
||||
r.lsid = a.id;
|
||||
r.failed_attempts = 0;
|
||||
r.show_player_count = server.options.IsShowPlayerCountEnabled();
|
||||
r.unk3 = 0;
|
||||
r.unk4 = 0;
|
||||
memcpy(r.key, m_key.c_str(), m_key.size());
|
||||
// not serializing any of the variable length strings so just use struct directly
|
||||
PlayerLoginReply r = m_client_version == cv_tob ? PlayerLoginReply(PlayerLoginReplyTOB{}) : PlayerLoginReply(PlayerLoginReplyOld{});
|
||||
|
||||
//todo: needs to be fixed
|
||||
//SendExpansionPacketData(r);
|
||||
r.set_success(true);
|
||||
r.set_error_code(LS::ErrStr::ERROR_NONE);
|
||||
r.set_lsid(a.id);
|
||||
r.set_show_player_count(server.options.IsShowPlayerCountEnabled());
|
||||
r.set_key(m_key);
|
||||
|
||||
char encrypted_buffer[80] = { 0 };
|
||||
|
||||
auto rc = eqcrypt_block((const char*)&r, sizeof(r), encrypted_buffer, 1);
|
||||
if (rc == nullptr) {
|
||||
LogDebug("Failed to encrypt eqcrypt block");
|
||||
}
|
||||
|
||||
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_LoginAccepted, outsize);
|
||||
outapp->WriteData(&h, sizeof(h));
|
||||
outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
|
||||
|
||||
m_connection->QueuePacket(outapp.get());
|
||||
if (m_client_version != cv_tob) {
|
||||
SendExpansionPacketData(r.old());
|
||||
}
|
||||
else {
|
||||
// unencrypted
|
||||
LoginBaseMessage h{};
|
||||
h.sequence = m_login_base_message.sequence;
|
||||
h.compressed = false;
|
||||
h.encrypt_type = m_login_base_message.encrypt_type;
|
||||
h.unk3 = m_login_base_message.unk3;
|
||||
|
||||
// not serializing any of the variable length strings so just use struct directly
|
||||
PlayerLoginReply r{};
|
||||
r.base_reply.success = true;
|
||||
r.base_reply.error_str_id = 101; // No Error
|
||||
r.unk1 = 0;
|
||||
r.unk2 = 0;
|
||||
r.lsid = a.id;
|
||||
r.failed_attempts = 0;
|
||||
r.show_player_count = server.options.IsShowPlayerCountEnabled();
|
||||
r.offer_min_days = 99;
|
||||
r.offer_min_views = -1;
|
||||
r.offer_cooldown_minutes = 0;
|
||||
r.web_offer_number = 0;
|
||||
r.web_offer_min_days = 99;
|
||||
r.web_offer_min_views = -1;
|
||||
r.web_offer_cooldown_minutes = 0;
|
||||
memcpy(r.key, m_key.c_str(), m_key.size());
|
||||
char encrypted_buffer[80] = { 0 };
|
||||
|
||||
SendExpansionPacketData(r);
|
||||
|
||||
char encrypted_buffer[80] = { 0 };
|
||||
|
||||
auto rc = eqcrypt_block((const char*)&r, sizeof(r), encrypted_buffer, 1);
|
||||
if (rc == nullptr) {
|
||||
LogDebug("Failed to encrypt eqcrypt block");
|
||||
}
|
||||
|
||||
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_LoginAccepted, outsize);
|
||||
outapp->WriteData(&h, sizeof(h));
|
||||
outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
|
||||
|
||||
m_connection->QueuePacket(outapp.get());
|
||||
auto rc = eqcrypt_block(r.data(), r.size(), encrypted_buffer, 1);
|
||||
if (rc == nullptr) {
|
||||
LogDebug("Failed to encrypt eqcrypt block");
|
||||
}
|
||||
|
||||
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_LoginAccepted, outsize);
|
||||
outapp->WriteData(&h, sizeof(h));
|
||||
outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
|
||||
|
||||
m_connection->QueuePacket(outapp.get());
|
||||
|
||||
m_client_status = cs_logged_in;
|
||||
}
|
||||
|
||||
void Client::SendExpansionPacketData(PlayerLoginReply &plrs)
|
||||
void Client::SendExpansionPacketData(PlayerLoginReplyOld &plrs)
|
||||
{
|
||||
SerializeBuffer buf;
|
||||
//from eqlsstr_us.txt id of each expansion, excluding 'Everquest'
|
||||
@ -607,7 +528,7 @@ void Client::SendExpansionPacketData(PlayerLoginReply &plrs)
|
||||
|
||||
//generate expansion data
|
||||
for (int i = 0; i < 19; i++) {
|
||||
buf.WriteInt32(i); //sequenctial number
|
||||
buf.WriteInt32(i); //sequential number
|
||||
buf.WriteInt32((expansion & (1 << i)) == (1 << i) ? 0x01 : 0x00); //1 own 0 not own
|
||||
buf.WriteInt8(0x00);
|
||||
buf.WriteInt32(ExpansionLookup[i]); //from eqlsstr_us.txt
|
||||
|
||||
@ -38,7 +38,7 @@ public:
|
||||
// Titanium uses the encrypted data block to contact the expansion (You own xxx:) and the max expansions (of yyy)
|
||||
// Rof uses a separate data packet specifically for the expansion data
|
||||
// Live, as of July 2021 uses a similar but slightly different seperate data packet
|
||||
void SendExpansionPacketData(PlayerLoginReply &plrs);
|
||||
void SendExpansionPacketData(PlayerLoginReplyOld &plrs);
|
||||
void SendPlayToWorld(const char *data);
|
||||
void SendServerListPacket(uint32 seq);
|
||||
void SendPlayResponse(EQApplicationPacket *outapp);
|
||||
|
||||
@ -194,7 +194,7 @@ ClientManager::ClientManager()
|
||||
|
||||
if (!m_tob_ops->LoadOpcodes(opcodes_path.c_str())) {
|
||||
LogError(
|
||||
"ClientManager fatal error: couldn't load opcodes for Steam Latest file [{}]",
|
||||
"ClientManager fatal error: couldn't load opcodes for TOB file [{}]",
|
||||
server.config.GetVariableString("client_configuration", "tob_opcodes", "login_opcodes.conf")
|
||||
);
|
||||
|
||||
@ -204,7 +204,7 @@ ClientManager::ClientManager()
|
||||
m_tob_stream->OnNewConnection(
|
||||
[this](std::shared_ptr<EQ::Net::EQStream> stream) {
|
||||
LogInfo(
|
||||
"New Steam Latest client connection from [{}:{}]",
|
||||
"New TOB client connection from [{}:{}]",
|
||||
long2ip(stream->GetRemoteIP()),
|
||||
stream->GetRemotePort()
|
||||
);
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
#include "common/types.h"
|
||||
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#pragma pack(push)
|
||||
#pragma pack(1)
|
||||
@ -45,7 +46,7 @@ struct LoginHandShakeReply {
|
||||
};
|
||||
|
||||
// variable length, can use directly if not serializing strings
|
||||
struct PlayerLoginReply {
|
||||
struct PlayerLoginReplyOld {
|
||||
// base header excluded to make struct data easier to encrypt
|
||||
//LoginBaseMessage base_header;
|
||||
LoginBaseReplyMessage base_reply;
|
||||
@ -61,25 +62,90 @@ struct PlayerLoginReply {
|
||||
int32_t offer_cooldown_minutes; // guess (default: 0)
|
||||
int32_t web_offer_number; // web order view number, 0 nothing (default: 0)
|
||||
int32_t web_offer_min_days; // number of days to show offer (based on first offer time in client eqls ini) (default: 99)
|
||||
int32_t web_offer_min_views; // mininum views, -1 for no minimum, 0 for never shows (based on client eqls ini) (default: -1)
|
||||
int32_t web_offer_min_views; // minimum views, -1 for no minimum, 0 for never shows (based on client eqls ini) (default: -1)
|
||||
int32_t web_offer_cooldown_minutes; // minimum minutes between offers (based on last offer time in client eqls ini) (default: 0)
|
||||
char username[1]; // variable length, if not empty client attempts to re-login to server select when quitting from char select and sends this in a struct
|
||||
char unknown[1]; // variable length, password unlikely? client doesn't send this on re-login from char select
|
||||
};
|
||||
|
||||
struct PlayerLoginReplyTOB
|
||||
{
|
||||
void set_success(bool v) { base_reply.success = v; }
|
||||
void set_error_code(int32_t v) { base_reply.error_str_id = v; }
|
||||
void set_lsid(int32_t v) { lsid = v; }
|
||||
void set_show_player_count(bool v) { show_player_count = v; }
|
||||
void set_hardcoded_success_values() {
|
||||
offer_min_days = 99;
|
||||
offer_min_views = -1;
|
||||
web_offer_min_days = 99;
|
||||
web_offer_min_views = -1;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(PlayerLoginReplyOld) == 58, "PlayerLoginReplyOld struct size does not match expected size");
|
||||
static_assert(std::is_trivially_copyable_v<PlayerLoginReplyOld>);
|
||||
static_assert(std::is_standard_layout_v<PlayerLoginReplyOld>);
|
||||
|
||||
struct PlayerLoginReplyTOB {
|
||||
LoginBaseReplyMessage base_reply;
|
||||
int8_t unk1; // (default: 0)
|
||||
int8_t unk2; // (default: 0)
|
||||
int32_t lsid; // (default: -1)
|
||||
char key[11]; // client reads until null (variable length)
|
||||
int32_t failed_attempts;
|
||||
bool show_player_count; // admin flag, enables admin button and shows server player counts (default: false)
|
||||
int32_t unk3; // guess, needs more investigation (default: 0)
|
||||
int32_t unk4; // guess, needs more investigation (default: 0)
|
||||
char username[1]; // variable length, if not empty client attempts to re-login to server select when quitting from char select and sends this in a struct
|
||||
char unknown[1]; // variable length, password unlikely? client doesn't send this on re-login from char select
|
||||
|
||||
int8_t unk1 = 0;
|
||||
int8_t unk2 = 0;
|
||||
int32_t lsid = -1;
|
||||
char key[11] = {};
|
||||
int32_t failed_attempts = 0;
|
||||
int32_t display_error_str_id = 0;
|
||||
int32_t unk3 = 0;
|
||||
bool show_player_count = false;
|
||||
char username[1] = {};
|
||||
char unk4[1] = {};
|
||||
|
||||
void set_success(bool v) { base_reply.success = v; }
|
||||
void set_error_code(int32_t v) { display_error_str_id = v; base_reply.error_str_id = v; }
|
||||
void set_lsid(int32_t v) { lsid = v; }
|
||||
void set_show_player_count(bool v) { show_player_count = v; }
|
||||
void set_hardcoded_success_values() {}
|
||||
};
|
||||
static_assert(sizeof(PlayerLoginReplyTOB) == 38, "PlayerLoginReplyTOB struct size does not match expected size");
|
||||
static_assert(std::is_trivially_copyable_v<PlayerLoginReplyTOB>);
|
||||
static_assert(std::is_standard_layout_v<PlayerLoginReplyTOB>);
|
||||
|
||||
class PlayerLoginReply {
|
||||
std::variant<PlayerLoginReplyOld, PlayerLoginReplyTOB> v_;
|
||||
static_assert(sizeof(PlayerLoginReplyOld::key) == sizeof(PlayerLoginReplyTOB::key), "Old and TOB key buffers must match in size due to code assumptions");
|
||||
public:
|
||||
PlayerLoginReply(PlayerLoginReplyOld s) : v_(s) {}
|
||||
PlayerLoginReply(PlayerLoginReplyTOB s) : v_(s) {}
|
||||
|
||||
void set_success(bool val) {
|
||||
std::visit([val](auto& s) { s.set_success(val); }, v_);
|
||||
}
|
||||
void set_error_code(int32_t val) {
|
||||
std::visit([val](auto& s) { s.set_error_code(val); }, v_);
|
||||
}
|
||||
void set_lsid(int32_t val) {
|
||||
std::visit([val](auto& s) { s.set_lsid(val); }, v_);
|
||||
}
|
||||
void set_show_player_count(bool val) {
|
||||
std::visit([val](auto& s) { s.set_show_player_count(val); }, v_);
|
||||
}
|
||||
void set_key(std::string_view s) {
|
||||
std::visit([&](auto& st) {
|
||||
const size_t n = s.copy(st.key, sizeof(st.key) - 1);
|
||||
st.key[n] = '\0';
|
||||
}, v_);
|
||||
}
|
||||
template<size_t N>
|
||||
void set_key(const char (&s)[N]) {
|
||||
static_assert(N != (sizeof(PlayerLoginReplyTOB::key) - 1), "Key literal does not match reply struct's key buffer (without null terminator)");
|
||||
set_key(std::string_view{s, N - 1});
|
||||
}
|
||||
|
||||
PlayerLoginReplyOld& old() { return std::get<PlayerLoginReplyOld>(v_); }
|
||||
const PlayerLoginReplyOld& old() const { return std::get<PlayerLoginReplyOld>(v_); }
|
||||
|
||||
char* data() noexcept {
|
||||
return std::visit([](auto& s) { return reinterpret_cast<char*>(&s); }, v_);
|
||||
}
|
||||
size_t size() const noexcept {
|
||||
return std::visit([](auto const& s) { return sizeof(s); }, v_);
|
||||
}
|
||||
};
|
||||
|
||||
// variable length, for reference
|
||||
@ -195,11 +261,14 @@ namespace LS {
|
||||
namespace ErrStr {
|
||||
constexpr static int ERROR_NONE = 101; // No Error
|
||||
constexpr static int ERROR_UNKNOWN = 102; // Error - Unknown Error Occurred
|
||||
constexpr static int ERROR_INVALID_CREDS = 105; // Error - Invalid Account Name or Password
|
||||
constexpr static int ERROR_ACTIVE_CHARACTER = 111; // Error 1018: You currently have an active character on that EverQuest Server, please allow a minute for synchronization and try again.
|
||||
constexpr static int ERROR_PASSWORD_RESET = 112; // Require password reset
|
||||
constexpr static int ERROR_SERVER_UNAVAILABLE = 326; // That server is currently unavailable. Please check the EverQuest webpage for current server status and try again later.
|
||||
constexpr static int ERROR_ACCOUNT_SUSPENDED = 337; // This account is currently suspended. Please contact customer service for more information.
|
||||
constexpr static int ERROR_ACCOUNT_BANNED = 338; // This account is currently banned. Please contact customer service for more information.
|
||||
constexpr static int ERROR_WORLD_MAX_CAPACITY = 339; // The world server is currently at maximum capacity and not allowing further logins until the number of players online decreases. Please try again later.
|
||||
constexpr static int ERROR_REQUIRE_2FA = 342; // This account requires two-factor authentication.
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user