Files
RustNotMC/rsx-client/tools/add_patch.py
T
sasheg dev 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 для запуска клиента
2026-10-18 21:31:27 +03:00

53 lines
1.7 KiB
Python

"""Append a hand fix to the patch file so it survives a re-decompile.
Keeping every fix in one reviewed file, rather than editing generated source,
is what makes prepare-game.ps1 reproducible: the tree is thrown away and rebuilt
on every run, so a fix that is not recorded simply disappears.
python add_patch.py <patch-file> <java-file-path> <before-file> <after-file>
The before/after files hold the exact text to replace and must match the
decompiled output byte for byte.
"""
import io
import os
import sys
def main():
patch_file, java_path, before_file, after_file = sys.argv[1:5]
with io.open(before_file, encoding='utf-8') as handle:
before = handle.read().rstrip('\n')
with io.open(after_file, encoding='utf-8') as handle:
after = handle.read().rstrip('\n')
if not before or not after:
print('ERROR: before/after must not be empty')
return 1
if before == after:
print('ERROR: before and after are identical')
return 1
existing = ''
if os.path.isfile(patch_file):
with io.open(patch_file, encoding='utf-8') as handle:
existing = handle.read()
header = '=== FILE %s\n' % java_path
if header + '--- BEFORE\n' + before + '\n--- AFTER\n' in existing:
print('patch already present')
return 0
with io.open(patch_file, 'a', encoding='utf-8') as handle:
if existing and not existing.endswith('\n'):
handle.write('\n')
handle.write('\n' + header)
handle.write('--- BEFORE\n' + before + '\n')
handle.write('--- AFTER\n' + after + '\n')
print('appended patch for %s' % java_path)
return 0
if __name__ == '__main__':
raise SystemExit(main())