docs(parity): mirror 40250 layout for the logic layer; evaluate embedded Python UI
- SKILL.md: logic units port into extension/src/port/<Lib>/<File>, platform adapters under extension/src/platform; legacy GDScript owners are migration sources; python layer frozen until the embedding decision - port_map.py: logic/python/platform layers with per-layer progress, queue --layer, LEGACY layout check; inventory adds EterBase, EterPythonLib, ScriptLib, SpeedTreeLib, SphereLib, EterImageLib - py_embed_spike.py + docs/PYTHON-EMBED-EVAL.md: the 40250 system.py bootstrap loads 67/74 root modules unchanged under CPython 2.7 with stub natives Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,32 +24,50 @@ The 40250 source is the specification. Do not design behavior; transcribe it. Co
|
||||
runtime never loads (e.g. a `*_system.gd` referenced only by its own `test_*_parity.gd`) must be
|
||||
deleted or wired in by porting the real 40250 caller, never cited as parity.
|
||||
|
||||
## Porting target per layer
|
||||
## Code layout: mirror 40250
|
||||
|
||||
| 40250 layer | How to port | Where |
|
||||
| --- | --- | --- |
|
||||
| Platform-independent C++ logic (packet structs/handlers, sequence table, formulas, state machines in `UserInterface/`, `GameLib/`; `EterPack`, `EterLocale`) | Transliterate C++ -> C++, keeping class/function structure and names so each function has one counterpart | `extension/src/` |
|
||||
| 40250 C++ logic that currently lives in GDScript | Port function by function, same names in snake_case, same order of statements | existing `.gd` owner |
|
||||
| Python UI (40250 `Client/Eternexus/root/*.py` + `uiscript/`, **not** `assets/root`) | Translate per `.py` file into its GDScript window, keeping method structure | `project/ui/` |
|
||||
| Direct3D, Granny, Miles, Win32 input/window, IME, DirectX math | Platform adapter; equivalence by observable output | `extension/`, `project/` |
|
||||
The logic layer has the 40250 structure, not a new architecture. `port_map.py` assigns every unit
|
||||
one of three layers:
|
||||
|
||||
Mark every ported function with a one-line tag at its definition so coverage can be scanned
|
||||
mechanically: `// 40250: CInstanceBase::SetMoveSpeed` (C++) or `# 40250: CInstanceBase::SetMoveSpeed`
|
||||
(GDScript). Tag only real counterparts.
|
||||
| Layer | 40250 units | How to port | Where |
|
||||
| --- | --- | --- | --- |
|
||||
| `logic` | `UserInterface/`, `GameLib/`, `EterLib` net/timer/text parsing, `EterPack`, `EterLocale` (everything not listed below) | Copy C++ -> C++. Same file name, class name, method names, member names and statement order; one reference file = one implementation file | `extension/src/port/<Lib>/<File>.{h,cpp}` |
|
||||
| `python` | `Client/Eternexus/root/*.py`, `uiscript/`, `UserInterface/*Module.cpp`, `EterPythonLib/`, `ScriptLib/` | Being evaluated as embedded CPython 2.7 running the scripts unchanged (`docs/PYTHON-EMBED-EVAL.md`). **Do not port or translate this layer until that evaluation is decided.** Reference is 40250 `Eternexus/root`, **not** `assets/root` | `extension/src/port/<Lib>/` (bindings), scripts from pack |
|
||||
| `platform` | Direct3D/`Grp*`, Granny (`EterGrnLib`), Miles, SpeedTree, `EffectLib`/terrain rendering, Win32 window/input/IME, threads, anti-cheat | Adapter behind the interface the 40250 caller uses; equivalence by observable output | `extension/src/platform/` + existing render code (`metin2_model`, `metin2_anim`, `gr2_bridge`, ...) |
|
||||
|
||||
Rules for the `logic` layer:
|
||||
|
||||
- Port a whole unit into its mirror file. Where 40250 calls a platform class (`CGraphicThingInstance`,
|
||||
`CStateManager`, `CSoundManager`, ...), call a same-named adapter interface declared under
|
||||
`extension/src/platform/`; never inline Godot calls into ported logic.
|
||||
- 40250 singletons (`CPythonPlayer::Instance()`, `CPythonCharacterManager`, `CPythonNetworkStream`)
|
||||
stay singletons owned by the extension. GDScript does not hold gameplay state.
|
||||
- GDScript (`net_play.gd`, `net_world.gd`, `game_scene.gd`, `player_controller.gd`) and
|
||||
`extension/src/net/entity_store.cpp` are migration sources. In the commit that ports a unit, delete
|
||||
the old logic it replaces there and leave only glue: node creation, forwarding Godot input to the
|
||||
ported input handlers, and reading ported state to place nodes.
|
||||
- Keep 40250 types and units (`TPixelPosition` in cm, `DWORD` ms from `ELTimer_GetMSec`, degrees).
|
||||
Convert to Godot space only in the adapter.
|
||||
- `port_map.py check` prints `LEGACY` for a `logic` function whose `impl` is outside its mirror file.
|
||||
|
||||
Mark each ported function with a one-line tag at its definition:
|
||||
`// 40250: CInstanceBase::SetMoveSpeed`. Tag real counterparts only. In mirror files the tag is
|
||||
optional, because the file and method name already map one to one.
|
||||
|
||||
## Round workflow
|
||||
|
||||
1. **Pick a unit** from the current batch in `audit/remediation-roadmap.md`, or from
|
||||
`port_map.py queue` (ordered by user-visible gameplay; see Priority). Run
|
||||
`port_map.py queue` (logic layer by default, ordered by user-visible gameplay; see Priority). Run
|
||||
`port_map.py init <unit>` and `port_map.py show <unit>` to get the function list.
|
||||
2. **Read the whole reference unit** under the reference root (see `references/project-map.md`;
|
||||
use `grep -a`, many files contain CP949 bytes). Read every current counterpart, found by the
|
||||
`40250:` tags, the port-map entry, or `references/project-map.md`.
|
||||
3. **For each reference function**, compare statement by statement: preconditions and early
|
||||
returns, branches, formulas, constants and units, state writes and their order, timing/event
|
||||
source, data source (proto/msa/msm/txt, never hardcoded), packets sent, cleanup. Then:
|
||||
- transliterate it where missing or divergent;
|
||||
- delete current-only logic it replaces;
|
||||
3. **Copy the unit into its mirror file** and, for each reference function, compare it with the
|
||||
old counterpart statement by statement: preconditions and early returns, branches, formulas,
|
||||
constants and units, state writes and their order, timing/event source, data source
|
||||
(proto/msa/msm/txt, never hardcoded), packets sent, cleanup. Then:
|
||||
- keep the 40250 version; record each old behavior that differed as a divergence found;
|
||||
- delete the old logic it replaces and rewire callers to the mirror class;
|
||||
- mark `N_A` only for pure platform plumbing (D3D state, Python binding glue, Win32), with a reason.
|
||||
4. **Test what changed**: a focused test using the reference's own boundary values for each changed
|
||||
formula/branch (fails before, passes after). Do not write tests for unchanged or trivial code.
|
||||
@@ -110,9 +128,12 @@ behavior verified from code inspection alone.
|
||||
|
||||
## Tools
|
||||
|
||||
- `scripts/port_map.py status | queue | init <unit> | show <unit> | check` — function inventory
|
||||
(active `.vcxproj` sources + the 40250 Python root), per-unit status, and tag/port-map consistency.
|
||||
Run `check` before every commit that touches `audit/port-map/`.
|
||||
- `scripts/port_map.py status | queue [--layer logic|python|platform] | init <unit> | show <unit> | check`
|
||||
— function inventory (active `.vcxproj` sources + the 40250 Python root), progress per library and
|
||||
per layer, and tag/port-map/layout consistency. Run `check` before every commit that touches
|
||||
`audit/port-map/`.
|
||||
- `scripts/py_embed_spike.py` (Python 2.7) — runs the 40250 `system.py` bootstrap with stub native
|
||||
modules; used by the embedded-Python evaluation.
|
||||
- `scripts/audit_ledger.py refresh --write | report --write | validate` — run once per batch, not
|
||||
per round. Missing reference, implementation or test files are an error; `evidence.tests` holds
|
||||
paths only, command lines go in `evidence.commands`.
|
||||
|
||||
@@ -34,7 +34,16 @@
|
||||
- Native extension: `extension/src/` (network in `extension/src/net/`, classic 40250 protocol in `extension/src/net/classic/`).
|
||||
- Format readers: `formats/`; Granny: `libgr2/`; assets/tables: `assets/`; Granny oracle: `oracle/`; tools: `tools/`.
|
||||
|
||||
### Known counterparts
|
||||
### Target layout
|
||||
|
||||
- `extension/src/port/<Lib>/<File>.{h,cpp}` — mirror of each 40250 `logic` unit (see SKILL.md
|
||||
"Code layout"). Does not exist yet; the first ported unit creates it and adds it to
|
||||
`extension/CMakeLists.txt`.
|
||||
- `extension/src/platform/` — adapter interfaces named after the 40250 platform classes they replace.
|
||||
- The files in the table below are **migration sources**: when a unit is ported, its logic moves out
|
||||
of them into the mirror file.
|
||||
|
||||
### Legacy counterparts (migration sources)
|
||||
|
||||
| 40250 | Current |
|
||||
| --- | --- |
|
||||
@@ -47,14 +56,12 @@
|
||||
| `CPythonPlayer` skill use | `project/player_skill.gd` |
|
||||
| `CFlyingObject*` | `project/fly_object.gd` |
|
||||
| `EterLib/Camera.cpp` + `PythonApplicationCamera` | `project/game_camera.gd` |
|
||||
| `ui*.py` windows | `project/ui/*_ui.gd` |
|
||||
| `ui*.py` windows | `project/ui/*_ui.gd` (ported from the wrong `assets/root`; replaced by the python layer) |
|
||||
|
||||
### Code that is not a counterpart
|
||||
### Removed non-counterparts
|
||||
|
||||
About 100 of the 111 `project/*_system.gd` files are loaded only by their own
|
||||
`project/test_*_parity.gd` (never by the runtime). They are not implementations of 40250
|
||||
behavior and their tests are not evidence; delete them, or replace them by porting the real 40250
|
||||
code path when that path exists.
|
||||
The 106 `project/*_system.gd` files that only their own `test_*_parity.gd` loaded were deleted on
|
||||
2026-09-22 (commit `a989b8f1`). Do not recreate them; port the real 40250 code path instead.
|
||||
|
||||
## Existing evidence (historical)
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ def manifest_data(repo: pathlib.Path) -> tuple[set[str], dict[str, list[str]]]:
|
||||
return listed, owners
|
||||
|
||||
|
||||
def active_project_sources(reference_root: pathlib.Path) -> set[str]:
|
||||
def active_project_sources(reference_root: pathlib.Path, dirs: tuple[str, ...] = REFERENCE_DIRS) -> set[str]:
|
||||
active: set[str] = set()
|
||||
for project in sorted((reference_root.parent / "vs_files").glob("*/*.vcxproj")):
|
||||
try:
|
||||
@@ -140,7 +140,7 @@ def active_project_sources(reference_root: pathlib.Path) -> set[str]:
|
||||
relative = candidate.relative_to(reference_root.resolve())
|
||||
except ValueError:
|
||||
continue
|
||||
if relative.parts and relative.parts[0] in REFERENCE_DIRS:
|
||||
if relative.parts and relative.parts[0] in dirs:
|
||||
if relative.suffix.lower() in SOURCE_SUFFIXES:
|
||||
active.add(relative.as_posix())
|
||||
return active
|
||||
|
||||
@@ -5,11 +5,16 @@ Units are the active 40250 C++ sources (from the .vcxproj files) plus the 40250
|
||||
root (`../../Client/Eternexus/root/*.py` relative to the reference root). Port-map entries
|
||||
live in `audit/port-map/<unit path>.json`; functions without an entry count as TODO.
|
||||
|
||||
port_map.py status [--lib UserInterface] coverage per library (and per unit with --units)
|
||||
port_map.py queue [--limit 20] next units by priority, then by TODO count
|
||||
port_map.py status [--lib UserInterface] coverage per library and per layer (per unit with --units)
|
||||
port_map.py queue [--limit 20] [--layer L] next units by priority, then by TODO count (default layer: logic)
|
||||
port_map.py init <unit> [<unit>...] create or extend entries (new functions as TODO)
|
||||
port_map.py show <unit> per-function status of one unit
|
||||
port_map.py check stale hashes, unknown functions, tag mismatches
|
||||
port_map.py check stale hashes, unknown functions, tag mismatches, layout
|
||||
|
||||
Layers: `logic` (ported into extension/src/port/<Lib>/<File>, same names as 40250), `python`
|
||||
(the embedded-Python track: Python binding modules, EterPythonLib window system, ScriptLib and the
|
||||
Python root) and `platform` (Direct3D/Granny/Miles/Win32 rendering, audio and OS plumbing, covered
|
||||
by adapters). Progress is reported per layer so platform code does not dilute the logic figure.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -24,11 +29,34 @@ from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
import refroot # script directory is on sys.path when run directly
|
||||
from audit_source_coverage import active_project_sources
|
||||
from audit_source_coverage import REFERENCE_DIRS, active_project_sources
|
||||
|
||||
STATUSES = ("TODO", "PORTED", "ADAPTED", "N_A", "DIVERGENT", "NEEDS_LIVE")
|
||||
DONE = {"PORTED", "ADAPTED", "N_A"}
|
||||
PY_ROOT = "../../Client/Eternexus/root"
|
||||
UNIT_DIRS = REFERENCE_DIRS + ("EterBase", "EterPythonLib", "ScriptLib", "SpeedTreeLib", "SphereLib", "EterImageLib")
|
||||
PORT_DIR = "extension/src/port"
|
||||
LAYERS = ("logic", "python", "platform")
|
||||
# First matching pattern wins; anything unmatched is `logic`.
|
||||
LAYER_PATTERNS = [
|
||||
("python", [f"{PY_ROOT}/*.py", "EterPythonLib/*", "ScriptLib/*", "UserInterface/*Module.cpp",
|
||||
"UserInterface/*Moudle.cpp", "UserInterface/PythonSkill.cpp", "UserInterface/PythonGuild.cpp",
|
||||
"UserInterface/PythonShop.cpp", "UserInterface/PythonSafeBox.cpp", "UserInterface/PythonQuest.cpp",
|
||||
"UserInterface/PythonMessenger.cpp"]),
|
||||
("platform", ["EterGrnLib/*", "MilesLib/*", "SpeedTreeLib/*", "SphereLib/*", "EterImageLib/*", "EffectLib/*",
|
||||
"PRTerrainLib/*", "EterLib/Grp*.cpp", "EterLib/StateManager.cpp", "EterLib/MSApplication.cpp",
|
||||
"EterLib/MSWindow.cpp", "EterLib/Input.cpp", "EterLib/IME.cpp", "EterLib/SkyBox.cpp",
|
||||
"EterLib/LensFlare.cpp", "EterLib/ScreenFilter.cpp", "EterLib/Decal.cpp", "EterLib/EnvironmentMap.cpp",
|
||||
"EterLib/JpegFile.cpp", "EterLib/TargaResource.cpp", "EterLib/DibBar.cpp", "EterLib/BlockTexture.cpp",
|
||||
"EterLib/CullingManager.cpp", "EterLib/Thread.cpp", "EterLib/Mutex.cpp", "EterLib/FileLoaderThread.cpp",
|
||||
"EterLib/TextBar.cpp", "EterBase/*", "GameLib/MapOutdoorRender*.cpp", "GameLib/MapOutdoorWater.cpp",
|
||||
"GameLib/SnowParticle.cpp", "GameLib/SnowEnvironment.cpp", "GameLib/TerrainDecal.cpp",
|
||||
"GameLib/TerrainPatch.cpp", "UserInterface/PythonApplicationLogo.cpp", "UserInterface/MovieMan.cpp",
|
||||
"UserInterface/HackShield.cpp", "UserInterface/NProtectGameGuard.cpp", "UserInterface/ProcessCRC.cpp",
|
||||
"UserInterface/ProcessScanner.cpp", "UserInterface/WiseLogicXTrap.cpp",
|
||||
"UserInterface/PythonApplicationWebPage.cpp", "UserInterface/PythonExceptionSender.cpp",
|
||||
"UserInterface/UserInterface.cpp", "UserInterface/CheckLatestFiles.cpp"]),
|
||||
]
|
||||
|
||||
# Order follows SKILL.md "Priority"; the first matching pattern wins.
|
||||
PRIORITY = [
|
||||
@@ -100,7 +128,8 @@ def py_functions(path: Path) -> list[str]:
|
||||
|
||||
|
||||
def inventory(ref: Path) -> dict[str, Path]:
|
||||
units = {p: ref / p for p in sorted(active_project_sources(ref)) if p.endswith(".cpp")}
|
||||
units = {p: ref / p for p in sorted(active_project_sources(ref, UNIT_DIRS))
|
||||
if p.endswith(".cpp") and not p.lower().endswith("stdafx.cpp")}
|
||||
py_dir = (ref / PY_ROOT).resolve()
|
||||
if py_dir.is_dir():
|
||||
for p in sorted(py_dir.glob("*.py")):
|
||||
@@ -119,6 +148,18 @@ def priority(unit: str) -> str:
|
||||
return DEFAULT_PRIORITY
|
||||
|
||||
|
||||
def layer(unit: str) -> str:
|
||||
for name, patterns in LAYER_PATTERNS:
|
||||
if any(fnmatch.fnmatch(unit, pat) for pat in patterns):
|
||||
return name
|
||||
return "logic"
|
||||
|
||||
|
||||
def port_file(unit: str) -> str:
|
||||
"""Expected implementation path stem for a `logic` unit (`extension/src/port/GameLib/ActorInstance`)."""
|
||||
return f"{PORT_DIR}/{unit.rsplit('.', 1)[0]}"
|
||||
|
||||
|
||||
def entry_path(repo: Path, unit: str) -> Path:
|
||||
rel = unit.replace("../../Client/Eternexus/", "Client/")
|
||||
return repo / "audit" / "port-map" / (rel + ".json")
|
||||
@@ -162,6 +203,9 @@ def cmd_status(repo: Path, units: dict[str, Path], args) -> int:
|
||||
per_lib.setdefault(lib, Counter()).update(counts)
|
||||
rows.append((unit, counts))
|
||||
total = Counter()
|
||||
per_layer: dict[str, Counter] = {}
|
||||
for unit, counts in rows:
|
||||
per_layer.setdefault(layer(unit), Counter()).update(counts)
|
||||
print(f"{'library':<16}{'funcs':>7}{'done':>7} " + " ".join(f"{s:>10}" for s in STATUSES))
|
||||
for lib, c in sorted(per_lib.items()):
|
||||
total.update(c)
|
||||
@@ -172,6 +216,13 @@ def cmd_status(repo: Path, units: dict[str, Path], args) -> int:
|
||||
done = sum(total[s] for s in DONE)
|
||||
print(f"{'TOTAL':<16}{n:>7}{done:>7} " + " ".join(f"{total[s]:>10}" for s in STATUSES)
|
||||
+ f" ({100.0 * done / max(n, 1):.1f}% done)")
|
||||
print()
|
||||
for name in LAYERS:
|
||||
c = per_layer.get(name, Counter())
|
||||
n = sum(c.values())
|
||||
done = sum(c[s] for s in DONE)
|
||||
print(f"layer {name:<10}{n:>7}{done:>7} " + " ".join(f"{c[s]:>10}" for s in STATUSES)
|
||||
+ f" ({100.0 * done / max(n, 1):.1f}% done)")
|
||||
if args.units:
|
||||
for unit, c in rows:
|
||||
if sum(c.values()) != c["TODO"]:
|
||||
@@ -182,6 +233,8 @@ def cmd_status(repo: Path, units: dict[str, Path], args) -> int:
|
||||
def cmd_queue(repo: Path, units: dict[str, Path], args) -> int:
|
||||
rows = []
|
||||
for unit, path in units.items():
|
||||
if layer(unit) != args.layer:
|
||||
continue
|
||||
c = unit_counts(repo, unit, path)
|
||||
open_ = c["TODO"] + c["DIVERGENT"]
|
||||
if open_:
|
||||
@@ -235,6 +288,7 @@ def scan_tags(repo: Path) -> dict[str, list[str]]:
|
||||
|
||||
def cmd_check(repo: Path, units: dict[str, Path], args) -> int:
|
||||
problems = 0
|
||||
legacy = 0
|
||||
recorded_ported: set[str] = set()
|
||||
for entry_file in sorted((repo / "audit" / "port-map").rglob("*.json")):
|
||||
entry = json.loads(entry_file.read_text(encoding="utf-8"))
|
||||
@@ -265,6 +319,10 @@ def cmd_check(repo: Path, units: dict[str, Path], args) -> int:
|
||||
if status == "ADAPTED" and not info.get("test"):
|
||||
print(f"ERROR {label}: {fn} is ADAPTED without test")
|
||||
problems += 1
|
||||
if status in {"PORTED", "NEEDS_LIVE"} and layer(unit) == "logic" and not any(
|
||||
i.startswith(port_file(unit) + ".") for i in info.get("impl", [])):
|
||||
print(f"LEGACY {label}: {fn} impl is outside {port_file(unit)}.{{h,cpp}}; move it when the unit is ported")
|
||||
legacy += 1
|
||||
if status in {"PORTED", "ADAPTED"}:
|
||||
recorded_ported.add(fn.split("::")[-1] if "::" not in fn else fn)
|
||||
recorded_ported.add(fn)
|
||||
@@ -272,7 +330,8 @@ def cmd_check(repo: Path, units: dict[str, Path], args) -> int:
|
||||
untracked = sorted(t for t in tags if t not in recorded_ported)
|
||||
for tag in untracked:
|
||||
print(f"WARN tag `40250: {tag}` at {tags[tag][0]} has no PORTED/ADAPTED port-map entry")
|
||||
print(f"check: {problems} error(s), {len(untracked)} untracked tag(s), {len(tags)} tag(s) scanned")
|
||||
print(f"check: {problems} error(s), {legacy} legacy location(s), {len(untracked)} untracked tag(s), "
|
||||
f"{len(tags)} tag(s) scanned")
|
||||
return 1 if problems else 0
|
||||
|
||||
|
||||
@@ -281,6 +340,7 @@ def main() -> int:
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
p = sub.add_parser("status"); p.add_argument("--lib"); p.add_argument("--units", action="store_true")
|
||||
p = sub.add_parser("queue"); p.add_argument("--limit", type=int, default=20)
|
||||
p.add_argument("--layer", choices=LAYERS, default="logic")
|
||||
p = sub.add_parser("init"); p.add_argument("units", nargs="+")
|
||||
p = sub.add_parser("show"); p.add_argument("unit")
|
||||
sub.add_parser("check")
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/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, json
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
REPO = os.path.abspath(os.path.join(HERE, "../../../.."))
|
||||
_ref = os.environ.get("MT_40250_SOURCE") or json.load(open(os.path.join(REPO, "audit/manifest.json")))["reference_root"]
|
||||
E = os.path.normpath(os.path.join(REPO, _ref, "../../Client/Eternexus"))
|
||||
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
|
||||
import __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())
|
||||
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__, str(e)[:100])))
|
||||
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")
|
||||
LOG.close()
|
||||
Reference in New Issue
Block a user