Fix 40250 classic login and world loading
This commit is contained in:
@@ -12,6 +12,9 @@ ClassicSession::ClassicSession() : m_parser(m_world) {
|
||||
return on_packet(h, b, l);
|
||||
};
|
||||
m_stream.on_error = [this](const std::string &e) {
|
||||
if (m_auth_transition) {
|
||||
return;
|
||||
}
|
||||
m_last_error = e;
|
||||
set_stage(Stage::Failed);
|
||||
if (on_error) {
|
||||
@@ -19,7 +22,8 @@ ClassicSession::ClassicSession() : m_parser(m_world) {
|
||||
}
|
||||
};
|
||||
m_stream.on_state_change = [this](ClassicStream::State s) {
|
||||
if (s == ClassicStream::State::Offline && m_stage != Stage::Failed &&
|
||||
if (s == ClassicStream::State::Offline && !m_auth_transition &&
|
||||
m_stage != Stage::Failed &&
|
||||
m_stage != Stage::Offline) {
|
||||
m_last_error = m_stream.last_error().empty() ? "disconnected" : m_stream.last_error();
|
||||
set_stage(Stage::Failed);
|
||||
@@ -27,6 +31,29 @@ ClassicSession::ClassicSession() : m_parser(m_world) {
|
||||
// handshake packets flow next; stay in Connecting until GC_PHASE(LOGIN)
|
||||
}
|
||||
};
|
||||
m_auth_stream.on_phase = [this](uint8_t p) { on_auth_phase(p); };
|
||||
m_auth_stream.on_packet = [this](uint8_t h, const uint8_t *b, uint32_t l) {
|
||||
return on_auth_packet(h, b, l);
|
||||
};
|
||||
m_auth_stream.on_error = [this](const std::string &e) {
|
||||
if (m_auth_transition) {
|
||||
return;
|
||||
}
|
||||
m_last_error = e;
|
||||
set_stage(Stage::Failed);
|
||||
if (on_error) {
|
||||
on_error(e);
|
||||
}
|
||||
};
|
||||
m_auth_stream.on_state_change = [this](ClassicStream::State s) {
|
||||
if (s == ClassicStream::State::Offline && !m_auth_transition &&
|
||||
m_use_auth && !m_auth_succeeded && m_stage != Stage::Failed &&
|
||||
m_stage != Stage::Offline) {
|
||||
m_last_error = m_auth_stream.last_error().empty() ? "auth peer closed" :
|
||||
m_auth_stream.last_error();
|
||||
set_stage(Stage::Failed);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void ClassicSession::set_stage(Stage s) {
|
||||
@@ -42,6 +69,12 @@ void ClassicSession::set_stage(Stage s) {
|
||||
void ClassicSession::start_offline(const std::string &id, const std::string &pw) {
|
||||
m_id = id;
|
||||
m_pw = pw;
|
||||
m_use_auth = false;
|
||||
m_auth_succeeded = false;
|
||||
m_game_started = true;
|
||||
m_authenticated_game = false;
|
||||
m_auth_login_key = 0;
|
||||
m_auth_login_sent = false;
|
||||
m_login_sent = false;
|
||||
m_entergame_sent = false;
|
||||
m_version_sent = false;
|
||||
@@ -50,13 +83,61 @@ void ClassicSession::start_offline(const std::string &id, const std::string &pw)
|
||||
// 0xFF. The normal game client switches to CG_TIME_SYNC only after the
|
||||
// server has advanced into the login/select side of the connection.
|
||||
m_stream.set_time_sync_mode(false);
|
||||
m_auth_stream.set_time_sync_mode(false);
|
||||
set_stage(Stage::Connecting);
|
||||
}
|
||||
|
||||
bool ClassicSession::connect(const std::string &game_host, uint16_t game_port,
|
||||
const std::string &id, const std::string &pw) {
|
||||
start_offline(id, pw);
|
||||
if (!m_stream.connect(game_host, game_port)) {
|
||||
m_game_host = game_host;
|
||||
m_game_port = game_port;
|
||||
m_authenticated_game = false;
|
||||
m_auth_transition = true;
|
||||
const bool connected = m_stream.connect(game_host, game_port);
|
||||
m_auth_transition = false;
|
||||
if (!connected) {
|
||||
m_last_error = m_stream.last_error();
|
||||
set_stage(Stage::Failed);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClassicSession::connect(const std::string &auth_host, uint16_t auth_port,
|
||||
const std::string &game_host, uint16_t game_port, const std::string &id,
|
||||
const std::string &pw) {
|
||||
start_offline(id, pw);
|
||||
m_use_auth = true;
|
||||
m_game_started = false;
|
||||
m_auth_succeeded = false;
|
||||
m_authenticated_game = false;
|
||||
m_auth_host = auth_host;
|
||||
m_auth_port = auth_port;
|
||||
m_game_host = game_host;
|
||||
m_game_port = game_port;
|
||||
m_auth_transition = true;
|
||||
const bool connected = m_auth_stream.connect(auth_host, auth_port);
|
||||
m_auth_transition = false;
|
||||
if (!connected) {
|
||||
m_last_error = m_auth_stream.last_error();
|
||||
set_stage(Stage::Failed);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClassicSession::connect_with_login_key(const std::string &game_host, uint16_t game_port,
|
||||
const std::string &id, uint32_t login_key) {
|
||||
start_offline(id, "");
|
||||
m_game_host = game_host;
|
||||
m_game_port = game_port;
|
||||
m_auth_login_key = login_key;
|
||||
m_authenticated_game = true;
|
||||
m_auth_transition = true;
|
||||
const bool connected = m_stream.connect(game_host, game_port);
|
||||
m_auth_transition = false;
|
||||
if (!connected) {
|
||||
m_last_error = m_stream.last_error();
|
||||
set_stage(Stage::Failed);
|
||||
return false;
|
||||
@@ -65,12 +146,40 @@ bool ClassicSession::connect(const std::string &game_host, uint16_t game_port,
|
||||
}
|
||||
|
||||
void ClassicSession::disconnect() {
|
||||
m_auth_transition = true;
|
||||
m_auth_stream.disconnect();
|
||||
m_stream.disconnect();
|
||||
m_auth_transition = false;
|
||||
set_stage(Stage::Offline);
|
||||
}
|
||||
|
||||
void ClassicSession::pump() {
|
||||
m_stream.process();
|
||||
if (m_use_auth && !m_auth_succeeded && m_stage != Stage::Failed) {
|
||||
m_auth_stream.process();
|
||||
}
|
||||
if (m_use_auth && m_auth_succeeded && !m_game_started && m_stage != Stage::Failed) {
|
||||
m_auth_transition = true;
|
||||
m_auth_stream.disconnect();
|
||||
m_auth_transition = false;
|
||||
m_authenticated_game = true;
|
||||
m_game_started = true;
|
||||
m_login_sent = false;
|
||||
m_entergame_sent = false;
|
||||
m_version_sent = false;
|
||||
m_stream.set_time_sync_mode(false);
|
||||
m_auth_transition = true;
|
||||
const bool connected = m_stream.connect(m_game_host, m_game_port);
|
||||
m_auth_transition = false;
|
||||
if (!connected) {
|
||||
m_last_error = m_stream.last_error();
|
||||
set_stage(Stage::Failed);
|
||||
return;
|
||||
}
|
||||
set_stage(Stage::Connecting);
|
||||
}
|
||||
if (m_game_started && m_stage != Stage::Failed) {
|
||||
m_stream.process();
|
||||
}
|
||||
// auto CG_ENTERGAME a beat into PHASE_LOADING (the m2dev client waits ~1.5s;
|
||||
// sending it too early makes the server drop us mid spawn-burst).
|
||||
if (m_stage == Stage::Loading && !m_entergame_sent && m_entergame_delay > 0 &&
|
||||
@@ -84,6 +193,10 @@ void ClassicSession::send_login() {
|
||||
if (m_login_sent) {
|
||||
return;
|
||||
}
|
||||
if (m_authenticated_game) {
|
||||
send_login_by_key();
|
||||
return;
|
||||
}
|
||||
// docs §2.2: the client turns sequence mode on around here; CG_LOGIN is a
|
||||
// bSeq packet and the server expects the trailing byte from packet #1.
|
||||
m_stream.set_sequence_mode(true);
|
||||
@@ -96,6 +209,78 @@ void ClassicSession::send_login() {
|
||||
m_login_sent = true;
|
||||
}
|
||||
|
||||
void ClassicSession::send_auth_login() {
|
||||
if (m_auth_login_sent) {
|
||||
return;
|
||||
}
|
||||
m_auth_stream.set_sequence_mode(true);
|
||||
CGLogin3 p{};
|
||||
p.header = HDR_CG_LOGIN3;
|
||||
std::strncpy(p.login, m_id.c_str(), sizeof(p.login) - 1);
|
||||
std::strncpy(p.passwd, m_pw.c_str(), sizeof(p.passwd) - 1);
|
||||
// The stock connector sends four client keys here. Zero is valid for this
|
||||
// client, provided the same values are used in the later CG_LOGIN2 packet.
|
||||
m_auth_stream.send_fixed(&p, sizeof(p));
|
||||
m_auth_login_sent = true;
|
||||
}
|
||||
|
||||
void ClassicSession::send_login_by_key() {
|
||||
// The game side must use the ticket returned by the auth server; sending
|
||||
// CG_LOGIN here would hit the server's locale/version gate.
|
||||
m_stream.set_sequence_mode(true);
|
||||
CGLogin2 p{};
|
||||
p.header = HDR_CG_LOGIN2;
|
||||
std::strncpy(p.login, m_id.c_str(), sizeof(p.login) - 1);
|
||||
p.login_key = m_auth_login_key;
|
||||
m_stream.send_fixed(&p, sizeof(p));
|
||||
m_login_sent = true;
|
||||
}
|
||||
|
||||
void ClassicSession::on_auth_phase(uint8_t phase) {
|
||||
if (phase == PHASE_AUTH) {
|
||||
set_stage(Stage::LoggingIn);
|
||||
send_auth_login();
|
||||
} else if (phase == PHASE_CLOSE) {
|
||||
m_last_error = "auth server closed phase";
|
||||
set_stage(Stage::Failed);
|
||||
}
|
||||
}
|
||||
|
||||
bool ClassicSession::on_auth_packet(uint8_t header, const uint8_t *body, uint32_t len) {
|
||||
if (header == HDR_GC_AUTH_SUCCESS) {
|
||||
if (len + 1u < sizeof(GCAuthSuccess)) {
|
||||
m_last_error = "short GC_AUTH_SUCCESS";
|
||||
set_stage(Stage::Failed);
|
||||
return false;
|
||||
}
|
||||
GCAuthSuccess p{};
|
||||
p.header = header;
|
||||
std::memcpy(reinterpret_cast<uint8_t *>(&p) + 1, body, sizeof(p) - 1);
|
||||
m_auth_login_key = p.login_key;
|
||||
if (p.result == 0 || m_auth_login_key == 0) {
|
||||
m_last_error = "auth rejected credentials";
|
||||
set_stage(Stage::Failed);
|
||||
return false;
|
||||
}
|
||||
m_auth_succeeded = true;
|
||||
return true;
|
||||
}
|
||||
if (header == HDR_GC_LOGIN_FAILURE) {
|
||||
if (len + 1u < sizeof(GCLoginFailure)) {
|
||||
m_last_error = "short auth GC_LOGIN_FAILURE";
|
||||
} else {
|
||||
const char *status = reinterpret_cast<const char *>(body);
|
||||
m_last_error = "auth login failed: " +
|
||||
std::string(status, strnlen(status, sizeof(GCLoginFailure) - 1));
|
||||
}
|
||||
set_stage(Stage::Failed);
|
||||
return false;
|
||||
}
|
||||
// Optional Panama/hybrid-crypt packets are already framed and can be
|
||||
// ignored by this asset-loader client.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClassicSession::create_character(int slot, const std::string &name, int job, int shape,
|
||||
int con, int intel, int str, int dex) {
|
||||
if (m_stage != Stage::CharSelect || slot < 0 || slot >= m_parser.slot_count() ||
|
||||
|
||||
Reference in New Issue
Block a user