Create Train Crash Fix
Fixes the crash that may occur when a train has infinite positions (even if the train data file doesn't show an infinite)
a very simple mod
Info for nerds (how it was fixed)
First we've got to take a look at the issue: the train does not have an invalid position in the train data file. That means it has to be somewhere in the serialisation.Creation of issue #6795
August 7th, 2024
I added two mixin injections. One at createEntity
(from create) and readNbt
(from minecraft).
@Mixin(Carriage.DimensionalCarriageEntity.class)
public abstract class DimensionalCarriageMixin {
@Shadow public Vec3d positionAnchor;
@Inject(at = @At("HEAD"), method = "createEntity")
private void sendEntityInfo(World level, boolean loadPassengers, CallbackInfo ci) {
CreateTrainFix.LOGGER.info(positionAnchor.toString());
if (!Double.isFinite(positionAnchor.getX()) || !Double.isFinite(positionAnchor.getY()) || !Double.isFinite(positionAnchor.getZ())) {
CreateTrainFix.LOGGER.info("Train failed to be created, because of infinity checks.");
}
}
}
@Mixin(Entity.class)
public abstract class EntityMixin {
@Shadow public abstract double getX();
@Shadow public abstract double getY();
@Shadow public abstract double getZ();
@Shadow public abstract Vec3d getPos();
@Inject(method = "readNbt", at = @At(value = "INVOKE", target = "Ljava/lang/Double;isFinite(D)Z"))
private void checkFiniteDebug(NbtCompound nbt, CallbackInfo ci) {
if (!Double.isFinite(getX()) || !Double.isFinite(getY()) || !Double.isFinite(getZ())) {
CreateTrainFix.LOGGER.info("INFINITE location " + getPos());
}
}
}
This is what's being logged, after which it inevitably crashes. The coordinates are somehow invalid, while still being normal in the createEntity
method.
[18:18:15] [Server thread/INFO]: (225.5, 58.0, -165.8600004762411)
[18:18:15] [Server thread/INFO]: INFINITE location (NaN, NaN, NaN)
After more thorough checking the NBT already comes as NaN, while the create_tracks.dat
file doesn't contain anything like that. create_tracks.dat check see line 123 (heh, funny number)
~2.5 months later IThundxr proposes the idea of adding something similar to the following into the code:
serialisedEntity.remove("Pos");
serialisedEntity.put("Pos", newDoubleList(positionAnchor.x(), positionAnchor.y(), positionAnchor.z()));
This is the final concept and it works!
This is the final version of what has been added
@Inject(at = @At("HEAD"), method = "createEntity")
private void createTrainFix$fixEntity(World level, boolean loadPassengers, CallbackInfo ci) {
try {
// RefUtil is a class with a few methods to access private fields from the superclass.
NbtCompound serialisedEntity = (NbtCompound) RefUtil.getPrivateFieldValue(this$0, "serialisedEntity");
serialisedEntity.remove("Pos");
serialisedEntity.put("Pos", newDoubleList(positionAnchor.x, positionAnchor.y, positionAnchor.z));
// Set the value again
RefUtil.setFieldValue(this$0, "serialisedEntity", serialisedEntity);
} catch (NoSuchFieldException | IllegalAccessException e) {
// If this all didn't work, throw an error
CreateTrainFix.LOGGER.error("(CreateTrainFix) Failed to fix train position");
throw new RuntimeException(e);
}
// Final check
if (!Double.isFinite(positionAnchor.getX()) || !Double.isFinite(positionAnchor.getY()) || !Double.isFinite(positionAnchor.getZ())) {
CreateTrainFix.LOGGER.info("Train failed to be created, because of infinity checks.");
}
}