謝謝 幫助很大 都解包完成並且有導出live2d檔了
要去馬賽克稍微麻煩點(live2d-py缺少部分api)
只好手動改圖檔
好久不见了。
我已经获取到场景文本了。谢谢。
我还有一些其他问题。
我正在尝试在 Unity 中播放 Live2D,但出现错误,无法播放。
播放需要哪些文件?
如果可以播放,请告诉我“CubismSdkForUnity”的版本。
顺便说一下,所有文件都是使用“https://drive.google.com/file/d/1rQOQD1lLv6lpISH9jm8H-5HgvFmfRmKD/view?usp=sharing”获取的,
并按“https://drive.google.com/file/d/146q-_W7dEMC_VCLnlRyZ46e_neZ0Hg9p/view?usp=sharing”排序。
如果您有任何解决方案的建议,例如排序方法不正确,我将不胜感激。
请帮忙。
当我在场景播放过程中展开 Live2D Assetbundle 文件时,似乎没有“moc3”文件。
有人知道这个文件在哪里吗?
我正在使用 assetstdioMOD 展开它。
朋友,你是不是回复错人了?我并没有成功播放Live2D。
找不到“moc3”文件可以试试这个版本的assetstdio: Releases · aelurum/AssetStudio · GitHub
我之前另一个游戏找不到moc3文件就是用这个解决的。
import os
import requests
from pathlib import Path
from hashlib import sha256
from AddressablesToolsPy.src.AddressablesTools import parse_binary
from AddressablesToolsPy.src.AddressablesTools.Catalog.SerializedObjectDecoder import SerializedObjectDecoder
from loguru import logger
abHashkey = ‘4ee2a4fb96258c7a9f4b430d8b2715fe’
resUrl = ‘v1.12.0_72e0d31647d4296ee248767b7dc22365’
assetUrl = f’https://cdn.app.siprj.com/{resUrl}/Addressables/WebGL/’
catalogName = “catalog_1.12.0.bin”
savePath = Path(‘resources’)
savePath.mkdir(exist_ok=True)
session = requests.session()
catalogData = session.get(f"{assetUrl}{catalogName}").content
def patcher(s):
if s == “GeePlus.GPUL.AddressablesManager; GeePlus.GPUL.AddressablesManager.ResourceProviders.EncryptedAssetBundleRequestOptions”:
return SerializedObjectDecoder.ABRO_MATCHNAME
else:
return s
cl = parse_binary(catalogData,patcher=patcher)
for nameKey,infos in cl.Resources.items():
if isinstance(nameKey,str) and nameKey.endswith(‘.bundle’):
info = infos[0]
abhash128 = info.Data.Object.Hash
reshash = sha256(f"{nameKey}+{abhash128}+{abHashkey}“.encode()).hexdigest()
saveLoc = savePath / reshash
if not os.path.exists(saveLoc) or info.Data.Object.BundleSize != os.path.getsize(saveLoc):
logger.info(f"downloading {nameKey}”)
res = session.get(f"{assetUrl}{reshash}")
with open(saveLoc, ‘wb’) as f:
f.write(res.content)
之前我用这个方法可以正常检索文件,但今天运行后,所有文件都只有 1kb。
有人知道原因吗?
我已经确认了以下更新:
“res_version”: “v1.12.0_72e0d31647d4296ee248767b7dc22365”,
“catalog_name”: “catalog_1.12.0.bin”,
“ab_hash_key”: “4ee2a4fb96258c7a9f4b430d8b2715fe”,
我还更新了 py 文件。
reshash = sha256(f"{nameKey}+{abHashkey}".encode()).hexdigest()
虽然迟了,但已经完成了。
谢谢您!
你好,抱歉打扰了,现在我使用这个脚本导出的路径信息好像有缺失,有部分文件无法从catalog_detailed_output.txt中找到对应的资源路径信息
#!/usr/bin/env python3
“”"
Unity Addressables Catalog Binary Parser
Parses catalog_*.bin files and exports to JSON format
“”"
import struct
import re
import json
import sys
from pathlib import Path
from collections import defaultdict
def read_string(data: bytes, offset: int) → tuple[str, int]:
“”“Read a length-prefixed string from data.”“”
if offset >= len(data):
return “”, offset
length = data[offset]
if offset + 1 + length > len(data):
return “”, offset
try:
s = data[offset + 1:offset + 1 + length].decode(‘utf-8’, errors=‘replace’)
except:
s = “”
return s, offset + 1 + length
def extract_strings(data: bytes, min_length: int = 4) → list[str]:
“”“Extract printable ASCII strings from binary data.”“”
strings =
current =
for b in data:
if 32 <= b < 127:
current.append(chr(b))
else:
if len(current) >= min_length:
strings.append(‘’.join(current))
current =
if len(current) >= min_length:
strings.append(‘’.join(current))
return strings
def parse_catalog(file_path: str) → dict:
“”“Parse Unity Addressables catalog binary file.”“”
with open(file_path, 'rb') as f:
data = f.read()
result = {
"file_info": {
"path": str(file_path),
"size": len(data),
},
"header": {},
"bundles": [],
"assets": [],
"hashes": [],
"providers": [],
"resource_paths": [],
}
# Parse header
if len(data) < 8:
return result
magic = struct.unpack('<I', data[:4])[0]
version = struct.unpack('<I', data[4:8])[0]
result["header"]["magic"] = hex(magic)
result["header"]["version"] = version
# Parse offset table
offsets = []
pos = 8
for i in range(7):
if pos + 4 <= len(data):
off = struct.unpack('<I', data[pos:pos+4])[0]
offsets.append(off)
pos += 4
result["header"]["offsets"] = [hex(o) for o in offsets]
# Extract bundle information
# Pattern: hash.bundle
bundle_pattern = rb'([0-9a-f]{32})\.bundle'
bundle_matches = re.findall(bundle_pattern, data)
unique_bundles = list(set(b.decode('ascii') for b in bundle_matches))
unique_bundles.sort()
for bundle_hash in unique_bundles:
result["bundles"].append({
"hash": bundle_hash,
"filename": f"{bundle_hash}.bundle",
"url": f"{{url}}/{bundle_hash}.bundle"
})
# Extract 32-char hashes (content hashes)
hash_pattern = rb'(?<![0-9a-f])([0-9a-f]{32})(?![0-9a-f\.])'
hash_matches = re.findall(hash_pattern, data)
unique_hashes = list(set(h.decode('ascii') for h in hash_matches))
# Filter out bundle hashes
bundle_hash_set = set(unique_bundles)
content_hashes = [h for h in unique_hashes if h not in bundle_hash_set]
content_hashes.sort()
result["hashes"] = content_hashes[:1000] # Limit to avoid huge output
# Extract asset paths
asset_patterns = [
rb'(Assets/[A-Za-z0-9_/\.\-]+(?:\.(?:prefab|asset|mat|png|jpg|spriteatlas|controller|anim|fbx|wav|mp3|ogg|ttf|otf|shader|unity|json|xml|txt|bytes)))',
rb'(Textures/[A-Za-z0-9_/\.\-]+)',
rb'(TextureChannels/[A-Za-z0-9_/\.\-]+)',
rb'(Prefabs/[A-Za-z0-9_/\.\-]+)',
rb'(Materials/[A-Za-z0-9_/\.\-]+)',
rb'(Scenes/[A-Za-z0-9_/\.\-]+)',
]
all_assets = set()
for pattern in asset_patterns:
matches = re.findall(pattern, data)
for m in matches:
try:
path = m.decode('utf-8', errors='ignore').strip()
# Clean up path - remove trailing non-alphanumeric chars
path = re.sub(r'[^A-Za-z0-9_/\.\-].*$', '', path)
if path and len(path) > 5:
all_assets.add(path)
except:
pass
result["assets"] = sorted(list(all_assets))
# Extract provider information
provider_pattern = rb'UnityEngine\.ResourceManagement\.ResourceProviders\.([A-Za-z]+Provider)'
provider_matches = re.findall(provider_pattern, data)
result["providers"] = list(set(p.decode('ascii') for p in provider_matches))
# Extract resource paths (Tqxre pattern from the file)
tqxre_pattern = rb'Tqxre[A-Za-z0-9/]+/ydz'
tqxre_matches = re.findall(tqxre_pattern, data)
for m in tqxre_matches[:100]: # Limit
try:
result["resource_paths"].append(m.decode('ascii'))
except:
pass
# Summary
result["summary"] = {
"total_bundles": len(result["bundles"]),
"total_assets": len(result["assets"]),
"total_content_hashes": len(content_hashes),
"total_providers": len(result["providers"]),
}
return result
def parse_catalog_detailed(file_path: str) → dict:
“”“Parse with more detailed entry extraction.”“”
with open(file_path, 'rb') as f:
data = f.read()
result = {
"file_info": {
"path": str(file_path),
"size": len(data),
},
"entries": [],
}
# Find all bundle entries with their associated data
# Pattern: hash.bundle followed by {url}/hash.bundle
entry_pattern = rb'([0-9a-f]{32})\.bundle.{0,10}\{url\}/\1\.bundle'
# Alternative: Find each bundle and extract surrounding context
bundle_pattern = rb'([0-9a-f]{32})\.bundle'
seen_bundles = set()
for match in re.finditer(bundle_pattern, data):
bundle_hash = match.group(1).decode('ascii')
if bundle_hash in seen_bundles:
continue
seen_bundles.add(bundle_hash)
# Extract context around this bundle reference
start = max(0, match.start() - 100)
end = min(len(data), match.end() + 200)
context = data[start:end]
# Look for associated hash (content hash)
# Usually appears before the bundle name
pre_context = data[max(0, match.start() - 50):match.start()]
content_hash_match = re.search(rb'([0-9a-f]{32})', pre_context)
content_hash = content_hash_match.group(1).decode('ascii') if content_hash_match else None
# Look for file size (4-byte integer before content hash)
size_match = re.search(rb'(.{4})' + bundle_hash.encode(), data[match.start()-50:match.start()+40])
entry = {
"bundle_hash": bundle_hash,
"bundle_file": f"{bundle_hash}.bundle",
"bundle_url": f"{{url}}/{bundle_hash}.bundle",
}
if content_hash and content_hash != bundle_hash:
entry["content_hash"] = content_hash
result["entries"].append(entry)
result["total_entries"] = len(result["entries"])
return result
def main():
# 修改部分:处理所有.bin后缀的文件
if len(sys.argv) < 2:
# 如果未指定路径,处理当前目录下所有.bin文件
bin_files = list(Path(“.”).glob(“.bin"))
if not bin_files:
print(“错误:当前目录未找到任何.bin文件”)
sys.exit(1)
else:
# 如果指定了路径/文件,先判断是目录还是文件
input_path = Path(sys.argv[1])
if input_path.is_dir():
# 目录:处理该目录下所有.bin文件
bin_files = list(input_path.glob(".bin”))
if not bin_files:
print(f"错误:目录 {input_path} 中未找到任何.bin文件")
sys.exit(1)
elif input_path.suffix == “.bin” and input_path.exists():
# 文件:仅处理该单个.bin文件
bin_files = [input_path]
else:
print(f"错误:不是有效的.bin文件或目录:{input_path}")
sys.exit(1)
# 遍历所有.bin文件进行处理
for input_file in bin_files:
print(f"\n=== 正在处理: {input_file} ===")
output_file = input_file.with_suffix('.json')
print(f"解析中: {input_file}")
print(f"输出文件: {output_file}")
# Parse catalog
result = parse_catalog(str(input_file))
# Also get detailed entries
detailed = parse_catalog_detailed(str(input_file))
result["entries"] = detailed["entries"]
result["summary"]["total_entries"] = detailed["total_entries"]
# Write JSON
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"{input_file} 解析完成!")
print(f"{input_file} 解析摘要:")
print(f" - 包数量: {result['summary']['total_bundles']}")
print(f" - 资源数量: {result['summary']['total_assets']}")
print(f" - 内容哈希数量: {result['summary']['total_content_hashes']}")
print(f" - 条目数量: {result['summary']['total_entries']}")
print(f"输出文件已保存至: {output_file}")
print(f"\n=== 所有.bin文件处理完成!总计处理文件数: {len(bin_files)} ===")
if name == “main”:
main()
这个脚本并没有输出每个bundle对应的解包路径信息
这个游戏的数据提取真是非常困难啊,哈哈。
