Compare commits

...

31 Commits

Author SHA1 Message Date
00b43abd02
chore: update to 1.21
All checks were successful
ci/woodpecker/push/jfmod Pipeline was successful
ci/woodpecker/tag/jfmod Pipeline was successful
2024-06-14 11:05:09 +02:00
981d10d426
chore: update to 1.20.5
All checks were successful
ci/woodpecker/push/jfmod Pipeline was successful
ci/woodpecker/tag/jfmod Pipeline was successful
2024-04-25 20:44:31 +02:00
6df388aad7
feat: rewrite config/network IO for 1.20.5 2024-04-25 20:42:31 +02:00
88f071d919
feat: add option to disable additional processors
All checks were successful
ci/woodpecker/push/jfmod Pipeline was successful
ci/woodpecker/tag/jfmod Pipeline was successful
2024-04-01 15:12:02 +02:00
e8d0b38121
fix: add translation for new options
All checks were successful
ci/woodpecker/push/jfmod Pipeline was successful
2024-03-31 13:09:14 +02:00
12dd3f3301
chore: add some more optional logging
All checks were successful
ci/woodpecker/push/jfmod Pipeline was successful
2024-03-31 12:59:22 +02:00
6495212095
chore: update to 1.20.4
All checks were successful
ci/woodpecker/push/jfmod Pipeline was successful
2023-12-07 20:01:56 +01:00
c4d424b792
chore: update to 1.20.2
All checks were successful
ci/woodpecker/push/jfmod Pipeline was successful
2023-09-22 20:34:51 +02:00
d828de9464
Bump to 1.20
All checks were successful
ci/woodpecker/push/jfmod Pipeline was successful
ci/woodpecker/tag/jfmod Pipeline was successful
2023-06-09 17:02:42 +02:00
4f6e31f584
Configure CF publish
All checks were successful
ci/woodpecker/push/jfmod Pipeline was successful
2023-03-18 11:36:10 +01:00
e9dd5644e8
Port to 1.19.4 and libjf-config. Also remove the unused API as mods can still use mixin
Some checks are pending
ci/woodpecker/push/jfmod Pipeline is pending
ci/woodpecker/tag/jfmod Pipeline was successful
2023-03-14 20:58:21 +01:00
79ff7b6279
Optimize imports
All checks were successful
ci/woodpecker/push/jfmod Pipeline was successful
2023-02-26 14:00:38 +01:00
28957f946e
1.19.4-pre1 port + modrinth fetcher 2023-02-26 14:00:07 +01:00
618c4c5f42
Add devutil
All checks were successful
ci/woodpecker/push/jfmod Pipeline was successful
2022-12-29 14:34:16 +01:00
a8fa45837b
Update to 1.19.3
All checks were successful
ci/woodpecker/push/jfmod Pipeline was successful
ci/woodpecker/tag/jfmod Pipeline was successful
2022-12-07 22:59:57 +01:00
01221ca98a
Update to new infrastructure
All checks were successful
ci/woodpecker/manual/jfmod Pipeline was successful
2022-12-02 17:53:44 +01:00
8eef2338c6
Update to 1.19.2 and LibJF 3 2022-08-28 15:29:47 +02:00
162fdee877
Implement cache pruning, closes #7 2022-07-31 14:18:49 +02:00
cb99626f20
Update to 1.19.1 2022-07-28 15:36:25 +02:00
e1187cbd1f
Bump deps 2022-06-07 23:24:58 +02:00
f699ab3bf3
Modernize, support CfCore and update for 1.19-rc2 2022-06-05 17:40:50 +02:00
906a0a4d74
Fix typo 2022-03-13 17:16:48 +01:00
1823c2fae6
Add README from modrinth 2022-03-13 17:16:17 +01:00
7279baa1fc
Remove ModMenu workaround 2022-03-02 18:19:43 +01:00
a35cb2250a
1.18.2 support 2022-02-28 20:53:46 +01:00
2932dccb1c
Update for 1.18 2021-11-30 20:19:54 +01:00
J. Fronny
27e0b9fed4 Fix 2021-11-10 18:05:50 +00:00
JFronny
3427079d97
Copied CI from the right source this time 2021-11-10 18:52:28 +01:00
JFronny
719c9769f2
Update deps 2021-11-10 17:50:37 +01:00
JFronny
b43b6f5722
Update deps 2021-10-09 13:19:41 +02:00
JFronny
df80d2a152
Don't attempt to create a thread pool if there is nothing to do (fix crash) 2021-06-11 18:49:29 +02:00
50 changed files with 848 additions and 845 deletions

View File

@ -1,27 +0,0 @@
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

1
.woodpecker.yml Normal file
View File

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

67
README.md Normal file
View File

@ -0,0 +1,67 @@
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
}
```

View File

@ -1,92 +0,0 @@
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)
}
}
*/

47
build.gradle.kts Normal file
View File

@ -0,0 +1,47 @@
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")
}

View File

@ -1,22 +0,0 @@
# 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=

Binary file not shown.

View File

@ -1,5 +0,0 @@
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
View File

@ -1,185 +0,0 @@
#!/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
View File

@ -1,89 +0,0 @@
@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

9
settings.gradle.kts Normal file
View File

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

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.newPacks) {
Resclone.LOGGER.info(Resclone.MOD_ID + "/" + meta.name);
resourcePacks.add(Resclone.MOD_ID + "/" + meta.name);
for (PackMetaLoaded meta : Resclone.NEW_PACKS) {
Resclone.LOGGER.info(Resclone.MOD_ID + "/" + meta.name());
resourcePacks.add(Resclone.MOD_ID + "/" + meta.name());
}
if (!Resclone.newPacks.isEmpty())
if (!Resclone.NEW_PACKS.isEmpty())
write();
Resclone.newPacks.clear();
Resclone.NEW_PACKS.clear();
}
}

View File

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

View File

@ -1,164 +1,176 @@
package io.gitlab.jfronny.resclone;
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.commons.logger.SystemLoggerPlus;
import io.gitlab.jfronny.resclone.data.PackMetaLoaded;
import io.gitlab.jfronny.resclone.data.PackMetaUnloaded;
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.fetchers.*;
import io.gitlab.jfronny.resclone.processors.*;
import io.gitlab.jfronny.resclone.util.PackUrlCache;
import io.gitlab.jfronny.resclone.util.Result;
import net.fabricmc.api.EnvType;
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.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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 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 static final String MOD_ID = "resclone";
public static final Logger LOGGER = LogManager.getLogger(MOD_ID);
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 PackUrlCache urlCache;
public static int COUNT = 0;
public static int packCount = 0;
@Override
public void onInitialize() {
LOGGER.info("Initialising Resclone.");
urlCache = new PackUrlCache(getConfigPath().resolve("urlCache.properties"));
conf.clear();
fetcherInstances.clear();
processors.clear();
downloadedPacks.clear();
FETCHER_INSTANCES.clear();
PROCESSORS.clear();
DOWNLOADED_PACKS.clear();
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 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 RemoveEmptyProcessor());
reload();
LOGGER.info("Installed {} resource pack{}.", COUNT, COUNT == 1 ? "" : "s");
LOGGER.info("Installed {0} resource pack{1}.", packCount, packCount == 1 ? "" : "s");
}
@Override
public void addFetcher(PackFetcher fetcher) {
fetcherInstances.put(fetcher.getSourceTypeName(), fetcher);
FETCHER_INSTANCES.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) {
conf.add(new PackMetaUnloaded(fetcher, pack, name, forceRedownload, forceEnable));
RescloneConfig.packs.add(new PackMetaUnloaded(fetcher, pack, name, forceRedownload, forceEnable));
}
@Override
public void reload() {
Set<PackMetaLoaded> metas = new LinkedHashSet<>();
try {
ExecutorService pool = Executors.newFixedThreadPool(conf.size());
for (PackMetaUnloaded s : conf) {
pool.submit(generateTask(s, metas));
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);
}
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();
downloadedPacks.clear();
downloadedPacks.addAll(metas);
DOWNLOADED_PACKS.clear();
DOWNLOADED_PACKS.addAll(metas);
if (RescloneConfig.pruneUnused) pruneCache();
}
private Runnable generateTask(PackMetaUnloaded meta, Set<PackMetaLoaded> metas) {
return () -> {
try {
if (!fetcherInstances.containsKey(meta.fetcher))
throw new Exception("Invalid fetcher: " + meta.fetcher);
if (!FETCHER_INSTANCES.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
Result fr = fetcherInstances.get(meta.fetcher).get(meta.source, cacheDir, meta.forceDownload);
p = new PackMetaLoaded(fr.downloadPath, meta.name, meta.forceEnable);
PackFetcher.Result fr = FETCHER_INSTANCES.get(meta.fetcher()).get(meta.source(), cacheDir, meta.forceDownload());
p = new PackMetaLoaded(fr.downloadPath(), meta.name(), meta.forceEnable());
metas.add(p);
if (isNew)
newPacks.add(p);
if (fr.freshDownload) {
if (isNew && FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) NEW_PACKS.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) {
e.printStackTrace();
LOGGER.error("Could not run pack processors on " + p.zipPath(), e);
}
}
} 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);
}
};
}
@Override
public Path getConfigPath() {
Path configPath = FabricLoader.getInstance().getConfigDir().resolve("resclone");
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);
if (!Files.isDirectory(configPath.resolve("cache"))) {
try {
Files.createDirectories(configPath.resolve("cache"));
} catch (IOException e) {
e.printStackTrace();
LOGGER.error("Could not create cache directory", e);
}
}
return configPath;
}
}
}

View File

@ -0,0 +1,156 @@
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

@ -1,25 +0,0 @@
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,27 +3,57 @@ 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.ZipResourcePack;
import net.minecraft.resource.*;
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;
private final String name;
public RescloneResourcePack(File file, String name) {
super(file);
this.name = name;
}
@Override
public String getName() {
return name;
RescloneResourcePack(ZipFileWrapper file, ResourcePackInfo info, String overlay) {
super(info, file, overlay);
this.info = info;
this.file = file;
}
@Override
public ModMetadata getFabricModMetadata() {
return FabricLoader.getInstance().getModContainer(Resclone.MOD_ID).get().getMetadata();
return METADATA;
}
}
@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,21 +0,0 @@
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

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

View File

@ -1,11 +0,0 @@
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,14 +2,5 @@ package io.gitlab.jfronny.resclone.data;
import java.nio.file.Path;
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;
}
}
public record PackMetaLoaded(Path zipPath, String name, boolean forceEnable) {
}

View File

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

View File

@ -0,0 +1,18 @@
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

@ -0,0 +1,13 @@
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

@ -0,0 +1,18 @@
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

@ -0,0 +1,8 @@
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

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

View File

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

View File

@ -1,14 +1,35 @@
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.CfAddon;
import io.gitlab.jfronny.resclone.util.UrlUtils;
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 net.minecraft.MinecraftVersion;
import java.text.SimpleDateFormat;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.net.URISyntaxException;
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() {
@ -18,34 +39,49 @@ public class CurseforgeFetcher extends BasePackFetcher {
@Override
public String getDownloadUrl(String baseUrl) throws Exception {
try {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSS'Z'");
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");
String version = MinecraftVersion.GAME_VERSION.getName();
String version = MinecraftVersion.CURRENT.getName();
CfAddon latest = null;
GetModFilesResponse.Data latest = null;
Date latestDate = null;
boolean foundMatchingVersion = false;
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)) {
for (GetModFilesResponse.Data addon : GET(baseUrl + "/files", GC_GetModFilesResponse::deserialize).data) {
if (foundMatchingVersion && !addon.gameVersions.contains(version)) continue;
if (!foundMatchingVersion && addon.gameVersions.contains(version)) {
foundMatchingVersion = true;
latest = null;
}
if (latest == null || d.after(latestDate)) {
if (latest == null || addon.fileDate.after(latestDate)) {
latest = addon;
latestDate = d;
latestDate = addon.fileDate;
}
}
if (!foundMatchingVersion)
Resclone.LOGGER.error("Could not find pack for matching version, using latest");
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");
return latest.downloadUrl;
} catch (Throwable e) {
throw new Exception("Could not get CF download for " + baseUrl, e);
throw new IOException("Could not get CurseForge 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,12 +1,17 @@
package io.gitlab.jfronny.resclone.fetchers;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
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.util.UrlUtils;
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 org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.net.URISyntaxException;
public class GitHubFetcher extends BasePackFetcher {
@Override
@ -38,21 +43,21 @@ public class GitHubFetcher extends BasePackFetcher {
//"user/repo/release" - Gets from the latest release.
else if (parts[2].equalsIgnoreCase("release")) {
try {
JsonObject latestRelease = UrlUtils.readJsonFromURL("https://api.github.com/repos/" + parts[0] + "/" + parts[1] + "/releases/latest", JsonObject.class);
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);
String res = null;
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();
for (Release.Asset asset : latestRelease.assets) {
if ("application/x-zip-compressed".equals(asset.content_type) || asset.name.endsWith(".zip")) {
res = asset.browser_download_url;
break;
}
}
Resclone.LOGGER.info("Getting from latest release.");
if (res == null) return latestRelease.get("zipball_url").getAsString();
if (res == null) return latestRelease.zipball_url;
else return res;
} catch (Throwable e) {
@ -73,9 +78,10 @@ public class GitHubFetcher extends BasePackFetcher {
private String getFromBranch(String repo, @Nullable String branch) {
if (branch == null) {
try {
branch = UrlUtils.readJsonFromURL("https://api.github.com/repos/" + repo, JsonObject.class).get("default_branch").getAsString();
} catch (IOException e) {
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) {
Resclone.LOGGER.error("Failed to fetch branch for " + repo + ". Choosing \"main\"", e);
branch = "main";
}
@ -88,4 +94,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

@ -0,0 +1,69 @@
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

@ -1,10 +1,10 @@
package io.gitlab.jfronny.resclone.api;
import io.gitlab.jfronny.resclone.util.Result;
package io.gitlab.jfronny.resclone.fetchers;
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

@ -3,38 +3,43 @@ 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.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 net.minecraft.resource.*;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.*;
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 field_25345;
@Shadow @Final private ResourcePackSource source;
@Shadow @Final private ResourceType type;
@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
@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
)
);
if (resourcePackProfile != null) {
consumer.accept(resourcePackProfile);
}
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -1,13 +1,13 @@
package io.gitlab.jfronny.resclone.processors;
import io.gitlab.jfronny.resclone.api.PackProcessor;
import io.gitlab.jfronny.resclone.Resclone;
import io.gitlab.jfronny.resclone.RescloneConfig;
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,6 +16,7 @@ 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));
}
}
@ -25,5 +26,4 @@ public class RootPathProcessor implements PackProcessor {
}
}
}
}
}

View File

@ -1,47 +0,0 @@
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

@ -0,0 +1,31 @@
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,14 +1,13 @@
package io.gitlab.jfronny.resclone.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import io.gitlab.jfronny.resclone.Resclone;
import java.io.*;
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();
@ -18,7 +17,7 @@ public class PackUrlCache {
try (BufferedReader r = Files.newBufferedReader(file)) {
properties.load(r);
} catch (IOException e) {
e.printStackTrace();
Resclone.LOGGER.error("Could not load pack URL cache");
}
}
}
@ -27,7 +26,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) {
e.printStackTrace();
Resclone.LOGGER.error("Could not write pack URL cache");
}
}
@ -42,5 +41,4 @@ public class PackUrlCache {
public void set(String key, String value) {
properties.setProperty(key, value);
}
}
}

View File

@ -1,15 +0,0 @@
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

@ -1,41 +0,0 @@
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,7 +5,6 @@ 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;
@ -36,5 +35,4 @@ public class MoveDirVisitor extends SimpleFileVisitor<Path> {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
}
}

View File

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

View File

@ -1,14 +1,10 @@
package io.gitlab.jfronny.resclone.util.io;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.*;
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);
@ -20,5 +16,4 @@ public class RemoveDirVisitor extends SimpleFileVisitor<Path> {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
}
}

View File

@ -0,0 +1,11 @@
{
"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,30 +1,36 @@
{
"schemaVersion": 1,
"id": "resclone",
"version": "${version}",
"name": "Resclone",
"description": "Downloads and updates resourcepacks.",
"authors": [
"JFronny"
],
"contact": {},
"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"
},
"license": "MIT",
"icon": "assets/resclone/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"io.gitlab.jfronny.resclone.Resclone"
],
"resclone": [
"io.gitlab.jfronny.resclone.RescloneEntryDefault"
"main": ["io.gitlab.jfronny.resclone.Resclone"],
"libjf:config": [
"io.gitlab.jfronny.resclone.RescloneConfig"
]
},
"mixins": [
"resclone.mixins.json"
"resclone.mixins.json",
{
"config": "resclone.client.mixins.json",
"environment": "client"
}
],
"accessWidener": "resclone.accesswidener",
"depends": {
"fabricloader": ">=0.10.8",
"fabric": "*",
"minecraft": "*"
"fabricloader": ">=0.12.0",
"minecraft": "*",
"libjf-base": "*"
}
}

View File

@ -0,0 +1,5 @@
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,10 +2,8 @@
"required": true,
"minVersion": "0.8",
"package": "io.gitlab.jfronny.resclone.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
"FileResourcePackProviderMixin",
"GameOptionsMixin"
"FileResourcePackProviderMixin"
],
"injectors": {
"defaultRequire": 1