嗯偶然间尝试了一下发现我之前抓到的一个居然已经是final key了 ,回溯一下发现base mask了,感谢佬
请问下 这游戏的global-metadata.dat如何提取,只提取的CS文件,好奇global-metadata.dat到底藏哪里了,内存中查找头部 AF 1B B1 FA也没找到,可能把头部也改了,还有其他方法可以提取吗
大佬好,目前我已经找到了assetbundle manifest,但是AssetBundleBaseKey这个字节数组始终没能找到,GameAssembly快看完了,依旧没什么头绪,断点尝试了很多位置,都没能找到。请问大佬能给点提示吗?
可以参考这个 IL2CPP Tutorial: Finding loaders for obfuscated global-metadata.dat files | Katy's Code
结构没改 直接内存搜global-metadata.dat找引用
对着上面github的代码看就行了,一个静态常量
更新8:研究了一下0人在意的wem id到音效名的音频对应关系
- 音频从Play_{XXX}到WWise ID通过固定的FNV-1A hash. szb还很贴心的帮你把对应表存下来了,头0x20为key 之后0x10是iv, RJ256解密
- pck文件都是1个bnk + N个wem, WWise版本2023.1, 至少需要支持AK_SOUNDBANK_VERISON>=150 的工具来替换
- bnk里面可以从WEM → CAkSound → CAkActionPlay → CAkEvent → FNV-1a(Play_{XXX}) 一级一级对应下去,折叠后可以得到wem到音效名的对应
脚本如下,需自行clone wwiser仓库
def decrypt_wwise_bytes(data: bytes) -> bytes:
key = data[:0x20]
iv = data[0x20:0x30]
ciphertext = data[0x30:]
cipher = AES.new(key, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(ciphertext), AES.block_size)
def read_wwise_table(data: bytes) -> dict:
stream = io.BytesIO(data)
count = le_u32(stream.read(4))
mapping = {}
for i in range(count):
wem_id = le_u32(stream.read(4))
name_len = le_u32(stream.read(4))
name = stream.read(name_len).decode('utf-8')
mapping[wem_id] = name
return mapping
def fnv1a_32(name: str, *, lower: bool = True) -> int:
utf8_bytes = (name.lower() if lower else name).encode("utf-8")
h = 0x811c9dc5
for b in utf8_bytes:
h = (h * 0x01000193) & 0xFFFF_FFFF
h ^= b
return h
def extract_banks(data: bytes,
out_dir: Path) -> int:
"""Scan *src_path* and dump every bank into *out_dir*.
Returns the number of banks written."""
CHUNK_IDS = {
b'BKHD', b'HIRC', b'DATA', b'FXPR', b'ENVS',
b'STID', b'STMG', b'DIDX', b'PLAT', b'INIT',
}
CHUNK_IDS_BE = {int.from_bytes(c, 'big') for c in CHUNK_IDS}
limit = len(data)
out_dir.mkdir(parents=True, exist_ok=True)
pos = 0
bnk_mapping = []
while True:
idx = data.find(b'BKHD', pos)
if idx == -1:
break # no more banks
# ── quick sanity checks (match QuickBMS logic) ───────────────
if idx + 16 > limit:
break # truncated file
size_field = le_u32(data[idx + 4:idx + 8])
if size_field > 0x10000: # unlikely bank size
pos = idx + 4
continue
bank_id = le_u32(data[idx + 12:idx + 16]) # bank numeric ID
# ── walk successive chunks to find total bank size ───────────
total = 0
first_blk = True
sub = idx
while sub + 8 <= limit:
cid = be_u32(data[sub:sub + 4]) # big-endian four-cc
csz = le_u32(data[sub + 4:sub + 8]) # little-endian size
# next BKHD/STID → start of a new bank
if not first_blk and cid in (0x424B4844, 0x53544944):
break
first_blk = False
if cid not in CHUNK_IDS_BE: # garbage, abort
break
total += csz + 8
sub += csz + 8
if total == 0: # invalid candidate
pos = idx + 4
continue
name = f"{bank_id:08X}.bnk"
(out_dir / name).write_bytes(data[idx:idx + total])
# print(f"Extracted {name} @ 0x{idx:X} size 0x{total:X}")
pos = idx + total # keep scanning
bnk_mapping.append(out_dir / name)
return bnk_mapping
def get_wem_mapping(wwise_event_to_name: dict, bnk_path: Path):
sys.path.append(Path('./wwiser/wwiser'))
from wwiser.wwiser.parser.wparser import Parser
parser = Parser()
parser.parse_bank(bnk_path)
bnk = parser.get_banks()[0]
items = bnk.find(name='listLoadedItem')
nodes = items.get_children()
wem_to_sound = {}
sound_to_action = {}
action_to_event = {}
wem_to_name = {}
for node in nodes:
if node.get_name() == 'CAkSound':
# ulID
sound_id = node.find(type='sid').get_attrs()['value']
# SoundInitialValues.AkBankSourceData.AkMediaInformation.sourceID
wem_id = node.find(name='AkMediaInformation').find(type='tid').get_attrs()['value']
wem_to_sound[wem_id] = sound_id
if node.get_name() == 'CAkActionPlay':
# ulID
action_id = node.find(type='sid').get_attrs()['value']
# ActionInitialValues.idExt
sound_id = node.find(name='ActionInitialValues').find(name='idExt').get_attrs()['value']
sound_to_action[sound_id] = action_id
if node.get_name() == 'CAkEvent':
# ulID
event_id = node.find(type='sid').get_attrs()['value']
# EventInitialValues.actions[0].ulActionID
action_id = node.find1(name='Action').find(type='tid').get_attrs()['value']
action_to_event[action_id] = event_id
for wid in wem_to_sound:
wem_to_name[wid] = wwise_event_to_name[
action_to_event[
sound_to_action[
wem_to_sound[wid]
]
]
]
return OrderedDict(sorted(wem_to_name.items()))
def rebuild_wem_name_mapping(mapping_file: Path, pck_file: Path):
with open(mapping_file, 'rb') as f:
wwise_id_mapping = read_wwise_table(decrypt_wwise_bytes(f.read()))
with open(pck_file, 'rb') as f:
bnk_path = extract_banks(f.read(), Path('./wwise_bnks'))[0]
return get_wem_mapping(wwise_id_mapping, bnk_path)
国服的pck文件是下载到哪个文件夹?