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 для запуска клиента
57 lines
1.6 KiB
Python
57 lines
1.6 KiB
Python
"""Find every class whose constant pool references a named method.
|
|
|
|
Used to answer "who actually calls this" across a whole jar, where grepping
|
|
source would only show the decompiled view and javap would need one command per
|
|
class.
|
|
|
|
python find_callers.py <classes-dir> <method-name>
|
|
"""
|
|
|
|
import os
|
|
import struct
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from struct_diff import Reader, read_constant_pool, CONSTANT_UTF8, SKIP_TWO_SLOT
|
|
|
|
|
|
def method_names(path):
|
|
with open(path, 'rb') as handle:
|
|
data = handle.read()
|
|
reader = Reader(data)
|
|
if reader.u4() != 0xCAFEBABE:
|
|
return None
|
|
reader.u2()
|
|
reader.u2()
|
|
pool = read_constant_pool(reader)
|
|
names = []
|
|
for index, entry in enumerate(pool):
|
|
if isinstance(entry, str):
|
|
names.append(entry)
|
|
return names
|
|
|
|
|
|
def main():
|
|
root, needle = sys.argv[1], sys.argv[2]
|
|
hits = []
|
|
for base, _dirs, files in os.walk(root):
|
|
for name in files:
|
|
if not name.endswith('.class'):
|
|
continue
|
|
full = os.path.join(base, name)
|
|
key = os.path.relpath(full, root).replace(os.sep, '/')
|
|
try:
|
|
names = method_names(full)
|
|
except Exception:
|
|
continue
|
|
if names is None:
|
|
continue
|
|
if any(n == needle or n.startswith(needle) for n in names):
|
|
hits.append(key)
|
|
print('%d class(es) reference %s' % (len(hits), needle))
|
|
for key in hits:
|
|
print(' %s' % key)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main() |