XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
"""Build a display-only index from the installed game's signed resources.

Uses the read-only galaxy inventory / pool-layout research outputs. Texture pixels
stay in the game installation; the output contains hashes, offsets and geometry.
"""
import hashlib, importlib.util, json, pathlib, re, struct, sys, zlib

saved = sys.argv
sys.argv = ['inventory', 'artifacts/nebula-0416/unused.json']
spec = importlib.util.spec_from_file_location('inventory', 'research/galaxy_asset_inventory.py')
assets = importlib.util.module_from_spec(spec)
spec.loader.exec_module(assets)
sys.argv = saved
out = pathlib.Path('artifacts/nebula-0416')
sources = json.loads(pathlib.Path('artifacts/navigation-0410-qa/game-galaxy-assets.json').read_text())['resources']
sources += json.loads((out/'special-assets.json').read_text())['resources']
references = {}
for row in sources:
    names = [m[1] for s in row['strings'] if (m := re.fullmatch(r'(.*_low)_volumetexture', s, re.I))]
    if not names:
        continue
    path = pathlib.Path(row['path'])
    resource = assets.decode(path)
    for match in re.finditer(rb'\x78[\x01\x9c\xda]', resource[:1600]):
        try:
            data = zlib.decompress(resource[match.start():])
            break
        except zlib.error:
            pass
    else:
        raise ValueError('Missing compressed texture: '+names[0])
    if len(data) != 361512:
        raise ValueError('Unexpected volume texture layout: '+names[0])
    first = struct.unpack_from('<I', data, 0x94)[0]
    if first not in (10240, 350208):
        raise ValueError('Unexpected texture buffer order')
    volume = 1064+(10240 if first == 10240 else 0)
    lookup = 1064+(350208 if first == 350208 else 0)
    value_range = struct.unpack_from('<4f', data, lookup)
    if not all(-10000 < v < 10000 for v in value_range):
        raise ValueError('Invalid transfer function: '+names[0]+' '+str(value_range))
    references[names[0].lower()] = dict(name=names[0], resourceHash=path.name,
        sourceSha256=hashlib.sha256(path.read_bytes()).hexdigest(), deflateOffset=match.start(),
        inflatedBytes=len(data), volumeOffset=volume, rangeOffset=lookup, size=64, rowPitch=256, slicePitch=4096)

instances = json.loads((out/'all-instances.json').read_text())
specific = {r['name']: r for r in json.loads((out/'actual-instances.json').read_text())}
nebulae = []
for entry in instances:
    direct = specific.get(entry['name'])
    family = 'Generic_Dark_nebula_' if 145065 <= entry['id'] <= 145096 else 'Generic_PlanataryNebula_' if 145097 <= entry['id'] <= 145195 else 'Generic_Bright_Nebula_'
    texture = direct['low'] if direct else family+f'{entry["id"]%5+1:02d}_Low'
    if texture.lower() not in references:
        raise ValueError('Missing texture: '+texture)
    nebulae.append(dict(name=entry['name'], position=dict(zip(('x','y','z'), entry['pos'])),
        halfSize=entry['radius'], rotation=direct['rotation'] if direct else [0,0,0],
        template=texture.lower(), dark=145065 <= entry['id'] <= 145096, exactTemplate=direct is not None))

index = dict(gameSha256=json.loads(pathlib.Path('XFEEDToolBox.Core/Rendering/local-model-reference.json').read_text())['gameSha256'],
    catalogSha256=hashlib.sha256((assets.ROOT/'0d'/'d30f11c57448cff8ec3dd0c5c80d9868482ccadb').read_bytes()).hexdigest(),
    templates=list(references.values()), nebulae=nebulae)
pathlib.Path('XFEEDToolBox.Core/Rendering/local-nebula-reference.json').write_text(json.dumps(index,indent=2),encoding='utf-8')
print(f'{len(references)} original volume textures; {len(nebulae)} original authored nebula positions and extents')