diff --git a/FCLCore/build.gradle b/FCLCore/build.gradle index cbe30da2..b7bc9c0b 100644 --- a/FCLCore/build.gradle +++ b/FCLCore/build.gradle @@ -27,6 +27,7 @@ android { } dependencies { + implementation 'com.github.marschall:zipfilesystem-standalone:1.0.1' implementation 'org.nanohttpd:nanohttpd:2.3.1' implementation 'com.github.steveice10:opennbt:1.4' implementation 'org.apache.commons:commons-lang3:3.12.0' diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/constant/RequestCodes.java b/FCLCore/src/main/java/com/tungsten/fclcore/constant/RequestCodes.java new file mode 100644 index 00000000..e620dcd5 --- /dev/null +++ b/FCLCore/src/main/java/com/tungsten/fclcore/constant/RequestCodes.java @@ -0,0 +1,4 @@ +package com.tungsten.fclcore.constant; + +public class RequestCodes { +} diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/download/forge/ForgeInstallTask.java b/FCLCore/src/main/java/com/tungsten/fclcore/download/forge/ForgeInstallTask.java index 69e7466f..08b02b6f 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/download/forge/ForgeInstallTask.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/download/forge/ForgeInstallTask.java @@ -13,6 +13,7 @@ import com.tungsten.fclcore.game.Version; import com.tungsten.fclcore.task.FileDownloadTask; import com.tungsten.fclcore.task.Task; import com.tungsten.fclcore.util.gson.JsonUtils; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.io.FileUtils; import com.tungsten.fclcore.util.versioning.VersionNumber; @@ -25,7 +26,6 @@ import java.util.Collections; import java.util.Map; import java.util.Optional; -// Todo : fix public final class ForgeInstallTask extends Task { private final DefaultDependencyManager dependencyManager; diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/download/game/GameVerificationFixTask.java b/FCLCore/src/main/java/com/tungsten/fclcore/download/game/GameVerificationFixTask.java index a1834361..fba81253 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/download/game/GameVerificationFixTask.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/download/game/GameVerificationFixTask.java @@ -4,6 +4,7 @@ import com.tungsten.fclcore.download.DefaultDependencyManager; import com.tungsten.fclcore.download.LibraryAnalyzer; import com.tungsten.fclcore.game.Version; import com.tungsten.fclcore.task.Task; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.versioning.VersionNumber; import java.io.File; @@ -18,7 +19,6 @@ import java.util.List; /** * Remove class digital verification file in game jar */ -// Todo : fix public final class GameVerificationFixTask extends Task { private final DefaultDependencyManager dependencyManager; private final String gameVersion; diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/download/optifine/OptiFineInstallTask.java b/FCLCore/src/main/java/com/tungsten/fclcore/download/optifine/OptiFineInstallTask.java index 144658fa..0d319465 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/download/optifine/OptiFineInstallTask.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/download/optifine/OptiFineInstallTask.java @@ -15,6 +15,7 @@ import com.tungsten.fclcore.game.LibraryDownloadInfo; import com.tungsten.fclcore.game.Version; import com.tungsten.fclcore.task.FileDownloadTask; import com.tungsten.fclcore.task.Task; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.io.FileUtils; import com.tungsten.fclcore.util.platform.CommandBuilder; import com.tungsten.fclcore.util.versioning.VersionNumber; diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/game/GameVersion.java b/FCLCore/src/main/java/com/tungsten/fclcore/game/GameVersion.java index 05756dc9..e3def51e 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/game/GameVersion.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/game/GameVersion.java @@ -5,6 +5,7 @@ import static com.tungsten.fclcore.util.Logging.LOG; import com.google.gson.JsonParseException; import com.tungsten.fclcore.util.gson.JsonUtils; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.io.FileUtils; import org.jenkinsci.constant_pool_scanner.ConstantPool; @@ -24,7 +25,6 @@ import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.StreamSupport; -// Todo : fix public final class GameVersion { private GameVersion() { } diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/game/World.java b/FCLCore/src/main/java/com/tungsten/fclcore/game/World.java index 0f014da7..119a9c7e 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/game/World.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/game/World.java @@ -1,6 +1,15 @@ package com.tungsten.fclcore.game; +import com.github.steveice10.opennbt.NBTIO; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.LongTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.tungsten.fclcore.util.Logging; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.io.FileUtils; +import com.tungsten.fclcore.util.io.Unzipper; +import com.tungsten.fclcore.util.io.Zipper; import java.io.IOException; import java.io.InputStream; @@ -13,7 +22,6 @@ import java.util.stream.Stream; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; -// Todo : fix public class World { private final Path file; diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/Datapack.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/Datapack.java index d7415457..9e92f3a5 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/Datapack.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/Datapack.java @@ -3,6 +3,7 @@ package com.tungsten.fclcore.mod; import com.google.gson.JsonParseException; import com.tungsten.fclcore.util.Logging; import com.tungsten.fclcore.util.gson.JsonUtils; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.io.FileUtils; import java.io.IOException; diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/FabricModMetadata.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/FabricModMetadata.java index 3abc9f34..af8f0889 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/FabricModMetadata.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/FabricModMetadata.java @@ -3,6 +3,7 @@ package com.tungsten.fclcore.mod; import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; import com.tungsten.fclcore.util.gson.JsonUtils; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.io.FileUtils; import java.io.IOException; @@ -15,7 +16,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -// Todo : fix public final class FabricModMetadata { private final String id; private final String name; diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/ForgeNewModMetadata.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/ForgeNewModMetadata.java index 17535e97..47a3ee76 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/ForgeNewModMetadata.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/ForgeNewModMetadata.java @@ -4,6 +4,7 @@ import static com.tungsten.fclcore.util.Logging.LOG; import com.google.gson.JsonParseException; import com.moandjiezana.toml.Toml; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.io.FileUtils; import java.io.IOException; @@ -16,7 +17,6 @@ import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.logging.Level; -// Todo : fix public final class ForgeNewModMetadata { private final String modLoader; diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/ForgeOldModMetadata.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/ForgeOldModMetadata.java index cce8271d..c152234d 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/ForgeOldModMetadata.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/ForgeOldModMetadata.java @@ -5,6 +5,7 @@ import com.google.gson.annotations.SerializedName; import com.google.gson.reflect.TypeToken; import com.tungsten.fclcore.util.StringUtils; import com.tungsten.fclcore.util.gson.JsonUtils; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.io.FileUtils; import java.io.IOException; @@ -13,7 +14,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -// Todo : fix public final class ForgeOldModMetadata { @SerializedName("modid") diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/MinecraftInstanceTask.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/MinecraftInstanceTask.java index bfdc2e32..37cb781d 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/MinecraftInstanceTask.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/MinecraftInstanceTask.java @@ -5,6 +5,7 @@ import static com.tungsten.fclcore.util.Hex.encodeHex; import com.tungsten.fclcore.task.Task; import com.tungsten.fclcore.util.gson.JsonUtils; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.io.FileUtils; import java.io.File; @@ -16,7 +17,6 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -// Todo : fix public final class MinecraftInstanceTask extends Task> { private final File zipFile; diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/ModManager.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/ModManager.java index cecc8dad..e74d75a8 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/ModManager.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/ModManager.java @@ -2,6 +2,7 @@ package com.tungsten.fclcore.mod; import com.tungsten.fclcore.game.GameRepository; import com.tungsten.fclcore.util.StringUtils; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.io.FileUtils; import com.tungsten.fclcore.util.versioning.VersionNumber; @@ -12,7 +13,6 @@ import java.util.HashMap; import java.util.Objects; import java.util.TreeSet; -// Todo : fix public final class ModManager { private final GameRepository repository; private final String id; diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/ModpackInstallTask.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/ModpackInstallTask.java index 60c23c20..f2b9e785 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/ModpackInstallTask.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/ModpackInstallTask.java @@ -5,6 +5,7 @@ import static com.tungsten.fclcore.util.Hex.encodeHex; import com.tungsten.fclcore.task.Task; import com.tungsten.fclcore.util.io.FileUtils; +import com.tungsten.fclcore.util.io.Unzipper; import java.io.File; import java.io.IOException; @@ -13,7 +14,6 @@ import java.nio.file.Files; import java.util.*; import java.util.function.Predicate; -// Todo : fix public class ModpackInstallTask extends Task { private final File modpackFile; diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/PackMcMeta.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/PackMcMeta.java index 93dd48ab..04a515df 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/PackMcMeta.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/PackMcMeta.java @@ -5,6 +5,7 @@ import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; import com.tungsten.fclcore.util.gson.JsonUtils; import com.tungsten.fclcore.util.gson.Validation; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.io.FileUtils; import java.io.IOException; @@ -16,7 +17,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -// Todo : fix public class PackMcMeta implements Validation { @SerializedName("pack") diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/curse/CurseModpackProvider.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/curse/CurseModpackProvider.java index b95ba997..6a7e4fad 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/curse/CurseModpackProvider.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/curse/CurseModpackProvider.java @@ -8,6 +8,7 @@ import com.tungsten.fclcore.mod.ModpackProvider; import com.tungsten.fclcore.mod.ModpackUpdateTask; import com.tungsten.fclcore.task.Task; import com.tungsten.fclcore.util.gson.JsonUtils; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.io.IOUtils; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; @@ -18,7 +19,6 @@ import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Path; -// Todo : fix public final class CurseModpackProvider implements ModpackProvider { public static final CurseModpackProvider INSTANCE = new CurseModpackProvider(); diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/mcbbs/McbbsModpackExportTask.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/mcbbs/McbbsModpackExportTask.java index 2a7be7f0..e2771955 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/mcbbs/McbbsModpackExportTask.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/mcbbs/McbbsModpackExportTask.java @@ -15,10 +15,13 @@ import com.tungsten.fclcore.mod.ModAdviser; import com.tungsten.fclcore.mod.Modpack; import com.tungsten.fclcore.mod.ModpackExportInfo; import com.tungsten.fclcore.mod.curse.CurseManifest; +import com.tungsten.fclcore.mod.curse.CurseManifestMinecraft; import com.tungsten.fclcore.mod.curse.CurseManifestModLoader; import com.tungsten.fclcore.task.Task; import com.tungsten.fclcore.util.Logging; +import com.tungsten.fclcore.util.StringUtils; import com.tungsten.fclcore.util.gson.JsonUtils; +import com.tungsten.fclcore.util.io.Zipper; import java.io.File; import java.io.IOException; @@ -28,7 +31,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -// Todo : fix public class McbbsModpackExportTask extends Task { private final DefaultGameRepository repository; private final String version; diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/mcbbs/McbbsModpackLocalInstallTask.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/mcbbs/McbbsModpackLocalInstallTask.java index cde95b9a..f920a2ca 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/mcbbs/McbbsModpackLocalInstallTask.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/mcbbs/McbbsModpackLocalInstallTask.java @@ -99,7 +99,6 @@ public class McbbsModpackLocalInstallTask extends Task { dependencies.add(repository.saveAsync(version.addPatch(patch))); } else { // This mcbbs modpack was installed by other launchers. - // TODO: maintain libraries. } dependencies.add(new McbbsModpackCompletionTask(dependencyManager, name, instanceTask.getResult())); diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/modrinth/ModrinthModpackProvider.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/modrinth/ModrinthModpackProvider.java index a8949c8d..c6534994 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/modrinth/ModrinthModpackProvider.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/modrinth/ModrinthModpackProvider.java @@ -8,6 +8,7 @@ import com.tungsten.fclcore.mod.ModpackProvider; import com.tungsten.fclcore.mod.ModpackUpdateTask; import com.tungsten.fclcore.task.Task; import com.tungsten.fclcore.util.gson.JsonUtils; +import com.tungsten.fclcore.util.io.CompressingUtils; import org.apache.commons.compress.archivers.zip.ZipFile; @@ -16,7 +17,6 @@ import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Path; -// Todo : fix public final class ModrinthModpackProvider implements ModpackProvider { public static final ModrinthModpackProvider INSTANCE = new ModrinthModpackProvider(); diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/multimc/MultiMCModpackExportTask.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/multimc/MultiMCModpackExportTask.java index 05bc4bcd..bd20f207 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/multimc/MultiMCModpackExportTask.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/multimc/MultiMCModpackExportTask.java @@ -7,10 +7,12 @@ import static com.tungsten.fclcore.download.LibraryAnalyzer.LibraryType.LITELOAD import com.tungsten.fclcore.download.LibraryAnalyzer; import com.tungsten.fclcore.game.DefaultGameRepository; import com.tungsten.fclcore.mod.ModAdviser; +import com.tungsten.fclcore.mod.Modpack; import com.tungsten.fclcore.mod.ModpackExportInfo; import com.tungsten.fclcore.task.Task; import com.tungsten.fclcore.util.Logging; import com.tungsten.fclcore.util.gson.JsonUtils; +import com.tungsten.fclcore.util.io.Zipper; import java.io.File; import java.io.IOException; @@ -21,7 +23,6 @@ import java.util.List; /** * Export the game to a mod pack file. */ -// Todo : fix public class MultiMCModpackExportTask extends Task { private final DefaultGameRepository repository; private final String versionId; diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/multimc/MultiMCModpackInstallTask.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/multimc/MultiMCModpackInstallTask.java index 94eff4a7..c6e1772e 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/multimc/MultiMCModpackInstallTask.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/multimc/MultiMCModpackInstallTask.java @@ -13,6 +13,7 @@ import com.tungsten.fclcore.mod.ModpackConfiguration; import com.tungsten.fclcore.mod.ModpackInstallTask; import com.tungsten.fclcore.task.Task; import com.tungsten.fclcore.util.gson.JsonUtils; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.io.FileUtils; import java.io.File; @@ -26,7 +27,6 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -// Todo : fix public final class MultiMCModpackInstallTask extends Task { private final File zipFile; diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/task/FileDownloadTask.java b/FCLCore/src/main/java/com/tungsten/fclcore/task/FileDownloadTask.java index da9323c2..1a52bdee 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/task/FileDownloadTask.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/task/FileDownloadTask.java @@ -19,12 +19,12 @@ import static java.util.Objects.requireNonNull; import com.tungsten.fclcore.util.Logging; import com.tungsten.fclcore.util.io.ChecksumMismatchException; +import com.tungsten.fclcore.util.io.CompressingUtils; import com.tungsten.fclcore.util.io.FileUtils; /** * A task that can download a file online. */ -// Todo : fix public class FileDownloadTask extends FetchTask { public static class IntegrityCheck { diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/util/io/CompressingUtils.java b/FCLCore/src/main/java/com/tungsten/fclcore/util/io/CompressingUtils.java new file mode 100644 index 00000000..f2e1e029 --- /dev/null +++ b/FCLCore/src/main/java/com/tungsten/fclcore/util/io/CompressingUtils.java @@ -0,0 +1,260 @@ +package com.tungsten.fclcore.util.io; + +import com.github.marschall.com.sun.nio.zipfs.ZipFileSystemProvider; +import com.tungsten.fclcore.util.platform.OperatingSystem; + +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; + +import java.io.File; +import java.io.IOException; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.*; +import java.nio.file.*; +import java.nio.file.spi.FileSystemProvider; +import java.util.*; +import java.util.zip.ZipError; +import java.util.zip.ZipException; + +/** + * Utilities of compressing + */ +public final class CompressingUtils { + + private static final FileSystemProvider ZIPFS_PROVIDER = new ZipFileSystemProvider(); + + private CompressingUtils() { + } + + private static CharsetDecoder newCharsetDecoder(Charset charset) { + return charset.newDecoder().onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(CodingErrorAction.REPORT); + } + + public static boolean testEncoding(Path zipFile, Charset encoding) throws IOException { + try (ZipFile zf = openZipFile(zipFile, encoding)) { + return testEncoding(zf, encoding); + } + } + + public static boolean testEncoding(ZipFile zipFile, Charset encoding) throws IOException { + Enumeration entries = zipFile.getEntries(); + CharsetDecoder cd = newCharsetDecoder(encoding); + CharBuffer cb = CharBuffer.allocate(32); + + while (entries.hasMoreElements()) { + ZipArchiveEntry entry = entries.nextElement(); + + if (entry.getGeneralPurposeBit().usesUTF8ForNames()) continue; + + cd.reset(); + byte[] ba = entry.getRawName(); + int clen = (int)(ba.length * cd.maxCharsPerByte()); + if (clen == 0) continue; + if (clen <= cb.capacity()) + ((Buffer) cb).clear(); // cast to prevent "java.lang.NoSuchMethodError: java.nio.CharBuffer.clear()Ljava/nio/CharBuffer;" when compiling with Java 9+ + else + cb = CharBuffer.allocate(clen); + + ByteBuffer bb = ByteBuffer.wrap(ba, 0, ba.length); + CoderResult cr = cd.decode(bb, cb, true); + if (!cr.isUnderflow()) return false; + cr = cd.flush(cb); + if (!cr.isUnderflow()) return false; + } + return true; + } + + public static Charset findSuitableEncoding(Path zipFile) throws IOException { + return findSuitableEncoding(zipFile, Charset.availableCharsets().values()); + } + + public static Charset findSuitableEncoding(Path zipFile, Collection candidates) throws IOException { + try (ZipFile zf = openZipFile(zipFile, StandardCharsets.UTF_8)) { + return findSuitableEncoding(zf, candidates); + } + } + + public static Charset findSuitableEncoding(ZipFile zipFile) throws IOException { + return findSuitableEncoding(zipFile, Charset.availableCharsets().values()); + } + + public static Charset findSuitableEncoding(ZipFile zipFile, Collection candidates) throws IOException { + if (testEncoding(zipFile, StandardCharsets.UTF_8)) return StandardCharsets.UTF_8; + if (OperatingSystem.NATIVE_CHARSET != StandardCharsets.UTF_8 && testEncoding(zipFile, OperatingSystem.NATIVE_CHARSET)) + return OperatingSystem.NATIVE_CHARSET; + + for (Charset charset : candidates) + if (charset != null && testEncoding(zipFile, charset)) + return charset; + throw new IOException("Cannot find suitable encoding for the zip."); + } + + public static ZipFile openZipFile(Path zipFile) throws IOException { + return new ZipFile(Files.newByteChannel(zipFile)); + } + + public static ZipFile openZipFile(Path zipFile, Charset charset) throws IOException { + return new ZipFile(Files.newByteChannel(zipFile), charset.name()); + } + + public static final class Builder { + private boolean autoDetectEncoding = false; + private Collection charsetCandidates; + private Charset encoding = StandardCharsets.UTF_8; + private boolean useTempFile = false; + private final boolean create; + private final Path zip; + + public Builder(Path zip, boolean create) { + this.zip = zip; + this.create = create; + } + + public Builder setAutoDetectEncoding(boolean autoDetectEncoding) { + this.autoDetectEncoding = autoDetectEncoding; + return this; + } + + public Builder setCharsetCandidates(Collection charsetCandidates) { + this.charsetCandidates = charsetCandidates; + return this; + } + + public Builder setEncoding(Charset encoding) { + this.encoding = encoding; + return this; + } + + public Builder setUseTempFile(boolean useTempFile) { + this.useTempFile = useTempFile; + return this; + } + + public FileSystem build() throws IOException { + if (autoDetectEncoding) { + if (!testEncoding(zip, encoding)) { + if (charsetCandidates == null) + charsetCandidates = Charset.availableCharsets().values(); + encoding = findSuitableEncoding(zip, charsetCandidates); + } + } + return createZipFileSystem(zip, create, useTempFile, encoding); + } + } + + public static Builder readonly(Path zipFile) { + return new Builder(zipFile, false); + } + + public static Builder writable(Path zipFile) { + return new Builder(zipFile, true).setUseTempFile(true); + } + + public static FileSystem createReadOnlyZipFileSystem(Path zipFile) throws IOException { + return createReadOnlyZipFileSystem(zipFile, null); + } + + public static FileSystem createReadOnlyZipFileSystem(Path zipFile, Charset charset) throws IOException { + return createZipFileSystem(zipFile, false, false, charset); + } + + public static FileSystem createWritableZipFileSystem(Path zipFile) throws IOException { + return createWritableZipFileSystem(zipFile, null); + } + + public static FileSystem createWritableZipFileSystem(Path zipFile, Charset charset) throws IOException { + return createZipFileSystem(zipFile, true, true, charset); + } + + public static FileSystem createZipFileSystem(Path zipFile, boolean create, boolean useTempFile, Charset encoding) throws IOException { + Map env = new HashMap<>(); + if (create) + env.put("create", "true"); + if (encoding != null) + env.put("encoding", encoding.name()); + if (useTempFile) + env.put("useTempFile", true); + try { + return ZIPFS_PROVIDER.newFileSystem(zipFile, env); + } catch (ZipError error) { + // Since Java 8 throws ZipError stupidly + throw new ZipException(error.getMessage()); + } catch (UnsupportedOperationException ex) { + throw new ZipException("Not a zip file"); + } catch (FileSystemNotFoundException ex) { + throw new ZipException("Java Environment is broken"); + } + } + + /** + * Read the text content of a file in zip. + * + * @param zipFile the zip file + * @param name the location of the text in zip file, something like A/B/C/D.txt + * @throws IOException if the file is not a valid zip file. + * @return the plain text content of given file. + */ + public static String readTextZipEntry(File zipFile, String name) throws IOException { + try (ZipFile s = new ZipFile(zipFile)) { + return readTextZipEntry(s, name); + } + } + + /** + * Read the text content of a file in zip. + * + * @param zipFile the zip file + * @param name the location of the text in zip file, something like A/B/C/D.txt + * @throws IOException if the file is not a valid zip file. + * @return the plain text content of given file. + */ + public static String readTextZipEntry(ZipFile zipFile, String name) throws IOException { + return IOUtils.readFullyAsString(zipFile.getInputStream(zipFile.getEntry(name)), StandardCharsets.UTF_8); + } + + /** + * Read the text content of a file in zip. + * + * @param zipFile the zip file + * @param name the location of the text in zip file, something like A/B/C/D.txt + * @throws IOException if the file is not a valid zip file. + * @return the plain text content of given file. + */ + public static String readTextZipEntry(Path zipFile, String name, Charset encoding) throws IOException { + try (ZipFile s = openZipFile(zipFile, encoding)) { + return IOUtils.readFullyAsString(s.getInputStream(s.getEntry(name)), StandardCharsets.UTF_8); + } + } + + /** + * Read the text content of a file in zip. + * + * @param file the zip file + * @param name the location of the text in zip file, something like A/B/C/D.txt + * @return the plain text content of given file. + */ + public static Optional readTextZipEntryQuietly(File file, String name) { + try { + return Optional.of(readTextZipEntry(file, name)); + } catch (IOException | NullPointerException e) { + return Optional.empty(); + } + } + + /** + * Read the text content of a file in zip. + * + * @param file the zip file + * @param name the location of the text in zip file, something like A/B/C/D.txt + * @return the plain text content of given file. + */ + public static Optional readTextZipEntryQuietly(Path file, String name, Charset encoding) { + try { + return Optional.of(readTextZipEntry(file, name, encoding)); + } catch (IOException | NullPointerException e) { + return Optional.empty(); + } + } +} diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/util/io/Unzipper.java b/FCLCore/src/main/java/com/tungsten/fclcore/util/io/Unzipper.java new file mode 100644 index 00000000..5e57ccff --- /dev/null +++ b/FCLCore/src/main/java/com/tungsten/fclcore/util/io/Unzipper.java @@ -0,0 +1,129 @@ +package com.tungsten.fclcore.util.io; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; + +public class Unzipper { + private final Path zipFile, dest; + private boolean replaceExistentFile = false; + private boolean terminateIfSubDirectoryNotExists = false; + private String subDirectory = "/"; + private FileFilter filter = null; + private Charset encoding = StandardCharsets.UTF_8; + + /** + * Decompress the given zip file to a directory. + * + * @param zipFile the input zip file to be uncompressed + * @param destDir the dest directory to hold uncompressed files + */ + public Unzipper(Path zipFile, Path destDir) { + this.zipFile = zipFile; + this.dest = destDir; + } + + /** + * Decompress the given zip file to a directory. + * + * @param zipFile the input zip file to be uncompressed + * @param destDir the dest directory to hold uncompressed files + */ + public Unzipper(File zipFile, File destDir) { + this(zipFile.toPath(), destDir.toPath()); + } + + /** + * True if replace the existent files in destination directory, + * otherwise those conflict files will be ignored. + */ + public Unzipper setReplaceExistentFile(boolean replaceExistentFile) { + this.replaceExistentFile = replaceExistentFile; + return this; + } + + /** + * Will be called for every entry in the zip file. + * Callback returns false if you want leave the specific file uncompressed. + */ + public Unzipper setFilter(FileFilter filter) { + this.filter = filter; + return this; + } + + /** + * Will only uncompress files in the "subDirectory", their path will be also affected. + * + * For example, if you set subDirectory to /META-INF, files in /META-INF/ will be + * uncompressed to the destination directory without creating META-INF folder. + * + * Default value: "/" + */ + public Unzipper setSubDirectory(String subDirectory) { + this.subDirectory = FileUtils.normalizePath(subDirectory); + return this; + } + + public Unzipper setEncoding(Charset encoding) { + this.encoding = encoding; + return this; + } + + public Unzipper setTerminateIfSubDirectoryNotExists() { + this.terminateIfSubDirectoryNotExists = true; + return this; + } + + /** + * Decompress the given zip file to a directory. + * + * @throws IOException if zip file is malformed or filesystem error. + */ + public void unzip() throws IOException { + Files.createDirectories(dest); + try (FileSystem fs = CompressingUtils.readonly(zipFile).setEncoding(encoding).setAutoDetectEncoding(true).build()) { + Path root = fs.getPath(subDirectory); + if (!root.isAbsolute() || (subDirectory.length() > 1 && subDirectory.endsWith("/"))) + throw new IllegalArgumentException("Subdirectory for unzipper must be absolute"); + + if (terminateIfSubDirectoryNotExists && Files.notExists(root)) + return; + + Files.walkFileTree(root, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, + BasicFileAttributes attrs) throws IOException { + String relativePath = root.relativize(file).toString(); + Path destFile = dest.resolve(relativePath); + if (filter != null && !filter.accept(file, false, destFile, relativePath)) + return FileVisitResult.CONTINUE; + try { + Files.copy(file, destFile, replaceExistentFile ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{}); + } catch (FileAlreadyExistsException e) { + if (replaceExistentFile) + throw e; + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, + BasicFileAttributes attrs) throws IOException { + String relativePath = root.relativize(dir).toString(); + Path dirToCreate = dest.resolve(relativePath); + if (filter != null && !filter.accept(dir, true, dirToCreate, relativePath)) + return FileVisitResult.SKIP_SUBTREE; + Files.createDirectories(dirToCreate); + return FileVisitResult.CONTINUE; + } + }); + } + } + + public interface FileFilter { + boolean accept(Path zipEntry, boolean isDirectory, Path destFile, String entryPath) throws IOException; + } +} \ No newline at end of file diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/util/io/Zipper.java b/FCLCore/src/main/java/com/tungsten/fclcore/util/io/Zipper.java new file mode 100644 index 00000000..e57480ea --- /dev/null +++ b/FCLCore/src/main/java/com/tungsten/fclcore/util/io/Zipper.java @@ -0,0 +1,103 @@ +package com.tungsten.fclcore.util.io; + +import com.tungsten.fclcore.util.function.ExceptionalPredicate; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; + +/** + * Non thread-safe + */ +public final class Zipper implements Closeable { + + private final FileSystem fs; + + public Zipper(Path zipFile) throws IOException { + this(zipFile, null); + } + + public Zipper(Path zipFile, Charset encoding) throws IOException { + Files.deleteIfExists(zipFile); + fs = CompressingUtils.createWritableZipFileSystem(zipFile, encoding); + } + + @Override + public void close() throws IOException { + fs.close(); + } + + /** + * Compress all the files in sourceDir + * + * @param source the file in basePath to be compressed + * @param rootDir the path of the directory in this zip file. + */ + public void putDirectory(Path source, String rootDir) throws IOException { + putDirectory(source, rootDir, null); + } + + /** + * Compress all the files in sourceDir + * + * @param source the file in basePath to be compressed + * @param targetDir the path of the directory in this zip file. + * @param filter returns false if you do not want that file or directory + */ + public void putDirectory(Path source, String targetDir, ExceptionalPredicate filter) throws IOException { + Path root = fs.getPath(targetDir); + Files.createDirectories(root); + Files.walkFileTree(source, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (".DS_Store".equals(file.getFileName().toString())) { + return FileVisitResult.SKIP_SUBTREE; + } + String relativePath = source.relativize(file).normalize().toString(); + if (filter != null && !filter.test(relativePath.replace('\\', '/'))) { + return FileVisitResult.SKIP_SUBTREE; + } + Files.copy(file, root.resolve(relativePath), StandardCopyOption.COPY_ATTRIBUTES); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + String relativePath = source.relativize(dir).normalize().toString(); + if (filter != null && !filter.test(relativePath.replace('\\', '/'))) { + return FileVisitResult.SKIP_SUBTREE; + } + Path path = root.resolve(relativePath); + if (Files.notExists(path)) { + Files.createDirectory(path); + } + return FileVisitResult.CONTINUE; + } + }); + } + + public void putFile(File file, String path) throws IOException { + putFile(file.toPath(), path); + } + + public void putFile(Path file, String path) throws IOException { + Files.copy(file, fs.getPath(path), StandardCopyOption.COPY_ATTRIBUTES); + } + + public void putStream(InputStream in, String path) throws IOException { + Files.copy(in, fs.getPath(path), StandardCopyOption.COPY_ATTRIBUTES); + } + + public void putTextFile(String text, String path) throws IOException { + putTextFile(text, "UTF-8", path); + } + + public void putTextFile(String text, String encoding, String pathName) throws IOException { + Files.write(fs.getPath(pathName), text.getBytes(encoding)); + } + +} \ No newline at end of file diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/util/platform/OperatingSystem.java b/FCLCore/src/main/java/com/tungsten/fclcore/util/platform/OperatingSystem.java index 79071d49..e65a734d 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/util/platform/OperatingSystem.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/util/platform/OperatingSystem.java @@ -1,5 +1,9 @@ package com.tungsten.fclcore.util.platform; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; + public enum OperatingSystem { /** * Microsoft Windows. @@ -28,4 +32,31 @@ public enum OperatingSystem { return checkedName; } + public static final Charset NATIVE_CHARSET; + + static { + String nativeEncoding = System.getProperty("native.encoding"); + String hmclNativeEncoding = System.getProperty("fcl.native.encoding"); + Charset nativeCharset = Charset.defaultCharset(); + + try { + if (hmclNativeEncoding != null) { + nativeCharset = Charset.forName(hmclNativeEncoding); + } else { + if (nativeEncoding != null && !nativeEncoding.equalsIgnoreCase(nativeCharset.name())) { + nativeCharset = Charset.forName(nativeEncoding); + } + + if (nativeCharset == StandardCharsets.UTF_8 || nativeCharset == StandardCharsets.US_ASCII) { + nativeCharset = StandardCharsets.UTF_8; + } else if ("GBK".equalsIgnoreCase(nativeCharset.name()) || "GB2312".equalsIgnoreCase(nativeCharset.name())) { + nativeCharset = Charset.forName("GB18030"); + } + } + } catch (UnsupportedCharsetException e) { + e.printStackTrace(); + } + NATIVE_CHARSET = nativeCharset; + } + } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 73279671..776ed2c2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,6 +10,9 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven { + url "https://jitpack.io" + } } } rootProject.name = "Fold Craft Launcher"