mirror of
https://github.com/SK8-ENGINE/skate-3-rust-engine.git
synced 2026-10-27 19:54:40 +00:00
108 lines
4.9 KiB
Python
108 lines
4.9 KiB
Python
"""Incrementally prepare original HUD assets from the user's extracted game.
|
|
|
|
No executable/game launch. Output contains copyrighted assets and stays private.
|
|
The vendored preview.18 UI extractor supplies the original decoding pipeline.
|
|
"""
|
|
from pathlib import Path
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import sys
|
|
import struct
|
|
|
|
from vendor.skate3_ui.project import extract_project
|
|
from vendor.skate3_ui.scene_graph import AssetCache, SceneFlattener
|
|
from vendor.skate3_ui.actions import Actions
|
|
from asset_pipeline.vlt import hash64
|
|
|
|
|
|
def font_mapping(collections: Path, cache: AssetCache) -> dict:
|
|
"""FontManager82808AE8 table; APT-name lookup82809208, load82809308.
|
|
|
|
Resolve the authored FileName, never infer family aliases from metrics.
|
|
"""
|
|
data = json.loads(collections.read_text(encoding='utf-8'))
|
|
result = {}
|
|
for row in data['collections']:
|
|
if row['class'] != 'Hash_FECFBCAF356518C4':
|
|
continue
|
|
def field(name):
|
|
return row['fields'].get(name, row['fields'].get(f'Hash_{hash64(name):016X}'))
|
|
apt_name = row['fields']['Hash_340832CCFD9FDEB4']['data']
|
|
filename = field('FileName')['data']
|
|
matches = [r for r in cache.manifest['bundles']
|
|
if 'font' in r and Path(r['name']).name == filename]
|
|
if not matches:
|
|
continue # The debug font is not a packaged front-end bitmap bank.
|
|
if len(matches) != 1 or apt_name in result:
|
|
raise ValueError(f'Ambiguous native font record: {apt_name}')
|
|
scale = {name: struct.unpack('>f',bytes.fromhex(field(name)['data']))[0]
|
|
for name in ('ScaleX','ScaleY','OffsetX','OffsetY')}
|
|
target = matches[0]['font']['family']
|
|
provenance = {'class': row['class'], 'key': row['key'],
|
|
'source_sha256': row['sha256'], 'file_name':filename,
|
|
'native_layout':scale}
|
|
cache.add_font_alias(apt_name,target,provenance)
|
|
result[apt_name] = provenance
|
|
return result
|
|
|
|
|
|
def prepare(game: Path, output: Path, collections: Path):
|
|
extract_project(game, output, prefixes=("data/fe/source/screens/hud2/",
|
|
"data/fe/source/controls/"), update=True)
|
|
cache = AssetCache(output)
|
|
mappings = font_mapping(collections,cache)
|
|
name = "data/fe/source/screens/hud2/trickdisplay2"
|
|
bundle = cache.load_bundle(name)
|
|
apt_path = output / "raw" / (name + ".apt")
|
|
const_path = apt_path.with_suffix('.const')
|
|
actions = Actions(apt_path.read_bytes(), const_path.read_bytes())
|
|
blocks = {}
|
|
for c in bundle['characters'].values():
|
|
for f in c.get('frames', []):
|
|
for control in f['controls']:
|
|
if control['type_name'] in ('do_action', 'do_init_action'):
|
|
offset = control.get('actions_offset', 0)
|
|
if offset:
|
|
blocks[str(offset)] = actions.stream(offset)
|
|
shapes = {}
|
|
fonts = {}
|
|
for c in bundle['characters'].values():
|
|
if c['type_name'] == 'shape':
|
|
scene = SceneFlattener(cache, lambda *_: {}).flatten(name, c['id'])
|
|
if scene['unresolved']:
|
|
raise ValueError(f"Unresolved original HUD shape: {scene['unresolved']}")
|
|
shapes[str(c['id'])] = scene['primitives']
|
|
elif c['type_name'] == 'font':
|
|
family = c['font']['name']
|
|
asset = cache.font_asset(family)
|
|
fonts[family] = asset
|
|
result = {
|
|
'format': 'skate3-scoring-hud', 'version': 1,
|
|
'source': {'bundle': name,
|
|
'apt_sha256': hashlib.sha256(apt_path.read_bytes()).hexdigest(),
|
|
'const_sha256': hashlib.sha256(const_path.read_bytes()).hexdigest(),
|
|
'collections_sha256': hashlib.sha256(collections.read_bytes()).hexdigest()},
|
|
'characters': list(bundle['characters'].values()),
|
|
'shapes': shapes, 'fonts': fonts, 'actions': blocks,
|
|
'font_mappings':mappings,
|
|
'unresolved_fonts': [family for family, asset in fonts.items() if asset is None],
|
|
}
|
|
target = output / 'runtime' / 'trickdisplay.json'
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
target.write_text(json.dumps(result, separators=(',', ':')) + '\n', encoding='utf-8')
|
|
print(f"Prepared {len(shapes)} original HUD shapes and {len(blocks)} action blocks: {target}")
|
|
if result['unresolved_fonts']:
|
|
print('Unresolved authored font families: ' + ', '.join(result['unresolved_fonts']))
|
|
return result
|
|
|
|
|
|
if __name__ == '__main__':
|
|
p = argparse.ArgumentParser(description=__doc__)
|
|
p.add_argument('--game', type=Path, required=True)
|
|
p.add_argument('--output', type=Path, required=True)
|
|
p.add_argument('--collections', type=Path, required=True,
|
|
help='Owned private/stock/skater-collections.json')
|
|
args = p.parse_args()
|
|
prepare(args.game.resolve(), args.output.resolve(), args.collections.resolve())
|