2V1-d: offline login -> select -> loading against a fake 40250 server

port.login_flow drives the real 40250 root scripts through the ported
CPythonNetworkStream and CAccountConnector against a loopback auth + game
server built from net/classic (wire layouts, sequence table, cipher) that
answers like the 40250 desc.cpp/input*.cpp: handshake, key agreement,
LOGIN3/AUTH_SUCCESS, LOGIN2, the character list, DirectEnter reconnect,
CHARACTER_SELECT, the loading packets and CLIENT_VERSION, with every
sequence byte checked.

- NetStream::ActivateCipher decrypts ciphertext already buffered behind
  KEY_AGREEMENT_COMPLETED (PORT)
- CPythonApplication is final (PORT): no application object exists on the
  platform yet, so its virtual overrides must be called directly
- app.GetTime (GetGlobalTime) and camera/center-position stubs
- SendClientVersionPacket verbatim in the PhaseGame stand-in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 15:20:32 +09:00
co-authored by Claude Opus 5.5
parent 4d9b800efb
commit 8d9d863c7e
10 changed files with 753 additions and 5 deletions
@@ -49,6 +49,15 @@ void CPythonApplication::Abort()
PythonBoot::AppRequestQuit();
}
void CPythonApplication::SetMinFog(float) { MT_PLATFORM_STUB(); }
// In 40250 Process() copies CTimer::GetCurrentSecond() into m_fGlobalTime right after Advance() and
// this returns it. PORT: no member to hold it; the timer only advances in Process(), so reading it
// here gives the same value.
float CPythonApplication::GetGlobalTime() { return CTimer::Instance().GetCurrentSecond(); }
// Camera (2V3).
void CPythonApplication::SetEventCamera(const SCameraSetting&) { MT_PLATFORM_STUB(); }
void CPythonApplication::SetDefaultCamera() { MT_PLATFORM_STUB(); }
// In 40250 this stores m_v3CenterPosition for the game view (introselect.py sets it on the character list).
void CPythonApplication::SetCenterPosition(float, float, float) { MT_PLATFORM_STUB(); }
void CPythonApplication::SetFrameSkip(bool) { MT_PLATFORM_STUB(); }
// PORT: 40250 creates the Win32 window, the D3D8 device, cursors, sound, keyboard, the net device,
// fonts and the IME here. The Godot window and its canvas are the device on this platform and they
@@ -3,6 +3,7 @@
// file in the commit that ports the unit into port/ (docs/PORT-PLAN.md 2V1).
#include "UserInterface/StdAfx.h"
#include "UserInterface/PythonNetworkStream.h"
#include "EterBase/Filename.h"
#include "../../PlatformStub.h"
@@ -44,10 +45,40 @@ bool CPythonNetworkStream::SendChatPacket(const char *, BYTE)
return mt_platform_stub_return<bool>();
}
// PORT: the 40250 body verbatim ahead of its unit: the loading phase sends it on the main-character packet.
bool CPythonNetworkStream::SendClientVersionPacket()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
std::string filename;
GetExcutedFileName(filename);
filename = CFileNameHelper::NoPath(filename);
CFileNameHelper::ChangeDosPath(filename);
if (LocaleService_IsEUROPE() && false == LocaleService_IsYMIR())
{
TPacketCGClientVersion2 kVersionPacket;
kVersionPacket.header = HEADER_CG_CLIENT_VERSION2;
strncpy(kVersionPacket.filename, filename.c_str(), sizeof(kVersionPacket.filename)-1);
strncpy(kVersionPacket.timestamp, "1215955205", sizeof(kVersionPacket.timestamp)-1); // # python time.time 앞자리
//strncpy(kVersionPacket.timestamp, __TIMESTAMP__, sizeof(kVersionPacket.timestamp)-1); // old_string_ver
//strncpy(kVersionPacket.timestamp, "1218055205", sizeof(kVersionPacket.timestamp)-1); // new_future
//strncpy(kVersionPacket.timestamp, "1214055205", sizeof(kVersionPacket.timestamp)-1); // old_past
if (!Send(sizeof(kVersionPacket), &kVersionPacket))
Tracef("SendClientReportPacket Error");
}
else
{
TPacketCGClientVersion kVersionPacket;
kVersionPacket.header = HEADER_CG_CLIENT_VERSION;
strncpy(kVersionPacket.filename, filename.c_str(), sizeof(kVersionPacket.filename)-1);
strncpy(kVersionPacket.timestamp, __TIMESTAMP__, sizeof(kVersionPacket.timestamp)-1);
if (!Send(sizeof(kVersionPacket), &kVersionPacket))
Tracef("SendClientReportPacket Error");
}
return SendSequence();
}
bool CPythonNetworkStream::SendExchangeAcceptPacket()
+9
View File
@@ -211,6 +211,15 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME)
COMMAND $<TARGET_FILE:port_app_loop_test> ${MT_40250_CLIENT} ${MT_PYTHON_STDLIB_ZIP})
set_tests_properties(port.app_loop PROPERTIES SKIP_RETURN_CODE 77)
add_executable(port_login_flow_test ${CMAKE_CURRENT_SOURCE_DIR}/../../tests/port_login_flow_test.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../tests/port_login_flow_server.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../net/classic/classic_cipher.cpp)
target_link_libraries(port_login_flow_test PRIVATE port_platform)
add_dependencies(port_login_flow_test mtpython_stdlib)
add_test(NAME port.login_flow
COMMAND $<TARGET_FILE:port_login_flow_test> ${MT_40250_CLIENT} ${MT_PYTHON_STDLIB_ZIP})
set_tests_properties(port.login_flow PROPERTIES SKIP_RETURN_CODE 77)
add_executable(port_python_ui_modules_test ${CMAKE_CURRENT_SOURCE_DIR}/../../tests/port_python_ui_modules_test.cpp)
target_link_libraries(port_python_ui_modules_test PRIVATE port_platform)
add_dependencies(port_python_ui_modules_test mtpython_stdlib)
+7 -1
View File
@@ -3006,6 +3006,12 @@ bool CNetworkStream::Activate(size_t agreed_length, const void* buffer, size_t l
void CNetworkStream::ActivateCipher()
{
return m_cipher.set_activated(true);
m_cipher.set_activated(true);
// PORT: __RecvInternalBuffer decrypts each recv as it arrives, so encrypted bytes the server sent right
// behind KEY_AGREEMENT_COMPLETED that landed in the same recv are still ciphertext. In 40250 the
// server's next pulse usually keeps them apart; on loopback or a fast LAN they arrive together, so
// decrypt the unread rest of the buffer here.
if (m_recvBufInputPos > m_recvBufOutputPos)
m_cipher.Decrypt(m_recvBuf + m_recvBufOutputPos, m_recvBufInputPos - m_recvBufOutputPos);
}
#endif // _IMPROVED_PACKET_ENCRYPTION_
@@ -42,7 +42,11 @@
#include "AbstractApplication.h"
#include "MovieMan.h"
class CPythonApplication : public CMSApplication, public CInputKeyboard, public IAbstractApplication
// PORT: `final` added. The platform has no CPythonApplication object yet (platform/UserInterface/
// PythonApplication.cpp), so its members are called on an empty Instance(); `final` lets the compiler
// call the IAbstractApplication/CMSApplication overrides directly instead of through a vtable that
// does not exist. Drop it with that note once the application object is constructed.
class CPythonApplication final : public CMSApplication, public CInputKeyboard, public IAbstractApplication
{
public:
enum EDeviceState