初步增加, 扫码登录成功

This commit is contained in:
yml2213
2026-08-12 16:22:55 +08:00
parent 7db3b2cd4f
commit d19c0902f2
60 changed files with 7413 additions and 315 deletions
@@ -0,0 +1,33 @@
"""YYB 加密引擎(goods + mall 统一框架,纯 Python)。
- algorithm.py: goods 侧 CHAOS VM(116 opcode)——web_save / web_new_encrypt
- pagedoo_vm.py: mall 侧 pagedoo VM(108 opcode)——PlaceOrder
- goods.py: goods 侧高层 API(GoodsSession + generate_encrypt_msg)
- mall.py: mall 侧高层 API(MallSession + generate_encrypt_msg)
- session.py: 会话态模型
"""
from .algorithm import (
build_plaintext,
decode_d,
generate_encrypt_msg,
generate_encrypt_msg_offline,
)
from .goods import GoodsSession, generate_encrypt_msg as goods_generate
from .mall import MallSession, generate_encrypt_msg as mall_generate
from .pagedoo_vm import PagedooVM, run_frame
from .session import SessionState, load_session
__all__ = [
"build_plaintext",
"decode_d",
"generate_encrypt_msg",
"generate_encrypt_msg_offline",
"GoodsSession",
"goods_generate",
"MallSession",
"mall_generate",
"PagedooVM",
"run_frame",
"SessionState",
"load_session",
]
File diff suppressed because it is too large Load Diff
+74
View File
@@ -0,0 +1,74 @@
"""goods 侧(web_save / web_new_encrypt,goodsBiz CHAOS VM 116 opcode)高层 API。
与 mall 侧(pyvm/mall.py)同属 YYB 加密体系。goods 侧 E3 已验证:
会话态(xMidasOps 59640 + key16/key1) + 订单参数
→ build_plaintext 21 字段明文(528 字符)
→ a:8 变换(Te0-3 + key16 按块轮转)
→ webSave(33 块变换)
→ encrypt_msg(1056 hex)
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from .algorithm import generate_encrypt_msg_offline
REPLAY = Path(__file__).resolve().parent.parent / "replay"
ORDER_FIELDS = ["token_id", "openid", "openkey", "session_id", "session_type", "zoneid",
"pay_method", "buy_quantity", "mb_pwd", "pay_id", "auth_key",
"card_value", "accounttype", "provide_uin", "extend", "ts",
"from_h5", "webversion"]
class GoodsSession:
"""goods 会话态:xMidasOps(59640,服务端生成)+ key16/key1 + args_template。"""
def __init__(self, xmidas_ops: list, key16: list, key1: list,
args_template_d: str = "", xmidas_token: str = ""):
self.xmidas_ops = xmidas_ops
self.key16 = key16
self.key1 = key1
self.args_template_d = args_template_d
self.xmidas_token = xmidas_token
self.validate()
def validate(self) -> None:
if len(self.xmidas_ops) != 59640:
raise ValueError(f"goods xMidasOps 应为 59640,实际 {len(self.xmidas_ops)}")
if len(self.key16) != 16 or len(self.key1) != 16:
raise ValueError("key16/key1 应为 16 字节")
@classmethod
def from_session_state(cls, path: str | Path) -> "GoodsSession":
"""从 scripts/capture-session.mjs 生成的 session-state.json 加载。"""
d = json.loads(Path(path).read_text(encoding="utf-8"))
return cls(d["xmidas_ops"], d["key16"], d["key1"],
d.get("args_template_d", ""), d.get("xmidas_token", ""))
def to_json(self) -> dict:
return {
"xmidas_ops": self.xmidas_ops,
"key16": self.key16,
"key1": self.key1,
"args_template_d": self.args_template_d,
"xmidas_token": self.xmidas_token,
}
@classmethod
def from_json(cls, d: dict) -> "GoodsSession":
return cls(d["xmidas_ops"], d["key16"], d["key1"],
d.get("args_template_d", ""), d.get("xmidas_token", ""))
def generate_encrypt_msg(session: GoodsSession, order: dict) -> str:
"""用会话态 + 订单参数生成 goods encrypt_msg(1056 hex)。"""
from .algorithm import decode_d
params = {k: order.get(k, "") for k in ORDER_FIELDS}
args_tpl = decode_d(session.args_template_d) if session.args_template_d else None
return generate_encrypt_msg_offline(
params, order.get("fk_extend", ""), order.get("ts", ""), order.get("_rand", ""),
xmidas=session.xmidas_ops, xmidas_token=session.xmidas_token,
args_template=args_tpl, key16=session.key16, key1=session.key1,
)
@@ -0,0 +1,27 @@
"""YYB mall APIs require different session fields for WeChat and QQ OAuth."""
from __future__ import annotations
WECHAT_APPID = "wxd44977328b36e647"
QQ_APPID = "102033112"
def midas_login_params(cookies: dict[str, str]) -> dict[str, str]:
"""Build the provider-specific Midas session fields from OAuth cookies."""
login_type = str(cookies.get("logintype", cookies.get("login_type", "WX"))).upper()
if login_type == "QC":
return {
"openid": cookies.get("openid", ""),
"openkey": cookies.get("accesstoken", ""),
"session_id": "openid",
"session_type": "kp_accesstoken",
"wx_appid": "",
"qq_appid": cookies.get("appid", QQ_APPID),
}
return {
"openid": cookies.get("openid", ""),
"openkey": cookies.get("accesstoken", ""),
"session_id": "hy_gameid",
"session_type": "wc_actoken",
"wx_appid": cookies.get("appid", WECHAT_APPID),
"qq_appid": "",
}
+147
View File
@@ -0,0 +1,147 @@
"""mall 侧(PlaceOrder,pagedoo CHAOS VM 108 opcode)高层 API。
与 goods 侧(pyvm/goods.py)同属 YYB 加密体系:
- goods: web_save / web_new_encrypt(midas.gtimg.cn/goodsBiz,116 opcode)
- mall: PlaceOrder(pagedoo.pay.qq.com,108 opcode,详情页 e377650 变换核心)
mall 加密链路(E3 已验证,U-2022 闭环):
会话态(xMidasOps 59620 + key16/d/Te + 624B 中间态)
→ e377650 变换核心(正确回调链 e344354=[624B中间态, d, e336201, key16])
→ 624B 密文 = encrypt_msg(1248 hex)
注意:xMidasOps 是 mall 详情页页面级数据表(59620 长度,服务端生成),
必须从浏览器捕获(与 goods 的 59640 不同)。
"""
from __future__ import annotations
import copy
import json
import re
from pathlib import Path
from typing import Any
from .algorithm import UNDEF, JSObject, Window
from .pagedoo_vm import PagedooVM
REPLAY = Path(__file__).resolve().parent.parent / "replay" / "mall"
GLOBALS = [UNDEF, None, True, False, 4294967295, 3995986053, 2103143698, 1622111212,
4263108271, 3162892160, 1960464030, 2867129963, 3224029870, 3514649446,
1382846327, 1898428403, 1268470028, 1457769175, 1595352606, 1100935262]
class MallSession:
"""mall 会话态:e377650 变换核心的完整输入。
transform_input: e377650 创建参数(18 槽)——从浏览器 deepCap/J 转储提取
[0]=key16(16B) [1..5]=Te/S-box [6]=d(16B) [7]=[211] [8]=S-box2
[9]=outbuf [10]=624B中间态(a:8输出) [11..16]=1024/256表 [17]=回调
xmidas_ops: mall 详情页 xMidasOps(59620,服务端生成,页面级)
"""
def __init__(self, transform_input: list, xmidas_ops: list):
self.transform_input = transform_input
self.xmidas_ops = xmidas_ops
self.validate()
def validate(self) -> None:
if len(self.transform_input) != 18:
raise ValueError(f"transform_input 应为 18 槽,实际 {len(self.transform_input)}")
if len(self.xmidas_ops) != 59620:
raise ValueError(f"mall xMidasOps 应为 59620(非 goods 59640),实际 {len(self.xmidas_ops)}")
mid = self.transform_input[10]
if isinstance(mid, list) and mid and isinstance(mid[0], list):
if len(mid[0]) != 624:
raise ValueError(f"624B 中间态长度 != 624: {len(mid[0])}")
@classmethod
def from_capture_file(cls, frames_jsonl: str | Path) -> "MallSession":
"""从捕获的 frames.jsonl 提取同会话 transform_input + xMidasOps。
frames.jsonl 由 scripts/capture-mall-session.mjs 实时落盘:
- J|...|e377650|{JSON} e377650 创建参数(transform_input)
- XMIDAS_OPS|url|59620数组 mall 详情页 xMidasOps
"""
lines = Path(frames_jsonl).read_text(encoding="utf-8", errors="replace").splitlines()
transform_input = None
xmidas = None
# xMidasOps:取 mall 详情页(z.iwan / pagedoo)那条
for l in lines:
if l.startswith("XMIDAS_OPS"):
parts = l.split("|")
if len(parts) >= 3 and ("z.iwan" in parts[1] or "pagedoo" in parts[1]):
xmidas = [int(x) for x in parts[2].split(",")]
break
if xmidas is None:
# 回退:任意 59620 长度
for l in lines:
if l.startswith("XMIDAS_OPS"):
parts = l.split("|")
vals = [int(x) for x in parts[2].split(",")]
if len(vals) == 59620:
xmidas = vals
break
for l in lines:
if l.startswith("J|") and re.search(r"\|e377650\|", l):
transform_input = json.loads(l.split("|e377650|", 1)[1])
break
if transform_input is None:
raise ValueError("frames.jsonl 中未找到 e377650 J 转储(需 mall 详情页购买触发加密)")
if xmidas is None:
raise ValueError("frames.jsonl 中未找到 59620 长度 xMidasOps")
return cls(transform_input, xmidas)
def to_json(self) -> dict:
return {
"transform_input": self.transform_input,
"xmidas_ops": self.xmidas_ops,
}
@classmethod
def from_json(cls, d: dict) -> "MallSession":
return cls(d["transform_input"], d["xmidas_ops"])
def _mk_window(xmidas: list) -> Window:
w = Window()
for k in ("window", "self", "globalThis", "top", "parent", "frames"):
w.set(k, w)
w.set("document", JSObject())
w.set("navigator", JSObject())
w.set("location", JSObject())
w.set("localStorage", JSObject())
w.set("sessionStorage", JSObject())
w.set("screen", JSObject())
w.set("history", JSObject())
w.set("XMLHttpRequest", type("XHR", (), {
"open": lambda *a: None, "send": lambda *a: None, "setRequestHeader": lambda *a: None}))
w.set("fetch", lambda *a: None)
w.set("xMidasOps", xmidas)
return w
def generate_encrypt_msg(session: MallSession, random_seed: int = 1) -> str:
"""用会话态重放 e377650 变换核心,产出 encrypt_msg(1248 hex)。
E3 验证:同会话 transform_input + xMidasOps(59620) → 1248 hex 完全一致。
"""
bytecode = json.loads((REPLAY / "vm" / "bytecode-478657.json").read_text())
w = _mk_window(session.xmidas_ops)
vm = PagedooVM(bytecode, GLOBALS, w, random_seed=random_seed)
vm._root_this = w
for k, v in vm._hosts.items():
if k != "Array.prototype" and w.get(k) is UNDEF:
w.set(k, v)
h = copy.deepcopy(session.transform_input)
frame_rand = vm.make(336201, [], w, GLOBALS, None)
# 正确回调链:e344354 = [624B中间态, d, e336201随机帧, key16]
h344 = [h[10], h[6], [frame_rand], h[0]]
frame344 = vm.make(344354, h344, w, GLOBALS, None)
h[17] = [frame344]
frame = vm.make(377650, h, w, GLOBALS, None)
vm.run(frame, [])
h9 = h[9][0] if isinstance(h[9], list) and h[9] else h[9]
if not isinstance(h9, list) or len(h9) != 624:
raise RuntimeError(f"e377650 输出异常: {type(h9).__name__} len={len(h9) if isinstance(h9, list) else '?'}")
return "".join(f"{x & 255:02x}" for x in h9)
@@ -0,0 +1,90 @@
"""Read YYB's official order list after a payment is completed."""
from __future__ import annotations
from typing import Any
from curl_cffi import requests
ORDER_LIST_URL = (
"https://ydd.yyb.qq.com/trpc.wesee_live.private_domain_creator_shop_svr."
"private_domain_creator_shop_svr/GetPrivateDomainOrderList"
)
USER_AGENT = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36"
)
def get_official_orders(cookies: dict[str, str], count: int = 20) -> dict[str, Any]:
"""Fetch the same completed-order list used by YYB's success page."""
response = requests.post(
ORDER_LIST_URL,
json={"count": count, "breakpoint": 0, "type": 1},
headers={
"Accept": "application/json, text/plain, */*",
"Origin": "https://m.yyb.qq.com",
"Referer": "https://m.yyb.qq.com/boc-mall/goods-mall/product-order-success",
"User-Agent": USER_AGENT,
},
cookies=cookies,
impersonate="chrome",
timeout=30,
)
if response.status_code != 200:
raise RuntimeError(f"订单状态查询失败: HTTP {response.status_code}")
try:
document = response.json()
except ValueError as exc:
raise RuntimeError("订单状态查询返回非 JSON") from exc
if not isinstance(document, dict):
raise RuntimeError("订单状态查询响应格式异常")
if document.get("ret_code") not in (None, 0, "0"):
raise RuntimeError(
f"订单状态查询失败: {document.get('ret_code')} {document.get('ret_msg', '')}"
)
if not isinstance(document.get("list", []), list):
raise RuntimeError("订单状态查询响应缺少 list")
return document
def order_ids(document: dict[str, Any]) -> set[str]:
"""Return the stable order IDs visible in an order-list response."""
return {
str(item["order_id"])
for item in document.get("list", [])
if isinstance(item, dict) and item.get("order_id") not in (None, "")
}
def is_finished(item: dict[str, Any]) -> bool:
"""YYB's completed-order representation observed on product-order-success."""
return item.get("is_finished") is True and item.get("status") in (1, "1")
def order_completion_states(document: dict[str, Any]) -> dict[str, bool]:
"""Snapshot whether each currently visible order is completed."""
return {
str(item["order_id"]): is_finished(item)
for item in document.get("list", [])
if isinstance(item, dict) and item.get("order_id") not in (None, "")
}
def find_completed_order(document: dict[str, Any], previous_states: dict[str, bool]) -> dict[str, Any] | None:
"""Find an order that appeared or transitioned to completed after the QR display."""
for item in document.get("list", []):
if not isinstance(item, dict):
continue
order_id = str(item.get("order_id", ""))
if order_id and is_finished(item) and not previous_states.get(order_id, False):
return item
return None
def completion_summary(item: dict[str, Any]) -> dict[str, Any]:
"""Persist only the state needed to identify a confirmed completion."""
return {
"order_id": str(item.get("order_id", "")),
"is_finished": item.get("is_finished"),
"status": item.get("status"),
}
@@ -0,0 +1,987 @@
"""pagedoo shop CHAOS VM(108 opcode)纯 Python 移植 — mall PlaceOrder 加密地基。
对应浏览器 `p_5c660516.*` chunk 内的 `__TENCENT_CHAOS_VM`(pagedoo 变体,108 opcode 0..107)。
与 goods 侧(pyvm/algorithm.py,116 opcode)同属 CHAOS VM 家族:语义相同,opcode 编号不同(F-2055)。
JS 语义辅助复用 algorithm.py(_ic/i32/js_add/js_index/JSObject/JSFunction 等)。
当前验证锚点:
- 帧 e215058(encodeURI + %XX 解码 → 字节数组):Node 重放返回输入 JSON 串的 UTF-8 字节(F-2059)
- 帧 e377650 变换核心:Node 指令级复现(F-2060,254937/254938 一致)
输出编排帧链(e454218/e423160 getter 链)仍依赖 VM C 栈续延语义,见 cases/yyb-chaos-vm-xmidas-webnewencrypt.md 未决项。
"""
from __future__ import annotations
import json
import math
import urllib.parse
from pathlib import Path
from .algorithm import (
JSDate,
UNDEF,
JSFunction,
JSObject,
HostFunction,
_ic,
i32,
u32,
ushr,
shl,
shr,
js_typeof,
js_truthy,
js_str,
js_add,
js_num,
js_eq,
js_streq,
js_index,
js_set,
js_del,
js_keys,
js_call,
js_apply,
js_new,
h_math_floor,
h_math_round,
h_math_ceil,
h_math_min,
h_math_max,
h_math_abs,
h_math_pow,
h_math_sqrt,
h_parseint,
h_parsefloat,
h_isnan,
h_encodeuri,
h_encodeuricomponent,
h_decodeuri,
h_decodeuricomponent,
h_string_fromcharcode,
h_new_date,
)
__all__ = ["PagedooVM", "run_frame", "REPLAY"]
# ---------------------------------------------------------------- 辅助
def _cmp_lt(a, b):
try:
return a < b
except Exception:
return False
def _cmp_le(a, b):
try:
return a <= b
except Exception:
return False
def _cmp_gt(a, b):
try:
return a > b
except Exception:
return False
def _cmp_ge(a, b):
try:
return a >= b
except Exception:
return False
# ---------------------------------------------------------------- 宿主函数
def h_arr_push(this, *args):
if this is UNDEF or this is None:
this = []
if len(args) == 1:
this.append(args[0])
else:
this.extend(args)
return len(this)
def h_arr_shift(this):
if not this:
return UNDEF
return this.pop(0)
def h_arr_join(this, sep=None):
if sep is None:
sep = ","
return sep.join("" if x is UNDEF or x is None else js_str(x) for x in this)
def h_arr_slice(this, a=None, b=None):
n = len(this)
if a is None or a is UNDEF:
a = 0
if b is None or b is UNDEF:
b = n
a = int(a) if a == a else 0
b = int(b) if b == b else n
if a < 0:
a = max(0, n + a)
if b < 0:
b = max(0, n + b)
return list(this[a:b])
def h_arr_indexof(this, x, frm=0):
try:
return this.index(x, frm)
except ValueError:
return -1
def h_arr_concat(this, *others):
out = list(this)
for o in others:
if isinstance(o, list):
out.extend(o)
else:
out.append(o)
return out
def h_arr_pop(this):
if not this:
return UNDEF
return this.pop()
def h_arr_unshift(this, *vals):
for v in reversed(vals):
this.insert(0, v)
return len(this)
def h_arr_reverse(this):
this.reverse()
return this
def h_arr_splice(this, start, delete_count=0, *items):
n = len(this)
if start < 0:
start = max(0, n + start)
if delete_count is UNDEF or delete_count is None:
delete_count = n - start
removed = this[start:start + delete_count]
del this[start:start + delete_count]
for i, it in enumerate(items):
this.insert(start + i, it)
return removed
def h_arr_map(this, fn):
return [js_call(fn, UNDEF, [x]) for x in this]
def h_arr_foreach(this, fn):
for x in this:
js_call(fn, UNDEF, [x])
return UNDEF
def h_arr_filter(this, fn):
return [x for x in this if js_truthy(js_call(fn, UNDEF, [x]))]
def h_char_code_at(this, s, i=0):
if isinstance(this, str) and isinstance(s, int) and 0 <= s < len(this):
return ord(this[s])
return float("nan")
def h_char_at(this, i=0):
if isinstance(this, str) and isinstance(i, int) and 0 <= i < len(this):
return this[i]
return ""
def h_str_indexof(this, search, frm=0):
if not isinstance(this, str):
return -1
try:
return this.index(search, frm)
except ValueError:
return -1
def h_str_slice(this, a=None, b=None):
return this[a:b] if isinstance(this, str) else ""
def h_str_split(this, sep=None):
return this.split(sep) if isinstance(this, str) else [this]
def h_str_tolower(this):
return this.lower() if isinstance(this, str) else this
def h_str_toupper(this):
return this.upper() if isinstance(this, str) else this
def h_str_substr(this, start=0, length=None):
if not isinstance(this, str):
return ""
n = len(this)
if start < 0:
start = max(0, n + start)
if length is None or length is UNDEF:
return this[start:]
return this[start:start + int(length)]
def h_str_substring(this, start=0, end=None):
if not isinstance(this, str):
return ""
if end is None or end is UNDEF:
end = len(this)
start = max(0, min(int(start), len(this)))
end = max(0, min(int(end), len(this)))
if start > end:
start, end = end, start
return this[start:end]
def h_str_tostring(this):
return js_str(this)
def h_object_ctor(this, *args):
if len(args) == 1 and args[0] is not UNDEF and args[0] is not None:
return args[0]
return JSObject()
def _js_to_py(v):
"""JSON.stringify 辅助:把 JS 值转 JSON 可序列化。"""
if v is UNDEF:
return None
if isinstance(v, list):
return [_js_to_py(x) for x in v]
if isinstance(v, JSObject):
keys = v._d if hasattr(v, "_d") else {}
return {k: _js_to_py(val) for k, val in (keys.items() if isinstance(keys, dict) else [])}
if isinstance(v, Window):
return {}
return v
def _py_to_js(v):
if isinstance(v, dict):
o = JSObject()
for k, val in v.items():
o.set(k, _py_to_js(val))
return o
if isinstance(v, list):
return [_py_to_js(x) for x in v]
return v
def _pg_index(obj, key):
"""pagedoo 专用 js_index:补充字符串原型方法(goods 侧不需要)。"""
if isinstance(obj, JSDate):
if key == "getTime":
return HostFunction(lambda this: this.t if isinstance(this, JSDate) else obj.t, "getTime")
if key == "toString":
return HostFunction(lambda this: str(this), "toString")
if key == "valueOf":
return HostFunction(lambda this: this.t if isinstance(this, JSDate) else obj.t, "valueOf")
if isinstance(obj, str):
if isinstance(key, str):
if key == "length":
return len(obj)
if key == "charCodeAt":
return HostFunction(h_char_code_at, "charCodeAt")
if key == "charAt":
return HostFunction(h_char_at, "charAt")
if key == "indexOf":
return HostFunction(h_str_indexof, "indexOf")
if key == "slice":
return HostFunction(h_str_slice, "slice")
if key == "split":
return HostFunction(h_str_split, "split")
if key == "toLowerCase":
return HostFunction(h_str_tolower, "toLowerCase")
if key == "toUpperCase":
return HostFunction(h_str_toupper, "toUpperCase")
if key == "toString":
return HostFunction(h_str_tostring, "toString")
if key == "substr":
return HostFunction(h_str_substr, "substr")
if key == "substring":
return HostFunction(h_str_substring, "substring")
return js_index(obj, key)
# ---------------------------------------------------------------- VM
class PagedooVM:
"""pagedoo CHAOS VM 解释器(108 opcode,0..107)。
bytecode: 字节码数组(如 vm/bytecode-478657.json)
constants: 常量数组(浏览器调用第 4 参,即 globals)
window: 宿主环境对象(JSObject/Window)
"""
def __init__(self, bytecode, constants, window, random_seed=None):
self.o = bytecode
self.constants = constants
self.window = window
self.inst_id = 0
self.use_init_c = False
self.init_c = None
self._root_this = UNDEF
self._host_log = []
import random as _r
self._random = _r
if random_seed is not None:
self._random.seed(random_seed)
self._hosts = self._build_hosts()
def _build_hosts(self):
m = {}
m["Math"] = JSObject()
m["Math"].set("random", HostFunction(lambda this: self._random.random(), "random"))
m["Math"].set("floor", HostFunction(h_math_floor, "floor"))
m["Math"].set("round", HostFunction(h_math_round, "round"))
m["Math"].set("ceil", HostFunction(h_math_ceil, "ceil"))
m["Math"].set("min", HostFunction(h_math_min, "min"))
m["Math"].set("max", HostFunction(h_math_max, "max"))
m["Math"].set("abs", HostFunction(h_math_abs, "abs"))
m["Math"].set("pow", HostFunction(h_math_pow, "pow"))
m["Math"].set("sqrt", HostFunction(h_math_sqrt, "sqrt"))
m["parseInt"] = HostFunction(h_parseint, "parseInt")
m["parseFloat"] = HostFunction(h_parsefloat, "parseFloat")
m["isNaN"] = HostFunction(h_isnan, "isNaN")
m["encodeURI"] = HostFunction(h_encodeuri, "encodeURI")
m["encodeURIComponent"] = HostFunction(h_encodeuricomponent, "encodeURIComponent")
m["decodeURI"] = HostFunction(h_decodeuri, "decodeURI")
m["decodeURIComponent"] = HostFunction(h_decodeuricomponent, "decodeURIComponent")
m["String"] = JSObject()
m["String"].set("fromCharCode", HostFunction(h_string_fromcharcode, "fromCharCode"))
m["Date"] = HostFunction(h_new_date, "Date")
m["Array"] = JSObject()
m["Array"].set("isArray", HostFunction(lambda this, x: isinstance(x, list), "isArray"))
m["Object"] = HostFunction(h_object_ctor, "Object")
m["Number"] = HostFunction(lambda this, x=None: js_num(x) if x is not None and x is not UNDEF else 0.0, "Number")
m["Boolean"] = HostFunction(lambda this, x=None: bool(js_truthy(x)) if x is not None and x is not UNDEF else False, "Boolean")
jso = JSObject()
jso.set("stringify", HostFunction(lambda this, x, *a: json.dumps(_js_to_py(x), ensure_ascii=False), "stringify"))
jso.set("parse", HostFunction(lambda this, s: _py_to_js(json.loads(s)), "parse"))
m["JSON"] = jso
m["RegExp"] = HostFunction(lambda this, *a: JSObject(), "RegExp")
# Array.prototype 方法挂到 Array 对象上(VM 通过 i[A].push 等调用)
proto = JSObject()
for name, fn in [
("push", h_arr_push), ("shift", h_arr_shift), ("join", h_arr_join),
("slice", h_arr_slice), ("indexOf", h_arr_indexof), ("concat", h_arr_concat),
("pop", h_arr_pop), ("unshift", h_arr_unshift), ("reverse", h_arr_reverse),
("splice", h_arr_splice), ("map", h_arr_map), ("forEach", h_arr_foreach),
("filter", h_arr_filter),
]:
proto.set(name, HostFunction(fn, name))
m["Array.prototype"] = proto
return m
def host(self, name):
h = self.window.get(name)
if h is not UNDEF:
return h
return self._hosts.get(name, UNDEF)
def make(self, entry, args, s, n, t):
return JSFunction(self, entry, args, s, n, t)
# -- 原型方法查找:i[A].push(...) 形式,i[A] 是数组 --
def _arr_method(self, name):
p = self._hosts.get("Array.prototype")
if p is not UNDEF and isinstance(p, JSObject):
v = p.get(name)
if v is not UNDEF:
return v
return UNDEF
def run(self, fn: JSFunction, call_args, trace=None, csnap=None):
self.inst_id += 1
if trace is not None:
self._trace = trace
self._csnap = csnap
if self.use_init_c and self.init_c is not None:
C = self.init_c
self.use_init_c = False
else:
root_this = getattr(self, "_root_this", UNDEF)
C = [fn.s, fn.n, fn.args, root_this, call_args, fn, self.o, 0]
C = list(C)
p = UNDEF
u = fn.entry
d = [] # 异常续延栈(JS C)
t = UNDEF # 最近异常值(op3/op52)
o = self.o
l = fn.t # 异常处理器(JS l)
_get = _pg_index
_set = js_set
_arr = self._arr_method
while True:
try:
while True:
u += 1
op = o[u]
if op in (0, 4, 11, 18, 23, 26, 43, 44, 48, 50, 84, 107) and len(self._host_log) < 2000:
# 记录调用目标(简化)
_tgt = o[u + 2] if op in (4, 11, 44, 50) else (o[u + 2] if op in (0, 18, 23, 26, 43, 48, 84, 107) else o[u + 2])
try:
_tv = C[_tgt] if 0 <= _tgt < len(C) else UNDEF
_tr = type(_tv).__name__ if not isinstance(_tv, (int, float, str, bool, type(None))) else repr(_tv)[:30]
self._host_log.append((op, u, _tr))
except Exception:
self._host_log.append((op, u, "?"))
if getattr(self, "_trace", None) is not None and len(self._trace) < 500000:
self._trace.append((op, u))
if self._csnap is not None and len(self._csnap) < 500:
snap = []
for _si in range(min(42, len(C))):
_v = C[_si]
if isinstance(_v, str):
snap.append("s:" + _v[:30])
elif isinstance(_v, list):
snap.append(f"a[{len(_v)}]")
elif _v is UNDEF:
snap.append("u")
elif _v is None:
snap.append("n")
elif isinstance(_v, (int, float, bool)):
snap.append(_v)
else:
snap.append("o")
self._csnap.append((op, u, snap))
# ---------------- opcode dispatch(108,逐条对照 interpreter-clean.js)----------------
# 约定:S() 读下一个槽索引操作数并取 C[slot];imm 直接读。
if op == 0:
# for(h=[],f=c[++u];f>0;f--)h.push(i[c[++u]]);i[A]=i[B].apply(i[C],h)
f = o[u + 1]; u += 1
h = []
for _ in range(f):
h.append(_get(C, o[u + 1])); u += 1
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, js_apply(_get(C, b), _get(C, c), h))
elif op == 1:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, js_num(_get(C, b)) - js_num(_get(C, c)))
elif op == 2:
a = o[u + 1]; u += 1
_set(C, a, False)
elif op == 3:
a = o[u + 1]; u += 1
_set(C, a, t)
elif op == 4:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; dd = o[u + 4]; u += 4
_set(C, a, js_call(_get(C, b), p, [_get(C, c), _get(C, dd)]))
elif op == 5:
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, _get(C, b))
c = o[u + 1]; dd = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, c, _get(_get(C, dd), imm))
e = o[u + 1]; u += 1
_set(C, e, "")
elif op == 6:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, _cmp_lt(js_num(_get(C, b)), js_num(_get(C, c))))
elif op == 7:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, js_del(_get(C, b), _get(C, c)))
elif op == 8:
a = o[u + 1]; b = o[u + 2]; imm1 = o[u + 3]; u += 3
_set(C, a, _get(_get(C, b), imm1))
c = o[u + 1]; dd = o[u + 2]; imm2 = o[u + 3]; u += 3
_set(C, c, _get(_get(C, dd), imm2))
elif op == 9:
if d:
d.pop()
elif op == 10:
f = o[u + 1]; u += 1
h = []
for _ in range(f):
h.append(_get(C, o[u + 1])); u += 1
dest = o[u + 1]; off = o[u + 2]; u += 2
_set(C, dest, self.make(u + off, h, C[0], C[1], l))
elif op == 11:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, js_call(_get(C, b), p, [_get(C, c)]))
elif op == 12:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, shl(js_num(_get(C, b)), imm))
elif op == 13:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; dd = o[u + 4]; e = o[u + 5]; u += 5
_set(C, a, _get(C, b))
v = _cmp_lt(js_num(_get(C, dd)), js_num(_get(C, e)))
_set(C, c, v)
u0 = u
if js_truthy(v):
u = u0 + o[u0 + 1]
else:
u = u0 + o[u0 + 2]
elif op == 14:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, i32(_ic(_get(C, b))) | i32(_ic(_get(C, c))))
elif op == 15:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, js_num(_get(C, b)) - imm)
elif op == 16:
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, js_new(_get(C, b), []))
elif op == 17:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, "")
_set(C, b, js_str(_get(C, b)) + chr(imm & 0xFFFF))
elif op == 18:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; dd = o[u + 4]; e = o[u + 5]; f = o[u + 6]; u += 6
_set(C, a, js_call(_get(C, b), _get(C, c), [_get(C, dd), _get(C, e), _get(C, f)]))
elif op == 19:
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, _get(C, b))
c = o[u + 1]; imm = o[u + 2]; u += 2
_set(C, c, imm)
dd = o[u + 1]; e = o[u + 2]; u += 2
_set(C, dd, _get(C, e))
elif op == 20:
a = o[u + 1]; u += 1
_set(C, a, "")
elif op == 21:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, _get(_get(C, b), _get(C, c)))
dd = o[u + 1]; e = o[u + 2]; imm = o[u + 3]; u += 3
v = _cmp_gt(js_num(_get(C, e)), imm)
_set(C, dd, v)
u0 = u
if js_truthy(v):
u = u0 + o[u0 + 1]
else:
u = u0 + o[u0 + 2]
elif op == 22:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, js_num(_get(C, b)) * js_num(_get(C, c)))
elif op == 23:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; dd = o[u + 4]; u += 4
_set(C, a, js_call(_get(C, b), _get(C, c), [_get(C, dd)]))
elif op == 24:
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, js_num(_get(C, b)))
c = o[u + 1]; dd = o[u + 2]; u += 2
_set(C, c, js_num(_get(C, dd)) + 1)
e = o[u + 1]; f = o[u + 2]; u += 2
_set(C, e, _get(C, f))
elif op == 25:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; dd = o[u + 4]; e = o[u + 5]; u += 5
_set(C, a, js_new(_get(C, b), [_get(C, c), _get(C, dd), _get(C, e)]))
elif op == 26:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, _get(_get(C, b), _get(C, c)))
dd = o[u + 1]; e = o[u + 2]; f = o[u + 3]; g = o[u + 4]; u += 4
_set(C, dd, js_call(_get(C, e), _get(C, f), [_get(C, g)]))
hh = o[u + 1]; ii = o[u + 2]; j = o[u + 3]; kk = o[u + 4]; ll = o[u + 5]; u += 5
_set(C, hh, js_call(_get(C, ii), _get(C, j), [_get(C, kk), _get(C, ll)]))
elif op == 27:
a = o[u + 1]; n = o[u + 2]; u += 2
_set(C, a, [UNDEF] * int(n))
elif op == 28:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, i32(_ic(_get(C, b))) ^ i32(_ic(_get(C, c))))
elif op == 29:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, _cmp_gt(js_num(_get(C, b)), imm))
elif op == 30:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, i32(_ic(_get(C, b))) & i32(_ic(_get(C, c))))
elif op == 31:
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, _get(C, b))
c = o[u + 1]; dd = o[u + 2]; u += 2
_set(C, c, js_num(_get(C, dd)))
e = o[u + 1]; f = o[u + 2]; u += 2
_set(C, e, js_num(_get(C, f)) + 1)
elif op == 32:
# u += i[A] ? c[++u] : c[(++u,++u)] —— LHS u 在 body 开始读取
u0 = u
a = o[u0 + 1]
if js_truthy(_get(C, a)):
u = u0 + o[u0 + 2]
else:
u = u0 + o[u0 + 3]
elif op == 33:
a = o[u + 1]; imm = o[u + 2]; u += 2
_set(C, a, js_str(_get(C, a)) + chr(imm & 0xFFFF))
b = o[u + 1]; c = o[u + 2]; dd = o[u + 3]; u += 3
_set(C, b, _get(_get(C, c), _get(C, dd)))
elif op == 34:
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, js_typeof(_get(C, b)))
elif op == 35:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, _cmp_le(js_num(_get(C, b)), js_num(_get(C, c))))
elif op == 36:
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, -js_num(_get(C, b)))
elif op == 37:
a = o[u + 1]; u += 1
_set(C, a, True)
elif op == 38:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, isinstance(_get(C, b), type(_get(C, c))) if isinstance(_get(C, c), type) else False)
elif op == 39:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, js_add(_get(C, b), imm))
elif op == 40:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
js_set(_get(C, a), _get(C, b), _get(C, c))
elif op == 41:
a = o[u + 1]; imm = o[u + 2]; u += 2
_set(C, a, imm)
b = o[u + 1]; u += 1
_set(C, b, _get(C, b))
c = o[u + 1]; dd = o[u + 2]; e = o[u + 3]; u += 3
_set(C, c, _cmp_lt(js_num(_get(C, dd)), js_num(_get(C, e))))
elif op == 42:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, js_num(_get(C, b)) / js_num(_get(C, c)))
elif op == 43:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; dd = o[u + 4]; e = o[u + 5]; u += 5
_set(C, a, js_call(_get(C, b), _get(C, c), [_get(C, dd), _get(C, e)]))
elif op == 44:
a = o[u + 1]; b = o[u + 2]; u += 2
_bv = _get(C, b)
if getattr(self, "_dbg_u", None) == u:
print(f" [dbg] op44@{u}: A={a} B={b} i[B]={type(_bv).__name__}: {str(_bv)[:80]}")
_set(C, a, js_call(_bv, p, []))
elif op == 45:
a = o[u + 1]; u += 1
_set(C, a, p)
b = o[u + 1]; n = o[u + 2]; u += 2
_set(C, b, [UNDEF] * int(n))
c = o[u + 1]; u += 1
_set(C, c, "")
elif op == 46:
a = o[u + 1]; u += 1
obj = JSObject()
_set(C, a, obj)
b = o[u + 1]; imm = o[u + 2]; c = o[u + 3]; u += 3
js_set(_get(C, b), imm, _get(C, c))
dd = o[u + 1]; imm2 = o[u + 2]; e = o[u + 3]; u += 3
js_set(_get(C, dd), imm2, _get(C, e))
elif op == 47:
a = o[u + 1]; imm = o[u + 2]; u += 2
_set(C, a, js_str(_get(C, a)) + chr(imm & 0xFFFF))
elif op == 48:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; dd = o[u + 4]; u += 4
_set(C, a, js_call(_get(C, b), _get(C, c), [_get(C, dd)]))
e = o[u + 1]; f = o[u + 2]; g = o[u + 3]; hh = o[u + 4]; ii = o[u + 5]; j = o[u + 6]; u += 6
_set(C, e, js_call(_get(C, f), _get(C, g), [_get(C, hh), _get(C, ii)]))
return _get(C, j)
elif op == 49:
a = o[u + 1]; u += 1
_set(C, a, JSObject())
elif op == 50:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; dd = o[u + 4]; u += 4
_set(C, a, js_call(_get(C, b), p, [_get(C, c), _get(C, dd)]))
elif op == 51:
a = o[u + 1]; imm = o[u + 2]; u += 2
_set(C, a, js_str(_get(C, a)) + chr(imm & 0xFFFF))
f = o[u + 1]; u += 1
h = []
for _ in range(f):
h.append(_get(C, o[u + 1])); u += 1
dest = o[u + 1]; off = o[u + 2]; u += 2
frame = self.make(u + off, h, C[0], C[1], l)
_set(C, dest, frame)
b = o[u + 1]; c = o[u + 2]; e = o[u + 3]; u += 3
js_set(_get(C, b), _get(C, c), _get(C, e))
elif op == 52:
t = _get(C, o[u + 1]); u += 1
raise _VMThrow(t)
elif op == 53:
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, js_num(_get(C, b)))
elif op == 54:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, _cmp_lt(js_num(_get(C, b)), imm))
elif op == 55:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, _get(_get(C, b), _get(C, c)))
elif op == 56:
# C.push(u+c[++u]) —— LHS u 在 c[++u] 前读取,且 u 推进 1
u0 = u
imm = o[u0 + 1]
d.append(u0 + imm)
u = u0 + 1
elif op == 57:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, _cmp_ge(js_num(_get(C, b)), js_num(_get(C, c))))
elif op == 58:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, js_eq(_get(C, b), _get(C, c)))
elif op == 59:
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, js_keys(_get(C, b)))
elif op == 60:
u += o[u + 1]
elif op == 61:
a = o[u + 1]; imm = o[u + 2]; b = o[u + 3]; u += 3
js_set(_get(C, a), imm, _get(C, b))
elif op == 62:
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, _get(C, b))
c = o[u + 1]; dd = o[u + 2]; e = o[u + 3]; u += 3
js_set(_get(C, c), _get(C, dd), _get(C, e))
elif op == 63:
return _get(C, o[u + 1])
elif op == 64:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, _get(_get(C, b), imm))
c = o[u + 1]; u += 1
_set(C, c, "")
dd = o[u + 1]; imm2 = o[u + 2]; u += 2
_set(C, dd, js_str(_get(C, dd)) + chr(imm2 & 0xFFFF))
elif op == 65:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, shr(js_num(_get(C, b)), js_num(_get(C, c))))
elif op == 66:
a = o[u + 1]; imm1 = o[u + 2]; u += 2
_set(C, a, js_str(_get(C, a)) + chr(imm1 & 0xFFFF))
b = o[u + 1]; imm2 = o[u + 2]; u += 2
_set(C, b, js_str(_get(C, b)) + chr(imm2 & 0xFFFF))
elif op == 67:
a = o[u + 1]; u += 1
_set(C, a, p)
elif op == 68:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, _get(_get(C, b), _get(C, c)))
dd = o[u + 1]; e = o[u + 2]; u += 2
_set(C, dd, _get(C, e))
f = o[u + 1]; u += 1
_set(C, f, "")
elif op == 69:
a = o[u + 1]; imm = o[u + 2]; u += 2
_set(C, a, js_num(_get(C, a)) - imm)
b = o[u + 1]; c = o[u + 2]; dd = o[u + 3]; e = o[u + 4]; u += 4
_set(C, b, js_new(_get(C, c), [_get(C, dd), _get(C, e)]))
f = o[u + 1]; g = o[u + 2]; u += 2
_set(C, f, _get(C, g))
elif op == 70:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, i32(_ic(_get(C, b))) | imm)
elif op == 71:
f = o[u + 1]; u += 1
h = []
for _ in range(f):
h.append(_get(C, o[u + 1])); u += 1
dest = o[u + 1]; off = o[u + 2]; u += 2
_set(C, dest, self.make(u + off, h, C[0], C[1], l))
elif op == 72:
a = o[u + 1]; u += 1
_set(C, a, None)
elif op == 73:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, i32(_ic(_get(C, b))) & imm)
elif op == 74:
a = o[u + 1]; imm1 = o[u + 2]; b = o[u + 3]; u += 3
js_set(_get(C, a), imm1, _get(C, b))
c = o[u + 1]; u += 1
obj = JSObject()
_set(C, c, obj)
dd = o[u + 1]; imm2 = o[u + 2]; e = o[u + 3]; u += 3
js_set(_get(C, dd), imm2, _get(C, e))
elif op == 75:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, shr(js_num(_get(C, b)), imm))
elif op == 76:
a = o[u + 1]; imm = o[u + 2]; b = o[u + 3]; u += 3
_set(C, a, imm + js_num(_get(C, b)))
elif op == 77:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; dd = o[u + 4]; u += 4
_set(C, a, js_new(_get(C, b), [_get(C, c), _get(C, dd)]))
elif op == 78:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, ushr(js_num(_get(C, b)), js_num(_get(C, c))))
elif op == 79:
a = o[u + 1]; u += 1
_set(C, a, js_num(_get(C, a)) + 1)
elif op == 80:
a = o[u + 1]; imm = o[u + 2]; u += 2
_set(C, a, imm)
elif op == 81:
a = o[u + 1]; imm = o[u + 2]; b = o[u + 3]; u += 3
_set(C, a, imm - js_num(_get(C, b)))
elif op == 82:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, _cmp_gt(js_num(_get(C, b)), js_num(_get(C, c))))
elif op == 83:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, ushr(js_num(_get(C, b)), imm))
elif op == 84:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, js_call(_get(C, b), _get(C, c), []))
elif op == 85:
a = o[u + 1]; imm1 = o[u + 2]; b = o[u + 3]; u += 3
js_set(_get(C, a), imm1, _get(C, b))
c = o[u + 1]; imm2 = o[u + 2]; dd = o[u + 3]; u += 3
js_set(_get(C, c), imm2, _get(C, dd))
e = o[u + 1]; u += 1
_set(C, e, "")
elif op == 86:
a = o[u + 1]; b = o[u + 2]; u += 2
h = _get(C, a)
if h is not UNDEF and h is not None and len(h):
_set(C, b, True)
c = o[u + 1]; u += 1
_set(C, c, h.pop(0))
else:
_set(C, b, False)
u += 1
elif op == 87:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, _get(_get(C, b), imm))
elif op == 88:
a = o[u + 1]; u += 1
_set(C, a, js_num(_get(C, a)) - 1)
elif op == 89:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, shl(js_num(_get(C, b)), js_num(_get(C, c))))
elif op == 90:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, js_eq(_get(C, b), _get(C, c)))
elif op == 91:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, js_new(_get(C, b), [_get(C, c)]))
elif op == 92:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, _cmp_le(js_num(_get(C, b)), imm))
elif op == 93:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, js_num(_get(C, b)) % js_num(_get(C, c)))
elif op == 94:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, i32(_ic(_get(C, b))) ^ imm)
elif op == 95:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, js_new(_get(C, b), [_get(C, c)]))
dd = o[u + 1]; e = o[u + 2]; u += 2
_set(C, dd, _get(C, e))
f = o[u + 1]; imm = o[u + 2]; u += 2
_set(C, f, imm)
elif op == 96:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
obj2 = _get(C, c)
_set(C, a, _get(C, b) in obj2 if isinstance(obj2, (list, dict, JSObject)) else False)
elif op == 97:
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, not js_truthy(_get(C, b)))
elif op == 98:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, _cmp_ge(js_num(_get(C, b)), imm))
elif op == 99:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, js_add(_get(C, b), _get(C, c)))
elif op == 100:
a = o[u + 1]; b = o[u + 2]; imm = o[u + 3]; u += 3
_set(C, a, _get(C, b) == imm)
elif op == 101:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
_set(C, a, js_eq(_get(C, b), _get(C, c)))
elif op == 102:
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, _get(C, b))
elif op == 103:
a = o[u + 1]; b = o[u + 2]; imm1 = o[u + 3]; u += 3
_set(C, a, _get(_get(C, b), imm1))
c = o[u + 1]; dd = o[u + 2]; u += 2
_set(C, c, _get(C, dd))
e = o[u + 1]; f = o[u + 2]; imm2 = o[u + 3]; u += 3
_set(C, e, _get(_get(C, f), imm2))
elif op == 104:
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, js_num(_get(C, b)))
elif op == 105:
a = o[u + 1]; b = o[u + 2]; c = o[u + 3]; u += 3
v = _get(_get(C, b), _get(C, c))
_set(C, a, v)
dd = o[u + 1]; e = o[u + 2]; f = o[u + 3]; u += 3
js_set(_get(C, dd), _get(C, e), _get(C, f))
u0 = u
u = u0 + o[u0 + 1]
elif op == 106:
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, ~i32(_ic(_get(C, b))))
elif op == 107:
f = o[u + 1]; u += 1
h = []
for _ in range(f):
h.append(_get(C, o[u + 1])); u += 1
a = o[u + 1]; b = o[u + 2]; u += 2
_set(C, a, js_apply(_get(C, b), p, h))
else:
raise RuntimeError(f"未知 opcode {op} @u={u}")
except _VMThrow as ex:
t = ex.value
if d:
u = d.pop()
continue
if l is not UNDEF and l is not None:
return js_call(l, UNDEF, [t, C, []])
raise
except Exception:
if d:
u = d.pop()
continue
raise
class _VMThrow(Exception):
def __init__(self, value):
super().__init__("vm throw")
self.value = value
# ---------------------------------------------------------------- 便捷入口
def _mk_window(vmlog=None):
from .algorithm import Window
w = Window()
for k in ("window", "self", "globalThis", "top", "parent", "frames"):
w.set(k, w)
w.set("document", JSObject())
w.set("navigator", JSObject())
w.set("location", JSObject())
w.set("localStorage", JSObject())
w.set("sessionStorage", JSObject())
w.set("screen", JSObject())
w.set("history", JSObject())
w.set("XMLHttpRequest", HostFunction(lambda this, *a: None, "XHR"))
w.set("fetch", HostFunction(lambda this, *a: None, "fetch"))
return w
def run_frame(entry, h, bytecode, constants, window=None, call_args=(), random_seed=None):
"""便捷入口:用 pagedoo VM 执行指定 entry 的帧(h 为创建参数数组)。"""
if window is None:
window = _mk_window()
vm = PagedooVM(bytecode, constants, window, random_seed=random_seed)
# 挂宿主
for k, v in vm._hosts.items():
if k != "Array.prototype" and window.get(k) is UNDEF:
window.set(k, v)
frame = vm.make(entry, h, window, constants, None)
return vm.run(frame, list(call_args)), vm
@@ -0,0 +1,91 @@
"""会话态模型:web_new_encrypt 离线生成所需的会话级输入。
F-2049/F-2051(E3):encrypt_msg 与当前会话绑定——
- xMidasOps: goods.shtml 页面内嵌伪随机表(59640 值,服务端每次生成,页面级)
- key16: goodsBiz VM 加载期 Math.random 前 16 次调用(页面级恒定)
- key1: 诱饵密钥(点击级,捕获即可)
服务端能验证 key 派生状态(随机 key 变体 ret:1099),因此新订单必须先捕获会话态。
"""
from __future__ import annotations
import json
from dataclasses import dataclass, field
from pathlib import Path
DEFAULT_XMIDAS_TOKEN = "DE46DBA4754D42A6B66ADD4319FF80C144D9ED22ED73384C271793D9A9AA2FFBD6C104BE8D4A7F4ED2A8688FCB6F7540"
@dataclass
class SessionState:
"""一次 goods 页加载捕获的会话态。
xmidas_ops: list[int] 59640 项(页面级)
key16: list[int] 16 字节(VM 加载期 Math.random 前 16 次)
key1: list[int] 16 字节(诱饵)
xmidas_token: str
cookies: dict[str, str](提交 web_save 用,可选)
openid/openkey: str(提交 web_save 用,可选)
"""
xmidas_ops: list[int] = field(default_factory=list)
key16: list[int] = field(default_factory=list)
key1: list[int] = field(default_factory=list)
xmidas_token: str = DEFAULT_XMIDAS_TOKEN
args_template_d: str = "" # webSave 18 参深拷贝(deepcap PC 85091 的 C[2] 原始 D 编码,会话绑定)
cookies: dict[str, str] = field(default_factory=dict)
openid: str = ""
openkey: str = ""
source: str = "" # 捕获来源说明
def validate(self) -> list[str]:
errs: list[str] = []
if len(self.xmidas_ops) != 59640:
errs.append(f"xmidas_ops 长度 {len(self.xmidas_ops)} != 59640")
if len(self.key16) != 16:
errs.append(f"key16 长度 {len(self.key16)} != 16")
if len(self.key1) != 16:
errs.append(f"key1 长度 {len(self.key1)} != 16")
if not self.xmidas_token:
errs.append("xmidas_token 为空")
if not self.args_template_d:
errs.append("args_template_d 为空(需 deepcap PC 85091 的 C[2] 原始 D 编码)")
return errs
def to_json(self) -> dict:
return {
"xmidas_ops": self.xmidas_ops,
"key16": self.key16,
"key1": self.key1,
"xmidas_token": self.xmidas_token,
"args_template_d": self.args_template_d,
"cookies": self.cookies,
"openid": self.openid,
"openkey": self.openkey,
"source": self.source,
}
@classmethod
def from_json(cls, d: dict) -> "SessionState":
return cls(
xmidas_ops=list(d.get("xmidas_ops", [])),
key16=list(d.get("key16", [])),
key1=list(d.get("key1", [])),
xmidas_token=d.get("xmidas_token", DEFAULT_XMIDAS_TOKEN),
args_template_d=d.get("args_template_d", ""),
cookies=dict(d.get("cookies", {})),
openid=d.get("openid", ""),
openkey=d.get("openkey", ""),
source=d.get("source", ""),
)
def load_session(path: str | Path) -> SessionState:
"""从 JSON 加载会话态并校验。"""
p = Path(path)
if not p.exists():
raise FileNotFoundError(f"会话态文件不存在: {p}")
st = SessionState.from_json(json.loads(p.read_text(encoding="utf-8")))
errs = st.validate()
if errs:
raise ValueError("会话态校验失败:\n " + "\n ".join(errs))
return st