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 (поиск маркера и слов дешифратора). План дополнен этими находками.
56 lines
1.6 KiB
Python
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))
|