bdf0ae866a
Проект начинается с двух вещей: клиент, который мы действительно собрали, и загрузчик, который является его частью, а не чем-то рядом. Форк - prepare-game.ps1 пересобирает клиент целиком: ванильный jar, SRG и MCP, SpecialSource, FernFlower, патчи, автофиксеры, javac. Идемпотентен. - Свой код игры лежит в src-overlay, потому что src пересоздаётся при каждой сборке и всё написанное вручную там погибает. - 116 патчей в patches/decompiler-fixes.txt. Каждый сверялся с оригинальным байткодом через javap: компиляция не ловит ошибки декомпиляции. - struct_diff.py сравнивает исходный байткод с нашей сборкой по классам, интерфейсам, суперклассам, методам и полям, и отдельно проверяет, что пропавшие классы действительно никому не нужны. Загрузчик - Собирается и тестируется без игры: build.ps1 не использует classpath вообще, поэтому SelfTest из 128 проверок проходит без запущенного форка. - Клиентский API не содержит ни одного игрового типа, реализацию даёт игра. - Политика: мод меняет то, что клиент показывает, но никогда то, что истинно в мире. Мир, предметы, игроки и сеть принадлежат серверу. - Capability приведены в соответствие с этой политикой: осталось шесть, и за каждым стоит реальная проверка. Прежние WORLD, PLAYER, NETWORK, TEXT, INPUT и COMMAND выдавались, но ничего не проверяли. - Команды модов имеют область CLIENT, SERVER или BOTH. Серверные не выполняются на клиенте никогда. - Свой jar загрузчика лежит в classpath игры и сборки форка. Инструменты - add_patch.py, чтобы каждое исправление переживало передекомпиляцию - find_self_comparisons.py, find_remove_index_bugs.py, find_callers.py - stage_run.py и fetch_missing.py для запуска клиента
70 lines
2.4 KiB
Python
70 lines
2.4 KiB
Python
"""Fetch library files the local meta folder never downloaded.
|
|
|
|
The AstralRinth install has the 1.12.2 java libraries but not the
|
|
natives-windows classifiers, and the client cannot open a window without
|
|
lwjgl.dll. Rather than write into another application's cache, missing files go
|
|
into our own mirror of the same layout, which stage_run.py searches first.
|
|
|
|
python fetch_missing.py <dest-root> <meta-dir>
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.request
|
|
|
|
|
|
def wanted(manifest):
|
|
"""Every artifact and natives-windows path the manifest declares."""
|
|
out = []
|
|
for entry in manifest['libraries']:
|
|
downloads = entry.get('downloads') or {}
|
|
artifact = (downloads.get('artifact') or {}).get('path')
|
|
if artifact:
|
|
out.append(artifact)
|
|
classifiers = downloads.get('classifiers') or {}
|
|
for key, value in classifiers.items():
|
|
if key.startswith('natives-windows') and value and value.get('path'):
|
|
out.append(value['path'])
|
|
return sorted(set(out))
|
|
|
|
|
|
def main():
|
|
dest_root, meta = sys.argv[1], sys.argv[2]
|
|
manifest_path = os.path.join(meta, 'versions', '1.12.2', '1.12.2.json')
|
|
with open(manifest_path, encoding='utf-8') as handle:
|
|
manifest = json.load(handle)
|
|
|
|
meta_lib = os.path.join(meta, 'libraries')
|
|
base = 'https://libraries.minecraft.net/'
|
|
fetched, present, failed = [], 0, []
|
|
|
|
for rel in wanted(manifest):
|
|
if os.path.isfile(os.path.join(meta_lib, rel.replace('/', os.sep))):
|
|
present += 1
|
|
continue
|
|
target = os.path.join(dest_root, rel.replace('/', os.sep))
|
|
url = base + rel
|
|
try:
|
|
os.makedirs(os.path.dirname(target), exist_ok=True)
|
|
with urllib.request.urlopen(url, timeout=60) as response:
|
|
blob = response.read()
|
|
with open(target, 'wb') as handle:
|
|
handle.write(blob)
|
|
fetched.append('%s (%d bytes)' % (os.path.basename(rel), len(blob)))
|
|
except Exception as error: # noqa: BLE001
|
|
failed.append('%s -> %s' % (rel, error))
|
|
|
|
print('already present: %d' % present)
|
|
print('fetched: %d' % len(fetched))
|
|
for line in fetched:
|
|
print(' %s' % line)
|
|
if failed:
|
|
print('failed: %d' % len(failed))
|
|
for line in failed:
|
|
print(' %s' % line)
|
|
return 1 if failed else 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main()) |