2V2-a: game.GameWindow opens after Loading and sends CG_ENTERGAME

The 2V0 stub modules now carry the 40250 module constants (copied from each
module's init function by stub_constants.py) and return zero values shaped like
each reference function's Py_BuildValue, so interfaceModule.MakeInterface runs
through (uiSafebox no longer divides by a zero SAFEBOX_SLOT_Y_COUNT).

UserInterface/StdAfx.h includes Locale.h before GameType.h as the 40250 PCH
does; the reversed order dropped GameType.h's ENABLE_NEW_EQUIPMENT_SYSTEM
branches.

port.login_flow's fake server checks CG_ENTERGAME; port.login_live reaches the
GameWindow on 192.168.21.203.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 16:56:23 +09:00
co-authored by Claude Opus 5.5
parent 0073469dd1
commit d16fabf61c
9 changed files with 2048 additions and 833 deletions
+7 -1
View File
@@ -434,7 +434,13 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
c.sequence = (c.sequence + 1) % SEQUENCE_TABLE_SIZE;
Event(name + ":client_version");
// Hold the connection (the loading screen) until the test stops us.
// game.GameWindow.Open (after the loading steps' net.StartGame()) sends CG_ENTERGAME.
auto enter = zeroed<CGEnterGame>();
if (read_packet(c, HDR_CG_ENTERGAME, &enter, sizeof(enter), &error) != 1)
return Fail(name + ": CG_ENTERGAME: " + error);
Event(name + ":entergame");
// Hold the connection until the test stops us.
std::uint8_t byte = 0;
while (!m_stop && c.Read(&byte, 1) == 1)
;
+17 -8
View File
@@ -1,4 +1,4 @@
// 批次 2V1-d: the real 40250 login → select → loading path, offline.
// 批次 2V1-d/2V2: the real 40250 login → select → loading → game path, offline.
//
// The 40250 root scripts (intrologin.py, introselect.py, introloading.py, networkmodule.py) drive the
// ported CPythonNetworkStream and CAccountConnector against FakeLoginServer (port_login_flow_server.h),
@@ -13,7 +13,9 @@
// slot's addr/port, logs in again, and in direct-enter mode the select phase goes straight to
// introLoading.LoadingWindow, which sends CG_CHARACTER_SELECT;
// 5. PHASE_LOADING, GC_MAIN_CHARACTER, GC_CHARACTER_POINTS: the loading phase answers the main
// character with CG_CLIENT_VERSION(2), the server's proof the client reached Loading.
// character with CG_CLIENT_VERSION(2), the server's proof the client reached Loading;
// 6. (批次 2V2) the loading steps end in net.StartGame(): networkModule.SetGamePhase opens
// game.GameWindow, whose Open sends CG_ENTERGAME.
//
// The server checks every sequence byte against the 40250 table, so a skipped or extra sequenced
// packet fails the run.
@@ -21,9 +23,9 @@
// port_login_flow_test <40250 Client dir> <python27.zip> [live]
//
// With `live` (批次 2V1-e, ctest port.login_live) the same path runs against a real 40250 server and
// the client's own state is the proof: the character list arrived, and the loading window got
// the client's own state is the proof: the character list arrived, the loading window got
// LoadData from GC_MAIN_CHARACTER, which CPythonNetworkStream::RecvMainCharacter answers with
// CG_CLIENT_VERSION. The test exits before net.StartGame() would enter the game. Everything about the
// CG_CLIENT_VERSION, and game.GameWindow is open, which sent CG_ENTERGAME. Everything about the
// server comes from the environment and nothing of it is printed:
//
// MT_LIVE_HOST, MT_LIVE_LOGIN, MT_LIVE_PASSWORD required; without them the test is skipped
@@ -226,16 +228,23 @@ int main(int argc, char** argv)
return failed() || (server ? server->Has("game2:client_version") : py_true(loaded));
});
CHECK(loading && !failed());
// Checked right away: introLoading's own update steps end in net.StartGame(), after which the stream
// opens game.GameWindow itself (CPythonNetworkStream::Process, m_isStartGame), which is 2V2.
CHECK(py_true(loaded));
}
// 6. introLoading's update steps end in net.StartGame(); the next CPythonNetworkStream::Process calls
// networkModule's SetGamePhase, which builds game.GameWindow (interfaceModule.MakeInterface) and
// opens it, and GameWindow.Open sends CG_ENTERGAME.
CHECK(py("import game"));
const char* gaming = "isinstance(_stream.curPhaseWindow, game.GameWindow) and _stream.curPhaseWindow.IsShow()";
const bool entered = pump_until(30, [&] {
return failed() || (py_true(gaming) && (!server || server->Has("game2:entergame")));
});
CHECK(entered && !failed());
}
if (server)
{
const std::vector<std::string> expected = {
"auth:handshake", "auth:login3", "game1:handshake", "game1:login2", "auth:closed",
"game1:closed", "game2:handshake", "game2:login2", "game2:select", "game2:client_version",
"game1:closed", "game2:handshake", "game2:login2", "game2:select", "game2:client_version", "game2:entergame",
};
auto events = server->Events();
// auth:closed races game1's handshake; compare the order within each connection.