Fix 40250 classic login and world loading
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
@@ -27,6 +28,17 @@ void set_nonblocking(int fd) {
|
||||
int fl = fcntl(fd, F_GETFL, 0);
|
||||
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
|
||||
}
|
||||
|
||||
bool plausible_character_add(const uint8_t *p, size_t available) {
|
||||
if (available < sizeof(GCCharacterAdd) || p[0] != HDR_GC_CHARACTER_ADD) {
|
||||
return false;
|
||||
}
|
||||
GCCharacterAdd packet{};
|
||||
std::memcpy(&packet, p, sizeof(packet));
|
||||
return packet.vid != 0 && packet.race != 0 && packet.type <= 8 &&
|
||||
std::isfinite(packet.angle) && std::fabs(packet.angle) <= 360.0f &&
|
||||
packet.moving_speed <= 255 && packet.attack_speed <= 255;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ClassicStream::~ClassicStream() {
|
||||
@@ -93,10 +105,15 @@ void ClassicStream::disconnect() {
|
||||
m_seq_idx = 0;
|
||||
m_seq_on = false;
|
||||
m_handshake_seen = false;
|
||||
m_phase = PHASE_HANDSHAKE;
|
||||
m_cipher.clean_up();
|
||||
set_state(State::Offline);
|
||||
}
|
||||
|
||||
void ClassicStream::discard_recv(size_t n) {
|
||||
m_recv.discard(n);
|
||||
}
|
||||
|
||||
void ClassicStream::decrypt_appended(size_t n) {
|
||||
if (n == 0 || !m_cipher.activated()) {
|
||||
return;
|
||||
@@ -234,7 +251,7 @@ bool ClassicStream::handle_control(uint8_t header, bool &consumed) {
|
||||
if (!m_recv.peek(&hs, sizeof(hs))) {
|
||||
return true; // need more
|
||||
}
|
||||
m_recv.discard(sizeof(hs));
|
||||
discard_recv(sizeof(hs));
|
||||
consumed = true;
|
||||
// mirror EterLib/PythonNetworkStreamPhaseHandshake.cpp + desc.cpp:
|
||||
m_server_time_base = hs.time + static_cast<uint32_t>(hs.delta);
|
||||
@@ -267,7 +284,7 @@ bool ClassicStream::handle_control(uint8_t header, bool &consumed) {
|
||||
if (!m_recv.peek(&b, sizeof(b))) {
|
||||
return true;
|
||||
}
|
||||
m_recv.discard(sizeof(b));
|
||||
discard_recv(sizeof(b));
|
||||
consumed = true;
|
||||
if (on_handshake_ok) {
|
||||
on_handshake_ok();
|
||||
@@ -279,8 +296,9 @@ bool ClassicStream::handle_control(uint8_t header, bool &consumed) {
|
||||
if (!m_recv.peek(&p, sizeof(p))) {
|
||||
return true;
|
||||
}
|
||||
m_recv.discard(sizeof(p));
|
||||
discard_recv(sizeof(p));
|
||||
consumed = true;
|
||||
m_phase = p.phase;
|
||||
if (m_trace) {
|
||||
std::fprintf(stderr, "[classic] phase -> %u\n", p.phase);
|
||||
}
|
||||
@@ -294,7 +312,7 @@ bool ClassicStream::handle_control(uint8_t header, bool &consumed) {
|
||||
if (!m_recv.peek(&b, sizeof(b))) {
|
||||
return true;
|
||||
}
|
||||
m_recv.discard(sizeof(b));
|
||||
discard_recv(sizeof(b));
|
||||
consumed = true;
|
||||
uint8_t pong = HDR_CG_PONG; // 0xFE
|
||||
send_fixed(&pong, 1); // CG_PONG is bSeq=true (sizeof(BYTE)+seq)
|
||||
@@ -305,7 +323,7 @@ bool ClassicStream::handle_control(uint8_t header, bool &consumed) {
|
||||
if (!m_recv.peek(&u, sizeof(u))) {
|
||||
return true;
|
||||
}
|
||||
m_recv.discard(sizeof(u));
|
||||
discard_recv(sizeof(u));
|
||||
consumed = true;
|
||||
return true; // UDP not used
|
||||
}
|
||||
@@ -321,7 +339,7 @@ bool ClassicStream::handle_control(uint8_t header, bool &consumed) {
|
||||
if (!m_recv.peek(&kac, sizeof(kac))) {
|
||||
return true;
|
||||
}
|
||||
m_recv.discard(sizeof(kac));
|
||||
discard_recv(sizeof(kac));
|
||||
consumed = true;
|
||||
m_cipher.set_activated(true);
|
||||
// the server may have pipelined encrypted bytes right behind this;
|
||||
@@ -347,7 +365,7 @@ bool ClassicStream::handle_key_agreement() {
|
||||
if (!m_recv.peek(&pkt, sizeof(pkt))) {
|
||||
return true; // need the whole 261-byte packet
|
||||
}
|
||||
m_recv.discard(sizeof(pkt));
|
||||
discard_recv(sizeof(pkt));
|
||||
|
||||
// generate our own DH2 public blob
|
||||
KeyAgreement out{};
|
||||
@@ -391,7 +409,7 @@ void ClassicStream::dispatch() {
|
||||
// zero padding between packets (cipher block alignment on the m2dev side;
|
||||
// harmless to skip here too).
|
||||
if (header == 0) {
|
||||
m_recv.discard(1);
|
||||
discard_recv(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -430,7 +448,7 @@ void ClassicStream::dispatch() {
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
m_recv.discard(raw.bytes);
|
||||
discard_recv(raw.bytes);
|
||||
continue;
|
||||
case RawPacketStatus::Error:
|
||||
m_last_error = "raw packet framing error";
|
||||
@@ -464,8 +482,11 @@ void ClassicStream::dispatch() {
|
||||
m_recv.discard(sizeof(DynHead));
|
||||
uint32_t body_len = dh.size - sizeof(DynHead);
|
||||
const uint8_t *body = m_recv.read_ptr();
|
||||
if (m_trace) {
|
||||
std::fprintf(stderr, "[classic] recv hdr=%u n=%u dynamic=1\n", header, dh.size);
|
||||
}
|
||||
bool ok = !on_packet || on_packet(header, body, body_len);
|
||||
m_recv.discard(body_len);
|
||||
discard_recv(body_len);
|
||||
if (!ok) {
|
||||
disconnect();
|
||||
return;
|
||||
@@ -477,6 +498,31 @@ void ClassicStream::dispatch() {
|
||||
int total = packet_size_gc(header);
|
||||
if (total <= 0) {
|
||||
m_last_error = "unknown/unsupported GC header " + std::to_string(header);
|
||||
// Some 40250 deployments append one legacy entity packet variant that
|
||||
// is not present in the checked-in header map. It has no length field,
|
||||
// but the following normal GC_CHARACTER_ADD stream is self-describing.
|
||||
// During PHASE_LOADING, recover only at a strongly validated character
|
||||
// boundary; this keeps a deployment-specific spawn packet from aborting
|
||||
// the whole login while leaving malformed encrypted data fatal elsewhere.
|
||||
if (m_phase == PHASE_LOADING) {
|
||||
const uint8_t *bytes = m_recv.read_ptr();
|
||||
bool recovered = false;
|
||||
for (size_t i = 1; i + sizeof(GCCharacterAdd) <= m_recv.readable(); ++i) {
|
||||
if (!plausible_character_add(bytes + i, m_recv.readable() - i)) {
|
||||
continue;
|
||||
}
|
||||
if (m_trace) {
|
||||
std::fprintf(stderr, "[classic] recovering loading stream at +%zu\n", i);
|
||||
}
|
||||
discard_recv(i);
|
||||
m_last_error.clear();
|
||||
recovered = true;
|
||||
break;
|
||||
}
|
||||
if (recovered) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (on_error) {
|
||||
on_error(m_last_error);
|
||||
}
|
||||
@@ -487,8 +533,11 @@ void ClassicStream::dispatch() {
|
||||
return;
|
||||
}
|
||||
const uint8_t *p = m_recv.read_ptr();
|
||||
if (m_trace) {
|
||||
std::fprintf(stderr, "[classic] recv hdr=%u n=%d dynamic=0\n", header, total);
|
||||
}
|
||||
bool ok = !on_packet || on_packet(header, p + 1, static_cast<uint32_t>(total - 1));
|
||||
m_recv.discard(static_cast<size_t>(total));
|
||||
discard_recv(static_cast<size_t>(total));
|
||||
if (!ok) {
|
||||
disconnect();
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user