Files
horizon-airdef/tools/fetch_client_assets.py
T
Ludovic Marie 67f51123e8 Add vanilla font metrics with unifont fallback, languages, styled text and a Brigadier command core
Font: bitmap glyph ascent/oversampling, unihex glyphs rasterized on demand
into a reserved atlas region, bold/italic/underline/strikethrough and
StringSplitter wrapping. Languages layer lang/<code>.json over en_us.
The command module ports Brigadier parsing, suggestions and smart usage
with the vanilla argument types and the 26.3 command tree; game rules
use the 26.3 registry names and saved-data file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:16:22 +02:00

70 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""Fetch the pinned version's indexed client assets that are not in the game
jar: language files and the language list (pack.mcmeta), the unifont glyph
archives and their font definitions, and the built-in Programmer Art and High
Contrast resource packs. Every object's SHA-1 is verified. Files go to the
private client resource directory (or --target) and are never committed."""
import argparse
import concurrent.futures
import hashlib
import json
from pathlib import Path
import urllib.request
ROOT = Path(__file__).resolve().parent.parent
LOCAL = ROOT / '.local'
def fetch(url):
with urllib.request.urlopen(url, timeout=120) as response:
return response.read()
def wanted(name):
return (name == 'pack.mcmeta'
or name.startswith('minecraft/lang/')
or name.startswith('minecraft/font/')
or name.startswith('minecraft/resourcepacks/'))
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--target', type=Path, default=LOCAL / 'horizon-client/26.3',
help='prepared resource directory (default: .local/horizon-client/26.3)')
args = parser.parse_args()
manifest = json.loads((LOCAL / 'minecraft/26.3/manifest.json').read_text())
index_info = manifest['assetIndex']
target = args.target
index_path = target / 'asset-index.json'
if not index_path.exists() or hashlib.sha1(index_path.read_bytes()).hexdigest() != index_info['sha1']:
data = fetch(index_info['url'])
if hashlib.sha1(data).hexdigest() != index_info['sha1']:
raise ValueError('asset index checksum mismatch')
target.mkdir(parents=True, exist_ok=True)
index_path.write_bytes(data)
objects = json.loads(index_path.read_text())['objects']
selected = {name: info for name, info in objects.items() if wanted(name)}
def download(item):
name, info = item
path = target / 'assets' / name
if path.exists() and hashlib.sha1(path.read_bytes()).hexdigest() == info['hash']:
return 0
h = info['hash']
data = fetch(f'https://resources.download.minecraft.net/{h[:2]}/{h}')
if hashlib.sha1(data).hexdigest() != h:
raise ValueError(f'checksum mismatch for {name}')
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(data)
return len(data)
total = 0
with concurrent.futures.ThreadPoolExecutor(16) as pool:
for size in pool.map(download, selected.items()):
total += size
print(f'Client assets ready: {len(selected)} files, {total / 1e6:.1f} MB downloaded')
if __name__ == '__main__':
main()