Skip to content

Commit 774084b

Browse files
committed
Implement NoCrash
1 parent 5095518 commit 774084b

File tree

5 files changed

+245
-1
lines changed

5 files changed

+245
-1
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.dimdev.ddutils;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.JsonObject;
5+
6+
import java.io.IOException;
7+
import java.io.InputStream;
8+
import java.io.InputStreamReader;
9+
import java.io.OutputStream;
10+
import java.net.HttpURLConnection;
11+
import java.net.URL;
12+
import java.nio.charset.StandardCharsets;
13+
14+
public final class HasteUpload {
15+
public static String uploadToHaste(String baseUrl, String extension, String str) throws IOException {
16+
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
17+
18+
URL uploadURL = new URL(baseUrl + "/documents");
19+
HttpURLConnection connection = (HttpURLConnection) uploadURL.openConnection();
20+
connection.setRequestMethod("POST");
21+
connection.setRequestProperty("Content-Type", "text/plain; charset=UTF-8");
22+
connection.setFixedLengthStreamingMode(bytes.length);
23+
connection.setDoInput(true);
24+
connection.setDoOutput(true);
25+
connection.connect();
26+
27+
try {
28+
try (OutputStream os = connection.getOutputStream()) {
29+
os.write(bytes);
30+
}
31+
32+
try (InputStream is = connection.getInputStream()) {
33+
JsonObject json = new Gson().fromJson(new InputStreamReader(is), JsonObject.class);
34+
return baseUrl + "/" + json.get("key").getAsString() + (extension == null || extension.equals("") ? "" : "." + extension);
35+
}
36+
} finally {
37+
connection.disconnect();
38+
}
39+
}
40+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package org.dimdev.vanillafix;
2+
3+
import net.minecraft.client.gui.*;
4+
import net.minecraft.client.resources.I18n;
5+
import net.minecraft.crash.CrashReport;
6+
import net.minecraftforge.fml.relauncher.ReflectionHelper;
7+
import net.minecraftforge.fml.relauncher.Side;
8+
import net.minecraftforge.fml.relauncher.SideOnly;
9+
import org.apache.logging.log4j.LogManager;
10+
import org.apache.logging.log4j.Logger;
11+
import org.dimdev.ddutils.HasteUpload;
12+
13+
import java.io.File;
14+
import java.net.URI;
15+
16+
@SideOnly(Side.CLIENT)
17+
public class GuiCrashScreen extends GuiScreen {
18+
private static final String HASTE_BASE_URL = "https://paste.dimdev.org";
19+
private static final Logger log = LogManager.getLogger();
20+
21+
private File reportFile;
22+
private final CrashReport report;
23+
private String hasteLink = null;
24+
25+
public GuiCrashScreen(File reportFile, CrashReport report) {
26+
this.reportFile = reportFile;
27+
this.report = report;
28+
}
29+
30+
@Override
31+
public void initGui() {
32+
buttonList.clear();
33+
buttonList.add(new GuiOptionButton(0, width / 2 - 155, height / 4 + 120 + 12, I18n.format("gui.toTitle")));
34+
buttonList.add(new GuiOptionButton(1, width / 2 - 155 + 160, height / 4 + 120 + 12, I18n.format("vanillafix.gui.getLink")));
35+
}
36+
37+
@Override
38+
protected void actionPerformed(GuiButton button) {
39+
try {
40+
if (button.id == 0) {
41+
mc.displayGuiScreen(new GuiMainMenu());
42+
} else if (button.id == 1) {
43+
if (hasteLink == null) {
44+
hasteLink = HasteUpload.uploadToHaste(HASTE_BASE_URL, "txt", report.getCompleteReport());
45+
}
46+
ReflectionHelper.findField(GuiScreen.class, "clickedLinkURI", "field_175286_t").set(this, new URI(hasteLink));
47+
mc.displayGuiScreen(new GuiConfirmOpenLink(this, hasteLink, 31102009, false));
48+
}
49+
} catch (Throwable e) {
50+
log.error(e);
51+
}
52+
}
53+
54+
@Override
55+
protected void keyTyped(char typedChar, int keyCode) {
56+
}
57+
58+
@Override
59+
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
60+
drawDefaultBackground();
61+
drawCenteredString(fontRenderer, "Minecraft crashed!", width / 2, height / 4 - 60 + 20, 0xFFFFFF);
62+
drawString(fontRenderer, "Minecraft ran into a problem and crashed.", width / 2 - 160, height / 4 - 60 + 60, 0xA0A0A0);
63+
drawString(fontRenderer, "This is probably caused by a bug in one of your mods or vanilla", width / 2 - 160, height / 4 - 60 + 60 + 18, 0xA0A0A0);
64+
drawString(fontRenderer, "Minecraft. Since you have VanillaFix installed, you can return to", width / 2 - 160, height / 4 - 60 + 60 + 27, 0xA0A0A0);
65+
drawString(fontRenderer, "the main menu and keep playing despite the crash.", width / 2 - 160, height / 4 - 60 + 60 + 36, 0xA0A0A0);
66+
drawString(fontRenderer, "A crash report has been generated, and can be found here (click):", width / 2 - 160, height / 4 - 60 + 60 + 54, 0xA0A0A0);
67+
drawCenteredString(fontRenderer, reportFile != null ? "\u00A7n" + reportFile.getName() : "(report failed to save, see the log instead)", width / 2, height / 4 - 60 + 60 + 65, 0x00FF00);
68+
drawString(fontRenderer, "You are encouraged to send it to the mod's author to fix this issue", width / 2 - 160, height / 4 - 60 + 60 + 78, 0xA0A0A0);
69+
drawString(fontRenderer, "Click the \"Get Link\" button to upload the crash report.", width / 2 - 160, height / 4 - 60 + 60 + 87, 0xA0A0A0);
70+
super.drawScreen(mouseX, mouseY, partialTicks);
71+
}
72+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package org.dimdev.vanillafix.mixins.client;
2+
3+
import net.minecraft.client.Minecraft;
4+
import net.minecraft.client.gui.GuiMemoryErrorScreen;
5+
import net.minecraft.client.gui.GuiScreen;
6+
import net.minecraft.crash.CrashReport;
7+
import net.minecraft.init.Bootstrap;
8+
import net.minecraft.profiler.ISnooperInfo;
9+
import net.minecraft.util.IThreadListener;
10+
import net.minecraft.util.MinecraftError;
11+
import net.minecraft.util.ReportedException;
12+
import net.minecraftforge.fml.relauncher.Side;
13+
import net.minecraftforge.fml.relauncher.SideOnly;
14+
import org.apache.logging.log4j.Logger;
15+
import org.dimdev.vanillafix.GuiCrashScreen;
16+
import org.lwjgl.LWJGLException;
17+
import org.spongepowered.asm.mixin.Final;
18+
import org.spongepowered.asm.mixin.Mixin;
19+
import org.spongepowered.asm.mixin.Overwrite;
20+
import org.spongepowered.asm.mixin.Shadow;
21+
22+
import javax.annotation.Nullable;
23+
import java.io.File;
24+
import java.io.IOException;
25+
import java.text.SimpleDateFormat;
26+
import java.util.Date;
27+
28+
@SuppressWarnings({"unused", "NonConstantFieldWithUpperCaseName", "RedundantThrows"}) // Shadow
29+
@SideOnly(Side.CLIENT)
30+
@Mixin(Minecraft.class)
31+
public abstract class MixinMinecraft implements IThreadListener, ISnooperInfo {
32+
33+
@Shadow volatile boolean running;
34+
@Shadow private boolean hasCrashed;
35+
@Shadow private CrashReport crashReporter;
36+
37+
@Shadow private void init() throws LWJGLException, IOException {}
38+
39+
@Shadow private void runGameLoop() throws IOException {}
40+
41+
@Shadow public void freeMemory() {}
42+
43+
@Shadow public void displayGuiScreen(@Nullable GuiScreen guiScreenIn) {}
44+
45+
@Shadow public CrashReport addGraphicsAndWorldToCrashReport(CrashReport theCrash) { return null; }
46+
47+
@Shadow @Final private static Logger LOGGER;
48+
49+
@Shadow public void shutdownMinecraftApplet() {}
50+
51+
@Shadow public void displayCrashReport(CrashReport crashReportIn) {}
52+
53+
@SuppressWarnings("CallToSystemGC")
54+
@Overwrite
55+
public void run() {
56+
running = true;
57+
58+
try {
59+
init();
60+
} catch (Throwable throwable) {
61+
CrashReport crashreport = CrashReport.makeCrashReport(throwable, "Initializing game");
62+
crashreport.makeCategory("Initialization");
63+
displayCrashReport(addGraphicsAndWorldToCrashReport(crashreport)); // TODO: GUI for this too
64+
return;
65+
}
66+
67+
try {
68+
while (running) {
69+
if (!hasCrashed || crashReporter == null) {
70+
try {
71+
runGameLoop();
72+
} catch (OutOfMemoryError e) {
73+
freeMemory();
74+
displayGuiScreen(new GuiMemoryErrorScreen());
75+
System.gc();
76+
} catch (ReportedException e) {
77+
addGraphicsAndWorldToCrashReport(e.getCrashReport());
78+
freeMemory();
79+
LOGGER.fatal("Reported exception thrown!", e);
80+
displayCrashScreen(e.getCrashReport());
81+
} catch (Throwable e) {
82+
CrashReport report = addGraphicsAndWorldToCrashReport(new CrashReport("Unexpected error", e));
83+
freeMemory();
84+
LOGGER.fatal("Unreported exception thrown!", e);
85+
displayCrashScreen(report);
86+
}
87+
} else {
88+
displayCrashReport(crashReporter);
89+
}
90+
}
91+
} catch (MinecraftError ignored) {
92+
} finally {
93+
shutdownMinecraftApplet();
94+
}
95+
}
96+
97+
public void displayCrashScreen(CrashReport report) {
98+
try {
99+
File crashReportsDir = new File(Minecraft.getMinecraft().mcDataDir, "crash-reports");
100+
File crashReportSaveFile = new File(crashReportsDir, "crash-" + new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss").format(new Date()) + "-client.txt");
101+
102+
// Print the report in bootstrap
103+
Bootstrap.printToSYSOUT(report.getCompleteReport());
104+
105+
// Save the report and print file in bootstrap
106+
File reportFile = null;
107+
if (report.getFile() != null) {
108+
reportFile = report.getFile();
109+
} else if (report.saveToFile(crashReportSaveFile)) {
110+
reportFile = crashReportSaveFile;
111+
}
112+
113+
if (reportFile != null) {
114+
Bootstrap.printToSYSOUT("Recoverable game crash! Crash report saved to: " + reportFile);
115+
} else {
116+
Bootstrap.printToSYSOUT("Recoverable game crash! Crash report could not be saved.");
117+
}
118+
119+
// Display the crash screen
120+
displayGuiScreen(new GuiCrashScreen(reportFile, report));
121+
122+
// Keep running
123+
hasCrashed = false;
124+
} catch (Throwable e) {
125+
LOGGER.error("The crash screen threw an error, reverting to default crash report", e);
126+
displayCrashReport(report);
127+
}
128+
}
129+
}

src/main/resources/assets/dimdoors/lang/en_US.lang

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,5 @@ dimdoors.graphics.riftSize=Rift Size
201201
dimdoors.graphics.riftSize.tooltip=Multiplier affecting how large rifts should be rendered, 1 being the default size.
202202
dimdoors.graphics.riftJitter=Rift Jitter
203203
dimdoors.graphics.riftJitter.tooltip=Multiplier affecting how much rifts should jitter, 1 being the default size.
204+
205+
vanillafix.gui.getLink=Get Link

src/main/resources/org.dimdev.vanillafix.mixins.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"mixins": [
88
"MixinNetHandlerPlayServer"
99
],
10-
"client": [],
10+
"client": [
11+
"client.MixinMinecraft"],
1112
"server": [],
1213
"injectors": {
1314
"defaultRequire": 1

0 commit comments

Comments
 (0)