This commit is contained in:
2026-02-12 17:55:25 -05:00
parent 0127c1d0c7
commit 9e98c54057
7 changed files with 512 additions and 42 deletions

View File

@@ -4,6 +4,7 @@ import java.nio.file.Paths;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
@@ -11,6 +12,7 @@ import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
/**
* Patches zombie.vehicles.BaseVehicle for Landtrain chain support:
@@ -20,10 +22,21 @@ import org.objectweb.asm.tree.MethodNode;
public final class BaseVehicleConstraintPatch {
private static final String TARGET_NAME = "addPointConstraint";
private static final String CONSTRAINT_CHANGED_NAME = "constraintChanged";
private static final String AUTHORIZATION_CHANGED_NAME = "authorizationChanged";
private static final String AUTHORIZATION_CLIENT_COLLIDE_NAME = "authorizationClientCollide";
private static final String AUTHORIZATION_SERVER_COLLIDE_NAME = "authorizationServerCollide";
private static final String AUTHORIZATION_SERVER_ON_SEAT_NAME = "authorizationServerOnSeat";
private static final String IS_ENTER_BLOCKED_NAME = "isEnterBlocked";
private static final String IS_ENTER_BLOCKED2_NAME = "isEnterBlocked2";
private static final String CLINIT_NAME = "<clinit>";
private static final String VOID_NOARG_DESC = "()V";
private static final String AUTHORIZATION_CHANGED_DESC =
"(Lzombie/characters/IsoGameCharacter;)V";
private static final String AUTHORIZATION_CLIENT_COLLIDE_DESC =
"(Lzombie/characters/IsoPlayer;)V";
private static final String AUTHORIZATION_SERVER_COLLIDE_DESC = "(SZ)V";
private static final String AUTHORIZATION_SERVER_ON_SEAT_DESC =
"(Lzombie/characters/IsoPlayer;Z)V";
private static final String ENTER_BLOCKED_DESC = "(Lzombie/characters/IsoGameCharacter;I)Z";
private static final String EXIT_BLOCKED_DESC = "(Lzombie/characters/IsoGameCharacter;I)Z";
private static final String EXIT_BLOCKED2_DESC = "(I)Z";
@@ -31,6 +44,11 @@ public final class BaseVehicleConstraintPatch {
private static final String BREAK_DESC_OBJECT_BOOL = "(ZLjava/lang/Boolean;)V";
private static final String BREAK_DESC_PRIMITIVE_BOOL = "(ZZ)V";
private static final String BASE_VEHICLE_OWNER = "zombie/vehicles/BaseVehicle";
private static final String BULLET_OWNER = "zombie/core/physics/Bullet";
private static final String BULLET_ADD_ROPE = "addRopeConstraint";
private static final String BULLET_ADD_POINT = "addPointConstraint";
private static final String BULLET_ADD_ROPE_DESC = "(IIFFFFFFF)I";
private static final String BULLET_ADD_POINT_DESC = "(IIFFFFFF)I";
private static final String GET_DRIVER_DESC = "()Lzombie/characters/IsoGameCharacter;";
private static final String HELPER_OWNER = "zombie/vehicles/LandtrainConstraintAuthHelper";
private static final String HELPER_METHOD = "resolveConstraintDriver";
@@ -41,6 +59,27 @@ public final class BaseVehicleConstraintPatch {
"(Lzombie/vehicles/BaseVehicle;Lzombie/characters/IsoGameCharacter;I)Z";
private static final String HELPER_ENTER_BLOCKED2 = "isEnterBlocked2Landtrain";
private static final String HELPER_ENTER_BLOCKED2_DESC = "(Lzombie/vehicles/BaseVehicle;I)Z";
private static final String HELPER_AUTHORIZATION_CHANGED =
"authorizationChangedLandtrain";
private static final String HELPER_AUTHORIZATION_CHANGED_DESC =
"(Lzombie/vehicles/BaseVehicle;Lzombie/characters/IsoGameCharacter;)V";
private static final String HELPER_AUTHORIZATION_CLIENT_COLLIDE =
"authorizationClientCollideLandtrain";
private static final String HELPER_AUTHORIZATION_CLIENT_COLLIDE_DESC =
"(Lzombie/vehicles/BaseVehicle;Lzombie/characters/IsoPlayer;)V";
private static final String HELPER_AUTHORIZATION_SERVER_COLLIDE =
"authorizationServerCollideLandtrain";
private static final String HELPER_AUTHORIZATION_SERVER_COLLIDE_DESC =
"(Lzombie/vehicles/BaseVehicle;SZ)V";
private static final String HELPER_AUTHORIZATION_SERVER_ON_SEAT =
"authorizationServerOnSeatLandtrain";
private static final String HELPER_AUTHORIZATION_SERVER_ON_SEAT_DESC =
"(Lzombie/vehicles/BaseVehicle;Lzombie/characters/IsoPlayer;Z)V";
private static final class AddPointPatchStats {
int removedBreakCalls;
int forcedRigidCalls;
}
private BaseVehicleConstraintPatch() {
}
@@ -59,18 +98,49 @@ public final class BaseVehicleConstraintPatch {
new ClassReader(original).accept(classNode, 0);
int removedCalls = 0;
int forcedRigidCalls = 0;
int inspectedAddPointMethods = 0;
int patchedConstraintDriverCalls = 0;
int patchedEnterBlockedCalls = 0;
int patchedEnterBlocked2Calls = 0;
int patchedAuthorizationChangedMethods = 0;
int patchedAuthorizationClientCollideMethods = 0;
int patchedAuthorizationServerCollideMethods = 0;
int patchedAuthorizationServerOnSeatMethods = 0;
for (MethodNode method : classNode.methods) {
if (TARGET_NAME.equals(method.name) && isTargetAddPointConstraint(method.desc)) {
inspectedAddPointMethods++;
removedCalls += patchAddPointConstraint(method);
AddPointPatchStats stats = patchAddPointConstraint(method);
removedCalls += stats.removedBreakCalls;
forcedRigidCalls += stats.forcedRigidCalls;
} else if (CONSTRAINT_CHANGED_NAME.equals(method.name)
&& VOID_NOARG_DESC.equals(method.desc)) {
patchedConstraintDriverCalls += patchConstraintChangedDriverCalls(method);
} else if (AUTHORIZATION_CHANGED_NAME.equals(method.name)
&& AUTHORIZATION_CHANGED_DESC.equals(method.desc)) {
patchedAuthorizationChangedMethods += patchMethodDelegateToHelper(
method,
HELPER_AUTHORIZATION_CHANGED,
HELPER_AUTHORIZATION_CHANGED_DESC);
} else if (AUTHORIZATION_CLIENT_COLLIDE_NAME.equals(method.name)
&& AUTHORIZATION_CLIENT_COLLIDE_DESC.equals(method.desc)) {
patchedAuthorizationClientCollideMethods += patchMethodDelegateToHelper(
method,
HELPER_AUTHORIZATION_CLIENT_COLLIDE,
HELPER_AUTHORIZATION_CLIENT_COLLIDE_DESC);
} else if (AUTHORIZATION_SERVER_COLLIDE_NAME.equals(method.name)
&& AUTHORIZATION_SERVER_COLLIDE_DESC.equals(method.desc)) {
patchedAuthorizationServerCollideMethods += patchMethodDelegateToHelper(
method,
HELPER_AUTHORIZATION_SERVER_COLLIDE,
HELPER_AUTHORIZATION_SERVER_COLLIDE_DESC);
} else if (AUTHORIZATION_SERVER_ON_SEAT_NAME.equals(method.name)
&& AUTHORIZATION_SERVER_ON_SEAT_DESC.equals(method.desc)) {
patchedAuthorizationServerOnSeatMethods += patchMethodDelegateToHelper(
method,
HELPER_AUTHORIZATION_SERVER_ON_SEAT,
HELPER_AUTHORIZATION_SERVER_ON_SEAT_DESC);
} else if (IS_ENTER_BLOCKED_NAME.equals(method.name)
&& ENTER_BLOCKED_DESC.equals(method.desc)) {
patchedEnterBlockedCalls += patchEnterBlockedCall(
@@ -98,11 +168,36 @@ public final class BaseVehicleConstraintPatch {
+ inspectedAddPointMethods
+ ")");
}
if (forcedRigidCalls < 1) {
throw new IllegalStateException(
"Expected to force at least 1 rope->point constraint call, patched "
+ forcedRigidCalls);
}
if (patchedConstraintDriverCalls < 1) {
throw new IllegalStateException(
"Expected to patch at least 1 constraintChanged getDriver call, patched "
+ patchedConstraintDriverCalls);
}
if (patchedAuthorizationChangedMethods < 1) {
throw new IllegalStateException(
"Expected to patch authorizationChanged, patched "
+ patchedAuthorizationChangedMethods);
}
if (patchedAuthorizationClientCollideMethods < 1) {
throw new IllegalStateException(
"Expected to patch authorizationClientCollide, patched "
+ patchedAuthorizationClientCollideMethods);
}
if (patchedAuthorizationServerCollideMethods < 1) {
throw new IllegalStateException(
"Expected to patch authorizationServerCollide, patched "
+ patchedAuthorizationServerCollideMethods);
}
if (patchedAuthorizationServerOnSeatMethods < 1) {
throw new IllegalStateException(
"Expected to patch authorizationServerOnSeat, patched "
+ patchedAuthorizationServerOnSeatMethods);
}
if (patchedEnterBlockedCalls < 1) {
throw new IllegalStateException(
"Expected to patch isEnterBlocked call, patched " + patchedEnterBlockedCalls);
@@ -123,8 +218,18 @@ public final class BaseVehicleConstraintPatch {
System.out.println(
"Patched BaseVehicle.class; removed breakConstraint calls: "
+ removedCalls
+ ", forced rigid constraints: "
+ forcedRigidCalls
+ ", constraint driver hooks: "
+ patchedConstraintDriverCalls
+ ", auth hooks (changed/client/server/seat): "
+ patchedAuthorizationChangedMethods
+ "/"
+ patchedAuthorizationClientCollideMethods
+ "/"
+ patchedAuthorizationServerCollideMethods
+ "/"
+ patchedAuthorizationServerOnSeatMethods
+ ", enter-block hooks: "
+ patchedEnterBlockedCalls
+ "/"
@@ -139,8 +244,8 @@ public final class BaseVehicleConstraintPatch {
.equals(methodDesc);
}
private static int patchAddPointConstraint(MethodNode method) {
int patched = 0;
private static AddPointPatchStats patchAddPointConstraint(MethodNode method) {
AddPointPatchStats stats = new AddPointPatchStats();
InsnList insns = method.instructions;
for (AbstractInsnNode node = insns.getFirst(); node != null; ) {
@@ -160,12 +265,27 @@ public final class BaseVehicleConstraintPatch {
replacement.add(new InsnNode(Opcodes.POP));
insns.insert(node, replacement);
insns.remove(node);
patched++;
stats.removedBreakCalls++;
} else if (node instanceof MethodInsnNode call
&& BULLET_OWNER.equals(call.owner)
&& BULLET_ADD_ROPE.equals(call.name)
&& BULLET_ADD_ROPE_DESC.equals(call.desc)) {
// Drop the rope-length float and call Bullet.addPointConstraint(...) instead.
insns.insertBefore(call, new InsnNode(Opcodes.POP));
MethodInsnNode replacement =
new MethodInsnNode(
Opcodes.INVOKESTATIC,
BULLET_OWNER,
BULLET_ADD_POINT,
BULLET_ADD_POINT_DESC,
false);
insns.set(call, replacement);
stats.forcedRigidCalls++;
}
node = next;
}
return patched;
return stats;
}
private static int patchConstraintChangedDriverCalls(MethodNode method) {
@@ -198,6 +318,45 @@ public final class BaseVehicleConstraintPatch {
return patched;
}
private static int patchMethodDelegateToHelper(
MethodNode method, String helperMethod, String helperDesc) {
InsnList insns = new InsnList();
int localIndex = 0;
int maxStack = 0;
if ((method.access & Opcodes.ACC_STATIC) == 0) {
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
localIndex = 1;
maxStack = 1;
}
Type[] argumentTypes = Type.getArgumentTypes(method.desc);
for (Type argumentType : argumentTypes) {
insns.add(new VarInsnNode(argumentType.getOpcode(Opcodes.ILOAD), localIndex));
localIndex += argumentType.getSize();
maxStack += argumentType.getSize();
}
insns.add(new MethodInsnNode(Opcodes.INVOKESTATIC, HELPER_OWNER, helperMethod, helperDesc, false));
Type returnType = Type.getReturnType(method.desc);
if (returnType.getSort() == Type.VOID) {
insns.add(new InsnNode(Opcodes.RETURN));
} else {
insns.add(new InsnNode(returnType.getOpcode(Opcodes.IRETURN)));
}
method.instructions.clear();
method.instructions.add(insns);
method.tryCatchBlocks.clear();
if (method.localVariables != null) {
method.localVariables.clear();
}
method.maxLocals = Math.max(method.maxLocals, localIndex);
method.maxStack = Math.max(method.maxStack, maxStack);
return 1;
}
private static int patchEnterBlockedCall(
MethodNode method,
String targetCallName,

View File

@@ -1,8 +1,12 @@
package zombie.vehicles;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import zombie.characters.IsoGameCharacter;
import zombie.characters.IsoPlayer;
/**
* Resolves the effective driver for constraint auth in chained towing.
@@ -62,4 +66,133 @@ public final class LandtrainConstraintAuthHelper {
private static boolean isVehicleBeingTowedInLandtrain(BaseVehicle vehicle) {
return vehicle != null && vehicle.getVehicleTowedBy() != null;
}
public static void authorizationChangedLandtrain(BaseVehicle vehicle, IsoGameCharacter character) {
if (vehicle == null) {
return;
}
if (character != null) {
applyAuthorizationAcrossChain(
vehicle, BaseVehicle.Authorization.Local, character.getOnlineID(), false);
} else {
applyAuthorizationAcrossChain(vehicle, BaseVehicle.Authorization.Server, -1, false);
}
}
public static void authorizationClientCollideLandtrain(BaseVehicle vehicle, IsoPlayer driver) {
if (vehicle == null || driver == null || vehicle.getDriver() != null) {
return;
}
applyAuthorizationAcrossChain(
vehicle, BaseVehicle.Authorization.LocalCollide, driver.getOnlineID(), true);
}
public static void authorizationServerCollideLandtrain(
BaseVehicle vehicle, short playerID, boolean isCollide) {
if (vehicle == null) {
return;
}
if (vehicle.isNetPlayerAuthorization(BaseVehicle.Authorization.Local)) {
return;
}
if (isCollide) {
applyAuthorizationAcrossChain(
vehicle, BaseVehicle.Authorization.LocalCollide, playerID, false);
return;
}
BaseVehicle.Authorization auth =
playerID == -1 ? BaseVehicle.Authorization.Server : BaseVehicle.Authorization.Local;
applyAuthorizationAcrossChain(vehicle, auth, playerID, false);
}
public static void authorizationServerOnSeatLandtrain(
BaseVehicle vehicle, IsoPlayer player, boolean enter) {
if (vehicle == null || player == null) {
return;
}
BaseVehicle vehicleA = vehicle.getVehicleTowing();
BaseVehicle vehicleB = vehicle.getVehicleTowedBy();
if (vehicle.isNetPlayerId((short) -1) && enter) {
if (vehicleA != null && vehicleA.getDriver() == null) {
vehicle.addPointConstraint(
null, vehicleA, vehicle.getTowAttachmentSelf(), vehicleA.getTowAttachmentSelf());
} else if (vehicleB != null && vehicleB.getDriver() == null) {
vehicle.addPointConstraint(
null, vehicleB, vehicle.getTowAttachmentSelf(), vehicleB.getTowAttachmentSelf());
} else {
applyAuthorizationAcrossChain(
vehicle, BaseVehicle.Authorization.Local, player.getOnlineID(), false);
}
} else if (vehicle.isNetPlayerId(player.getOnlineID()) && !enter) {
if (vehicleA != null && vehicleA.getDriver() != null) {
vehicleA.addPointConstraint(
null, vehicle, vehicleA.getTowAttachmentSelf(), vehicle.getTowAttachmentSelf());
} else if (vehicleB != null && vehicleB.getDriver() != null) {
vehicleB.addPointConstraint(
null, vehicle, vehicleB.getTowAttachmentSelf(), vehicle.getTowAttachmentSelf());
} else {
applyAuthorizationAcrossChain(vehicle, BaseVehicle.Authorization.Server, -1, false);
}
}
}
private static List<BaseVehicle> collectConnectedChain(BaseVehicle start) {
ArrayList<BaseVehicle> chain = new ArrayList<>();
if (start == null) {
return chain;
}
ArrayDeque<BaseVehicle> pending = new ArrayDeque<>();
Set<Integer> visitedIds = new HashSet<>();
pending.add(start);
while (!pending.isEmpty()) {
BaseVehicle cursor = pending.removeFirst();
if (cursor == null) {
continue;
}
int id = cursor.getId();
if (!visitedIds.add(id)) {
continue;
}
chain.add(cursor);
BaseVehicle front = cursor.getVehicleTowedBy();
BaseVehicle rear = cursor.getVehicleTowing();
if (front != null && front != cursor) {
pending.add(front);
}
if (rear != null && rear != cursor) {
pending.add(rear);
}
}
return chain;
}
private static void applyAuthorizationAcrossChain(
BaseVehicle start,
BaseVehicle.Authorization authorization,
int playerId,
boolean refreshSimulation) {
long now = System.currentTimeMillis();
for (BaseVehicle vehicle : collectConnectedChain(start)) {
if (vehicle == null) {
continue;
}
vehicle.setNetPlayerAuthorization(authorization, playerId);
if (refreshSimulation) {
vehicle.authSimulationTime = now;
if (vehicle.interpolation != null) {
vehicle.interpolation.clear();
}
}
}
}
}