Fix MC-128953

This commit is contained in:
Runemoro 2018-04-25 14:59:32 -04:00
parent 774084be35
commit 32bac210ff
4 changed files with 104 additions and 45 deletions

View file

@ -1,5 +1,6 @@
package org.dimdev.vanillafix; package org.dimdev.vanillafix;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.*; import net.minecraft.client.gui.*;
import net.minecraft.client.resources.I18n; import net.minecraft.client.resources.I18n;
import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReport;
@ -9,6 +10,8 @@ import net.minecraftforge.fml.relauncher.SideOnly;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dimdev.ddutils.HasteUpload; import org.dimdev.ddutils.HasteUpload;
import org.dimdev.vanillafix.mixins.client.IFixedMinecraft;
import org.dimdev.vanillafix.mixins.client.MixinMinecraft;
import java.io.File; import java.io.File;
import java.net.URI; import java.net.URI;
@ -39,6 +42,7 @@ public class GuiCrashScreen extends GuiScreen {
try { try {
if (button.id == 0) { if (button.id == 0) {
mc.displayGuiScreen(new GuiMainMenu()); mc.displayGuiScreen(new GuiMainMenu());
((IFixedMinecraft) mc).clearCurrentReport();
} else if (button.id == 1) { } else if (button.id == 1) {
if (hasteLink == null) { if (hasteLink == null) {
hasteLink = HasteUpload.uploadToHaste(HASTE_BASE_URL, "txt", report.getCompleteReport()); hasteLink = HasteUpload.uploadToHaste(HASTE_BASE_URL, "txt", report.getCompleteReport());

View file

@ -9,7 +9,6 @@ import net.minecraft.network.play.INetHandlerPlayServer;
import net.minecraft.network.play.client.CPacketConfirmTeleport; import net.minecraft.network.play.client.CPacketConfirmTeleport;
import net.minecraft.network.play.client.CPacketPlayer; import net.minecraft.network.play.client.CPacketPlayer;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.util.text.TextComponentTranslation;
@ -117,9 +116,7 @@ public abstract class MixinNetHandlerPlayServer implements INetHandlerPlayServer
// Instead, we will send another packet both here and in processConfirmTeleport if the position the client // Instead, we will send another packet both here and in processConfirmTeleport if the position the client
// was sent is no longer good (exceeds tolerance): // was sent is no longer good (exceeds tolerance):
LOGGER.info("Player sent move packets before confirming");
if (targetPos.squareDistanceTo(player.posX, player.posY, player.posZ) > 1.0D) { if (targetPos.squareDistanceTo(player.posX, player.posY, player.posZ) > 1.0D) {
LOGGER.info("Distance became too big: " + targetPos + " vs. " + new Vec3d(player.posX, player.posY, player.posY));
setPlayerLocation(player.posX, player.posY, player.posZ, player.rotationYaw, player.rotationPitch); setPlayerLocation(player.posX, player.posY, player.posZ, player.rotationYaw, player.rotationPitch);
} }
@ -199,7 +196,6 @@ public abstract class MixinNetHandlerPlayServer implements INetHandlerPlayServer
// Entity.move already made sure that the player didn't cheat, and reverting the move would be wrong because // Entity.move already made sure that the player didn't cheat, and reverting the move would be wrong because
// the prevX/Y/Z is no longer good in the new dimension. This fixes MC-123364. // the prevX/Y/Z is no longer good in the new dimension. This fixes MC-123364.
if (player.isInvulnerableDimensionChange()) { if (player.isInvulnerableDimensionChange()) {
LOGGER.info("Doing teleport");
// A better name for invulnerableDimensionChange would be "lastBlockCollisionCausedPlayerMove". See // A better name for invulnerableDimensionChange would be "lastBlockCollisionCausedPlayerMove". See
// https://github.com/ModCoderPack/MCPBot-Issues/issues/624. This happens when the move caused a // https://github.com/ModCoderPack/MCPBot-Issues/issues/624. This happens when the move caused a
// collision that teleported the player elsewhere. // collision that teleported the player elsewhere.
@ -283,11 +279,9 @@ public abstract class MixinNetHandlerPlayServer implements INetHandlerPlayServer
if (packet.getTeleportId() == teleportId) { if (packet.getTeleportId() == teleportId) {
if (targetPos.squareDistanceTo(player.posX, player.posY, player.posZ) > 1.0D) { if (targetPos.squareDistanceTo(player.posX, player.posY, player.posZ) > 1.0D) {
LOGGER.info("Position accepted no longer good");
// The client accepted the position change, but it's too late, something moved the player. Sync it again. // The client accepted the position change, but it's too late, something moved the player. Sync it again.
setPlayerLocation(player.posX, player.posY, player.posZ, player.rotationYaw, player.rotationPitch); setPlayerLocation(player.posX, player.posY, player.posZ, player.rotationYaw, player.rotationPitch);
} else { } else {
LOGGER.info("Teleport accepted");
targetPos = null; targetPos = null;
} }
} }

View file

@ -0,0 +1,5 @@
package org.dimdev.vanillafix.mixins.client;
public interface IFixedMinecraft {
public void clearCurrentReport();
}

View file

@ -3,6 +3,8 @@ package org.dimdev.vanillafix.mixins.client;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiMemoryErrorScreen; import net.minecraft.client.gui.GuiMemoryErrorScreen;
import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReport;
import net.minecraft.init.Bootstrap; import net.minecraft.init.Bootstrap;
import net.minecraft.profiler.ISnooperInfo; import net.minecraft.profiler.ISnooperInfo;
@ -14,10 +16,7 @@ import net.minecraftforge.fml.relauncher.SideOnly;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dimdev.vanillafix.GuiCrashScreen; import org.dimdev.vanillafix.GuiCrashScreen;
import org.lwjgl.LWJGLException; import org.lwjgl.LWJGLException;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.File; import java.io.File;
@ -28,18 +27,18 @@ import java.util.Date;
@SuppressWarnings({"unused", "NonConstantFieldWithUpperCaseName", "RedundantThrows"}) // Shadow @SuppressWarnings({"unused", "NonConstantFieldWithUpperCaseName", "RedundantThrows"}) // Shadow
@SideOnly(Side.CLIENT) @SideOnly(Side.CLIENT)
@Mixin(Minecraft.class) @Mixin(Minecraft.class)
public abstract class MixinMinecraft implements IThreadListener, ISnooperInfo { @Implements(@Interface(iface = IFixedMinecraft.class, prefix = "minecraft$"))
public abstract class MixinMinecraft implements IThreadListener, ISnooperInfo, IFixedMinecraft {
@Shadow volatile boolean running; @Shadow volatile boolean running;
@Shadow private boolean hasCrashed; @Shadow private boolean hasCrashed;
@Shadow private CrashReport crashReporter; @Shadow private CrashReport crashReporter;
@Shadow private long debugCrashKeyPressTime;
@Shadow private void init() throws LWJGLException, IOException {} @Shadow private void init() throws LWJGLException, IOException {}
@Shadow private void runGameLoop() throws IOException {} @Shadow private void runGameLoop() throws IOException {}
@Shadow public void freeMemory() {}
@Shadow public void displayGuiScreen(@Nullable GuiScreen guiScreenIn) {} @Shadow public void displayGuiScreen(@Nullable GuiScreen guiScreenIn) {}
@Shadow public CrashReport addGraphicsAndWorldToCrashReport(CrashReport theCrash) { return null; } @Shadow public CrashReport addGraphicsAndWorldToCrashReport(CrashReport theCrash) { return null; }
@ -50,6 +49,20 @@ public abstract class MixinMinecraft implements IThreadListener, ISnooperInfo {
@Shadow public void displayCrashReport(CrashReport crashReportIn) {} @Shadow public void displayCrashReport(CrashReport crashReportIn) {}
@Shadow public abstract void loadWorld(@Nullable WorldClient worldClientIn);
@Shadow public WorldClient world;
@Shadow public static byte[] memoryReserve;
@Shadow public RenderGlobal renderGlobal;
private CrashReport currentReport = null;
/**
* Allows the player to choose to return to the title screen after a crash, or get
* a pasteable link to the crash report on paste.dimdev.org.
*/
@SuppressWarnings("CallToSystemGC") @SuppressWarnings("CallToSystemGC")
@Overwrite @Overwrite
public void run() { public void run() {
@ -58,9 +71,9 @@ public abstract class MixinMinecraft implements IThreadListener, ISnooperInfo {
try { try {
init(); init();
} catch (Throwable throwable) { } catch (Throwable throwable) {
CrashReport crashreport = CrashReport.makeCrashReport(throwable, "Initializing game"); CrashReport report = CrashReport.makeCrashReport(throwable, "Initializing game");
crashreport.makeCategory("Initialization"); report.makeCategory("Initialization");
displayCrashReport(addGraphicsAndWorldToCrashReport(crashreport)); // TODO: GUI for this too displayCrashReport(addGraphicsAndWorldToCrashReport(report)); // TODO: GUI for this too
return; return;
} }
@ -71,8 +84,8 @@ public abstract class MixinMinecraft implements IThreadListener, ISnooperInfo {
runGameLoop(); runGameLoop();
} catch (OutOfMemoryError e) { } catch (OutOfMemoryError e) {
freeMemory(); freeMemory();
// TODO: Use displayCrashScreen?
displayGuiScreen(new GuiMemoryErrorScreen()); displayGuiScreen(new GuiMemoryErrorScreen());
System.gc();
} catch (ReportedException e) { } catch (ReportedException e) {
addGraphicsAndWorldToCrashReport(e.getCrashReport()); addGraphicsAndWorldToCrashReport(e.getCrashReport());
freeMemory(); freeMemory();
@ -95,35 +108,78 @@ public abstract class MixinMinecraft implements IThreadListener, ISnooperInfo {
} }
public void displayCrashScreen(CrashReport report) { public void displayCrashScreen(CrashReport report) {
try { if (currentReport != null) {
File crashReportsDir = new File(Minecraft.getMinecraft().mcDataDir, "crash-reports"); // There was already a crash being reported, the crash screen might have
File crashReportSaveFile = new File(crashReportsDir, "crash-" + new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss").format(new Date()) + "-client.txt"); // crashed. Report it normally instead.
LOGGER.error("An uncaught exception occured while displaying the crash screen, making normal report instead", report.getCrashCause());
// Print the report in bootstrap
Bootstrap.printToSYSOUT(report.getCompleteReport());
// Save the report and print file in bootstrap
File reportFile = null;
if (report.getFile() != null) {
reportFile = report.getFile();
} else if (report.saveToFile(crashReportSaveFile)) {
reportFile = crashReportSaveFile;
}
if (reportFile != null) {
Bootstrap.printToSYSOUT("Recoverable game crash! Crash report saved to: " + reportFile);
} else {
Bootstrap.printToSYSOUT("Recoverable game crash! Crash report could not be saved.");
}
// Display the crash screen
displayGuiScreen(new GuiCrashScreen(reportFile, report));
// Keep running
hasCrashed = false;
} catch (Throwable e) {
LOGGER.error("The crash screen threw an error, reverting to default crash report", e);
displayCrashReport(report); displayCrashReport(report);
return;
} }
currentReport = report;
File crashReportsDir = new File(Minecraft.getMinecraft().mcDataDir, "crash-reports");
File crashReportSaveFile = new File(crashReportsDir, "crash-" + new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss").format(new Date()) + "-client.txt");
// Print the report in bootstrap
Bootstrap.printToSYSOUT(report.getCompleteReport());
// Save the report and print file in bootstrap
File reportFile = null;
if (report.getFile() != null) {
reportFile = report.getFile();
} else if (report.saveToFile(crashReportSaveFile)) {
reportFile = crashReportSaveFile;
}
if (reportFile != null) {
Bootstrap.printToSYSOUT("Recoverable game crash! Crash report saved to: " + reportFile);
} else {
Bootstrap.printToSYSOUT("Recoverable game crash! Crash report could not be saved.");
}
// Reset hasCrashed and debugCrashKeyPressTime
hasCrashed = false;
debugCrashKeyPressTime = -1;
// Display the crash screen
displayGuiScreen(new GuiCrashScreen(reportFile, report));
}
/**
* Disconnect from the current world and free memory, using a memory reserve
* to make sure that an OutOfMemory doesn't happen while doing this.
* <p>
* Bugs Fixed:
* - https://bugs.mojang.com/browse/MC-128953
* - Memory reserve not recreated after out-of memory
*/
@SuppressWarnings("CallToSystemGC")
@Overwrite
public void freeMemory() {
try {
memoryReserve = new byte[0];
renderGlobal.deleteAllDisplayLists();
} catch (Throwable ignored) {}
try {
System.gc();
// Fix: Close the connection to avoid receiving packets from old server
// when playing in another world (MC-128953)
if (world != null) {
world.sendQuittingDisconnectingPacket();
}
loadWorld(null);
} catch (Throwable ignored) {}
System.gc();
// Fix: Re-create memory reserve so that future crashes work well too
memoryReserve = new byte[10485760];
}
@Override
public void clearCurrentReport() {
currentReport = null;
} }
} }