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 для запуска клиента
68 lines
2.2 KiB
Python
68 lines
2.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Regex rules that repair systematic decompiler output.
|
|
|
|
These are the fixes that recur across many files, where a patch entry per
|
|
occurrence would be hundreds of lines of noise. Each rule is a named regex
|
|
with a stated reason, applied to the whole tree.
|
|
|
|
Rules run after the patch file, because they can rewrite lines a patch already
|
|
touched and it is easier to reason about "patches first, then shape rules".
|
|
|
|
python fix_decompiler_rules.py <tree>
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
RULES = [
|
|
(
|
|
'super-cast-generic-render',
|
|
# RenderLiving<T>.doRender takes T. The decompiler inserted a cast to the
|
|
# supertype, which turns the argument into something that is no longer a T.
|
|
# Passing the variable as declared is always valid here: the parameter is
|
|
# the subclass type this renderer was parameterised with.
|
|
re.compile(r'(super\.\w+\()\((?:Entity|EntityLiving|EntityLivingBase|EntityMob)\)(\s+\w+,)'),
|
|
r'\1\2',
|
|
),
|
|
]
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) != 2:
|
|
print('usage: fix_decompiler_rules.py <tree>')
|
|
return 2
|
|
tree = sys.argv[1]
|
|
touched = {}
|
|
files = set()
|
|
|
|
for root, _dirs, names in os.walk(tree):
|
|
for name in names:
|
|
if not name.endswith('.java'):
|
|
continue
|
|
path = os.path.join(root, name)
|
|
with open(path, encoding='utf-8', errors='replace') as handle:
|
|
text = handle.read()
|
|
original = text
|
|
for label, pattern, replacement in RULES:
|
|
new_text = pattern.sub(replacement, text)
|
|
if new_text != text:
|
|
touched[label] = touched.get(label, 0) + \
|
|
len(pattern.findall(text))
|
|
text = new_text
|
|
if text != original:
|
|
files.add(path)
|
|
with open(path, 'w', encoding='utf-8', newline='\n') as handle:
|
|
handle.write(text)
|
|
|
|
if not touched:
|
|
print('decompiler rules: nothing to change')
|
|
return 0
|
|
for label, count in sorted(touched.items()):
|
|
print(' %s: %d' % (label, count))
|
|
print('files rewritten: %d' % len(files))
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main()) |