求助DMM新事前游戏STELLAR IDOL PROJECT的资源路径获取

謝謝 幫助很大 都解包完成並且有導出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()

1 个赞

虽然迟了,但已经完成了。
谢谢您!

你好,抱歉打扰了,现在我使用这个脚本导出的路径信息好像有缺失,有部分文件无法从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对应的解包路径信息

这个游戏的数据提取真是非常困难啊,哈哈。