port: ServerStateChecker + module (batch 2 P2)
Verbatim 40250 CServerStateChecker and the ServerStateChecker Python
module replace the stub; PythonBoot owns the instance like
CPythonApplication::m_kServerStateChecker. The fake server gets a state
port that answers CG_STATE_CHECKER the way CInputHandshake and
CInputDB::RespondChannelStatus do; login_flow requests the channel list
after the character list (live: {1: 1, 2: 1}).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
be5a68ff35
commit
ce9f96398e
@@ -2,7 +2,6 @@
|
||||
// The module constants of the 2V0 stub modules (GameplayModules.cpp), copied from each 40250
|
||||
// module's init function with the preprocessor lines around them.
|
||||
#include "UserInterface/StdAfx.h"
|
||||
#include "UserInterface/ServerStateChecker.h"
|
||||
#include "UserInterface/PythonSystem.h"
|
||||
#include "UserInterface/PythonBackground.h"
|
||||
#include "EterLib/StateManager.h"
|
||||
@@ -36,12 +35,6 @@
|
||||
#include "UserInterface/PythonPlayer.h"
|
||||
#include "UserInterface/PythonTextTail.h"
|
||||
|
||||
// 40250 UserInterface/ServerStateCheckerModule.cpp
|
||||
void AddStubConstants_ServerStateChecker(PyObject* poModule)
|
||||
{
|
||||
(void) poModule;
|
||||
}
|
||||
|
||||
// 40250 UserInterface/PythonBackgroundModule.cpp
|
||||
void AddStubConstants_background(PyObject* poModule)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "../EterLib/UIRenderCommands.h"
|
||||
|
||||
// GameplayModuleConstants.cpp: each module's 40250 constants.
|
||||
void AddStubConstants_ServerStateChecker(PyObject* poModule);
|
||||
void AddStubConstants_background(PyObject* poModule);
|
||||
void AddStubConstants_event(PyObject* poModule);
|
||||
void AddStubConstants_eventMgr(PyObject* poModule);
|
||||
@@ -34,20 +33,6 @@ PyObject* stub_call_named(const char* name, PyObject* result = nullptr) {
|
||||
return result ? result : Py_BuildValue("i", 0);
|
||||
}
|
||||
|
||||
// 40250 UserInterface/ServerStateCheckerModule.cpp: ServerStateChecker export names.
|
||||
PyObject* stub_ServerStateChecker_Create(PyObject*, PyObject*) { return stub_call_named("ServerStateChecker.Create", Py_BuildNone()); }
|
||||
PyObject* stub_ServerStateChecker_Update(PyObject*, PyObject*) { return stub_call_named("ServerStateChecker.Update", Py_BuildNone()); }
|
||||
PyObject* stub_ServerStateChecker_Request(PyObject*, PyObject*) { return stub_call_named("ServerStateChecker.Request", Py_BuildNone()); }
|
||||
PyObject* stub_ServerStateChecker_AddChannel(PyObject*, PyObject*) { return stub_call_named("ServerStateChecker.AddChannel", Py_BuildNone()); }
|
||||
PyObject* stub_ServerStateChecker_Initialize(PyObject*, PyObject*) { return stub_call_named("ServerStateChecker.Initialize", Py_BuildNone()); }
|
||||
PyMethodDef methods_ServerStateChecker[] = {
|
||||
{ "Create", stub_ServerStateChecker_Create, METH_VARARGS },
|
||||
{ "Update", stub_ServerStateChecker_Update, METH_VARARGS },
|
||||
{ "Request", stub_ServerStateChecker_Request, METH_VARARGS },
|
||||
{ "AddChannel", stub_ServerStateChecker_AddChannel, METH_VARARGS },
|
||||
{ "Initialize", stub_ServerStateChecker_Initialize, METH_VARARGS },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
// 40250 UserInterface/PythonBackgroundModule.cpp: background export names.
|
||||
PyObject* stub_background_IsSoftwareTiling(PyObject*, PyObject*) { return stub_call_named("background.IsSoftwareTiling", Py_BuildValue("i", 0)); }
|
||||
PyObject* stub_background_EnableSoftwareTiling(PyObject*, PyObject*) { return stub_call_named("background.EnableSoftwareTiling", Py_BuildNone()); }
|
||||
@@ -892,11 +877,6 @@ PyMethodDef methods_udp[] = {
|
||||
} // namespace
|
||||
|
||||
void init_2v0_gameplay_stubs() {
|
||||
{
|
||||
PyObject* module = Py_InitModule("ServerStateChecker", methods_ServerStateChecker);
|
||||
PyModule_AddIntConstant(module, "__port_stub__", 1);
|
||||
AddStubConstants_ServerStateChecker(module);
|
||||
}
|
||||
{
|
||||
PyObject* module = Py_InitModule("background", methods_background);
|
||||
PyModule_AddIntConstant(module, "__port_stub__", 1);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "../EterLib/HostCursor.h"
|
||||
#include "UserInterface/PythonNetworkStream.h"
|
||||
#include "UserInterface/AccountConnector.h"
|
||||
#include "UserInterface/ServerStateChecker.h"
|
||||
#include "UserInterface/PythonPlayer.h"
|
||||
#include "GameLib/FlyingObjectManager.h"
|
||||
#include "GameLib/RaceManager.h"
|
||||
@@ -58,9 +59,11 @@ std::unique_ptr<CTimer> g_timer;
|
||||
std::unique_ptr<CPythonNetworkStream> g_network_stream;
|
||||
std::unique_ptr<CAccountConnector> g_account_connector;
|
||||
// 40250 CPythonApplication::m_pyPlayer, m_FlyingManager and m_kEftMgr: the loading phase clears them
|
||||
// (CPythonNetworkStream::SetLoadingPhase). PythonPlayer and FlyingObjectManager are platform/pending stand-ins.
|
||||
// (CPythonNetworkStream::SetLoadingPhase).
|
||||
std::unique_ptr<CPythonPlayer> g_player;
|
||||
std::unique_ptr<CFlyingManager> g_flying_manager;
|
||||
// 40250 CPythonApplication::m_kServerStateChecker: intrologin.py's channel status list.
|
||||
std::unique_ptr<CServerStateChecker> g_server_state_checker;
|
||||
std::unique_ptr<CRaceManager> g_race_manager;
|
||||
std::unique_ptr<CGameEventManager> g_game_event_manager;
|
||||
std::unique_ptr<CEffectManager> g_effect_manager;
|
||||
@@ -314,6 +317,7 @@ bool Start(const char* stdlib_path, std::string* error)
|
||||
g_account_connector = std::make_unique<CAccountConnector>();
|
||||
g_player = std::make_unique<CPythonPlayer>();
|
||||
g_flying_manager = std::make_unique<CFlyingManager>();
|
||||
g_server_state_checker = std::make_unique<CServerStateChecker>();
|
||||
g_race_manager = std::make_unique<CRaceManager>();
|
||||
g_game_event_manager = std::make_unique<CGameEventManager>();
|
||||
g_effect_manager = std::make_unique<CEffectManager>();
|
||||
@@ -347,6 +351,7 @@ bool run_main_script_body(const char* lpCmdLine, std::string* error)
|
||||
initnet();
|
||||
initeffect();
|
||||
initfly();
|
||||
initServerStateChecker();
|
||||
init_2v0_gameplay_stubs();
|
||||
|
||||
ClearLogBoxMessage();
|
||||
@@ -517,6 +522,7 @@ void Stop()
|
||||
g_effect_manager.reset();
|
||||
g_game_event_manager.reset();
|
||||
g_race_manager.reset();
|
||||
g_server_state_checker.reset();
|
||||
g_flying_manager.reset();
|
||||
g_player.reset();
|
||||
g_account_connector.reset();
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#include "StdAfx.h"
|
||||
#include "Packet.h"
|
||||
#include "ServerStateChecker.h"
|
||||
|
||||
#include "../EterLib/NetAddress.h"
|
||||
|
||||
#pragma pack(push)
|
||||
#pragma pack(1)
|
||||
|
||||
typedef unsigned char ServerStateChecker_Header;
|
||||
typedef unsigned long ServerStateChecker_Key;
|
||||
typedef unsigned long ServerStateChecker_Index;
|
||||
typedef unsigned char ServerStateChecker_State;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
CServerStateChecker::CServerStateChecker()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CServerStateChecker::~CServerStateChecker()
|
||||
{
|
||||
Initialize();
|
||||
m_poWnd = NULL;
|
||||
}
|
||||
|
||||
void CServerStateChecker::Create(PyObject* poWnd)
|
||||
{
|
||||
m_poWnd = poWnd;
|
||||
}
|
||||
|
||||
void CServerStateChecker::AddChannel(UINT uServerIndex, const char* c_szAddr, UINT uPort)
|
||||
{
|
||||
TChannel c;
|
||||
c.uServerIndex = uServerIndex;
|
||||
c.c_szAddr = c_szAddr;
|
||||
c.uPort = uPort;
|
||||
|
||||
m_lstChannel.push_back(c);
|
||||
}
|
||||
|
||||
void CServerStateChecker::Request()
|
||||
{
|
||||
if (m_lstChannel.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_kStream.Connect(m_lstChannel.begin()->c_szAddr, m_lstChannel.begin()->uPort))
|
||||
{
|
||||
for (std::list<TChannel>::const_iterator it = m_lstChannel.begin(); it != m_lstChannel.end(); ++it) {
|
||||
PyCallClassMemberFunc(m_poWnd, "NotifyChannelState", Py_BuildValue("(ii)", it->uServerIndex, 0));
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_kStream.ClearRecvBuffer();
|
||||
m_kStream.SetSendBufferSize(1024);
|
||||
m_kStream.SetRecvBufferSize(1024);
|
||||
|
||||
BYTE bHeader = HEADER_CG_STATE_CHECKER;
|
||||
if (!m_kStream.Send(sizeof(bHeader), &bHeader))
|
||||
{
|
||||
for (std::list<TChannel>::const_iterator it = m_lstChannel.begin(); it != m_lstChannel.end(); ++it) {
|
||||
PyCallClassMemberFunc(m_poWnd, "NotifyChannelState", Py_BuildValue("(ii)", it->uServerIndex, 0));
|
||||
}
|
||||
Initialize();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void CServerStateChecker::Update()
|
||||
{
|
||||
m_kStream.Process();
|
||||
|
||||
BYTE bHeader;
|
||||
if (!m_kStream.Recv(sizeof(bHeader), &bHeader)) {
|
||||
return;
|
||||
}
|
||||
if (HEADER_GC_RESPOND_CHANNELSTATUS != bHeader) {
|
||||
return;
|
||||
}
|
||||
int nSize;
|
||||
if (!m_kStream.Recv(sizeof(nSize), &nSize)) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < nSize; i++) {
|
||||
TChannelStatus channelStatus;
|
||||
if (!m_kStream.Recv(sizeof(channelStatus), &channelStatus)) {
|
||||
return;
|
||||
}
|
||||
for (std::list<TChannel>::const_iterator it = m_lstChannel.begin(); it != m_lstChannel.end(); ++it) {
|
||||
if (channelStatus.nPort == it->uPort) {
|
||||
PyCallClassMemberFunc(m_poWnd, "NotifyChannelState", Py_BuildValue("(ii)", it->uServerIndex, channelStatus.bStatus));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void CServerStateChecker::Initialize()
|
||||
{
|
||||
m_lstChannel.clear();
|
||||
m_kStream.Disconnect();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "StdAfx.h"
|
||||
#include "ServerStateChecker.h"
|
||||
|
||||
PyObject * ServerStateCheckerCreate(PyObject* poSelf, PyObject* poArgs)
|
||||
{
|
||||
PyObject * poWnd;
|
||||
if (!PyTuple_GetObject(poArgs, 0, &poWnd))
|
||||
return Py_BadArgument();
|
||||
|
||||
CServerStateChecker::Instance().Create(poWnd);
|
||||
return Py_BuildNone();
|
||||
}
|
||||
|
||||
PyObject * ServerStateCheckerUpdate(PyObject* poSelf, PyObject* poArgs)
|
||||
{
|
||||
CServerStateChecker::Instance().Update();
|
||||
return Py_BuildNone();
|
||||
}
|
||||
|
||||
PyObject * ServerStateCheckerRequest(PyObject* poSelf, PyObject* poArgs)
|
||||
{
|
||||
CServerStateChecker::Instance().Request();
|
||||
return Py_BuildNone();
|
||||
}
|
||||
|
||||
PyObject * ServerStateCheckerAddChannel(PyObject* poSelf, PyObject* poArgs)
|
||||
{
|
||||
int nServerIndex;
|
||||
if (!PyTuple_GetInteger(poArgs, 0, &nServerIndex))
|
||||
return Py_BuildException();
|
||||
|
||||
char * szAddr;
|
||||
if (!PyTuple_GetString(poArgs, 1, &szAddr))
|
||||
return Py_BuildException();
|
||||
|
||||
int nPort;
|
||||
if (!PyTuple_GetInteger(poArgs, 2, &nPort))
|
||||
return Py_BuildException();
|
||||
|
||||
CServerStateChecker::Instance().AddChannel(nServerIndex, szAddr, nPort);
|
||||
return Py_BuildNone();
|
||||
}
|
||||
|
||||
PyObject * ServerStateCheckerInitialize(PyObject* poSelf, PyObject* poArgs)
|
||||
{
|
||||
CServerStateChecker::Instance().Initialize();
|
||||
return Py_BuildNone();
|
||||
}
|
||||
|
||||
void initServerStateChecker()
|
||||
{
|
||||
static PyMethodDef s_methods[] =
|
||||
{
|
||||
{ "Create", ServerStateCheckerCreate, METH_VARARGS },
|
||||
{ "Update", ServerStateCheckerUpdate, METH_VARARGS },
|
||||
{ "Request", ServerStateCheckerRequest, METH_VARARGS },
|
||||
{ "AddChannel", ServerStateCheckerAddChannel, METH_VARARGS },
|
||||
{ "Initialize", ServerStateCheckerInitialize, METH_VARARGS },
|
||||
{ NULL, NULL, NULL },
|
||||
};
|
||||
|
||||
//PyObject * poModule = Py_InitModule("ServerStateChecker", s_methods);
|
||||
Py_InitModule("ServerStateChecker", s_methods);
|
||||
}
|
||||
@@ -51,3 +51,4 @@ void initChat();
|
||||
void initnet();
|
||||
void initeffect();
|
||||
void initfly();
|
||||
void initServerStateChecker();
|
||||
|
||||
@@ -142,9 +142,11 @@ bool FakeLoginServer::Start()
|
||||
{
|
||||
m_auth_listener = listen_loopback(&m_auth_port);
|
||||
m_game_listener = listen_loopback(&m_game_port);
|
||||
if (m_auth_listener < 0 || m_game_listener < 0)
|
||||
m_state_listener = listen_loopback(&m_state_port);
|
||||
if (m_auth_listener < 0 || m_game_listener < 0 || m_state_listener < 0)
|
||||
return false;
|
||||
m_thread = std::thread([this] { Run(); });
|
||||
m_state_thread = std::thread([this] { ServeState(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -153,7 +155,9 @@ void FakeLoginServer::Stop()
|
||||
m_stop = true;
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
for (int* fd : {&m_auth_listener, &m_game_listener})
|
||||
if (m_state_thread.joinable())
|
||||
m_state_thread.join();
|
||||
for (int* fd : {&m_auth_listener, &m_game_listener, &m_state_listener})
|
||||
if (*fd >= 0)
|
||||
{
|
||||
close(*fd);
|
||||
@@ -287,6 +291,52 @@ bool FakeLoginServer::Handshake(Connection& c, std::uint8_t phase_after)
|
||||
return c.Phase(phase_after) || Fail("send phase after handshake");
|
||||
}
|
||||
|
||||
// CServerStateChecker::Request connects to the first channel and sends the bare header. The game server
|
||||
// has already started the handshake (DESC::Setup), so the client reads past those bytes one per Update;
|
||||
// CInputHandshake::Analyze(HEADER_CG_STATE_CHECKER) asks the db, and CInputDB::RespondChannelStatus writes
|
||||
// the header, the count, the TChannelStatus {short nPort; BYTE bStatus} array and a success byte.
|
||||
void FakeLoginServer::ServeState()
|
||||
{
|
||||
Connection c;
|
||||
c.stop = &m_stop;
|
||||
for (;;)
|
||||
{
|
||||
if (m_stop)
|
||||
return;
|
||||
pollfd pfd = {m_state_listener, POLLIN, 0};
|
||||
if (poll(&pfd, 1, 50) > 0)
|
||||
break;
|
||||
}
|
||||
c.fd = accept(m_state_listener, nullptr, nullptr);
|
||||
if (c.fd < 0)
|
||||
return;
|
||||
mtnet::classic::Handshake hs = {HDR_HANDSHAKE, kHandshake, now_ms(), 0};
|
||||
if (!c.Phase(PHASE_HANDSHAKE) || !c.Send(hs))
|
||||
return;
|
||||
std::uint8_t header = 0;
|
||||
if (c.Read(&header, 1) != 1 || header != 206)
|
||||
{
|
||||
Fail("state checker: expected CG_STATE_CHECKER");
|
||||
return;
|
||||
}
|
||||
Event("state:checker");
|
||||
std::vector<std::uint8_t> out = {210};
|
||||
const std::int32_t count = 2;
|
||||
out.insert(out.end(), reinterpret_cast<const std::uint8_t*>(&count), reinterpret_cast<const std::uint8_t*>(&count) + 4);
|
||||
for (int port : {m_state_port, kOtherChannelPort})
|
||||
{
|
||||
const std::int16_t short_port = static_cast<std::int16_t>(port);
|
||||
out.insert(out.end(), reinterpret_cast<const std::uint8_t*>(&short_port), reinterpret_cast<const std::uint8_t*>(&short_port) + 2);
|
||||
out.push_back(static_cast<std::uint8_t>(kOtherChannelStatus));
|
||||
}
|
||||
out.push_back(1);
|
||||
c.Send(out.data(), out.size());
|
||||
// Keep the socket open until the client disconnects (CServerStateChecker::Initialize).
|
||||
std::uint8_t rest;
|
||||
while (c.Read(&rest, 1) == 1)
|
||||
;
|
||||
}
|
||||
|
||||
// Reads one client packet of a known header: the static size from packet_info.cpp plus the sequence
|
||||
// byte CInputProcessor::Process checks against gc_abSequence.
|
||||
static int read_packet(FakeLoginServer::Connection& c, std::uint8_t expect, void* packet, size_t size,
|
||||
|
||||
@@ -30,6 +30,11 @@ public:
|
||||
|
||||
int AuthPort() const { return m_auth_port; }
|
||||
int GamePort() const { return m_game_port; }
|
||||
// A third game-server port that only answers intrologin's ServerStateChecker (CG_STATE_CHECKER).
|
||||
int StatePort() const { return m_state_port; }
|
||||
// The channel port the state answer reports besides StatePort(), with status kOtherChannelStatus.
|
||||
static constexpr int kOtherChannelPort = 13002;
|
||||
static constexpr int kOtherChannelStatus = 1;
|
||||
|
||||
// Milestones in arrival order ("auth:login3", "game1:login2", ...), and the first protocol error.
|
||||
std::vector<std::string> Events();
|
||||
@@ -45,13 +50,15 @@ private:
|
||||
bool ServeAuth(Connection& c);
|
||||
bool ServeGame(Connection& c, int index);
|
||||
bool Handshake(Connection& c, std::uint8_t phase_after);
|
||||
void ServeState();
|
||||
bool Accept(int listener, Connection& c);
|
||||
void Event(const std::string& event);
|
||||
bool Fail(const std::string& error);
|
||||
|
||||
int m_auth_listener = -1, m_game_listener = -1;
|
||||
int m_auth_port = 0, m_game_port = 0;
|
||||
std::thread m_thread;
|
||||
int m_state_listener = -1;
|
||||
int m_auth_port = 0, m_game_port = 0, m_state_port = 0;
|
||||
std::thread m_thread, m_state_thread;
|
||||
std::atomic<bool> m_stop{false};
|
||||
std::mutex m_mutex;
|
||||
std::vector<std::string> m_events;
|
||||
|
||||
@@ -269,6 +269,34 @@ int main(int argc, char** argv)
|
||||
"assert net.GetEmpireID() in (1, 2, 3), net.GetEmpireID()"));
|
||||
}
|
||||
|
||||
// 3b. intrologin's channel status list (__RequestServerStateList / NotifyChannelState): ServerStateChecker
|
||||
// connects to the first channel, sends CG_STATE_CHECKER and reports each channel the answer lists
|
||||
// by port. TChannelStatus::nPort is a short, so the fake server's ephemeral state port (> 32767)
|
||||
// never matches its own channel; offline the proof is the second channel (13002) it also lists.
|
||||
// Live the game port answers for the channels the db knows, the game port among them.
|
||||
{
|
||||
const int state_port = server ? server->StatePort() : game_port;
|
||||
const int key = server ? 2 : 1;
|
||||
char channels[256];
|
||||
std::snprintf(channels, sizeof(channels),
|
||||
"ServerStateChecker.AddChannel(1, _host, %d)\nServerStateChecker.AddChannel(2, _host, %d)\n"
|
||||
"_channel_key = %d\n",
|
||||
state_port, FakeLoginServer::kOtherChannelPort, key);
|
||||
CHECK(py(std::string("import ServerStateChecker\n"
|
||||
"class _ChannelStates:\n"
|
||||
" def __init__(self): self.states = {}\n"
|
||||
" def NotifyChannelState(self, key, state): self.states[key] = state\n"
|
||||
"_channel_states = _ChannelStates()\n"
|
||||
"ServerStateChecker.Create(_channel_states)\n"
|
||||
"ServerStateChecker.Initialize()\n") +
|
||||
channels + "ServerStateChecker.Request()"));
|
||||
CHECK(pump_until(10, [] { return py("ServerStateChecker.Update()") && py_true("_channel_key in _channel_states.states"); }));
|
||||
py("import sys\nsys.stderr.write('port_login_flow_test: channel states %r\\n' % _channel_states.states)");
|
||||
CHECK(py_true("_channel_states.states.get(_channel_key, 0) > 0"));
|
||||
if (server)
|
||||
CHECK(server->Has("state:checker"));
|
||||
}
|
||||
|
||||
// 4-5. Start the game on the slot: DirectEnter, the second game login and the loading phase.
|
||||
if (selecting)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user