Форк клиента собирается из исходников, модзагрузчик встроен в игру
Проект начинается с двух вещей: клиент, который мы действительно собрали, и загрузчик, который является его частью, а не чем-то рядом. Форк - 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 для запуска клиента
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package net.rustnotmc.client;
|
||||
|
||||
import net.rustnotmc.loader.Log;
|
||||
|
||||
/**
|
||||
* Temporary diagnostics, added to work out why jumping and flight misbehave.
|
||||
*
|
||||
* Static analysis kept producing confident and wrong answers about this path, so
|
||||
* the state is measured instead. Delete this file and its patch entry once the
|
||||
* movement bugs are closed.
|
||||
*/
|
||||
public final class RsxDebug {
|
||||
|
||||
private static int ticks;
|
||||
|
||||
private RsxDebug() {
|
||||
}
|
||||
|
||||
/** One-off trace for the empty sound registry. */
|
||||
public static void sound(String message) {
|
||||
Log.info("DBG snd " + message);
|
||||
}
|
||||
|
||||
/** Called from EntityPlayerSP.onLivingUpdate, once a second. */
|
||||
public static void movement(Object self) {
|
||||
if (++ticks % 20 != 0) {
|
||||
return;
|
||||
}
|
||||
Log.info("DBG sp onGround=" + read(self, "onGround")
|
||||
+ " inJump=" + read(self, "movementInput")
|
||||
+ " isJumping=" + read(self, "isJumping")
|
||||
+ " riding=" + read(self, "isRiding")
|
||||
+ " srvWorld=" + read(self, "isServerWorld")
|
||||
+ " allowFly=" + read(self, "allowFlying")
|
||||
+ " flying=" + read(self, "isFlying")
|
||||
+ " mY=" + read(self, "motionY")
|
||||
+ " y=" + read(self, "posY"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a field by name.
|
||||
*
|
||||
* Deliberately reflective and deliberately forgiving: a diagnostic that
|
||||
* throws when a field is renamed is worse than one that prints null.
|
||||
*/
|
||||
private static String read(Object target, String field) {
|
||||
if (target == null) {
|
||||
return "?";
|
||||
}
|
||||
try {
|
||||
java.lang.reflect.Field f = findField(target.getClass(), field);
|
||||
if (f == null) {
|
||||
return "?";
|
||||
}
|
||||
f.setAccessible(true);
|
||||
return String.valueOf(f.get(target));
|
||||
} catch (Throwable t) {
|
||||
return "!";
|
||||
}
|
||||
}
|
||||
|
||||
private static java.lang.reflect.Field findField(Class<?> type, String name) {
|
||||
while (type != null) {
|
||||
try {
|
||||
return type.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException e) {
|
||||
type = type.getSuperclass();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user