Files
mtgodot-poc/.agents/skills/metin2-40250-parity-audit/scripts/py_embed_spike.py
T
shenleiandClaude Opus 5 b02c49bb2f spike(python): CPython 2.7.18 runs the 40250 root scripts on Android arm64
libpython2.7.a cross-builds with NDK 27.2 (API 24) after disabling
HAVE_LANGINFO_H; 30 C modules built in. On a OnePlus 13 (API 36) the
harness runs system.py -> prototype.RunApp() and imports the root modules
with results identical to macOS (66/74; the 8 failures are stub values or
dev-only scripts). py_embed_spike.py now isolates __main__ like
CPythonLauncher and reports stdlib usage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 18:16:44 +09:00

97 lines
4.8 KiB
Python

#!/usr/bin/env python2.7
"""Embedded-Python feasibility spike (Python 2.7 only; see docs/PYTHON-EMBED-EVAL.md).
Emulates CPythonLauncher::Create + system.py bootstrap: stub native modules (every call returns 0),
a directory-backed `pack` module over the 40250 Eternexus tree, then imports every root module
through system.py's own `__pack_import`. Reports loaded/failed modules to --out (default
py_embed_spike.txt); system.py redirects stdout, so nothing useful is printed.
python2.7 py_embed_spike.py [--out FILE] (reference root: MT_40250_SOURCE or audit manifest)
"""
import sys, types, os, glob, traceback
if os.environ.get("MT_ETERNEXUS"): # on-device run: Eternexus tree pushed next to the binary
E = os.environ["MT_ETERNEXUS"]
else:
HERE = os.path.dirname(os.path.abspath(__file__))
REPO = os.path.abspath(os.path.join(HERE, "../../../.."))
import json
_ref = os.environ.get("MT_40250_SOURCE") or json.load(open(os.path.join(REPO, "audit/manifest.json")))["reference_root"]
E = str(os.path.normpath(os.path.join(REPO, _ref, "../../Client/Eternexus"))) # json gives unicode
OUT = sys.argv[sys.argv.index("--out") + 1] if "--out" in sys.argv else "py_embed_spike.txt"
FILES = {}
for p in glob.glob(E + "/root/*"): FILES[os.path.basename(p).lower()] = p
for base, pre in ((E + "/locale_en/locale/en", "locale/en/"), (E + "/uiscript/uiscript", "uiscript/")):
for d, _, fs in os.walk(base):
for f in fs:
full = os.path.join(d, f)
FILES[(pre + os.path.relpath(full, base)).lower()] = full
LOG = open(OUT, "w")
MISSING = {}
class Stub(types.ModuleType):
def __getattr__(self, name):
if name.startswith("__"): raise AttributeError(name)
key = "%s.%s" % (self.__name__, name)
def f(*a, **k):
MISSING[key] = MISSING.get(key, 0) + 1
return 0
setattr(self, name, f); return f
NATIVE = "app net player chr chrmgr wndMgr grp grpImage grpText grpThing item skill snd chat background miniMap ime systemSetting guild safebox shop exchange quest messenger textTail nonplayer effect event eventMgr fly dbg profiler ServerStateChecker udp".split()
for n in NATIVE: sys.modules[n] = Stub(n)
pack = types.ModuleType("pack")
def norm(n): return n.replace("\\", "/").lower().lstrip("/")
pack.Exist = lambda n: norm(n) in FILES
pack.Get = lambda n: open(FILES[norm(n)], "rb").read()
sys.modules["pack"] = pack
sys.modules["dbg"].Trace = sys.modules["dbg"].TraceError = lambda *a: None
sys.modules["dbg"].LogBox = lambda m: LOG.write("LOGBOX: %s\n" % m)
sys.modules["app"].Loop = lambda: LOG.write("app.Loop reached\n")
STR = {"app.GetLocalePath": "locale/en", "app.GetLocaleName": "en", "app.GetLocaleServiceName": "en",
"app.GetLocalePathCommon": "locale/common"}
for k, v in STR.items():
m, f = k.split(".")
setattr(sys.modules[m], f, (lambda v: lambda *a: v)(v))
import __builtin__
__builtin__.TRUE = 1; __builtin__.FALSE = 0 # CPythonLauncher::Create
# A fresh __main__ like CPythonLauncher::Create (whose __builtins__ is the module, not a dict);
# system.py and prototype.py exec into it, so they must not share this script's globals.
__main__ = types.ModuleType("__main__")
__main__.__builtins__ = __builtin__
sys.modules["__main__"] = __main__
__main__.__DEBUG__ = 0
__main__.__COMMAND_LINE__ = ""
src = pack.Get("system.py")
try:
exec compile(src, "system.py", "exec") in __main__.__dict__
except BaseException:
LOG.write("system.py raised:\n" + traceback.format_exc())
sys.stdout, sys.stderr = sys.__stdout__, sys.__stderr__ # system.py redirects both to dbg
ok, bad = [], []
_pimp = __main__.__dict__["__pack_import"]
for name in sorted(FILES):
if not name.endswith(".py") or "/" in name: continue
mod = name[:-3]
real = [m for m in sys.modules if m.lower() == mod]
if real: ok.append(mod); continue
try:
_pimp(mod); ok.append(mod)
except BaseException as e:
bad.append((mod, "%s: %s" % (type(e).__name__, repr(e.args)[:100])))
try:
LOG.write("modules loaded %d, failed %d\n" % (len(ok), len(bad)))
for m, e in bad: LOG.write(" FAIL %s %s\n" % (m, e))
LOG.write("native functions called during bootstrap: %d\n" % len(MISSING))
LOG.write(" " + " ".join(sorted(MISSING)) + "\n")
std = []
for name, m in sorted(sys.modules.items()):
if m is None or name in NATIVE or name in ("pack", "__main__"): continue
try: f = m.__dict__.get("__file__")
except Exception: continue
if not isinstance(f, str) and f is not None: continue
if f is None: std.append("builtin:" + name)
elif f.endswith(".so"): std.append("ext:" + name)
elif not f.startswith(E) and not f.startswith("<"): std.append("py:" + name)
LOG.write("stdlib modules in use: " + " ".join(std) + "\n")
except BaseException:
LOG.write("report failed:\n" + traceback.format_exc())
LOG.close()