fix(server/game): replace the old session on a new login

This commit is contained in:
capyb2222 committed 2026-09-12 20:30:03 -07:00
1 parent 59e7471014
commit c16c95db94
6 files changed
+38

No files matched your search

+3
View File
@@ -32,6 +32,9 @@ void fillBasicInfo(proto::PlayerBasicInfo& info, const Player& player) {
void onGetToken(net::Session& session, const proto::PlayerGetTokenCsReq& req) { void onGetToken(net::Session& session, const proto::PlayerGetTokenCsReq& req) {
uint32_t uid = core::Config::get().player.uid; uint32_t uid = core::Config::get().player.uid;
// Before the save is read: a session this login replaces saves its newest state on the
// way out, and cannot write a stale copy over this one's later.
session.dropOtherLogins(uid);
auto player = std::make_shared<Player>(uid); auto player = std::make_shared<Player>(uid);
player->setMainCharacter(core::Config::get().gameplay.mainCharacter); player->setMainCharacter(core::Config::get().gameplay.mainCharacter);
player->setMarchType(core::Config::get().gameplay.marchType); player->setMarchType(core::Config::get().gameplay.marchType);
+14
View File
@@ -5,6 +5,7 @@
#include "core/logger.h" #include "core/logger.h"
#include "core/util.h" #include "core/util.h"
#include "game/player.h"
namespace net { namespace net {
namespace { namespace {
@@ -102,6 +103,19 @@ void Gateway::drop(uint32_t conv, const char* why) {
logging::info("game", "conv {} from {} disconnected ({})", conv, session->remote().str(), why); logging::info("game", "conv {} from {} disconnected ({})", conv, session->remote().str(), why);
} }
void Gateway::dropOthers(const Session& keep, uint32_t uid) {
std::vector<uint32_t> stale;
{
std::lock_guard lock(mutex_);
for (const auto& [conv, session] : sessions_) {
if (session.get() == &keep) continue;
const game::Player* player = session->player();
if (player != nullptr && player->uid() == uid) stale.push_back(conv);
}
}
for (uint32_t conv : stale) drop(conv, "the account logged in again");
}
void Gateway::receiveLoop() { void Gateway::receiveLoop() {
std::vector<char> buffer(kMaxDatagram); std::vector<char> buffer(kMaxDatagram);
while (running_) { while (running_) {
+3
View File
@@ -24,6 +24,9 @@ public:
// `why` is logged: knowing whether the client hung up, timed out or handshook // `why` is logged: knowing whether the client hung up, timed out or handshook
// again is most of the diagnosis when a session ends unexpectedly. // again is most of the diagnosis when a session ends unexpectedly.
void drop(uint32_t conv, const char* why); void drop(uint32_t conv, const char* why);
// A restarted client comes from a new port, so its old session outlives it; this drops
// that one before the new login reads the save it would otherwise overwrite later.
void dropOthers(const Session& keep, uint32_t uid);
std::shared_ptr<Session> find(uint32_t conv); std::shared_ptr<Session> find(uint32_t conv);
// Every session still connected, in conv order. The admin routes act on these. // Every session still connected, in conv order. The admin routes act on these.
std::vector<std::shared_ptr<Session>> sessions(); std::vector<std::shared_ptr<Session>> sessions();
+2
View File
@@ -143,6 +143,8 @@ void Session::sendRaw(uint16_t cmdId, std::string_view body) {
void Session::sendEmpty(uint16_t cmdId) { sendRaw(cmdId, {}); } void Session::sendEmpty(uint16_t cmdId) { sendRaw(cmdId, {}); }
void Session::dropOtherLogins(uint32_t uid) { gateway_.dropOthers(*this, uid); }
void Session::flushOutbound() { void Session::flushOutbound() {
if (!kcp_) return; if (!kcp_) return;
// ikcp_flush stamps every segment from kcp->current, and only ikcp_update ever // ikcp_flush stamps every segment from kcp->current, and only ikcp_update ever
+3
View File
@@ -54,6 +54,9 @@ public:
void sendRaw(uint16_t cmdId, std::string_view body); void sendRaw(uint16_t cmdId, std::string_view body);
void sendEmpty(uint16_t cmdId); void sendEmpty(uint16_t cmdId);
// Drops every other session logged in as `uid`; each saves on its way out.
void dropOtherLogins(uint32_t uid);
template <class T> template <class T>
void send(uint16_t cmdId, const T& message) { void send(uint16_t cmdId, const T& message) {
sendRaw(cmdId, message.serialize()); sendRaw(cmdId, message.serialize());
+13
View File
@@ -1073,5 +1073,18 @@ void runFlowTests() {
check(!client.await(cmd::ChessRogueQueryScRsp, packet, 600), check(!client.await(cmd::ChessRogueQueryScRsp, packet, 600),
"a silenced module gets no reply"); "a silenced module gets no reply");
// The same account logging in from a new port replaces the old session, instead of
// leaving it to write its stale copy over the new login's save when it times out.
{
TestClient second;
check(second.connect(kTestPort), "a second client connects");
proto::PlayerGetTokenCsReq again;
again.account_uid = "capybara";
second.send(cmd::PlayerGetTokenCsReq, again);
net::Packet secondPacket;
check(second.await(cmd::PlayerGetTokenScRsp, secondPacket), "and logs in");
check(gateway.sessions().size() == 1, "leaving one session for the account");
}
gateway.stop(); gateway.stop();
} }