Files
RustNotMC/tools/find-decryptor.py
sasheg dev c472bd4a8c Огнестрел: таблица стволов с вики и границы реверса
- weapons.csv: 23 ствола с уроном, множителем в голову, темпом, магазином,
  перезарядкой, разбросом (конус прицела), отдачей, типом патрона, верстаком и
  ценами исследования. Источники - официальная вики и community-вики, расхождения
  помечены в колонке source. Не хватает цены (2 из 23) и дальности (18 из 23).

Границы реверса ремейка, чтобы не тратить на них время снова:
- классы в game.jar помечены DE AD C0 DE и зашифрованы своим форматом, не
  однобайтовым XOR как ассеты; единственный незашифрованный класс - точка входа
  net/minecraft/client/main/RustexMain, в ней видны два статических дешифратора
  строк (qnbt.super и vyuhke.super), а сами классы зашифрованы;
- REA не берет .class как цель (только архивы), Ghidra 12.1.4 стоит в
  D:\ghidra-tools и через analyzeHeadless импортирует распакованные классы;
- главное: урона, отдачи и спрея огнестрела в клиенте нет вообще, это серверные
  данные. Клиент знает только магазин, тип патрона, зум ADS, прочность и reach.
  REA даст формулы, а не числа, поэтому числа берём с вики.

Инструменты в tools/: inspect-constants.py (разбор constant pool класса) и
find-decryptor.py (поиск маркера и слов дешифратора).

План дополнен этими находками.
2026-10-21 20:17:55 +03:00

56 lines
1.6 KiB
Python

#!/usr/bin/env python3
"""Find who reads the encrypted classes of the remake.
The class files inside game.jar start with DE AD C0 DE and are not readable
after that. Something has to decrypt them at runtime, and that something
lives in the launcher, in one of the jars or in the java directory. This
script looks for the marker and for the words that go with it.
Usage: find-decryptor.py <file or folder> [...]
"""
import sys
from pathlib import Path
MARKER = b"\xde\xad\xc0\xde"
WORDS = [b"deadcode", b"DEADC0DE", b"decrypt", b"Decrypt", b"cipher", b"Cipher",
b"ClassLoader", b"defineClass", b"xload", b"scramble", b"obfus"]
def scan(path):
hits = []
try:
data = path.read_bytes()
except OSError:
return hits
pos = data.find(MARKER)
if pos >= 0:
hits.append(("marker", pos))
for word in WORDS:
pos = data.find(word)
if pos >= 0:
hits.append((word.decode("ascii", "replace"), pos))
return hits
def main(argv):
if len(argv) < 2:
print(__doc__)
return 1
for name in argv[1:]:
path = Path(name)
if path.is_dir():
for child in path.rglob("*"):
if child.is_file() and child.stat().st_size < 200_000_000:
hits = scan(child)
for what, where in hits:
print("%-70s %s at %d" % (str(child)[-70:], what, where))
elif path.is_file():
for what, where in scan(path):
print("%-70s %s at %d" % (str(path)[-70:], what, where))
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))