132 lines
3.9 KiB
Python
132 lines
3.9 KiB
Python
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",
|
||
]
|