Better JSON null validation / Disable mods on startup crash

This commit is contained in:
Anuken
2021-02-19 13:28:31 -05:00
parent b46a5c0bda
commit fa7697fc40
12 changed files with 93 additions and 16 deletions

View File

@@ -113,7 +113,7 @@ committingchanges = Committing Changes
done = Done done = Done
feature.unsupported = Your device does not support this feature. feature.unsupported = Your device does not support this feature.
mods.alphainfo = Keep in mind that mods are in alpha, and[scarlet] may be very buggy[].\nReport any issues you find to the Mindustry GitHub. mods.initfailed = [red]⚠[] The previous Mindustry instance failed to initialize. This was likely caused by misbehaving mods.\n\nTo prevent a crash loop, [red]all mods have been disabled.[]\n\nTo disable this feature, turn it off in [accent]Settings->Game->Disable Mods On Startup Crash[].
mods = Mods mods = Mods
mods.none = [lightgray]No mods found! mods.none = [lightgray]No mods found!
mods.guide = Modding Guide mods.guide = Modding Guide
@@ -800,6 +800,7 @@ setting.logichints.name = Logic Hints
setting.flow.name = Display Resource Flow Rate setting.flow.name = Display Resource Flow Rate
setting.backgroundpause.name = Pause In Background setting.backgroundpause.name = Pause In Background
setting.buildautopause.name = Auto-Pause Building setting.buildautopause.name = Auto-Pause Building
setting.modcrashdisable = Disable Mods On Startup Crash
setting.animatedwater.name = Animated Surfaces setting.animatedwater.name = Animated Surfaces
setting.animatedshields.name = Animated Shields setting.animatedshields.name = Animated Shields
setting.antialias.name = Antialias[lightgray] (requires restart)[] setting.antialias.name = Antialias[lightgray] (requires restart)[]

View File

@@ -34,6 +34,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
@Override @Override
public void setup(){ public void setup(){
checkLaunch();
loadLogger(); loadLogger();
loader = new LoadRenderer(); loader = new LoadRenderer();
@@ -145,7 +146,12 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
finished = true; finished = true;
Events.fire(new ClientLoadEvent()); Events.fire(new ClientLoadEvent());
super.resize(graphics.getWidth(), graphics.getHeight()); super.resize(graphics.getWidth(), graphics.getHeight());
app.post(() -> app.post(() -> app.post(() -> app.post(() -> super.resize(graphics.getWidth(), graphics.getHeight()))))); app.post(() -> app.post(() -> app.post(() -> app.post(() -> {
super.resize(graphics.getWidth(), graphics.getHeight());
//mark initialization as complete
finishLaunch();
}))));
} }
}else{ }else{
asyncCore.begin(); asyncCore.begin();
@@ -168,6 +174,12 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
lastTime = Time.nanos(); lastTime = Time.nanos();
} }
@Override
public void exit(){
//on graceful exit, finish the launch normally.
Vars.finishLaunch();
}
@Override @Override
public void init(){ public void init(){
setup(); setup();
@@ -182,6 +194,11 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
@Override @Override
public void pause(){ public void pause(){
//when the user tabs out on mobile, the exit() event doesn't fire reliably - in that case, just assume they're about to kill the app
//this isn't 100% reliable but it should work for most cases
if(mobile){
Vars.finishLaunch();
}
if(finished){ if(finished){
super.pause(); super.pause();
} }

View File

@@ -32,6 +32,8 @@ import java.util.*;
import static arc.Core.*; import static arc.Core.*;
public class Vars implements Loadable{ public class Vars implements Loadable{
/** Whether the game failed to launch last time. */
public static boolean failedToLaunch = false;
/** Whether to load locales.*/ /** Whether to load locales.*/
public static boolean loadLocales = true; public static boolean loadLocales = true;
/** Whether the logger is loaded. */ /** Whether the logger is loaded. */
@@ -172,6 +174,8 @@ public class Vars implements Loadable{
public static Fi schematicDirectory; public static Fi schematicDirectory;
/** data subdirectory used for bleeding edge build versions */ /** data subdirectory used for bleeding edge build versions */
public static Fi bebuildDirectory; public static Fi bebuildDirectory;
/** file used to store launch ID */
public static Fi launchIDFile;
/** empty map, indicates no current map */ /** empty map, indicates no current map */
public static Map emptyMap; public static Map emptyMap;
/** map file extension */ /** map file extension */
@@ -284,6 +288,27 @@ public class Vars implements Loadable{
maps.load(); maps.load();
} }
/** Checks if a launch failure occurred.
* If this is the case, failedToLaunch is set to true. */
public static void checkLaunch(){
settings.setAppName(appName);
launchIDFile = settings.getDataDirectory().child("launchid.dat");
if(launchIDFile.exists()){
failedToLaunch = true;
}else{
failedToLaunch = false;
launchIDFile.writeString("go away");
}
}
/** Cleans up after a successful launch. */
public static void finishLaunch(){
if(launchIDFile != null){
launchIDFile.delete();
}
}
public static void loadLogger(){ public static void loadLogger(){
if(loadedLogger) return; if(loadedLogger) return;

View File

@@ -59,6 +59,15 @@ public class Control implements ApplicationListener, Loadable{
saves = new Saves(); saves = new Saves();
sound = new SoundControl(); sound = new SoundControl();
//show dialog saying that mod loading was skipped.
Events.on(ClientLoadEvent.class, e -> {
if(Vars.mods.skipModLoading() && Vars.mods.list().any()){
Time.runTask(4f, () -> {
ui.showInfo("@mods.initfailed");
});
}
});
Events.on(StateChangeEvent.class, event -> { Events.on(StateChangeEvent.class, event -> {
if((event.from == State.playing && event.to == State.menu) || (event.from == State.menu && event.to != State.menu)){ if((event.from == State.playing && event.to == State.menu) || (event.from == State.menu && event.to != State.menu)){
Time.runTask(5f, platform::updateRPC); Time.runTask(5f, platform::updateRPC);

View File

@@ -406,6 +406,12 @@ public class ContentParser{
this.currentMod = mod; this.currentMod = mod;
this.currentContent = cont; this.currentContent = cont;
run.run(); run.run();
//check nulls after parsing
if(cont != null){
toBeParsed.remove(cont);
checkNullFields(cont);
}
}); });
} }

View File

@@ -259,6 +259,11 @@ public class Mods implements Loadable{
return requiresReload; return requiresReload;
} }
/** @return whether to skip mod loading due to previous initialization failure. */
public boolean skipModLoading(){
return failedToLaunch && Core.settings.getBool("modcrashdisable", true);
}
/** Loads all mods from the folder, but does not call any methods on them.*/ /** Loads all mods from the folder, but does not call any methods on them.*/
public void load(){ public void load(){
for(Fi file : modDirectory.list()){ for(Fi file : modDirectory.list()){
@@ -683,8 +688,13 @@ public class Mods implements Loadable{
//make sure the main class exists before loading it; if it doesn't just don't put it there //make sure the main class exists before loading it; if it doesn't just don't put it there
//if the mod is explicitly marked as java, try loading it anyway //if the mod is explicitly marked as java, try loading it anyway
if((mainFile.exists() || meta.java) && if(
Core.settings.getBool("mod-" + baseName + "-enabled", true) && Version.isAtLeast(meta.minGameVersion) && (meta.getMinMajor() >= 105 || headless)){ (mainFile.exists() || meta.java) &&
!skipModLoading() &&
Core.settings.getBool("mod-" + baseName + "-enabled", true) &&
Version.isAtLeast(meta.minGameVersion) &&
(meta.getMinMajor() >= 105 || headless)
){
if(ios){ if(ios){
throw new IllegalArgumentException("Java class mods are not supported on iOS."); throw new IllegalArgumentException("Java class mods are not supported on iOS.");
@@ -711,11 +721,16 @@ public class Mods implements Loadable{
} }
} }
//skip mod loading if it failed
if(skipModLoading()){
Core.settings.put("mod-" + baseName + "-enabled", false);
}
if(!headless){ if(!headless){
Log.info("Loaded mod '@' in @ms", meta.name, Time.elapsed()); Log.info("Loaded mod '@' in @ms", meta.name, Time.elapsed());
} }
return new LoadedMod(sourceFile, zip, mainMod, loader, meta);
return new LoadedMod(sourceFile, zip, mainMod, loader, meta);
}catch(Exception e){ }catch(Exception e){
//delete root zip file so it can be closed on windows //delete root zip file so it can be closed on windows
if(rootZip != null) rootZip.delete(); if(rootZip != null) rootZip.delete();

View File

@@ -106,11 +106,6 @@ public class ModsDialog extends BaseDialog{
} }
}); });
shown(() -> Core.app.post(() -> {
Core.settings.getBoolOnce("modsalpha", () -> {
ui.showText("@mods", "@mods.alphainfo");
});
}));
} }
void modError(Throwable error){ void modError(Throwable error){

View File

@@ -327,6 +327,10 @@ public class SettingsMenuDialog extends SettingsDialog{
game.checkPref("buildautopause", false); game.checkPref("buildautopause", false);
} }
if(!ios){
game.checkPref("modcrashdisable", true);
}
if(steam){ if(steam){
game.sliderPref("playerlimit", 16, 2, 32, i -> { game.sliderPref("playerlimit", 16, 2, 32, i -> {
platform.updateLobby(); platform.updateLobby();

View File

@@ -1,10 +1,14 @@
package mindustry.world.blocks.environment; package mindustry.world.blocks.environment;
import arc.util.*;
import mindustry.world.*; import mindustry.world.*;
//do not use in mods! /**
* Do not use in mods. This class provides no new functionality, and is only used for the Mindustry sprite generator.
* Use the standard Floor class instead.
* */
public class ShallowLiquid extends Floor{ public class ShallowLiquid extends Floor{
public Floor liquidBase, floorBase; public @Nullable Floor liquidBase, floorBase;
public float liquidOpacity = 0.35f; public float liquidOpacity = 0.35f;
public ShallowLiquid(String name){ public ShallowLiquid(String name){

View File

@@ -38,7 +38,7 @@ public class Drill extends Block{
public float warmupSpeed = 0.02f; public float warmupSpeed = 0.02f;
//return variables for countOre //return variables for countOre
protected Item returnItem; protected @Nullable Item returnItem;
protected int returnCount; protected int returnCount;
/** Whether to draw the item this drill is mining. */ /** Whether to draw the item this drill is mining. */

View File

@@ -3,6 +3,7 @@ package mindustry.world.blocks.production;
import arc.graphics.g2d.*; import arc.graphics.g2d.*;
import arc.math.*; import arc.math.*;
import arc.struct.*; import arc.struct.*;
import arc.util.*;
import arc.util.io.*; import arc.util.io.*;
import mindustry.content.*; import mindustry.content.*;
import mindustry.entities.*; import mindustry.entities.*;
@@ -13,8 +14,8 @@ import mindustry.world.draw.*;
import mindustry.world.meta.*; import mindustry.world.meta.*;
public class GenericCrafter extends Block{ public class GenericCrafter extends Block{
public ItemStack outputItem; public @Nullable ItemStack outputItem;
public LiquidStack outputLiquid; public @Nullable LiquidStack outputLiquid;
public float craftTime = 80; public float craftTime = 80;
public Effect craftEffect = Fx.none; public Effect craftEffect = Fx.none;

View File

@@ -1,3 +1,3 @@
org.gradle.daemon=true org.gradle.daemon=true
org.gradle.jvmargs=-Xms256m -Xmx1024m org.gradle.jvmargs=-Xms256m -Xmx1024m
archash=e402c9743e107fb53ed6f069ae72a19a59fbfd3b archash=f6caa903fd34c016a902b70c4d41690f5f022431