Registry: Allow ignoring missing mods from the GUI, with confirm+backup

Registry: Add confirm+backup for automated corrupted id table fixup
Require the user to confirm loading from a backup level.dat
This commit is contained in:
Player 2014-04-01 21:56:53 +02:00
parent 3eaa002091
commit 407f6f79af
11 changed files with 228 additions and 147 deletions

View file

@ -10,7 +10,7 @@
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.CompressedStreamTools;
import net.minecraft.nbt.NBTTagCompound;
@@ -107,14 +110,22 @@
@@ -107,20 +110,29 @@
NBTTagCompound nbttagcompound;
NBTTagCompound nbttagcompound1;
@ -34,7 +34,14 @@
catch (Exception exception1)
{
exception1.printStackTrace();
@@ -129,8 +140,14 @@
}
}
+ FMLCommonHandler.instance().confirmBackupLevelDatUse();
file1 = new File(this.field_75770_b, "level.dat_old");
if (file1.exists())
@@ -129,8 +141,14 @@
{
nbttagcompound = CompressedStreamTools.func_74796_a(new FileInputStream(file1));
nbttagcompound1 = nbttagcompound.func_74775_l("Data");
@ -50,7 +57,7 @@
catch (Exception exception)
{
exception.printStackTrace();
@@ -146,6 +163,8 @@
@@ -146,6 +164,8 @@
NBTTagCompound nbttagcompound2 = new NBTTagCompound();
nbttagcompound2.func_74782_a("Data", nbttagcompound1);
@ -59,7 +66,7 @@
try
{
File file1 = new File(this.field_75770_b, "level.dat_new");
@@ -184,6 +203,8 @@
@@ -184,6 +204,8 @@
NBTTagCompound nbttagcompound1 = new NBTTagCompound();
nbttagcompound1.func_74782_a("Data", nbttagcompound);

View file

@ -21,9 +21,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import net.minecraft.client.Minecraft;
@ -57,6 +55,7 @@ import net.minecraft.network.ServerStatusResponse;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.WorldSettings;
import net.minecraft.world.storage.ISaveFormat;
import org.apache.logging.log4j.Level;
import org.lwjgl.input.Mouse;
@ -77,7 +76,6 @@ import cpw.mods.fml.client.registry.RenderingRegistry;
import cpw.mods.fml.common.DummyModContainer;
import cpw.mods.fml.common.DuplicateModsFoundException;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.FMLContainer;
import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.common.IFMLSidedHandler;
import cpw.mods.fml.common.Loader;
@ -88,7 +86,6 @@ import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.ModMetadata;
import cpw.mods.fml.common.ObfuscationReflectionHelper;
import cpw.mods.fml.common.StartupQuery;
import cpw.mods.fml.common.WorldAccessContainer;
import cpw.mods.fml.common.WrongMinecraftVersionException;
import cpw.mods.fml.common.event.FMLMissingMappingsEvent;
import cpw.mods.fml.common.event.FMLMissingMappingsEvent.Action;
@ -441,6 +438,18 @@ public class FMLClientHandler implements IFMLSidedHandler
{
client.displayGuiScreen(new GuiConfirmation(query));
}
if (query.isSynchronous())
{
while (!(client.currentScreen instanceof GuiMainMenu))
{
if (Thread.interrupted()) throw new InterruptedException();
client.loadingScreen.resetProgresAndWorkingMessage("");
Thread.sleep(50);
}
}
}
public boolean handleLoadingScreen(ScaledResolution scaledResolution)
@ -486,6 +495,12 @@ public class FMLClientHandler implements IFMLSidedHandler
// NOOP
}
@Override
public ISaveFormat getSaveFormat()
{
return client.getSaveLoader();
}
@Override
public MinecraftServer getServer()
{
@ -634,9 +649,16 @@ public class FMLClientHandler implements IFMLSidedHandler
showGuiScreen(new GuiOldSaveLoadConfirm(dirName, saveName, selectWorldGUI));
}
else
{
try
{
client.launchIntegratedServer(dirName, saveName, (WorldSettings)null);
}
catch (StartupQuery.AbortedException e)
{
// ignore
}
}
}
public void showInGameModOptions(GuiIngameMenu guiIngameMenu)
@ -820,19 +842,6 @@ public class FMLClientHandler implements IFMLSidedHandler
}
}
public void setDefaultMissingAction(FMLMissingMappingsEvent.Action action)
{
this.defaultMissingAction = action;
}
private Action defaultMissingAction = FMLMissingMappingsEvent.Action.FAIL;
@Override
public Action getDefaultMissingAction()
{
return defaultMissingAction;
}
@Override
public boolean shouldAllowPlayerLogins()
{

View file

@ -1,84 +0,0 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package cpw.mods.fml.client;
import java.util.Iterator;
import java.util.List;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
public class GuiModItemsMissing extends GuiScreen
{
private List<String> missingItems;
public GuiModItemsMissing(List<String> items)
{
this.missingItems = items;
}
@SuppressWarnings("unchecked")
@Override
public void initGui()
{
this.buttonList.add(new GuiButton(1, this.width / 2 - 100, this.height - 38, I18n.format("gui.done")));
}
@Override
protected void actionPerformed(GuiButton p_73875_1_)
{
if (p_73875_1_.enabled && p_73875_1_.id == 1)
{
FMLClientHandler.instance().showGuiScreen(null);
}
}
@Override
public void drawScreen(int p_73863_1_, int p_73863_2_, float p_73863_3_)
{
this.drawDefaultBackground();
int spaceAvailable = this.height - 38 - 20;
int spaceRequired = Math.min(spaceAvailable, 10 + 6 * 10 + missingItems.size());
int offset = 10 + (spaceAvailable - spaceRequired) / 2; // vertically centered
this.drawCenteredString(this.fontRendererObj, "Forge Mod Loader could load this save", this.width / 2, offset, 0xFFFFFF);
offset += 20;
this.drawCenteredString(this.fontRendererObj, String.format("There are %d unassigned blocks and items in this save", missingItems.size()), this.width / 2, offset, 0xFFFFFF);
offset += 10;
this.drawCenteredString(this.fontRendererObj, "You will not be able to load until they are present again", this.width / 2, offset, 0xFFFFFF);
offset += 20;
this.drawCenteredString(this.fontRendererObj, "Missing Blocks/Items:", this.width / 2, offset, 0xFFFFFF);
offset += 10;
Iterator<String> it = missingItems.iterator();
while (it.hasNext())
{
String item = it.next();
this.drawCenteredString(this.fontRendererObj, item, this.width / 2, offset, 0xFFFFFF);
offset += 10;
if (offset >= spaceAvailable) break;
}
if (it.hasNext())
{
this.drawCenteredString(this.fontRendererObj, "...", this.width / 2, offset, 0xFFFFFF);
}
super.drawScreen(p_73863_1_, p_73863_2_, p_73863_3_);
}
}

View file

@ -14,6 +14,7 @@ import org.apache.logging.log4j.Level;
import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.common.ObfuscationReflectionHelper;
import cpw.mods.fml.common.StartupQuery;
import cpw.mods.fml.common.ZipperUtil;
public class GuiOldSaveLoadConfirm extends GuiYesNo {
@ -69,7 +70,15 @@ public class GuiOldSaveLoadConfirm extends GuiYesNo {
return;
}
FMLClientHandler.instance().showGuiScreen(null);
try
{
mc.launchIntegratedServer(dirName, saveName, (WorldSettings)null);
}
catch (StartupQuery.AbortedException e)
{
// ignore
}
}
}
}

View file

@ -15,8 +15,6 @@ package cpw.mods.fml.common;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory;
@ -30,6 +28,7 @@ import net.minecraft.network.INetHandler;
import net.minecraft.network.NetworkManager;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.World;
import net.minecraft.world.storage.ISaveFormat;
import net.minecraft.world.storage.SaveHandler;
import net.minecraft.world.storage.WorldInfo;
@ -285,6 +284,10 @@ public class FMLCommonHandler
Loader.instance().serverStopping();
}
public ISaveFormat getSaveFormat() {
return sidedDelegate.getSaveFormat();
}
public MinecraftServer getMinecraftServerInstance()
{
return sidedDelegate.getServer();
@ -397,6 +400,33 @@ public class FMLCommonHandler
}
}
public void confirmBackupLevelDatUse()
{
// ignore invocations from the world ctor, those are always preceded by another invocation
for (StackTraceElement e : Thread.currentThread().getStackTrace()) {
try
{
if (e.getMethodName().equals("<init>") &&
Class.forName(e.getClassName()) == World.class)
{
return;
}
}
catch (ClassNotFoundException e1)
{
// nothing
}
}
String text = "Forge Mod Loader detected that the backup level.dat is being used.\n\n" +
"This may happen due to a bug or corruption, continuing can damage\n" +
"your world beyond repair or lose data / progress.\n\n" +
"It's recommended to create a world backup before continuing.";
boolean confirmed = StartupQuery.confirm(text);
if (!confirmed) StartupQuery.abort();
}
public boolean shouldServerBeKilledQuietly()
{
if (sidedDelegate == null)
@ -516,11 +546,6 @@ public class FMLCommonHandler
sidedDelegate.fireNetRegistrationEvent(bus(), manager, channelSet, channel, side);
}
public FMLMissingMappingsEvent.Action getDefaultMissingAction()
{
return sidedDelegate.getDefaultMissingAction();
}
public boolean shouldAllowPlayerLogins()
{
return sidedDelegate.shouldAllowPlayerLogins();

View file

@ -219,7 +219,7 @@ public class FMLContainer extends DummyModContainer implements WorldAccessContai
if (!tag.hasKey("BlockedItemIds")) // no blocked id info -> old 1.7 save
{
// old 1.7 save potentially affected by the registry mapping bug
// old early 1.7 save potentially affected by the registry mapping bug
// fix the ids the best we can...
GameData.fixBrokenIds(dataList);
}
@ -248,9 +248,9 @@ public class FMLContainer extends DummyModContainer implements WorldAccessContai
if (failedElements != null && !failedElements.isEmpty())
{
String text = "Forge Mod Loader could not load this save\n\n" +
"There are "+failedElements.size()+" unassigned blocks and items in this save\n"+
"You will not be able to load until they are present again\n\n"+
String text = "Forge Mod Loader could not load this save.\n\n" +
"There are "+failedElements.size()+" unassigned blocks and items in this save.\n" +
"You will not be able to load until they are present again.\n\n" +
"Missing Blocks/Items:\n";
for (String s : failedElements) text += s + "\n";

View file

@ -14,12 +14,11 @@ package cpw.mods.fml.common;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import net.minecraft.network.INetHandler;
import net.minecraft.network.NetworkManager;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.storage.ISaveFormat;
import cpw.mods.fml.common.event.FMLMissingMappingsEvent;
import cpw.mods.fml.common.eventhandler.EventBus;
import cpw.mods.fml.relauncher.Side;
@ -40,6 +39,8 @@ public interface IFMLSidedHandler
void finishServerLoading();
ISaveFormat getSaveFormat();
MinecraftServer getServer();
boolean shouldServerShouldBeKilledQuietly();
@ -60,7 +61,5 @@ public interface IFMLSidedHandler
void fireNetRegistrationEvent(EventBus bus, NetworkManager manager, Set<String> channelSet, String channel, Side side);
FMLMissingMappingsEvent.Action getDefaultMissingAction();
boolean shouldAllowPlayerLogins();
}

View file

@ -3,6 +3,8 @@ package cpw.mods.fml.common;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import net.minecraft.server.MinecraftServer;
public class StartupQuery {
// internal class/functionality, do not use
@ -21,7 +23,9 @@ public class StartupQuery {
public static void abort()
{
FMLCommonHandler.instance().getMinecraftServerInstance().initiateShutdown();
MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance();
if (server != null) server.initiateShutdown();
aborted = true; // to abort loading and go back to the main menu
throw new AbortedException(); // to halt the server
}
@ -78,6 +82,11 @@ public class StartupQuery {
return text;
}
public boolean isSynchronous()
{
return synchronous;
}
public void finish()
{
signal.countDown();
@ -105,15 +114,23 @@ public class StartupQuery {
FMLLog.warning("Invalid value for fml.queryResult: %s, expected confirm or cancel", prop);
}
synchronous = false;
pending = this; // let the other thread start the query
// the client will eventually check pending and execute the query
// command handling in mc is synchronous, execute the server-side query directly
if (FMLCommonHandler.instance().getSide().isServer()) check();
// from the integrated server thread: the client will eventually check pending and execute the query
// from the client thread: synchronous execution
// dedicated server: command handling in mc is synchronous, execute the server-side query directly
if (FMLCommonHandler.instance().getSide().isServer() ||
FMLCommonHandler.instance().getEffectiveSide().isClient())
{
synchronous = true;
check();
}
try
{
signal.await();
reset();
}
catch (InterruptedException e)
{
@ -125,6 +142,7 @@ public class StartupQuery {
private String text;
private AtomicBoolean result;
private CountDownLatch signal = new CountDownLatch(1);
private volatile boolean synchronous;
/**

View file

@ -10,8 +10,13 @@ import java.util.Deque;
import java.util.LinkedList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.logging.log4j.Level;
import com.google.common.io.Files;
import cpw.mods.fml.client.FMLClientHandler;
/**
* Copied from http://stackoverflow.com/questions/1399126/java-util-zip-recreating-directory-structure
* because the code looked very tidy and neat. Thanks, McDowell!
@ -55,4 +60,39 @@ public class ZipperUtil {
res.close();
}
}
public static void backupWorld() throws IOException
{
String dirName = FMLCommonHandler.instance().getMinecraftServerInstance().getFolderName();
String saveName;
if (FMLCommonHandler.instance().getSide().isClient())
{
saveName = FMLCommonHandler.instance().getMinecraftServerInstance().getWorldName();
}
else
{
saveName = dirName;
}
backupWorld(dirName, saveName);
}
public static void backupWorld(String dirName, String saveName) throws IOException
{
File dstFolder = FMLCommonHandler.instance().getSaveFormat().getSaveLoader(dirName, false).getWorldDirectory().getParentFile();
File zip = new File(dstFolder, String.format("%s-%2$tY%2$tm%2$td-%2$tH%2$tM%2$tS.zip", saveName, System.currentTimeMillis()));
try
{
ZipperUtil.zip(new File(dstFolder, dirName), zip);
}
catch (IOException e)
{
FMLLog.log(Level.WARN, e, "World backup failed.");
throw e;
}
FMLLog.info("World backup created at %s.", zip.getCanonicalPath());
}
}

View file

@ -41,11 +41,13 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import com.google.common.io.Files;
import cpw.mods.fml.client.FMLClientHandler;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.StartupQuery;
import cpw.mods.fml.common.ZipperUtil;
import cpw.mods.fml.common.event.FMLMissingMappingsEvent;
import cpw.mods.fml.common.event.FMLMissingMappingsEvent.MissingMapping;
import cpw.mods.fml.common.registry.GameRegistry.Type;
@ -243,6 +245,7 @@ public class GameData {
}
Set<String> itemsToAllocate = new HashSet<String>();
Map<String, Integer> itemsToRelocate = new HashMap<String, Integer>();
// check all ids occupied by items
for (Entry<String, Integer> entry : dataList.entrySet())
@ -259,18 +262,17 @@ public class GameData {
String blockName = '\u0001' + realName;
if (!dataList.containsKey(blockName) ||
!(getMain().iItemRegistry.getRaw(realName) instanceof ItemBlock)) // the slot is occupied by something else and this item is no ItemBlock
(getMain().iItemRegistry.getRaw(realName) != null && // don't assume missing items are no ItemBlock
!(getMain().iItemRegistry.getRaw(realName) instanceof ItemBlock))) // the slot is occupied by something else and this item is no ItemBlock
{
// relocate the item later, after all correct ids have been claimed
// allocate the item later, after all correct ids have been claimed
itemsToAllocate.add(itemName);
}
else if (dataList.get(blockName) != oldId) // occupied, but this is an ItemBlock for a different block than whatever may use its id
{
// relocate to the matching block
int newId = dataList.get(blockName);
entry.setValue(newId);
FMLLog.warning("Fixed ItemBlock %s not using the id of its block, old id %d, new id %d.", realName, oldId, newId);
itemsToRelocate.put(entry.getKey(), newId);
}
}
else // unused id, occupy
@ -280,6 +282,27 @@ public class GameData {
}
}
if (itemsToAllocate.isEmpty() && itemsToRelocate.isEmpty()) return; // nothing to do
String text = "Forge Mod Loader detected that this save is damaged.\n\n" +
"It's likely that an automatic repair can successfully restore\n" +
"most of it, except some items which may get swapped with others.\n\n" +
"A world backup will be created as a zip file in your saves\n"+
"directory automatically.";
boolean confirmed = StartupQuery.confirm(text);
if (!confirmed) StartupQuery.abort();
try
{
ZipperUtil.backupWorld();
}
catch (IOException e)
{
StartupQuery.notify("The world backup couldn't be created.\n\n"+e);
StartupQuery.abort();
}
for (String itemName : itemsToAllocate)
{
int oldId = dataList.get(itemName);
@ -289,6 +312,16 @@ public class GameData {
FMLLog.warning("Fixed Item %s conflicting with another block/item, old id %d, new id %d.", itemName.substring(1), oldId, newId);
}
for (Map.Entry<String, Integer> entry : itemsToRelocate.entrySet())
{
String itemName = entry.getKey();
int newId = entry.getValue();
int oldId = dataList.put(itemName, newId);
FMLLog.warning("Fixed ItemBlock %s not using the id of its block, old id %d, new id %d.", itemName.substring(1), oldId, newId);
}
}
public static List<String> injectWorldIDMap(Map<String, Integer> dataList, boolean injectFrozenData, boolean isLocalWorld)
@ -419,6 +452,7 @@ public class GameData {
List<String> failed = Lists.newArrayList();
List<String> ignored = Lists.newArrayList();
List<String> warned = Lists.newArrayList();
List<String> defaulted = Lists.newArrayList();
for (MissingMapping remap : missedMappings)
{
@ -462,10 +496,9 @@ public class GameData {
// block item missing, warn as requested and block the id
if (action == FMLMissingMappingsEvent.Action.DEFAULT)
{
action = FMLCommonHandler.instance().getDefaultMissingAction();
defaulted.add(remap.name);
}
if (action == FMLMissingMappingsEvent.Action.IGNORE)
else if (action == FMLMissingMappingsEvent.Action.IGNORE)
{
ignored.add(remap.name);
}
@ -477,14 +510,36 @@ public class GameData {
{
warned.add(remap.name);
}
else
{
throw new RuntimeException(String.format("Invalid default missing id action specified: %s", action.name()));
}
gameData.block(remap.id); // prevent the id from being reused later
}
}
if (!defaulted.isEmpty())
{
String text = "Forge Mod Loader detected missing blocks/items.\n\n" +
"There are "+defaulted.size()+" missing blocks and items in this save.\n" +
"If you continue the missing blocks/items will get removed.\n" +
"A world backup will be automatically created in your saves directory.\n\n" +
"Missing Blocks/Items:\n";
for (String s : defaulted) text += s + "\n";
boolean confirmed = StartupQuery.confirm(text);
if (!confirmed) StartupQuery.abort();
try
{
ZipperUtil.backupWorld();
}
catch (IOException e)
{
StartupQuery.notify("The world backup couldn't be created.\n\n"+e);
StartupQuery.abort();
}
warned.addAll(defaulted);
}
if (!failed.isEmpty())
{
FMLLog.severe("This world contains blocks and items that refuse to be remapped. The world will not be loaded");
@ -557,6 +612,7 @@ public class GameData {
iItemRegistry.set(data.iItemRegistry);
availabilityMap.clear();
availabilityMap.or(data.availabilityMap);
blockedIds.clear();
blockedIds.addAll(data.blockedIds);
}

View file

@ -15,8 +15,6 @@ package cpw.mods.fml.server;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import net.minecraft.command.ServerCommand;
import net.minecraft.network.INetHandler;
@ -24,6 +22,7 @@ import net.minecraft.network.NetHandlerPlayServer;
import net.minecraft.network.NetworkManager;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.world.storage.ISaveFormat;
import com.google.common.collect.ImmutableList;
@ -33,7 +32,6 @@ import cpw.mods.fml.common.IFMLSidedHandler;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.StartupQuery;
import cpw.mods.fml.common.WorldAccessContainer;
import cpw.mods.fml.common.event.FMLMissingMappingsEvent;
import cpw.mods.fml.common.eventhandler.EventBus;
import cpw.mods.fml.common.network.FMLNetworkEvent;
@ -103,6 +101,12 @@ public class FMLServerHandler implements IFMLSidedHandler
throw new RuntimeException(message, exception);
}
@Override
public ISaveFormat getSaveFormat()
{
return server.getActiveAnvilConverter();
}
/**
* Get the server instance
*/
@ -159,6 +163,8 @@ public class FMLServerHandler implements IFMLSidedHandler
"\nAlternatively start the server with -Dfml.queryResult=confirm or -Dfml.queryResult=cancel to preselect the answer.";
FMLLog.warning("%s", text);
if (!query.isSynchronous()) return; // no-op until mc does commands in another thread (if ever)
boolean done = false;
while (!done && server.isServerRunning())
@ -250,11 +256,7 @@ public class FMLServerHandler implements IFMLSidedHandler
{
bus.post(new FMLNetworkEvent.CustomPacketRegistrationEvent<NetHandlerPlayServer>(manager, channelSet, channel, side, NetHandlerPlayServer.class));
}
@Override
public FMLMissingMappingsEvent.Action getDefaultMissingAction()
{
return FMLMissingMappingsEvent.Action.valueOf(System.getProperty("fml.missingBlockAction", "FAIL"));
}
@Override
public boolean shouldAllowPlayerLogins()
{