Register observable 2V0 gameplay stubs through RunApp

This commit is contained in:
shen
2026-09-22 20:05:22 -07:00
parent b221a9eaf5
commit 9c30f06083
8 changed files with 2014 additions and 14 deletions
+1
View File
@@ -503,3 +503,4 @@
{"time": "2026-09-23T11:40:00Z", "event": "port_round", "unit": "PORT-PLAN 批次 2V0-a: the EterPythonLib window-system headers enter the mirror", "ported": ["EterPythonLib/StdAfx.h", "EterPythonLib/PythonGraphic.h", "EterPythonLib/PythonWindow.h", "EterPythonLib/PythonWindowManager.h", "EterPythonLib/PythonSlotWindow.h", "EterPythonLib/PythonGridSlotWindow.h"], "adapted": [], "deleted": [], "divergent": [], "needs_live": [], "staging": {"scope": "headers only; no .cpp unit and therefore no port-map entry — declarations, nothing to call. The EterPythonLib .cpp units are 2V0-b.", "mechanical": "port_copy.py copy, 0 of 6 with manual edits: every one compiles as a mechanical CP949->UTF-8 / CRLF->LF / include-case copy.", "stdafx": "EterPythonLib/StdAfx.h had to come first: port_header_gate compiles each header behind its library's StdAfx.h, as 40250 does, and without it these five see neither <Python.h>, the STL, nor CSingleton.", "slice_list_correction": "audit/slices/2V0.json lists EterLib/Dimm.h as a missing header. It is not: 40250 vendors the MIDL output of the Windows SDK <dimm.h> as EterLib/Dimm.h, and extension/src/port/common/shim/win32/Dimm.h already shims it (a mirror copy shadows the shim for EterLib/IME.h's quoted include and breaks the build on 'rpc.h'). Copied, found, and removed again."}, "status_note": "2V0 entry state measured: of the slice's 212 headers 164 were already in the mirror, 48 were not (1 EterLib = the SDK shim above, 5 EterPythonLib = this round, 42 UserInterface = 2V0-d, all behind UserInterface/PythonApplication.h). Of the 10 units, 2 are ported (ScriptLib/PythonLauncher.cpp, UserInterface/PythonPackModule.cpp) and 8 are not.", "tests": ["cmake --build build --target port_header_gate -> the 6 new gate translation units compile (each header alone, twice, behind EterPythonLib/StdAfx.h)", "ctest --test-dir build -> 27/27 passed"], "not_done": "EterPythonLib/*.cpp(2V0-b);wndMgr/grp/grpImage/grpText 模块(2V0-c);42 个 UserInterface 头 + PythonApplicationModule.cpp(2V0-d)", "commit": "2V0-a"} {"time": "2026-09-23T11:40:00Z", "event": "port_round", "unit": "PORT-PLAN 批次 2V0-a: the EterPythonLib window-system headers enter the mirror", "ported": ["EterPythonLib/StdAfx.h", "EterPythonLib/PythonGraphic.h", "EterPythonLib/PythonWindow.h", "EterPythonLib/PythonWindowManager.h", "EterPythonLib/PythonSlotWindow.h", "EterPythonLib/PythonGridSlotWindow.h"], "adapted": [], "deleted": [], "divergent": [], "needs_live": [], "staging": {"scope": "headers only; no .cpp unit and therefore no port-map entry — declarations, nothing to call. The EterPythonLib .cpp units are 2V0-b.", "mechanical": "port_copy.py copy, 0 of 6 with manual edits: every one compiles as a mechanical CP949->UTF-8 / CRLF->LF / include-case copy.", "stdafx": "EterPythonLib/StdAfx.h had to come first: port_header_gate compiles each header behind its library's StdAfx.h, as 40250 does, and without it these five see neither <Python.h>, the STL, nor CSingleton.", "slice_list_correction": "audit/slices/2V0.json lists EterLib/Dimm.h as a missing header. It is not: 40250 vendors the MIDL output of the Windows SDK <dimm.h> as EterLib/Dimm.h, and extension/src/port/common/shim/win32/Dimm.h already shims it (a mirror copy shadows the shim for EterLib/IME.h's quoted include and breaks the build on 'rpc.h'). Copied, found, and removed again."}, "status_note": "2V0 entry state measured: of the slice's 212 headers 164 were already in the mirror, 48 were not (1 EterLib = the SDK shim above, 5 EterPythonLib = this round, 42 UserInterface = 2V0-d, all behind UserInterface/PythonApplication.h). Of the 10 units, 2 are ported (ScriptLib/PythonLauncher.cpp, UserInterface/PythonPackModule.cpp) and 8 are not.", "tests": ["cmake --build build --target port_header_gate -> the 6 new gate translation units compile (each header alone, twice, behind EterPythonLib/StdAfx.h)", "ctest --test-dir build -> 27/27 passed"], "not_done": "EterPythonLib/*.cpp(2V0-b);wndMgr/grp/grpImage/grpText 模块(2V0-c);42 个 UserInterface 头 + PythonApplicationModule.cpp(2V0-d)", "commit": "2V0-a"}
{"time": "2026-09-23T02:17:52Z", "event": "port_round", "unit": "PORT-PLAN 批次 2V0-b: window tree completion and 64-bit LZO boundary repair", "ported": ["EterPythonLib/PythonWindow.cpp (211 functions)", "EterPythonLib/PythonWindowManager.cpp (76)", "EterPythonLib/PythonSlotWindow.cpp (80)", "EterPythonLib/PythonGridSlotWindow.cpp (14)", "EterLib/ReferenceObject.cpp (9)", "EterLib/Resource.cpp (18)", "EterLib/ResourceManager.cpp (25)"], "adapted": ["EterBase/lzo.cpp: CLZObject::Compress and CLZObject::Decompress use native-width lzo_uint at the liblzo API while retaining 32-bit pack-header lengths"], "divergent": [], "needs_live": [], "status_note": "The seven units in b4c9d6ce are exact mechanical copies of 40250 (0 manual edits); their 433 functions are recorded in port-map. The original port.window_tree test had four expectations contrary to 40250: DeactivateWindow restores the previous active window and SetTop changes update order; corrected the expectations. A fresh 64-bit build exposed stack corruption in the older LZO caller because Win32 UINT (4 bytes) was passed as native lzo_uint* (8 bytes); fixed the boundary. The normal app path still does not load the UI shell; module registration and rendering remain 2V0-c through 2V0-f.", "tests": ["port_copy.py diff on seven units: 0 manual edits", "ctest -R port.window_tree: pass", "ctest -R port.eterpack: pass, including added LZO compress/decompress round trip and full pack inventory", "ctest -R port.python_launcher: pass", "ctest --test-dir build --output-on-failure -j6: 28/28 pass", "port_map.py check: 0 errors"], "not_done": "2V0-c through 2V0-f; normal app startup and visible interactive Logo/Popup are the whole 2V0 acceptance gate, not the 2V0-b substep.", "commit": "2V0-b-completion"} {"time": "2026-09-23T02:17:52Z", "event": "port_round", "unit": "PORT-PLAN 批次 2V0-b: window tree completion and 64-bit LZO boundary repair", "ported": ["EterPythonLib/PythonWindow.cpp (211 functions)", "EterPythonLib/PythonWindowManager.cpp (76)", "EterPythonLib/PythonSlotWindow.cpp (80)", "EterPythonLib/PythonGridSlotWindow.cpp (14)", "EterLib/ReferenceObject.cpp (9)", "EterLib/Resource.cpp (18)", "EterLib/ResourceManager.cpp (25)"], "adapted": ["EterBase/lzo.cpp: CLZObject::Compress and CLZObject::Decompress use native-width lzo_uint at the liblzo API while retaining 32-bit pack-header lengths"], "divergent": [], "needs_live": [], "status_note": "The seven units in b4c9d6ce are exact mechanical copies of 40250 (0 manual edits); their 433 functions are recorded in port-map. The original port.window_tree test had four expectations contrary to 40250: DeactivateWindow restores the previous active window and SetTop changes update order; corrected the expectations. A fresh 64-bit build exposed stack corruption in the older LZO caller because Win32 UINT (4 bytes) was passed as native lzo_uint* (8 bytes); fixed the boundary. The normal app path still does not load the UI shell; module registration and rendering remain 2V0-c through 2V0-f.", "tests": ["port_copy.py diff on seven units: 0 manual edits", "ctest -R port.window_tree: pass", "ctest -R port.eterpack: pass, including added LZO compress/decompress round trip and full pack inventory", "ctest -R port.python_launcher: pass", "ctest --test-dir build --output-on-failure -j6: 28/28 pass", "port_map.py check: 0 errors"], "not_done": "2V0-c through 2V0-f; normal app startup and visible interactive Logo/Popup are the whole 2V0 acceptance gate, not the 2V0-b substep.", "commit": "2V0-b-completion"}
{"time": "2026-09-23T02:57:22Z", "event": "port_round", "unit": "PORT-PLAN 2V0-c/d and beginning of 2V0-e", "ported": ["EterPythonLib/PythonWindowManagerModule.cpp", "EterPythonLib/PythonGraphicModule.cpp", "EterPythonLib/PythonGraphicImageModule.cpp", "EterPythonLib/PythonGraphicTextModule.cpp", "EterPythonLib/PythonGraphicThingModule.cpp", "UserInterface/PythonApplicationModule.cpp", "ScriptLib/PythonDebugModule.cpp", "42 UserInterface dependency headers"], "adapted": ["Python pointer handles via PyLong_FromVoidPtr/PyLong_AsVoidPtr (native pointer width)", "Win32 FindFirstFile/FindNextFile shim for app file enumeration", "Godot PythonBoot registers dbg, grp, grpImage, grpText, wndMgr and app and propagates dbg.LogBox failures"], "divergent": ["CPythonApplication platform lifecycle and graphics methods remain MT_PLATFORM_STUB; no visible Logo/Popup yet", "DevIL image and DirectShow logo backends remain MT_PLATFORM_STUB"], "tests": ["cmake --build build -> pass", "ctest --test-dir build --output-on-failure -j6 -> 29/29 passed, 1 skipped", "godot --headless --path project --script python_host_test.gd -> system.py reaches constInfo.py import net and reports ImportError through RunMainScript", "port_map.py check -> 0 errors"], "not_done": "2V0-e native gameplay module stubs and prototype.RunApp; 2V0-f Godot UI renderer/input/Logo/Popup runtime acceptance", "status_note": "The app binding imports, but its CPythonApplication adapter is only a link surface. The 2V0 acceptance gate is not yet met."} {"time": "2026-09-23T02:57:22Z", "event": "port_round", "unit": "PORT-PLAN 2V0-c/d and beginning of 2V0-e", "ported": ["EterPythonLib/PythonWindowManagerModule.cpp", "EterPythonLib/PythonGraphicModule.cpp", "EterPythonLib/PythonGraphicImageModule.cpp", "EterPythonLib/PythonGraphicTextModule.cpp", "EterPythonLib/PythonGraphicThingModule.cpp", "UserInterface/PythonApplicationModule.cpp", "ScriptLib/PythonDebugModule.cpp", "42 UserInterface dependency headers"], "adapted": ["Python pointer handles via PyLong_FromVoidPtr/PyLong_AsVoidPtr (native pointer width)", "Win32 FindFirstFile/FindNextFile shim for app file enumeration", "Godot PythonBoot registers dbg, grp, grpImage, grpText, wndMgr and app and propagates dbg.LogBox failures"], "divergent": ["CPythonApplication platform lifecycle and graphics methods remain MT_PLATFORM_STUB; no visible Logo/Popup yet", "DevIL image and DirectShow logo backends remain MT_PLATFORM_STUB"], "tests": ["cmake --build build -> pass", "ctest --test-dir build --output-on-failure -j6 -> 29/29 passed, 1 skipped", "godot --headless --path project --script python_host_test.gd -> system.py reaches constInfo.py import net and reports ImportError through RunMainScript", "port_map.py check -> 0 errors"], "not_done": "2V0-e native gameplay module stubs and prototype.RunApp; 2V0-f Godot UI renderer/input/Logo/Popup runtime acceptance", "status_note": "The app binding imports, but its CPythonApplication adapter is only a link surface. The 2V0 acceptance gate is not yet met."}
{"time": "2026-09-23T03:04:18Z", "event": "port_round", "unit": "PORT-PLAN 2V0-e: native gameplay import stubs through prototype.RunApp", "ported": [], "adapted": ["27 native module import surfaces generated from 40250 UserInterface export names; functions log module.method and return a placeholder zero", "LocaleService default en/1252 from supplied Client/locale.cfg; ForceSetLocale stores requested path", "CPythonApplication::Create platform stub raises CREATE_DEVICE so prototype.RunApp handles failure as original Python code expects"], "divergent": ["Gameplay module methods and constants are placeholders, not ports; their calls do not implement gameplay", "The app platform adapter does not create a device, so RunApp exits before Logo/Popup"], "tests": ["godot --headless --path project --script python_host_test.gd -> PASS: system.py reaches prototype.RunApp, catches CREATE_DEVICE, and net/player/item are marked __port_stub__", "ctest --test-dir build --output-on-failure -j6 -> 29/29 passed, one skipped", "port_map.py check -> 0 errors"], "not_done": "2V0-f: real Godot UI adapter, visible interactive Logo/Popup, input/focus/clipping/text/image tests"}
+1 -1
View File
@@ -399,7 +399,7 @@ extension/third_party/cpython-2.7.18/ # 静态库(2P 批次
| 2V0-b | `EterPythonLib/PythonWindow.cpp`、`PythonWindowManager.cpp`(窗口树逻辑;含 Slot/GridSlot 与资源层依赖) | 完成:原版源码机械移植,`port.window_tree` 通过;正式 UI 渲染仍属 2V0-f | | 2V0-b | `EterPythonLib/PythonWindow.cpp`、`PythonWindowManager.cpp`(窗口树逻辑;含 Slot/GridSlot 与资源层依赖) | 完成:原版源码机械移植,`port.window_tree` 通过;正式 UI 渲染仍属 2V0-f |
| 2V0-c | `wndMgr` / `grp` / `grpImage` / `grpText` / `grpThing` 模块 | 绑定已移植并通过导入/窗口句柄测试;图形绘制仍属 2V0-f | | 2V0-c | `wndMgr` / `grp` / `grpImage` / `grpText` / `grpThing` 模块 | 绑定已移植并通过导入/窗口句柄测试;图形绘制仍属 2V0-f |
| 2V0-d | 42 个 `UserInterface` 头(都在 `PythonApplication.h` 后面)+ `PythonApplicationModule.cpp`(`app`) | 头与绑定已编译、链接并导入;应用生命周期 adapter 仍属 2V0-f | | 2V0-d | 42 个 `UserInterface` 头(都在 `PythonApplication.h` 后面)+ `PythonApplicationModule.cpp`(`app`) | 头与绑定已编译、链接并导入;应用生命周期 adapter 仍属 2V0-f |
| 2V0-e | `dbg` 等桩模块补齐,`system.py` 跑到 `prototype.RunApp()` | 进行中:`dbg` 原版绑定已移植;Godot 进程内 `system.py` 目前停在 `constInfo.py` 的 `import net` | | 2V0-e | `dbg` 等桩模块补齐,`system.py` 跑到 `prototype.RunApp()` | 完成:27 个玩法 native 模块导出名桩记录每次调用;真实 `system.py` 已进入 `RunApp()`,目前由待实现的 `app.Create()` 报 `CREATE_DEVICE` |
| 2V0-f | Godot 渲染器上的 platform UI adapter;Logo/Popup 可显示可操作 | 未做 | | 2V0-f | Godot 渲染器上的 platform UI adapter;Logo/Popup 可显示可操作 | 未做 |
2. **2V1 — 登录/选角**:加入 `PythonNetworkStream{,PhaseHandShake,PhaseLogin,PhaseSelect,PhaseLoading}.cpp`、 2. **2V1 — 登录/选角**:加入 `PythonNetworkStream{,PhaseHandShake,PhaseLogin,PhaseSelect,PhaseLoading}.cpp`、
`PythonNetworkStreamModule.cpp`、AccountConnector 及登录/选角脚本真实调用到的模块。验收为 FakeServer 离线完成 phase 顺序, `PythonNetworkStreamModule.cpp`、AccountConnector 及登录/选角脚本真实调用到的模块。验收为 FakeServer 离线完成 phase 顺序,
File diff suppressed because one or more lines are too long
@@ -24,6 +24,8 @@ bool fail(std::string* error, const std::string& text)
} }
} }
void init_2v0_gameplay_stubs();
namespace PythonBoot namespace PythonBoot
{ {
@@ -77,6 +79,7 @@ bool RunMainScript(const char* lpCmdLine, std::string* error)
initgrpText(); initgrpText();
initwndMgr(); initwndMgr();
initapp(); initapp();
init_2v0_gameplay_stubs();
ClearLogBoxMessage(); ClearLogBoxMessage();
@@ -26,8 +26,8 @@ bool Start(const char* stdlib_path, std::string* error);
bool IsRunning(); bool IsRunning();
// 40250: RunMainScript(CPythonLauncher&, const char*) — the module initializers, __DEBUG__, // 40250: RunMainScript(CPythonLauncher&, const char*) — the module initializers, __DEBUG__,
// __COMMAND_LINE__ and RunFile("system.py"). Only the initializers that are ported are called; the rest // __COMMAND_LINE__ and RunFile("system.py"). 2V0-e registers temporary observable gameplay modules;
// arrive with the 2V slices, which is why system.py still stops at its first import. // they are replaced by the real modules in later 2V slices.
// False leaves *error set to what the launcher reported through LogBox (platform/EterBase/LogBox.h). // False leaves *error set to what the launcher reported through LogBox (platform/EterBase/LogBox.h).
bool RunMainScript(const char* lpCmdLine, std::string* error); bool RunMainScript(const char* lpCmdLine, std::string* error);
@@ -18,7 +18,13 @@ void CPythonApplication::Exit() { MT_PLATFORM_STUB(); }
void CPythonApplication::Abort() { MT_PLATFORM_STUB(); } void CPythonApplication::Abort() { MT_PLATFORM_STUB(); }
void CPythonApplication::SetMinFog(float) { MT_PLATFORM_STUB(); } void CPythonApplication::SetMinFog(float) { MT_PLATFORM_STUB(); }
void CPythonApplication::SetFrameSkip(bool) { MT_PLATFORM_STUB(); } void CPythonApplication::SetFrameSkip(bool) { MT_PLATFORM_STUB(); }
bool CPythonApplication::Create(PyObject*, const char*, int, int, int) { MT_PLATFORM_STUB(); return false; } bool CPythonApplication::Create(PyObject*, const char*, int, int, int) {
MT_PLATFORM_STUB();
// appCreate returns NULL on failure. Give Python the exception that
// prototype.RunApp expects rather than a SystemError with no exception set.
PyErr_SetString(PyExc_RuntimeError, "CREATE_DEVICE");
return false;
}
void CPythonApplication::UpdateGame() { MT_PLATFORM_STUB(); } void CPythonApplication::UpdateGame() { MT_PLATFORM_STUB(); }
void CPythonApplication::RenderGame() { MT_PLATFORM_STUB(); } void CPythonApplication::RenderGame() { MT_PLATFORM_STUB(); }
bool CPythonApplication::Process() { MT_PLATFORM_STUB(); return false; } bool CPythonApplication::Process() { MT_PLATFORM_STUB(); return false; }
@@ -79,10 +85,19 @@ bool LoadLocaleData(const char*) { MT_PLATFORM_STUB(); return false; }
void SetGuildSymbolPath(const char*) { MT_PLATFORM_STUB(); } void SetGuildSymbolPath(const char*) { MT_PLATFORM_STUB(); }
void DefaultFont_SetName(const char*) { MT_PLATFORM_STUB(); } void DefaultFont_SetName(const char*) { MT_PLATFORM_STUB(); }
void TextTail_SetLivingTime(long) { MT_PLATFORM_STUB(); } void TextTail_SetLivingTime(long) { MT_PLATFORM_STUB(); }
const char* LocaleService_GetName() { MT_PLATFORM_STUB(); return ""; } namespace {
// The supplied 40250 Client/locale.cfg selects "en" (code page 1252).
// Keep these mutable for app.ForceSetLocale, as Locale.cpp does.
std::string locale_name = "en";
std::string locale_path = "locale/en";
}
const char* LocaleService_GetName() { return "EUROPE"; }
bool LocaleService_IsCHEONMA() { MT_PLATFORM_STUB(); return false; } bool LocaleService_IsCHEONMA() { MT_PLATFORM_STUB(); return false; }
void LocaleService_SetCHEONMA(bool) { MT_PLATFORM_STUB(); } void LocaleService_SetCHEONMA(bool) { MT_PLATFORM_STUB(); }
unsigned LocaleService_GetCodePage() { MT_PLATFORM_STUB(); return 0; } unsigned LocaleService_GetCodePage() { return 1252; }
const char* LocaleService_GetLocaleName() { MT_PLATFORM_STUB(); return ""; } const char* LocaleService_GetLocaleName() { return locale_name.c_str(); }
const char* LocaleService_GetLocalePath() { MT_PLATFORM_STUB(); return ""; } const char* LocaleService_GetLocalePath() { return locale_path.c_str(); }
void LocaleService_ForceSetLocale(const char*, const char*) { MT_PLATFORM_STUB(); } void LocaleService_ForceSetLocale(const char* name, const char* path) {
locale_name = name ? name : "";
locale_path = path ? path : "";
}
+2 -2
View File
@@ -6,8 +6,8 @@
// var err := Metin2PythonHost.run_main_script("") // var err := Metin2PythonHost.run_main_script("")
// //
// Both calls answer with "" on success and with the reason otherwise, so GDScript never has to guess // Both calls answer with "" on success and with the reason otherwise, so GDScript never has to guess
// from a bool. Until 批次 2V0 registers the C++ modules, run_main_script() is expected to come back with // from a bool. 2V0-e reaches prototype.RunApp(); until 2V0-f implements the application adapter,
// system.py's first import error; that is what step 4 verifies on each target platform. // app.Create reports CREATE_DEVICE and the original script displays its error popup.
// //
// This is the only unit that knows both godot-cpp and the port tree: the sequence itself lives in // This is the only unit that knows both godot-cpp and the port tree: the sequence itself lives in
// platform/ScriptLib/PythonBoot.h, which stays godot-free. // platform/ScriptLib/PythonBoot.h, which stays godot-free.
+6 -3
View File
@@ -2,7 +2,8 @@
# #
# 和 ctest 的 `port.python_launcher` 跑的是同一条序列,区别只有一个:那边是独立可执行文件,这边在 # 和 ctest 的 `port.python_launcher` 跑的是同一条序列,区别只有一个:那边是独立可执行文件,这边在
# Godot 进程里,由 GDScript 发起 —— step 4 要验的就是这个差别。2V0 把 C++ 模块注册上之前, # Godot 进程里,由 GDScript 发起 —— step 4 要验的就是这个差别。2V0 把 C++ 模块注册上之前,
# 在玩法模块补齐前,system.py 会停在下一个未注册的 native import。 # 2V0-e 的玩法桩让 system.py 真正走到 prototype.RunApp();
# 2V0-f 的应用 adapter 尚未创建图形设备,RunApp 捕获 CREATE_DEVICE 并给出原版提示。
# #
# 真机上通常没有 40250 的 Client/pack(APK 里只有 python27.zip),所以分两段:解释器 + 标准库这一段 # 真机上通常没有 40250 的 Client/pack(APK 里只有 python27.zip),所以分两段:解释器 + 标准库这一段
# 不需要 pack,任何平台都跑;system.py 这一段需要 pack,没有就跳过并说清楚。 # 不需要 pack,任何平台都跑;system.py 这一段需要 pack,没有就跳过并说清楚。
@@ -10,7 +11,7 @@
# 桌面:python_host_test.gd(--script);真机:MT_TEST_MODE=python 从 client_main.gd 进来。 # 桌面:python_host_test.gd(--script);真机:MT_TEST_MODE=python 从 client_main.gd 进来。
extends RefCounted extends RefCounted
const EXPECTED_STOP := "No module named net" const EXPECTED_STOP := "Sorry, Your system does not support 3D graphics"
var fail := 0 var fail := 0
@@ -50,7 +51,9 @@ func run() -> int:
var err: String = Metin2PythonHost.run_main_script("") var err: String = Metin2PythonHost.run_main_script("")
print("system.py -> %s" % err) print("system.py -> %s" % err)
_ck(err.contains(EXPECTED_STOP), _ck(err.contains(EXPECTED_STOP),
"system.py 停在预期的 '%s'(2V0 之后这里会往前走)" % EXPECTED_STOP) "system.py 进入 prototype.RunApp(),Create() 报告待实现的图形设备")
_ck(Metin2PythonHost.run_line("import net, player, item; assert net.__port_stub__ == 1 and player.__port_stub__ == 1 and item.__port_stub__ == 1") == "",
"2V0 玩法模块桩已注册,调用会记录到 stderr")
else: else:
print("SKIP: system.py 这一段(没有 40250 Client/pack:%s)" % AssetRoot.client_path()) print("SKIP: system.py 这一段(没有 40250 Client/pack:%s)" % AssetRoot.client_path())