Fix 40250 classic login and world loading

This commit is contained in:
shen
2026-09-02 07:03:25 +08:00
parent 8f61990a83
commit 44bdcb0781
17 changed files with 446 additions and 45 deletions
+60 -11
View File
@@ -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;