From 8f5a2be6fd5471586b80e8cde1202c2b63e7512a Mon Sep 17 00:00:00 2001 From: cpw Date: Fri, 1 Mar 2019 22:02:25 -0500 Subject: [PATCH] Fix #5584 by copying the zipped resource to a temporary directory if paulscode is requesting it. This is so icky. Signed-off-by: cpw --- .../minecraftforge/common/util/HexDumper.java | 1 + .../minecraftforge/fml/StackTraceUtils.java | 31 +++++++ .../fml/packs/ModFileResourcePack.java | 82 +++++++++++++------ 3 files changed, 91 insertions(+), 23 deletions(-) create mode 100644 src/main/java/net/minecraftforge/fml/StackTraceUtils.java diff --git a/src/main/java/net/minecraftforge/common/util/HexDumper.java b/src/main/java/net/minecraftforge/common/util/HexDumper.java index 1aeaa1f3c..6a058411c 100644 --- a/src/main/java/net/minecraftforge/common/util/HexDumper.java +++ b/src/main/java/net/minecraftforge/common/util/HexDumper.java @@ -16,6 +16,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + package net.minecraftforge.common.util; import io.netty.buffer.ByteBuf; diff --git a/src/main/java/net/minecraftforge/fml/StackTraceUtils.java b/src/main/java/net/minecraftforge/fml/StackTraceUtils.java new file mode 100644 index 000000000..88392b84d --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/StackTraceUtils.java @@ -0,0 +1,31 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.fml; + +import java.util.Objects; + +public final class StackTraceUtils { + private StackTraceUtils() {} + + public static boolean threadClassNameEquals(final String className) { + final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + return Objects.equals(stackTrace[stackTrace.length-1].getClassName(), className); + } +} diff --git a/src/main/java/net/minecraftforge/fml/packs/ModFileResourcePack.java b/src/main/java/net/minecraftforge/fml/packs/ModFileResourcePack.java index 695c3f283..68d012be9 100644 --- a/src/main/java/net/minecraftforge/fml/packs/ModFileResourcePack.java +++ b/src/main/java/net/minecraftforge/fml/packs/ModFileResourcePack.java @@ -23,18 +23,26 @@ import net.minecraft.resources.AbstractResourcePack; import net.minecraft.resources.ResourcePackInfo; import net.minecraft.resources.ResourcePackType; import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.StackTraceUtils; import net.minecraftforge.fml.loading.moddiscovery.ModFile; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.nio.file.*; +import java.nio.channels.FileChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.Collection; import java.util.Collections; -import java.util.Objects; import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -49,6 +57,25 @@ public class ModFileResourcePack extends AbstractResourcePack private static final Logger LOGGER = LogManager.getLogger(); private final ModFile modFile; private ResourcePackInfo packInfo; + private static final ExecutorService STUPIDPAULSCODEISSTUPIDWORKAROUNDTHREAD = Executors.newSingleThreadExecutor(); + private static final Path tempDir; + + static { + try { + tempDir = Files.createTempDirectory("modpacktmp"); + Runtime.getRuntime().addShutdownHook(new Thread(()-> { + try { + Files.walk(tempDir).forEach(f->{try {Files.deleteIfExists(f);}catch (IOException ioe) {}}); + Files.delete(tempDir); + } catch (IOException ioe) { + LOGGER.fatal("Failed to clean up tempdir {}", tempDir); + } + })); + } catch (IOException e) { + LOGGER.fatal(CORE, "Failed to create temporary directory", e); + throw new RuntimeException(e); + } + } public ModFileResourcePack(final ModFile modFile) { @@ -77,31 +104,40 @@ public class ModFileResourcePack extends AbstractResourcePack if (path.getFileSystem() == FileSystems.getDefault()) { LOGGER.trace(CORE, "Request for resource {} returning FileInputStream for regular file {}", name, path); return new FileInputStream(path.toFile()); - } else if (Objects.equals(Thread.currentThread().getStackTrace()[0].getClassName(), "paulscode.sound.CommandThread")) { - final Path tempFile = Files.createTempFile("modpack", "soundresource"); - Files.copy(Files.newInputStream(path, StandardOpenOption.READ), tempFile); - LOGGER.trace(CORE, "Request for resource {} returning DeletingTemporaryFileInputStream for packed file {} on paulscode thread", name, path); - return new DeletingTemporaryFileInputStream(tempFile); + // If the resource is in a zip file, and paulscode is the requester, we need to return a file input stream, + // but we can't just use path.tofile to do it. Instead, we copy the resource to a temporary file. As all operations + // with an nio channel are interruptible, we do this at arms length on another thread, while paulscode spams + // interrupts on the paulscode main thread, which we politely ignore. + } else if (StackTraceUtils.threadClassNameEquals("paulscode.sound.CommandThread")) { + final Path tempFile = Files.createTempFile(tempDir, "modpack", "soundresource"); + Future fis = STUPIDPAULSCODEISSTUPIDWORKAROUNDTHREAD.submit(()->{ + try (final SeekableByteChannel resourceChannel = Files.newByteChannel(path, StandardOpenOption.READ); + final FileChannel tempFileChannel = FileChannel.open(tempFile, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) { + long size = resourceChannel.size(); + for (long written = 0; written < size; ) { + written += tempFileChannel.transferFrom(resourceChannel, written, size - written); + } + } + LOGGER.trace(CORE, "Request for resource {} returning DeletingTemporaryFileInputStream for packed file {} on paulscode thread", name, path); + return new FileInputStream(tempFile.toFile()); + }); + try { + while (true) { + try { + return fis.get(); + } catch (InterruptedException ie) { + // no op + } + } + } catch (ExecutionException e) { + LOGGER.fatal(CORE, "Encountered fatal exception copying sound resource", e); + throw new RuntimeException(e); + } } else { return Files.newInputStream(path, StandardOpenOption.READ); } } - private final class DeletingTemporaryFileInputStream extends FileInputStream { - private final Path tempfile; - - DeletingTemporaryFileInputStream(final Path tempfile) throws FileNotFoundException { - super(tempfile.toFile()); - this.tempfile = tempfile; - } - - @Override - public void close() throws IOException { - super.close(); - Files.deleteIfExists(tempfile); - } - } - @Override protected boolean resourceExists(String name) {