diff --git a/build.gradle b/build.gradle index e838020014..14ff10a9dd 100644 --- a/build.gradle +++ b/build.gradle @@ -232,6 +232,10 @@ project(":kryonet") { } } +project(":reporter"){ + apply plugin: "java" +} + tasks.eclipse.doLast { delete ".project" } diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 255fb51585..2369de3f50 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -40,6 +40,7 @@ public class Vars{ //discord group URL public static final String discordURL = "https://discord.gg/BKADYds"; public static final String releasesURL = "https://api.github.com/repos/Anuken/Mindustry/releases"; + public static final String crashReportURL = "http://mindustry.us.to/report"; public static final int maxTextLength = 150; public static final int maxNameLength = 40; public static final float itemSize = 5f; diff --git a/core/src/io/anuke/mindustry/core/GameState.java b/core/src/io/anuke/mindustry/core/GameState.java index 76a0ae95b1..a38a46e01f 100644 --- a/core/src/io/anuke/mindustry/core/GameState.java +++ b/core/src/io/anuke/mindustry/core/GameState.java @@ -14,7 +14,6 @@ public class GameState{ public boolean gameOver = false; public GameMode mode = GameMode.waves; public Difficulty difficulty = Difficulty.normal; - public boolean friendlyFire; public WaveSpawner spawner = new WaveSpawner(); public Teams teams = new Teams(); private State state = State.menu; diff --git a/core/src/io/anuke/mindustry/net/Administration.java b/core/src/io/anuke/mindustry/net/Administration.java index 87ca337415..51129ac9f7 100644 --- a/core/src/io/anuke/mindustry/net/Administration.java +++ b/core/src/io/anuke/mindustry/net/Administration.java @@ -63,55 +63,6 @@ public class Administration{ return editLogs; } - /* - public void rollbackWorld(int rollbackTimes) { - for(IntMap.Entry> editLog : editLogs.entries()) { - int coords = editLog.key; - Array logs = editLog.value; - - for(int i = 0; i < rollbackTimes; i++) { - - EditLog log = logs.get(logs.size - 1); - - int x = coords % world.width(); - int y = coords / world.width(); - Block result = log.block; - int rotation = log.rotation; - - //TODO fix this mess, broken with 4.0 - - if(log.action == EditLog.EditAction.PLACE) { - // Build.breakBlock(x, y, false, false); - - Packets.BreakPacket packet = new Packets.BreakPacket(); - packet.x = (short) x; - packet.y = (short) y; - packet.playerid = 0; - - Net.send(packet, Net.SendMode.tcp); - } - else if(log.action == EditLog.EditAction.BREAK) { - //Build.placeBlock(x, y, result, rotation, false, false); - - Packets.PlacePacket packet = new Packets.PlacePacket(); - packet.x = (short) x; - packet.y = (short) y; - packet.rotation = (byte) rotation; - packet.playerid = 0; - //packet.block = result.id; - - Net.send(packet, Net.SendMode.tcp); - } - - logs.removeIndex(logs.size - 1); - if(logs.size == 0) { - editLogs.remove(coords); - break; - } - } - } - }*/ - public boolean validateBreak(String id, String ip){ if(!isAntiGrief() || isAdmin(id, ip)) return true; diff --git a/core/src/io/anuke/mindustry/net/Net.java b/core/src/io/anuke/mindustry/net/Net.java index a2201c2365..720e57eaf5 100644 --- a/core/src/io/anuke/mindustry/net/Net.java +++ b/core/src/io/anuke/mindustry/net/Net.java @@ -309,8 +309,12 @@ public class Net{ } public static void http(String url, String method, Consumer listener, Consumer failure){ + http(url, method, null, listener, failure); + } + + public static void http(String url, String method, String body, Consumer listener, Consumer failure){ HttpRequest req = new HttpRequestBuilder().newRequest() - .method(method).url(url).build(); + .method(method).url(url).content(body).build(); Gdx.net.sendHttpRequest(req, new HttpResponseListener(){ @Override diff --git a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java index cfa1fba347..b0bfcb5f19 100644 --- a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java +++ b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java @@ -1,21 +1,26 @@ package io.anuke.mindustry.desktop; +import com.badlogic.gdx.utils.JsonValue; +import com.badlogic.gdx.utils.JsonValue.ValueType; +import com.badlogic.gdx.utils.JsonWriter.OutputType; +import io.anuke.mindustry.Vars; import io.anuke.mindustry.game.Version; import io.anuke.mindustry.net.Net; import io.anuke.ucore.core.Settings; -import io.anuke.ucore.util.Strings; +import io.anuke.ucore.util.Log; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.io.PrintWriter; +import java.io.StringWriter; public class CrashHandler{ public static void handle(Throwable e){ - //TODO send full error report to server via HTTP e.printStackTrace(); + //don't create crash logs for me (anuke), as it's expected + //also don't create logs for custom builds + if(System.getProperty("user.name").equals("anuke") || Version.build == -1) return; + boolean netActive = false, netServer = false; //attempt to close connections, if applicable @@ -27,45 +32,39 @@ public class CrashHandler{ p.printStackTrace(); } - //don't create crash logs for me (anuke), as it's expected - if(System.getProperty("user.name").equals("anuke")) return; + JsonValue value = new JsonValue(ValueType.object); - String header = "--CRASH REPORT--\n"; + boolean fn = netActive, fs = netServer; + //add all relevant info, ignoring exceptions + ex(() -> value.addChild("build", new JsonValue(Version.build))); + ex(() -> value.addChild("net", new JsonValue(fn))); + ex(() -> value.addChild("server", new JsonValue(fs))); + ex(() -> value.addChild("gamemode", new JsonValue(Vars.state.mode.toString()))); + ex(() -> value.addChild("os", new JsonValue(System.getProperty("os.name")))); + ex(() -> value.addChild("multithreading", new JsonValue(Settings.getBool("multithread")))); + ex(() -> value.addChild("trace", new JsonValue(parseException(e)))); + + Log.info("Sending crash report."); + //post to crash report URL + Net.http(Vars.crashReportURL, "POST", value.toJson(OutputType.json), r -> System.exit(1), t -> System.exit(1)); + + //sleep forever + try{ Thread.sleep(Long.MAX_VALUE); }catch(InterruptedException ignored){} + } + + private static String parseException(Throwable e){ + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + return sw.toString(); + } + + private static void ex(Runnable r){ try{ - header += "--GAME INFO--\n"; - header += "Build: " + Version.build + "\n"; - header += "Net Active: " + netActive + "\n"; - header += "Net Server: " + netServer + "\n"; - header += "OS: " + System.getProperty("os.name") + "\n"; - header += "Multithreading: " + Settings.getBool("multithread") + "\n----\n"; - }catch(Throwable e4){ - header += "--error getting additional info--\n"; - e4.printStackTrace(); - } - - //parse exception - String result = header + Strings.parseFullException(e); - boolean failed = false; - - String filename = ""; - - //try to write it - try{ - filename = "crash-report-" + new SimpleDateFormat("dd-MM-yy h.mm.ss").format(new Date()) + ".txt"; - Files.write(Paths.get(System.getProperty("user.home"), filename), result.getBytes()); - }catch(Throwable i){ - i.printStackTrace(); - failed = true; - } - - try{ - javax.swing.JOptionPane.showMessageDialog(null, "An error has occured: \n" + result + "\n\n" + - (!failed ? "A crash report has been written to " + Paths.get(System.getProperty("user.home"), filename).toFile().getAbsolutePath() + ".\nPlease send this file to the developer!" - : "Failed to generate crash report.\nPlease send an image of this crash log to the developer!")); - }catch(Throwable i){ - i.printStackTrace(); - //what now? + r.run(); + }catch(Throwable t){ + t.printStackTrace(); } } } diff --git a/server/src/io/anuke/mindustry/server/CrashHandler.java b/server/src/io/anuke/mindustry/server/CrashHandler.java new file mode 100644 index 0000000000..706e02343b --- /dev/null +++ b/server/src/io/anuke/mindustry/server/CrashHandler.java @@ -0,0 +1,70 @@ +package io.anuke.mindustry.server; + +import com.badlogic.gdx.utils.JsonValue; +import com.badlogic.gdx.utils.JsonValue.ValueType; +import com.badlogic.gdx.utils.JsonWriter.OutputType; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.game.Version; +import io.anuke.mindustry.net.Net; +import io.anuke.ucore.core.Settings; +import io.anuke.ucore.util.Log; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public class CrashHandler{ + + public static void handle(Throwable e){ + e.printStackTrace(); + + //don't create crash logs for me (anuke), as it's expected + //also don't create logs for custom builds + if(System.getProperty("user.name").equals("anuke") || Version.build == -1) return; + + //if getting the crash report property failed, OR if it set to false... don't send it + try{ + if(!Settings.getBool("crashreport")) return; + }catch(Throwable ignored){ + return; + } + + //attempt to close connections, if applicable + try{ + Net.dispose(); + }catch(Throwable p){ + p.printStackTrace(); + } + + JsonValue value = new JsonValue(ValueType.object); + + //add all relevant info, ignoring exceptions + ex(() -> value.addChild("build", new JsonValue(Version.build))); + ex(() -> value.addChild("mode", new JsonValue(Vars.state.mode.toString()))); + ex(() -> value.addChild("difficulty", new JsonValue(Vars.state.difficulty.toString()))); + ex(() -> value.addChild("players", new JsonValue(Vars.playerGroup.size()))); + ex(() -> value.addChild("os", new JsonValue(System.getProperty("os.name")))); + ex(() -> value.addChild("trace", new JsonValue(parseException(e)))); + + Log.info("&lcSending crash report."); + //post to crash report URL + Net.http(Vars.crashReportURL, "POST", value.toJson(OutputType.json), r -> System.exit(1), t -> System.exit(1)); + + //sleep forever + try{ Thread.sleep(Long.MAX_VALUE); }catch(InterruptedException ignored){} + } + + private static String parseException(Throwable e){ + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + return sw.toString(); + } + + private static void ex(Runnable r){ + try{ + r.run(); + }catch(Throwable t){ + t.printStackTrace(); + } + } +} diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index a579ca4710..a8d0a2ab7f 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -54,6 +54,7 @@ public class ServerControl extends Module{ "admins", "", "sector_x", 0, "sector_y", 1, + "crashreport", false, "port", port ); @@ -338,7 +339,14 @@ public class ServerControl extends Module{ } }); - handler.register("debug", "", "Disables or enables debug ode", arg -> { + handler.register("crashreport", "", "Disables or enables automatic crash reporting", arg -> { + boolean value = arg[0].equalsIgnoreCase("on"); + Settings.putBool("crashreport", value); + Settings.save(); + info("Crash reporting is now {0}.", value ? "on" : "off"); + }); + + handler.register("debug", "", "Disables or enables debug mode", arg -> { boolean value = arg[0].equalsIgnoreCase("on"); debug = value; info("Debug mode is now {0}.", value ? "on" : "off"); diff --git a/server/src/io/anuke/mindustry/server/ServerLauncher.java b/server/src/io/anuke/mindustry/server/ServerLauncher.java index dbc25ea35b..3ed3280250 100644 --- a/server/src/io/anuke/mindustry/server/ServerLauncher.java +++ b/server/src/io/anuke/mindustry/server/ServerLauncher.java @@ -52,20 +52,24 @@ public class ServerLauncher extends HeadlessApplication{ } public static void main(String[] args){ + try{ - Net.setClientProvider(new KryoClient()); - Net.setServerProvider(new KryoServer()); + Net.setClientProvider(new KryoClient()); + Net.setServerProvider(new KryoServer()); - HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration(); - config.preferencesDirectory = OS.getAppDataDirectoryString("Mindustry"); + HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration(); + config.preferencesDirectory = OS.getAppDataDirectoryString("Mindustry"); - new ServerLauncher(new MindustryServer(args), config); + new ServerLauncher(new MindustryServer(args), config); + }catch(Throwable t){ + CrashHandler.handle(t); + } //find and handle uncaught exceptions in libGDX thread for(Thread thread : Thread.getAllStackTraces().keySet()){ if(thread.getName().equals("HeadlessApplication")){ thread.setUncaughtExceptionHandler((t, throwable) -> { - throwable.printStackTrace(); + CrashHandler.handle(throwable); System.exit(-1); }); break; diff --git a/settings.gradle b/settings.gradle index 802a36e0dd..f0ebb1b115 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ -include 'desktop', 'html', 'core', 'android', 'kryonet', 'server', 'ios', 'annotations', 'packer' +include 'desktop', 'html', 'core', 'android', 'kryonet', 'server', 'ios', 'annotations', 'packer', 'reporter' if(System.properties["release"] == null || System.properties["release"].equals("false")){ if (new File(settingsDir, '../uCore').exists()) {