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", ]