Compare commits

..

No commits in common. "master" and "v1.7.1" have entirely different histories.

50 changed files with 845 additions and 848 deletions

27
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,27 @@
image: gradle:jdk16
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
before_script:
- export GRADLE_USER_HOME=`pwd`/.gradle
build_test:
stage: deploy
script:
- gradle --build-cache assemble
- cp build/libs/* ./
- rm *-dev.jar
- mv *.jar latest.jar
artifacts:
paths:
- build/libs
- latest.jar
only:
- master
deploy:
stage: deploy
when: manual
script:
- gradle --build-cache publishModrinth

View File

@ -1 +0,0 @@
#link https://pages.frohnmeyer-wds.de/scripts/jfmod.yml

View File

@ -1,67 +0,0 @@
Resclone automatically downloads and updates resource and data packs specified in its config.
Packs are updated on startup whenever possible, meaning that very large packs will slow down your startup.
Example config:
```
{
// The packs to be loaded by resclone
"packs": [
{
"fetcher": "file",
"source": "https://github.com/FaithfulTeam/Faithful/raw/releases/1.16.zip",
"name": "Faithful",
"forceDownload": false,
"forceEnable": false
},
{
"fetcher": "github",
"source": "seasnail8169/SnailPack",
"name": "SnailPack",
"forceDownload": false,
"forceEnable": false
},
{
"fetcher": "github",
"source": "spiralhalo/LumiLights/release",
"name": "LumiLights",
"forceDownload": true,
"forceEnable": false
},
{
"fetcher": "github",
"source": "spiralhalo/LumiPBRExt/tag/v0.7",
"name": "LumiPBRExt",
"forceDownload": false,
"forceEnable": false
},
{
"fetcher": "github",
"source": "FaithfulTeam/Faithful/branch/1.16",
"name": "Faithful",
"forceDownload": false,
"forceEnable": false
},
{
"fetcher": "file",
"source": "https://media.forgecdn.net/files/3031/178/BattyCoordinates_005.zip",
"name": "Battys Coordinates",
"forceDownload": false,
"forceEnable": false
},
{
"fetcher": "curseforge",
"source": "325017",
"name": "Vanilla Additions",
"forceDownload": false,
"forceEnable": true
},
{
"fetcher": "modrinth",
"source": "new-in-town",
"name": "New in Town"
}
],
// Whether to prune unused packs from the cache
"pruneUnused": true
}
```

92
build.gradle Normal file
View File

@ -0,0 +1,92 @@
apply from: "https://gitlab.com/-/snippets/2121059/raw/master/jfbase.gradle"
repositories {
maven {
name = 'TerraformersMC'
url = 'https://maven.terraformersmc.com/'
}
}
dependencies {
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.minecraft_version}+${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
modImplementation "com.terraformersmc:modmenu:2.0.0-beta.7"
}
/*plugins {
id 'fabric-loom' version '0.7-SNAPSHOT'
id 'maven-publish'
id "com.modrinth.minotaur" version "1.1.0"
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
archivesBaseName = project.archives_base_name
version = project.mod_version
group = project.maven_group
repositories {
maven { url = "https://maven.terraformersmc.com/"; name = "ModMenu" }
}
dependencies {
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.minecraft_version}+${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
modImplementation "com.terraformersmc:modmenu:1.16.9"
}
processResources {
inputs.property "version", project.version
from(sourceSets.main.resources.srcDirs) {
include "fabric.mod.json"
expand "version": project.version
}
from(sourceSets.main.resources.srcDirs) {
exclude "fabric.mod.json"
}
}
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this task, sources will not be generated.
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = "sources"
from sourceSets.main.allSource
}
jar {
from "LICENSE"
}
import com.modrinth.minotaur.TaskModrinthUpload
task publishModrinth (type: TaskModrinthUpload){
token = System.getenv("MODRINTH_API_TOKEN") // Use an environment property!
projectId = 'kVAQyCLX'
versionNumber = "${project.mod_version}"
uploadFile = remapJar // This is the java jar task
addGameVersion("${project.minecraft_version}")
addLoader('fabric')
versionName = "[${project.minecraft_version}] ${project.mod_version}"
afterEvaluate {
tasks.publishModrinth.dependsOn(remapJar)
tasks.publishModrinth.dependsOn(sourcesJar)
}
}
*/

View File

@ -1,47 +0,0 @@
plugins {
id("jfmod") version "1.6-SNAPSHOT"
}
loom {
accessWidenerPath.set(file("src/main/resources/resclone.accesswidener"))
}
allprojects { group = "io.gitlab.jfronny" }
base.archivesName = "resclone"
val modmenuVersion = "11.0.0-beta.1"
val commonsVersion = "2.0.0-SNAPSHOT"
jfMod {
minecraftVersion = "1.21"
yarn("build.1")
loaderVersion = "0.15.11"
libJfVersion = "3.16.0"
fabricApiVersion = "0.100.1+1.21"
modrinth {
projectId = "resclone"
optionalDependencies.add("fabric-api")
}
curseforge {
projectId = "839008"
optionalDependencies.add("fabric-api")
}
}
dependencies {
include(modImplementation("io.gitlab.jfronny.libjf:libjf-base")!!) // for JfCommons
include(modImplementation("io.gitlab.jfronny.libjf:libjf-config-core-v2")!!) // for JfCommons
include(modImplementation("net.fabricmc.fabric-api:fabric-resource-loader-v0")!!)
compileOnly("io.gitlab.jfronny:commons-serialize-generator-annotations:$commonsVersion")
annotationProcessor("io.gitlab.jfronny:commons-serialize-generator:$commonsVersion")
// Dev env
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-config-ui-tiny")
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-devutil")
modLocalRuntime("com.terraformersmc:modmenu:$modmenuVersion")
// for modmenu
modLocalRuntime("net.fabricmc.fabric-api:fabric-resource-loader-v0")
modLocalRuntime("net.fabricmc.fabric-api:fabric-screen-api-v1")
modLocalRuntime("net.fabricmc.fabric-api:fabric-key-binding-api-v1")
}

22
gradle.properties Normal file
View File

@ -0,0 +1,22 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx1G
# Fabric Properties
# check these on https://modmuss50.me/fabric.html
minecraft_version=1.17
yarn_mappings=build.1
loader_version=0.11.3
# Mod Properties
mod_version=1.7.1
maven_group=io.gitlab.jfronny
archives_base_name=resclone
# Dependencies
# check this on https://modmuss50.me/fabric.html
fabric_version=0.34.9+1.17
modrinth_id=kVAQyCLX
modrinth_required_dependencies=
modrinth_optional_dependencies=
curseforge_id=none
curseforge_required_dependencies=
curseforge_optional_dependencies=

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -1,9 +0,0 @@
pluginManagement {
repositories {
maven("https://maven.fabricmc.net/") // FabricMC
maven("https://maven.frohnmeyer-wds.de/artifacts") // scripts
gradlePluginPortal()
}
}
rootProject.name = "resclone"

View File

@ -1,12 +0,0 @@
{
"required": true,
"minVersion": "0.8",
"package": "io.gitlab.jfronny.resclone.mixin",
"compatibilityLevel": "JAVA_8",
"client": [
"GameOptionsMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View File

@ -1,176 +1,164 @@
package io.gitlab.jfronny.resclone;
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus;
import com.google.gson.Gson;
import io.gitlab.jfronny.resclone.api.PackFetcher;
import io.gitlab.jfronny.resclone.api.RescloneApi;
import io.gitlab.jfronny.resclone.api.RescloneEntry;
import io.gitlab.jfronny.resclone.data.PackMetaLoaded;
import io.gitlab.jfronny.resclone.data.PackMetaUnloaded;
import io.gitlab.jfronny.resclone.fetchers.*;
import io.gitlab.jfronny.resclone.processors.*;
import io.gitlab.jfronny.resclone.api.PackProcessor;
import io.gitlab.jfronny.resclone.processors.RemoveEmptyProcessor;
import io.gitlab.jfronny.resclone.processors.RootPathProcessor;
import io.gitlab.jfronny.resclone.util.PackUrlCache;
import net.fabricmc.api.EnvType;
import io.gitlab.jfronny.resclone.util.Result;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.net.URI;
import java.nio.file.*;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Resclone implements ModInitializer {
public static final Map<String, PackFetcher> FETCHER_INSTANCES = new LinkedHashMap<>();
public static final Set<PackProcessor> PROCESSORS = new LinkedHashSet<>();
public static final Set<PackMetaLoaded> DOWNLOADED_PACKS = new LinkedHashSet<>();
public static final Set<PackMetaLoaded> NEW_PACKS = new LinkedHashSet<>(); // Client-only!
public class Resclone implements ModInitializer, RescloneApi {
public static final Set<PackMetaUnloaded> conf = new LinkedHashSet<>();
public static final Map<String, PackFetcher> fetcherInstances = new LinkedHashMap<>();
public static final Set<PackProcessor> processors = new LinkedHashSet<>();
public static final Set<PackMetaLoaded> downloadedPacks = new LinkedHashSet<>();
public static final Set<PackMetaLoaded> newPacks = new LinkedHashSet<>();
public static final Gson gson = new Gson();
public static final String MOD_ID = "resclone";
public static final SystemLoggerPlus LOGGER = SystemLoggerPlus.forName(MOD_ID);
public static final String USER_AGENT = "jfmods/" + MOD_ID + "/" + FabricLoader.getInstance()
.getModContainer(MOD_ID).orElseThrow()
.getMetadata()
.getVersion()
.getFriendlyString();
public static final Logger LOGGER = LogManager.getLogger(MOD_ID);
public static PackUrlCache urlCache;
public static int packCount = 0;
public static int COUNT = 0;
@Override
public void onInitialize() {
LOGGER.info("Initialising Resclone.");
urlCache = new PackUrlCache(getConfigPath().resolve("urlCache.properties"));
FETCHER_INSTANCES.clear();
PROCESSORS.clear();
DOWNLOADED_PACKS.clear();
conf.clear();
fetcherInstances.clear();
processors.clear();
downloadedPacks.clear();
addProcessor(new RootPathProcessor()); //This should be run before any other processor to make sure the root is correct
addFetcher(new BasicFileFetcher());
addFetcher(new GitHubFetcher());
addFetcher(new CurseforgeFetcher());
addFetcher(new ModrinthFetcher());
if (RescloneConfig.filterPacks) {
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) addProcessor(new PruneVanillaProcessor());
addProcessor(new RemoveEmptyProcessor());
addProcessor(new RootPathProcessor()); //This should be run before any other processor to make sure the path is valid
for (RescloneEntry entry : FabricLoader.getInstance().getEntrypoints(MOD_ID, RescloneEntry.class)) {
try {
entry.init(this);
} catch (Exception e) {
e.printStackTrace();
}
}
addProcessor(new RemoveEmptyProcessor());
reload();
LOGGER.info("Installed {0} resource pack{1}.", packCount, packCount == 1 ? "" : "s");
LOGGER.info("Installed {} resource pack{}.", COUNT, COUNT == 1 ? "" : "s");
}
@Override
public void addFetcher(PackFetcher fetcher) {
FETCHER_INSTANCES.put(fetcher.getSourceTypeName(), fetcher);
fetcherInstances.put(fetcher.getSourceTypeName(), fetcher);
}
@Override
public void addProcessor(PackProcessor processor) {
PROCESSORS.add(processor);
processors.add(processor);
}
@Override
public void addPack(String fetcher, String pack, String name) {
addPack(fetcher, pack, name, false);
}
@Override
public void addPack(String fetcher, String pack, String name, boolean forceRedownload) {
addPack(fetcher, pack, name, forceRedownload, false);
}
@Override
public void addPack(String fetcher, String pack, String name, boolean forceRedownload, boolean forceEnable) {
RescloneConfig.packs.add(new PackMetaUnloaded(fetcher, pack, name, forceRedownload, forceEnable));
conf.add(new PackMetaUnloaded(fetcher, pack, name, forceRedownload, forceEnable));
}
@Override
public void reload() {
Set<PackMetaLoaded> metas = new LinkedHashSet<>();
if (RescloneConfig.packs.isEmpty()) {
LOGGER.info("No resclone pack was specified, add one");
}
else {
try (ExecutorService pool = Executors.newFixedThreadPool(RescloneConfig.packs.size())) {
for (PackMetaUnloaded s : RescloneConfig.packs) {
pool.submit(generateTask(s, metas));
}
pool.shutdown();
if (!pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS)) {
LOGGER.error("Download timed out. This shouldn't be possible");
}
} catch (InterruptedException e) {
LOGGER.error("Could not execute pack download task", e);
try {
ExecutorService pool = Executors.newFixedThreadPool(conf.size());
for (PackMetaUnloaded s : conf) {
pool.submit(generateTask(s, metas));
}
pool.shutdown();
if (!pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS)) {
LOGGER.error("Download timed out. This shouldn't be possible");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
urlCache.save();
DOWNLOADED_PACKS.clear();
DOWNLOADED_PACKS.addAll(metas);
if (RescloneConfig.pruneUnused) pruneCache();
downloadedPacks.clear();
downloadedPacks.addAll(metas);
}
private Runnable generateTask(PackMetaUnloaded meta, Set<PackMetaLoaded> metas) {
return () -> {
try {
if (!FETCHER_INSTANCES.containsKey(meta.fetcher()))
throw new Exception("Invalid fetcher: " + meta.fetcher());
if (!fetcherInstances.containsKey(meta.fetcher))
throw new Exception("Invalid fetcher: " + meta.fetcher);
Path cacheDir = getConfigPath().resolve("cache");
PackMetaLoaded p;
try {
boolean isNew = !urlCache.containsKey(meta.source());
boolean isNew = !urlCache.containsKey(meta.source);
//Download
PackFetcher.Result fr = FETCHER_INSTANCES.get(meta.fetcher()).get(meta.source(), cacheDir, meta.forceDownload());
p = new PackMetaLoaded(fr.downloadPath(), meta.name(), meta.forceEnable());
Result fr = fetcherInstances.get(meta.fetcher).get(meta.source, cacheDir, meta.forceDownload);
p = new PackMetaLoaded(fr.downloadPath, meta.name, meta.forceEnable);
metas.add(p);
if (isNew && FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) NEW_PACKS.add(p);
if (fr.freshDownload()) {
if (isNew)
newPacks.add(p);
if (fr.freshDownload) {
//Process
Map<String, String> props = new HashMap<>();
props.put("create", "false");
URI zipfile = URI.create("jar:" + p.zipPath().toUri());
URI zipfile = URI.create("jar:" + p.zipPath.toUri());
try (FileSystem zipfs = FileSystems.newFileSystem(zipfile, props)) {
for (PackProcessor processor : PROCESSORS) {
for (PackProcessor processor : processors) {
processor.process(zipfs);
}
} catch (Throwable e) {
LOGGER.error("Could not run pack processors on " + p.zipPath(), e);
e.printStackTrace();
}
}
} catch (Throwable e) {
throw new Exception("Failed to download pack", e);
}
} catch (Throwable e) {
LOGGER.error("Encountered issue while preparing " + meta.name(), e);
LOGGER.error("Encountered issue while preparing " + meta.name, e);
}
};
}
private void pruneCache() {
Set<Path> loadedPacks = DOWNLOADED_PACKS.stream().map(PackMetaLoaded::zipPath).collect(Collectors.toUnmodifiableSet());
Set<Path> toDelete = new HashSet<>();
try (Stream<Path> cacheEntries = Files.list(getConfigPath().resolve("cache"))) {
cacheEntries
.filter(s -> !Files.isRegularFile(s) || !loadedPacks.contains(s))
.forEach(toDelete::add);
} catch (IOException e) {
LOGGER.error("Could find cache entries to prune", e);
}
if (!toDelete.isEmpty()) {
LOGGER.info("Pruning " + toDelete.size() + " unused cache entries");
for (Path path : toDelete) {
try {
Files.delete(path);
} catch (IOException e) {
LOGGER.error("Could not delete unused cache entry: " + path, e);
}
}
}
}
public static Path getConfigPath() {
Path configPath = FabricLoader.getInstance().getConfigDir().resolve(MOD_ID);
@Override
public Path getConfigPath() {
Path configPath = FabricLoader.getInstance().getConfigDir().resolve("resclone");
if (!Files.isDirectory(configPath.resolve("cache"))) {
try {
Files.createDirectories(configPath.resolve("cache"));
} catch (IOException e) {
LOGGER.error("Could not create cache directory", e);
e.printStackTrace();
}
}
return configPath;
}
}
}

View File

@ -1,156 +0,0 @@
package io.gitlab.jfronny.resclone;
import com.google.gson.reflect.TypeToken;
import io.gitlab.jfronny.commons.serialize.MalformedDataException;
import io.gitlab.jfronny.commons.serialize.Token;
import io.gitlab.jfronny.commons.serialize.json.JsonReader;
import io.gitlab.jfronny.commons.serialize.json.JsonWriter;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v2.JfCustomConfig;
import io.gitlab.jfronny.libjf.config.api.v2.dsl.DSL;
import io.gitlab.jfronny.resclone.data.GC_PackMetaUnloaded;
import io.gitlab.jfronny.resclone.data.PackMetaUnloaded;
import io.gitlab.jfronny.resclone.util.ListAdaptation;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
public class RescloneConfig implements JfCustomConfig {
public static Set<PackMetaUnloaded> packs;
public static boolean pruneUnused;
public static boolean filterPacks;
public static boolean logProcessing;
private static final String ERR_DUPLICATE = "Unexpected duplicate \"%s\" in Resclone config";
private static final String PACKS = "packs";
private static final String PRUNE_UNUSED = "pruneUnused";
private static final String FILTER_PACKS = "filterPacks";
private static final String LOG_PROCESSING = "logProcessing";
private static void load(Path path) throws IOException {
if (!Files.exists(path)) {
packs = new HashSet<>();
pruneUnused = true;
filterPacks = true;
logProcessing = false;
write(path);
return;
}
boolean updateRequired = false;
try (BufferedReader br = Files.newBufferedReader(path);
JsonReader reader = LibJf.LENIENT_TRANSPORT.createReader(br)) {
if (reader.peek() == Token.BEGIN_ARRAY) {
// Legacy format compatibility
packs = ListAdaptation.deserializeSet(reader, GC_PackMetaUnloaded::deserialize);
updateRequired = true;
} else if (reader.peek() == Token.BEGIN_OBJECT) {
// New format
reader.beginObject();
Set<PackMetaUnloaded> packs = null;
Boolean pruneUnused = null;
Boolean filterPacks = null;
Boolean logProcessing = null;
while (reader.peek() != Token.END_OBJECT) {
final String name = reader.nextName();
switch (name) {
case PACKS -> {
if (packs != null) throw new MalformedDataException(ERR_DUPLICATE.formatted(PACKS));
if (reader.peek() == Token.BEGIN_ARRAY) {
packs = ListAdaptation.deserializeSet(reader, GC_PackMetaUnloaded::deserialize);
} else {
packs = Set.of(GC_PackMetaUnloaded.deserialize(reader));
}
}
case PRUNE_UNUSED -> {
if (pruneUnused != null) throw new MalformedDataException(ERR_DUPLICATE.formatted(PRUNE_UNUSED));
pruneUnused = reader.nextBoolean();
}
case FILTER_PACKS -> {
if (filterPacks != null) throw new MalformedDataException(ERR_DUPLICATE.formatted(FILTER_PACKS));
filterPacks = reader.nextBoolean();
}
case LOG_PROCESSING -> {
if (logProcessing != null) throw new MalformedDataException(ERR_DUPLICATE.formatted(LOG_PROCESSING));
logProcessing = reader.nextBoolean();
}
default -> throw new MalformedDataException("Unexpected element: \"" + name + "\" in Resclone config");
}
}
reader.endObject();
if (packs == null) throw new MalformedDataException("Expected Resclone config object to contain packs");
if (pruneUnused == null) {
pruneUnused = true;
updateRequired = true;
}
if (filterPacks == null) {
filterPacks = true;
updateRequired = true;
}
if (logProcessing == null) {
logProcessing = false;
updateRequired = true;
}
RescloneConfig.packs = packs;
RescloneConfig.pruneUnused = pruneUnused;
RescloneConfig.filterPacks = filterPacks;
RescloneConfig.logProcessing = logProcessing;
} else throw new MalformedDataException("Expected Resclone config to be an object or array");
}
if (updateRequired) write(path);
}
private static void write(Path path) throws IOException {
try (BufferedWriter bw = Files.newBufferedWriter(path);
JsonWriter writer = LibJf.LENIENT_TRANSPORT.createWriter(bw)) {
writer.beginObject()
.comment("The packs to be loaded by resclone")
.name(PACKS)
.beginArray();
for (PackMetaUnloaded pack : packs) {
GC_PackMetaUnloaded.serialize(pack, writer);
}
writer.endArray()
.comment("Automatically remove all downloaded packs that are not in the config to free up unneeded space")
.name(PRUNE_UNUSED)
.value(pruneUnused)
.comment("Whether to filter packs to remove files unchanged from vanilla and empty directories")
.name(FILTER_PACKS)
.value(filterPacks)
.comment("Log automatic processing steps applied to downloaded packs")
.name(LOG_PROCESSING)
.value(logProcessing)
.endObject();
}
}
static {
Path path = Resclone.getConfigPath().resolve("config.json");
DSL.create(Resclone.MOD_ID).register(builder ->
builder.setLoadMethod(configInstance -> {
try {
load(path);
} catch (IOException e) {
Resclone.LOGGER.error("Could not load config", e);
}
}).setWriteMethod(configInstance -> {
try {
write(path);
} catch (IOException e) {
Resclone.LOGGER.error("Could not write config", e);
}
}).setPath(path)
.<Set<PackMetaUnloaded>>value(PACKS, new HashSet<>(), Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, io.gitlab.jfronny.libjf.config.api.v2.type.Type.ofClass(new TypeToken<Set<PackMetaUnloaded>>(){}.getType()), 100, () -> packs, p -> packs = p)
.value(PRUNE_UNUSED, pruneUnused, () -> pruneUnused, p -> pruneUnused = p)
.value(FILTER_PACKS, filterPacks, () -> filterPacks, p -> filterPacks = p)
.value(LOG_PROCESSING, logProcessing, () -> logProcessing, p -> logProcessing = p)
).load();
}
@Override
public void register(DSL.Defaulted dsl) {
}
}

View File

@ -0,0 +1,25 @@
package io.gitlab.jfronny.resclone;
import io.gitlab.jfronny.resclone.api.RescloneApi;
import io.gitlab.jfronny.resclone.api.RescloneEntry;
import io.gitlab.jfronny.resclone.util.ConfigLoader;
import io.gitlab.jfronny.resclone.fetchers.BasicFileFetcher;
import io.gitlab.jfronny.resclone.fetchers.CurseforgeFetcher;
import io.gitlab.jfronny.resclone.fetchers.GitHubFetcher;
import io.gitlab.jfronny.resclone.processors.PruneVanillaProcessor;
import net.fabricmc.api.EnvType;
import net.fabricmc.loader.api.FabricLoader;
public class RescloneEntryDefault implements RescloneEntry {
@Override
public void init(RescloneApi api) {
api.addFetcher(new BasicFileFetcher());
api.addFetcher(new GitHubFetcher());
api.addFetcher(new CurseforgeFetcher());
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT)
api.addProcessor(new PruneVanillaProcessor());
ConfigLoader.load(api);
}
}

View File

@ -3,57 +3,27 @@ package io.gitlab.jfronny.resclone;
import net.fabricmc.fabric.api.resource.ModResourcePack;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.metadata.ModMetadata;
import net.minecraft.resource.*;
import net.minecraft.resource.ZipResourcePack;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class RescloneResourcePack extends ZipResourcePack implements ModResourcePack {
private static final ModMetadata METADATA = FabricLoader.getInstance().getModContainer(Resclone.MOD_ID).orElseThrow().getMetadata();
private final ResourcePackInfo info;
private final ZipFileWrapper file;
RescloneResourcePack(ZipFileWrapper file, ResourcePackInfo info, String overlay) {
super(info, file, overlay);
this.info = info;
this.file = file;
private final String name;
public RescloneResourcePack(File file, String name) {
super(file);
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public ModMetadata getFabricModMetadata() {
return METADATA;
return FabricLoader.getInstance().getModContainer(Resclone.MOD_ID).get().getMetadata();
}
@Override
public ModResourcePack createOverlay(String overlay) {
return new RescloneResourcePack(this.file, this.info, overlay);
}
public static class Factory implements ResourcePackProfile.PackFactory {
private final File file;
private final ResourcePackInfo info;
public Factory(File file, ResourcePackInfo info) {
this.file = file;
this.info = info;
}
@Override
public ResourcePack open(ResourcePackInfo info) {
ZipFileWrapper zipFileWrapper = new ZipFileWrapper(this.file);
return new RescloneResourcePack(zipFileWrapper, this.info, "");
}
@Override
public ResourcePack openWithOverlays(ResourcePackInfo info, ResourcePackProfile.Metadata metadata) {
ZipFileWrapper zipFileWrapper = new ZipFileWrapper(this.file);
ZipResourcePack resourcePack = new RescloneResourcePack(zipFileWrapper, this.info, "");
List<String> overlays = metadata.overlays();
if (overlays.isEmpty()) return resourcePack;
List<ResourcePack> overlayPacks = new ArrayList<>(overlays.size());
for (String string : overlays) overlayPacks.add(new RescloneResourcePack(zipFileWrapper, this.info, string));
return new OverlayResourcePack(resourcePack, overlayPacks);
}
}
}
}

View File

@ -1,10 +1,10 @@
package io.gitlab.jfronny.resclone.fetchers;
package io.gitlab.jfronny.resclone.api;
import io.gitlab.jfronny.resclone.util.Result;
import java.nio.file.Path;
public interface PackFetcher {
Result get(String baseUrl, Path targetDir, boolean forceDownload) throws Exception;
String getSourceTypeName(); // The name for users to specify in the config
record Result(Path downloadPath, boolean freshDownload) { }
}

View File

@ -1,7 +1,7 @@
package io.gitlab.jfronny.resclone.processors;
package io.gitlab.jfronny.resclone.api;
import java.nio.file.FileSystem;
public interface PackProcessor {
void process(FileSystem p) throws Exception;
}
}

View File

@ -0,0 +1,21 @@
package io.gitlab.jfronny.resclone.api;
import java.nio.file.Path;
public interface RescloneApi {
void addFetcher(PackFetcher fetcher);
void addProcessor(PackProcessor processor);
void addPack(String fetcher, String pack, String name);
void addPack(String fetcher, String pack, String name, boolean forceRedownload);
void addPack(String fetcher, String pack, String name, boolean forceRedownload, boolean forceEnable);
void reload();
Path getConfigPath();
}

View File

@ -0,0 +1,5 @@
package io.gitlab.jfronny.resclone.api;
public interface RescloneEntry {
void init(RescloneApi api) throws Exception;
}

View File

@ -0,0 +1,11 @@
package io.gitlab.jfronny.resclone.data;
import java.util.Set;
public class CfAddon {
public String downloadUrl;
public String fileDate;
public Set<String> gameVersion;
}

View File

@ -2,5 +2,14 @@ package io.gitlab.jfronny.resclone.data;
import java.nio.file.Path;
public record PackMetaLoaded(Path zipPath, String name, boolean forceEnable) {
}
public class PackMetaLoaded {
public final Path zipPath;
public final String name;
public final boolean forceEnable;
public PackMetaLoaded(Path zipPath, String name, boolean forceEnable) {
this.zipPath = zipPath;
this.name = name;
this.forceEnable = forceEnable;
}
}

View File

@ -1,7 +1,17 @@
package io.gitlab.jfronny.resclone.data;
import io.gitlab.jfronny.commons.serialize.generator.annotations.GSerializable;
public class PackMetaUnloaded {
public final String fetcher;
public final String source;
public final String name;
public final boolean forceDownload;
public final boolean forceEnable;
@GSerializable
public record PackMetaUnloaded(String fetcher, String source, String name, boolean forceDownload, boolean forceEnable) {
public PackMetaUnloaded(String fetcher, String source, String name, boolean forceDownload, boolean forceEnable) {
this.fetcher = fetcher;
this.source = source;
this.name = name;
this.forceDownload = forceDownload;
this.forceEnable = forceEnable;
}
}

View File

@ -1,18 +0,0 @@
package io.gitlab.jfronny.resclone.data.curseforge;
import io.gitlab.jfronny.commons.serialize.generator.annotations.GSerializable;
import java.util.Date;
import java.util.List;
@GSerializable
public class GetModFilesResponse {
public List<Data> data;
@GSerializable
public static class Data {
public String downloadUrl;
public Date fileDate;
public List<String> gameVersions;
}
}

View File

@ -1,13 +0,0 @@
package io.gitlab.jfronny.resclone.data.curseforge;
import io.gitlab.jfronny.commons.serialize.generator.annotations.GSerializable;
@GSerializable
public class GetModResponse {
public Data data;
@GSerializable
public static class Data {
public Boolean allowModDistribution;
}
}

View File

@ -1,18 +0,0 @@
package io.gitlab.jfronny.resclone.data.github;
import io.gitlab.jfronny.commons.serialize.generator.annotations.GSerializable;
import java.util.List;
@GSerializable
public class Release {
public List<Asset> assets;
public String zipball_url;
@GSerializable
public static class Asset {
public String name;
public String content_type;
public String browser_download_url;
}
}

View File

@ -1,8 +0,0 @@
package io.gitlab.jfronny.resclone.data.github;
import io.gitlab.jfronny.commons.serialize.generator.annotations.GSerializable;
@GSerializable
public class Repository {
public String default_branch;
}

View File

@ -1,69 +0,0 @@
package io.gitlab.jfronny.resclone.data.modrinth;
import io.gitlab.jfronny.commons.serialize.annotations.SerializedName;
import io.gitlab.jfronny.commons.serialize.generator.annotations.GSerializable;
import org.jetbrains.annotations.Nullable;
import java.util.Date;
import java.util.List;
@GSerializable
public class Version {
public String name;
public String version_number;
@Nullable public String changelog;
public List<Dependency> dependencies;
public List<String> game_versions;
public VersionType version_type;
public List<String> loaders;
public Boolean featured;
public Status status;
@Nullable public Status requested_status;
public String id;
public String project_id;
public String author_id;
public Date date_published;
public Integer downloads;
public List<File> files;
@GSerializable
public static class Dependency {
@Nullable public String version_id;
@Nullable public String project_id;
@Nullable public String file_name;
public Type dependency_type;
public enum Type {
required, optional, incompatible, embedded
}
}
public enum VersionType {
release, beta, alpha
}
public enum Status {
listed, archived, draft, unlisted, scheduled, unknown
}
@GSerializable
public static class File {
public Hashes hashes;
public String url;
public String filename;
public Boolean primary;
public Integer size;
@Nullable public Type file_type;
@GSerializable
public static class Hashes {
public String sha512;
public String sha1;
}
public enum Type {
@SerializedName("required-resource-pack") REQUIRED_RESOURCE_PACK,
@SerializedName("optional-resource-pack") OPTIONAL_RESOURCE_PACK
}
}
}

View File

@ -1,9 +1,12 @@
package io.gitlab.jfronny.resclone.fetchers;
import io.gitlab.jfronny.commons.http.client.HttpClient;
import io.gitlab.jfronny.resclone.Resclone;
import io.gitlab.jfronny.resclone.api.PackFetcher;
import io.gitlab.jfronny.resclone.util.Result;
import java.io.*;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
@ -17,7 +20,7 @@ public abstract class BasePackFetcher implements PackFetcher {
Resclone.urlCache.set(baseUrl, url);
} catch (Exception e) {
if (Resclone.urlCache.containsKey(baseUrl)) {
Resclone.LOGGER.error("Could not get download URL for " + baseUrl + ", using cached", e);
e.printStackTrace();
url = Resclone.urlCache.get(baseUrl);
} else {
throw e;
@ -26,14 +29,14 @@ public abstract class BasePackFetcher implements PackFetcher {
Path p = targetDir.resolve(Integer.toHexString(url.hashCode()));
if (!forceDownload && Files.exists(p)) {
Resclone.packCount++;
Resclone.COUNT++;
return new Result(p, false);
}
Resclone.LOGGER.info("Downloading pack: " + url);
try (InputStream is = HttpClient.get(url).userAgent(Resclone.USER_AGENT).sendInputStream();
OutputStream os = Files.newOutputStream(p)) {
try (InputStream is = new URL(url).openStream()) {
FileOutputStream os = new FileOutputStream(p.toFile());
byte[] dataBuffer = new byte[1024];
int bytesRead;
while ((bytesRead = is.read(dataBuffer, 0, 1024)) != -1) {
@ -41,9 +44,9 @@ public abstract class BasePackFetcher implements PackFetcher {
}
Resclone.LOGGER.info("Finished downloading.");
} catch (Throwable e) {
throw new IOException("Could not download pack", e);
throw new Exception("Could not download pack", e);
}
Resclone.packCount++;
Resclone.COUNT++;
return new Result(p, true);
}
}
}

View File

@ -1,6 +1,7 @@
package io.gitlab.jfronny.resclone.fetchers;
public class BasicFileFetcher extends BasePackFetcher {
@Override
public String getSourceTypeName() {
return "file";
@ -10,4 +11,5 @@ public class BasicFileFetcher extends BasePackFetcher {
public String getDownloadUrl(String baseUrl) {
return baseUrl;
}
}
}

View File

@ -1,35 +1,14 @@
package io.gitlab.jfronny.resclone.fetchers;
import io.gitlab.jfronny.commons.http.client.HttpClient;
import io.gitlab.jfronny.commons.serialize.json.JsonReader;
import io.gitlab.jfronny.commons.throwable.ThrowingFunction;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.resclone.Resclone;
import io.gitlab.jfronny.resclone.data.curseforge.GC_GetModFilesResponse;
import io.gitlab.jfronny.resclone.data.curseforge.GC_GetModResponse;
import io.gitlab.jfronny.resclone.data.curseforge.GetModFilesResponse;
import io.gitlab.jfronny.resclone.data.curseforge.GetModResponse;
import io.gitlab.jfronny.resclone.data.CfAddon;
import io.gitlab.jfronny.resclone.util.UrlUtils;
import net.minecraft.MinecraftVersion;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
public class CurseforgeFetcher extends BasePackFetcher {
// So you found the API key.
// Please be aware that CurseForge requires you to change this if you make any kind of derivative work
// Creating your own API key is relatively simple, so please don't abuse this
private static final String API_KEY = new String(unsalt(new byte[] {
-30, 50, -60, -121, 62, -31, 35, 17, 16, -53,
-53, -88, 21, -21, 15, -105, -115, -108, 114, -50,
-49, -4, 56, -65, -70, 108, -65, -3, -55, -4,
36, -86, -40, 116, 71, -5, 75, -9, -43, 4,
91, -91, -29, 40, 66, 87, -80, -74, 71, 41,
76, -96, 108, -61, -113, 118, 7, -39, -116, -120
}, 1024));
@Override
public String getSourceTypeName() {
@ -39,49 +18,34 @@ public class CurseforgeFetcher extends BasePackFetcher {
@Override
public String getDownloadUrl(String baseUrl) throws Exception {
try {
GetModResponse response = GET(baseUrl, GC_GetModResponse::deserialize);
if (!response.data.allowModDistribution)
throw new Exception("The author of " + baseUrl + " disabled access to this pack outside of the curseforge launcher");
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSS'Z'");
String version = MinecraftVersion.CURRENT.getName();
String version = MinecraftVersion.GAME_VERSION.getName();
GetModFilesResponse.Data latest = null;
CfAddon latest = null;
Date latestDate = null;
boolean foundMatchingVersion = false;
for (GetModFilesResponse.Data addon : GET(baseUrl + "/files", GC_GetModFilesResponse::deserialize).data) {
if (foundMatchingVersion && !addon.gameVersions.contains(version)) continue;
if (!foundMatchingVersion && addon.gameVersions.contains(version)) {
for (CfAddon addon : UrlUtils.readJsonFromURLSet("https://addons-ecs.forgesvc.net/api/v2/addon/" + baseUrl + "/files", CfAddon.class)) {
Date d = df.parse(addon.fileDate);
if (foundMatchingVersion && !addon.gameVersion.contains(version))
continue;
if (!foundMatchingVersion && addon.gameVersion.contains(version)) {
foundMatchingVersion = true;
latest = null;
}
if (latest == null || addon.fileDate.after(latestDate)) {
if (latest == null || d.after(latestDate)) {
latest = addon;
latestDate = addon.fileDate;
latestDate = d;
}
}
if (latest == null) throw new FileNotFoundException("Could not identify valid version");
if (!foundMatchingVersion) Resclone.LOGGER.error("Could not find matching version of " + baseUrl + ", using latest");
if (!foundMatchingVersion)
Resclone.LOGGER.error("Could not find pack for matching version, using latest");
return latest.downloadUrl;
} catch (Throwable e) {
throw new IOException("Could not get CurseForge download for " + baseUrl, e);
throw new Exception("Could not get CF download for " + baseUrl, e);
}
}
private static <T> T GET(String suffix, ThrowingFunction<JsonReader, T, IOException> klazz) throws URISyntaxException, IOException {
try (Reader r = HttpClient.get("https://api.curseforge.com/v1/mods/" + suffix).header("x-api-key", API_KEY).sendReader();
JsonReader jr = LibJf.LENIENT_TRANSPORT.createReader(r)) {
return klazz.apply(jr);
}
}
private static byte[] unsalt(byte[] data, int salt) {
byte[] result = new byte[data.length];
new Random(salt).nextBytes(result);
for (int i = 0; i < data.length; i++) {
result[i] ^= data[i];
}
return result;
}
}
}

View File

@ -1,17 +1,12 @@
package io.gitlab.jfronny.resclone.fetchers;
import io.gitlab.jfronny.commons.http.client.HttpClient;
import io.gitlab.jfronny.commons.serialize.json.JsonReader;
import io.gitlab.jfronny.libjf.LibJf;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.gitlab.jfronny.resclone.Resclone;
import io.gitlab.jfronny.resclone.data.github.GC_Release;
import io.gitlab.jfronny.resclone.data.github.GC_Repository;
import io.gitlab.jfronny.resclone.data.github.Release;
import io.gitlab.jfronny.resclone.util.UrlUtils;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.net.URISyntaxException;
public class GitHubFetcher extends BasePackFetcher {
@Override
@ -43,21 +38,21 @@ public class GitHubFetcher extends BasePackFetcher {
//"user/repo/release" - Gets from the latest release.
else if (parts[2].equalsIgnoreCase("release")) {
try (Reader r = HttpClient.get("https://api.github.com/repos/" + parts[0] + "/" + parts[1] + "/releases/latest").sendReader();
JsonReader jr = LibJf.LENIENT_TRANSPORT.createReader(r)) {
Release latestRelease = GC_Release.deserialize(jr);
try {
JsonObject latestRelease = UrlUtils.readJsonFromURL("https://api.github.com/repos/" + parts[0] + "/" + parts[1] + "/releases/latest", JsonObject.class);
String res = null;
for (Release.Asset asset : latestRelease.assets) {
if ("application/x-zip-compressed".equals(asset.content_type) || asset.name.endsWith(".zip")) {
res = asset.browser_download_url;
for (JsonElement element : latestRelease.get("assets").getAsJsonArray()) {
JsonObject o = element.getAsJsonObject();
if ("application/x-zip-compressed".equals(o.get("content_type").getAsString()) || o.get("name").getAsString().endsWith(".zip")) {
res = o.get("browser_download_url").getAsString();
break;
}
}
Resclone.LOGGER.info("Getting from latest release.");
if (res == null) return latestRelease.zipball_url;
if (res == null) return latestRelease.get("zipball_url").getAsString();
else return res;
} catch (Throwable e) {
@ -78,10 +73,9 @@ public class GitHubFetcher extends BasePackFetcher {
private String getFromBranch(String repo, @Nullable String branch) {
if (branch == null) {
try (Reader r = HttpClient.get("https://api.github.com/repos/" + repo).sendReader();
JsonReader jr = LibJf.LENIENT_TRANSPORT.createReader(r)) {
branch = GC_Repository.deserialize(jr).default_branch;
} catch (IOException | URISyntaxException e) {
try {
branch = UrlUtils.readJsonFromURL("https://api.github.com/repos/" + repo, JsonObject.class).get("default_branch").getAsString();
} catch (IOException e) {
Resclone.LOGGER.error("Failed to fetch branch for " + repo + ". Choosing \"main\"", e);
branch = "main";
}
@ -94,4 +88,4 @@ public class GitHubFetcher extends BasePackFetcher {
Resclone.LOGGER.info("Getting from tag " + tag + ".");
return "https://codeload.github.com/" + repo + "/legacy.zip/refs/tags/" + tag;
}
}
}

View File

@ -1,69 +0,0 @@
package io.gitlab.jfronny.resclone.fetchers;
import io.gitlab.jfronny.commons.http.client.HttpClient;
import io.gitlab.jfronny.commons.serialize.json.JsonReader;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.resclone.Resclone;
import io.gitlab.jfronny.resclone.data.modrinth.GC_Version;
import io.gitlab.jfronny.resclone.data.modrinth.Version;
import io.gitlab.jfronny.resclone.util.ListAdaptation;
import net.minecraft.MinecraftVersion;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.util.Date;
import java.util.List;
public class ModrinthFetcher extends BasePackFetcher {
@Override
public String getSourceTypeName() {
return "modrinth";
}
@Override
public String getDownloadUrl(String baseUrl) throws Exception {
try {
String version = MinecraftVersion.CURRENT.getName();
Version latest = null;
Date latestDate = null;
boolean foundMatchingVersion = false;
List<Version> versions;
try (Reader r = HttpClient.get("https://api.modrinth.com/v2/project/" + baseUrl + "/version")
.userAgent(Resclone.USER_AGENT)
.sendReader();
JsonReader jr = LibJf.LENIENT_TRANSPORT.createReader(r)) {
versions = ListAdaptation.deserializeList(jr, GC_Version::deserialize);
}
for (Version ver : versions) {
if (foundMatchingVersion && !ver.game_versions.contains(version)) continue;
if (ver.files.isEmpty()) continue;
if (!foundMatchingVersion && ver.game_versions.contains(version)) {
foundMatchingVersion = true;
latest = null;
}
if (latest == null || ver.date_published.after(latestDate)) {
latest = ver;
latestDate = ver.date_published;
}
}
if (latest == null) throw new FileNotFoundException("Could not identify valid version");
if (!foundMatchingVersion) Resclone.LOGGER.error("Could not find matching version of " + baseUrl + ", using latest");
for (Version.File file : latest.files) {
if (file.primary) return file.url;
}
Resclone.LOGGER.error("Could not identify primary file of " + baseUrl + ", attempting identification by file_type");
for (Version.File file : latest.files) {
if (file.file_type == Version.File.Type.REQUIRED_RESOURCE_PACK) return file.url;
}
Resclone.LOGGER.error("Identification failed, using first file of " + baseUrl);
return latest.files.getFirst().url;
} catch (Throwable e) {
throw new IOException("Could not get Modrinth download for " + baseUrl, e);
}
}
}

View File

@ -3,43 +3,38 @@ package io.gitlab.jfronny.resclone.mixin;
import io.gitlab.jfronny.resclone.Resclone;
import io.gitlab.jfronny.resclone.RescloneResourcePack;
import io.gitlab.jfronny.resclone.data.PackMetaLoaded;
import net.minecraft.resource.*;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.*;
import net.minecraft.resource.FileResourcePackProvider;
import net.minecraft.resource.ResourcePackProfile;
import net.minecraft.resource.ResourcePackSource;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Optional;
import java.util.function.Consumer;
@Mixin(FileResourcePackProvider.class)
public class FileResourcePackProviderMixin {
@Shadow @Final private ResourcePackSource source;
@Shadow @Final private ResourceType type;
@Shadow
@Final
private ResourcePackSource field_25345;
@Inject(at = @At("TAIL"), method = "register(Ljava/util/function/Consumer;)V")
public void registerExtra(Consumer<ResourcePackProfile> consumer, CallbackInfo info) {
for (PackMetaLoaded meta : Resclone.DOWNLOADED_PACKS) {
ResourcePackInfo ifo = new ResourcePackInfo(
"resclone/" + meta.name(),
Text.literal(meta.name()),
source,
Optional.empty()
);
ResourcePackProfile resourcePackProfile = ResourcePackProfile.create(
ifo,
new RescloneResourcePack.Factory(meta.zipPath().toFile(), ifo),
type,
new ResourcePackPosition(
meta.forceEnable(),
ResourcePackProfile.InsertionPosition.TOP,
false
)
@Inject(at = @At("TAIL"), method = "register(Ljava/util/function/Consumer;Lnet/minecraft/resource/ResourcePackProfile$Factory;)V")
public void registerExtra(Consumer<ResourcePackProfile> consumer, ResourcePackProfile.Factory factory, CallbackInfo info) {
for (PackMetaLoaded meta : Resclone.downloadedPacks) {
ResourcePackProfile resourcePackProfile = ResourcePackProfile.of(
"resclone/" + meta.name,
meta.forceEnable,
() -> new RescloneResourcePack(meta.zipPath.toFile(), meta.name),
factory,
ResourcePackProfile.InsertionPosition.TOP,
this.field_25345
);
if (resourcePackProfile != null) {
consumer.accept(resourcePackProfile);
}
}
}
}
}

View File

@ -19,12 +19,12 @@ public abstract class GameOptionsMixin {
@Inject(at = @At("TAIL"), method = "load()V")
public void load(CallbackInfo ci) {
for (PackMetaLoaded meta : Resclone.NEW_PACKS) {
Resclone.LOGGER.info(Resclone.MOD_ID + "/" + meta.name());
resourcePacks.add(Resclone.MOD_ID + "/" + meta.name());
for (PackMetaLoaded meta : Resclone.newPacks) {
Resclone.LOGGER.info(Resclone.MOD_ID + "/" + meta.name);
resourcePacks.add(Resclone.MOD_ID + "/" + meta.name);
}
if (!Resclone.NEW_PACKS.isEmpty())
if (!Resclone.newPacks.isEmpty())
write();
Resclone.NEW_PACKS.clear();
Resclone.newPacks.clear();
}
}

View File

@ -1,19 +1,21 @@
package io.gitlab.jfronny.resclone.processors;
import io.gitlab.jfronny.resclone.Resclone;
import io.gitlab.jfronny.resclone.RescloneConfig;
import io.gitlab.jfronny.resclone.api.PackProcessor;
import io.gitlab.jfronny.resclone.util.io.PathPruneVisitor;
import net.minecraft.server.MinecraftServer;
import net.minecraft.client.MinecraftClient;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
public class PruneVanillaProcessor implements PackProcessor {
@Override
public void process(FileSystem p) throws Exception {
ClassLoader cl = MinecraftServer.class.getClassLoader();
ClassLoader cl = MinecraftClient.class.getClassLoader();
try {
if (Files.isDirectory(p.getPath("/assets/minecraft"))) {
Files.walkFileTree(p.getPath("/assets/minecraft"), new PathPruneVisitor((s) -> {
@ -23,14 +25,11 @@ public class PruneVanillaProcessor implements PackProcessor {
InputStream vn = cl.getResourceAsStream(p.getPath("/").relativize(s).toString());
if (vn != null) {
try (InputStream pk = Files.newInputStream(s, StandardOpenOption.READ)) {
if (IOUtils.contentEquals(vn, pk)) {
if (RescloneConfig.logProcessing) Resclone.LOGGER.info("Pruning file unchanged from vanilla: {}", s);
return true;
}
return IOUtils.contentEquals(vn, pk);
}
}
} catch (Throwable e) {
Resclone.LOGGER.error("Could not prune unchanged assets", e);
e.printStackTrace();
}
return false;
}));
@ -39,4 +38,5 @@ public class PruneVanillaProcessor implements PackProcessor {
throw new Exception("Could not prune vanilla files", e);
}
}
}
}

View File

@ -1,13 +1,16 @@
package io.gitlab.jfronny.resclone.processors;
import io.gitlab.jfronny.resclone.Resclone;
import io.gitlab.jfronny.resclone.RescloneConfig;
import io.gitlab.jfronny.resclone.api.PackProcessor;
import io.gitlab.jfronny.resclone.util.io.PathPruneVisitor;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
public class RemoveEmptyProcessor implements PackProcessor {
@Override
public void process(FileSystem p) throws Exception {
if (Files.exists(p.getPath("/assets"))) {
@ -15,12 +18,9 @@ public class RemoveEmptyProcessor implements PackProcessor {
Files.walkFileTree(p.getPath("/assets"), new PathPruneVisitor(s -> {
if (Files.isDirectory(s)) {
try (DirectoryStream<Path> paths = Files.newDirectoryStream(s)) {
if (!paths.iterator().hasNext()) {
if (RescloneConfig.logProcessing) Resclone.LOGGER.info("Pruning empty directory: {}", s);
return true;
}
return !paths.iterator().hasNext();
} catch (IOException e) {
Resclone.LOGGER.error("Could not check whether directory has entries", e);
e.printStackTrace();
}
}
return false;
@ -30,4 +30,5 @@ public class RemoveEmptyProcessor implements PackProcessor {
}
}
}
}
}

View File

@ -1,13 +1,13 @@
package io.gitlab.jfronny.resclone.processors;
import io.gitlab.jfronny.resclone.Resclone;
import io.gitlab.jfronny.resclone.RescloneConfig;
import io.gitlab.jfronny.resclone.api.PackProcessor;
import io.gitlab.jfronny.resclone.util.io.MoveDirVisitor;
import java.io.IOException;
import java.nio.file.*;
public class RootPathProcessor implements PackProcessor {
@Override
public void process(FileSystem p) throws Exception {
if (!Files.exists(p.getPath("/pack.mcmeta"))) {
@ -16,7 +16,6 @@ public class RootPathProcessor implements PackProcessor {
try (DirectoryStream<Path> paths = Files.newDirectoryStream(root)) {
for (Path path : paths) {
if (Files.isDirectory(path) && Files.exists(path.resolve("pack.mcmeta"))) {
if (RescloneConfig.logProcessing) Resclone.LOGGER.info("Moving discovered root out of: {}", path);
Files.walkFileTree(path, new MoveDirVisitor(path, root, StandardCopyOption.REPLACE_EXISTING));
}
}
@ -26,4 +25,5 @@ public class RootPathProcessor implements PackProcessor {
}
}
}
}
}

View File

@ -0,0 +1,47 @@
package io.gitlab.jfronny.resclone.util;
import com.google.gson.reflect.TypeToken;
import io.gitlab.jfronny.resclone.Resclone;
import io.gitlab.jfronny.resclone.api.RescloneApi;
import io.gitlab.jfronny.resclone.data.PackMetaUnloaded;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
public class ConfigLoader {
public static void load(RescloneApi api) {
Path configPath = api.getConfigPath().resolve("config.json");
if (!Files.exists(configPath)) {
save(api, new HashSet<>());
}
try {
StringBuilder text = new StringBuilder();
for (String s : Files.readAllLines(configPath)) {
text.append("\r\n");
text.append(s);
}
Set<PackMetaUnloaded> data = Resclone.gson.fromJson(text.toString(), new TypeToken<Set<PackMetaUnloaded>>(){}.getType());
for (PackMetaUnloaded meta : data) {
api.addPack(meta.fetcher, meta.source, meta.name, meta.forceDownload, meta.forceEnable);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void save(RescloneApi api, Set<PackMetaUnloaded> data) {
Path configPath = api.getConfigPath().resolve("config.json");
Set<String> text = new HashSet<>();
text.add(Resclone.gson.toJson(data));
try {
Files.write(configPath, text);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -1,31 +0,0 @@
package io.gitlab.jfronny.resclone.util;
import io.gitlab.jfronny.commons.serialize.SerializeReader;
import io.gitlab.jfronny.commons.throwable.ThrowingFunction;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public class ListAdaptation {
public static <T, TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> List<T> deserializeList(Reader reader, ThrowingFunction<Reader, T, TEx> deserializeOne) throws TEx {
List<T> result = new ArrayList<>();
reader.beginArray();
while (reader.hasNext()) {
result.add(deserializeOne.apply(reader));
}
reader.endArray();
return result;
}
public static <T, TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> Set<T> deserializeSet(Reader reader, ThrowingFunction<Reader, T, TEx> serializeOne) throws TEx {
Set<T> result = new LinkedHashSet<>();
reader.beginArray();
while (reader.hasNext()) {
result.add(serializeOne.apply(reader));
}
reader.endArray();
return result;
}
}

View File

@ -1,13 +1,14 @@
package io.gitlab.jfronny.resclone.util;
import io.gitlab.jfronny.resclone.Resclone;
import java.io.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
public class PackUrlCache {
private final Path file;
Properties properties = new Properties();
@ -17,7 +18,7 @@ public class PackUrlCache {
try (BufferedReader r = Files.newBufferedReader(file)) {
properties.load(r);
} catch (IOException e) {
Resclone.LOGGER.error("Could not load pack URL cache");
e.printStackTrace();
}
}
}
@ -26,7 +27,7 @@ public class PackUrlCache {
try (BufferedWriter w = Files.newBufferedWriter(file)) {
properties.store(w, "This is an internal file used for offline pack loading, do not edit");
} catch (IOException e) {
Resclone.LOGGER.error("Could not write pack URL cache");
e.printStackTrace();
}
}
@ -41,4 +42,5 @@ public class PackUrlCache {
public void set(String key, String value) {
properties.setProperty(key, value);
}
}
}

View File

@ -0,0 +1,15 @@
package io.gitlab.jfronny.resclone.util;
import java.nio.file.Path;
public class Result {
public final Path downloadPath;
public final boolean freshDownload;
public Result(Path downloadPath, boolean freshDownload) {
this.downloadPath = downloadPath;
this.freshDownload = freshDownload;
}
}

View File

@ -0,0 +1,41 @@
package io.gitlab.jfronny.resclone.util;
import com.google.gson.reflect.TypeToken;
import io.gitlab.jfronny.resclone.Resclone;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.Set;
public class UrlUtils {
public static boolean urlValid(String url) {
try {
HttpURLConnection connection = (HttpURLConnection) new URL(url).toURI().toURL().openConnection();
connection.setRequestMethod("GET");
connection.connect();
return connection.getResponseCode() == 200;
} catch (Throwable e) {
return false;
}
}
public static String readStringFromURL(String requestURL) throws IOException {
try (Scanner scanner = new Scanner(new URL(requestURL).openStream(), StandardCharsets.UTF_8.toString())) {
scanner.useDelimiter("\\A");
return scanner.hasNext() ? scanner.next() : "";
}
}
public static <T> T readJsonFromURL(String requestUrl, Class<T> classOfT) throws IOException {
return Resclone.gson.fromJson(readStringFromURL(requestUrl), classOfT);
}
public static <T> Set<T> readJsonFromURLSet(String requestUrl, Class<T> classOfT) throws IOException {
return Resclone.gson.fromJson(readStringFromURL(requestUrl), TypeToken.getParameterized(Set.class, classOfT).getType());
}
}

View File

@ -5,6 +5,7 @@ import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
public class MoveDirVisitor extends SimpleFileVisitor<Path> {
private final Path fromPath;
private final Path toPath;
private final CopyOption copyOption;
@ -35,4 +36,5 @@ public class MoveDirVisitor extends SimpleFileVisitor<Path> {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
}
}

View File

@ -1,12 +1,16 @@
package io.gitlab.jfronny.resclone.util.io;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.function.Predicate;
public class PathPruneVisitor extends SimpleFileVisitor<Path> {
private final Predicate<Path> removalSelector;
Predicate<Path> removalSelector;
public PathPruneVisitor(Predicate<Path> removalSelector) {
this.removalSelector = removalSelector;
@ -23,4 +27,5 @@ public class PathPruneVisitor extends SimpleFileVisitor<Path> {
if (removalSelector.test(dir)) Files.walkFileTree(dir, new RemoveDirVisitor());
return super.postVisitDirectory(dir, exc);
}
}
}

View File

@ -1,10 +1,14 @@
package io.gitlab.jfronny.resclone.util.io;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
public class RemoveDirVisitor extends SimpleFileVisitor<Path> {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
@ -16,4 +20,5 @@ public class RemoveDirVisitor extends SimpleFileVisitor<Path> {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
}
}

View File

@ -1,11 +0,0 @@
{
"resclone.jfconfig.title": "Resclone",
"resclone.jfconfig.packs": "Packs",
"resclone.jfconfig.packs.tooltip": "The packs to download and add",
"resclone.jfconfig.pruneUnused": "Prune Unused",
"resclone.jfconfig.pruneUnused.tooltip": "Automatically remove all downloaded packs that are not in the config to free up unneeded space",
"resclone.jfconfig.filterPacks": "Filter Packs",
"resclone.jfconfig.filterPacks.tooltip": "Whether to filter packs to remove files unchanged from vanilla and empty directories",
"resclone.jfconfig.logProcessing": "Log Processing",
"resclone.jfconfig.logProcessing.tooltip": "Log automatic processing steps applied to downloaded packs"
}

View File

@ -1,36 +1,30 @@
{
"schemaVersion": 1,
"id": "resclone",
"name": "Resclone",
"version": "${version}",
"description": "Downloads and updates resource packs",
"authors": ["JFronny"],
"contact": {
"email": "projects.contact@frohnmeyer-wds.de",
"homepage": "https://jfronny.gitlab.io",
"issues": "https://git.frohnmeyer-wds.de/JfMods/Resclone/issues",
"sources": "https://git.frohnmeyer-wds.de/JfMods/Resclone"
},
"name": "Resclone",
"description": "Downloads and updates resourcepacks.",
"authors": [
"JFronny"
],
"contact": {},
"license": "MIT",
"icon": "assets/resclone/icon.png",
"environment": "*",
"entrypoints": {
"main": ["io.gitlab.jfronny.resclone.Resclone"],
"libjf:config": [
"io.gitlab.jfronny.resclone.RescloneConfig"
"main": [
"io.gitlab.jfronny.resclone.Resclone"
],
"resclone": [
"io.gitlab.jfronny.resclone.RescloneEntryDefault"
]
},
"mixins": [
"resclone.mixins.json",
{
"config": "resclone.client.mixins.json",
"environment": "client"
}
"resclone.mixins.json"
],
"accessWidener": "resclone.accesswidener",
"depends": {
"fabricloader": ">=0.12.0",
"minecraft": "*",
"libjf-base": "*"
"fabricloader": ">=0.10.8",
"fabric": "*",
"minecraft": "*"
}
}

View File

@ -1,5 +0,0 @@
accessWidener v2 named
accessible class net/minecraft/resource/ZipResourcePack$ZipFileWrapper
accessible method net/minecraft/resource/ZipResourcePack <init> (Lnet/minecraft/resource/ResourcePackInfo;Lnet/minecraft/resource/ZipResourcePack$ZipFileWrapper;Ljava/lang/String;)V
accessible method net/minecraft/resource/ZipResourcePack$ZipFileWrapper <init> (Ljava/io/File;)V

View File

@ -2,8 +2,10 @@
"required": true,
"minVersion": "0.8",
"package": "io.gitlab.jfronny.resclone.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
"FileResourcePackProviderMixin"
"FileResourcePackProviderMixin",
"GameOptionsMixin"
],
"injectors": {
"defaultRequire": 1