ksjsb/core/dfp_cache.py
2026-07-30 20:25:56 +08:00

132 lines
3.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import hashlib
from collections.abc import Iterable, Mapping
from .device_profile import DeviceProfile
PERSISTED_CACHE_KEYS = (
"k6",
"k8",
"k16",
"k19",
"k23",
"k27",
"k29",
"k40",
"k105",
"k110",
)
def uq0_s_a(text: str) -> str:
"""复现 uq0.s.a(): 非空且非 KWE* 时返回 MD5 小写 hex 前 16 位。"""
if not text or text.startswith("KWE"):
return text
return hashlib.md5(text.encode("utf-8")).hexdigest()[:16]
def java_string_hashcode(text: str) -> int:
"""Java String.hashCode() 的 32-bit 结果。"""
value = 0
for char in text:
value = (31 * value + ord(char)) & 0xFFFFFFFF
return value
def java_hashmap_spread(hashcode: int) -> int:
"""OpenJDK HashMap.hash(): h ^ (h >>> 16)。"""
unsigned = hashcode & 0xFFFFFFFF
return (unsigned ^ (unsigned >> 16)) & 0xFFFFFFFF
def java_hashmap_to_string(entries: Iterable[tuple[str, object]], capacity: int = 16) -> str:
"""复现当前 DFP cache_m 使用场景下的 HashMap.toString() 顺序。
c.s(Context) 只 put 10 个固定 String key默认 HashMap 首次扩容到 16
不会触发二次 resize。这里按 bucket 下标递增、bucket 内插入顺序输出。
"""
buckets: list[list[tuple[str, object]]] = [[] for _ in range(capacity)]
for key, value in entries:
index = java_hashmap_spread(java_string_hashcode(key)) & (capacity - 1)
bucket = buckets[index]
for item_index, (existing_key, _) in enumerate(bucket):
if existing_key == key:
bucket[item_index] = (key, value)
break
else:
bucket.append((key, value))
pairs = [
f"{key}={'' if value is None else str(value)}"
for bucket in buckets
for key, value in bucket
]
return "{" + ", ".join(pairs) + "}"
def _dfp_non_empty(value: object) -> str:
text = "" if value is None else str(value)
return text if text else "KWE_N"
def _target_sdk_grdi(value: str, target_sdk_version: int) -> str:
if target_sdk_version < 30 or not value or value.startswith("KWE"):
return value
try:
parts = value.split("|")
return "".join(f"{part}|" for index, part in enumerate(parts) if index != 3)
except Exception:
return value
def persisted_cache_source_values(
profile: DeviceProfile,
*,
target_sdk_version: int = 30,
) -> dict[str, str]:
"""构造 com.kuaishou.dfp.c.c.s(Context) 写入 HashMap 的 10 个字段。"""
hints = profile.runtime_hints
return {
"k6": _dfp_non_empty("0"),
"k8": _dfp_non_empty(profile.build_type),
"k16": _dfp_non_empty(""),
"k19": _dfp_non_empty("sun"),
"k23": _dfp_non_empty(profile.manufacturer),
"k27": _dfp_non_empty(profile.model),
"k29": _dfp_non_empty(
f"Dalvik/2.1.0 (Linux; U; Android {profile.android_release}; "
f"{profile.model} Build/{profile.build_id})"
),
"k40": _dfp_non_empty(""),
"k105": _dfp_non_empty(_target_sdk_grdi(hints.grdi, target_sdk_version)),
"k110": _dfp_non_empty(hints.keeper_seed),
}
def build_persisted_cache_m_from_values(values: Mapping[str, object]) -> str:
entries = [(key, values.get(key, "KWE_N")) for key in PERSISTED_CACHE_KEYS]
return uq0_s_a(java_hashmap_to_string(entries))
def build_persisted_cache_m(
profile: DeviceProfile,
*,
target_sdk_version: int = 30,
) -> str:
values = persisted_cache_source_values(profile, target_sdk_version=target_sdk_version)
return build_persisted_cache_m_from_values(values)
__all__ = [
"PERSISTED_CACHE_KEYS",
"build_persisted_cache_m",
"build_persisted_cache_m_from_values",
"java_hashmap_spread",
"java_hashmap_to_string",
"java_string_hashcode",
"persisted_cache_source_values",
"uq0_s_a",
]