Merge branch 'refactor' into 'master'

Refactor

See merge request jfmods/LibJF!1
This commit is contained in:
J. Fronny 2021-10-09 10:49:57 +00:00
commit d90671c005
101 changed files with 2211 additions and 537 deletions

5
.gitignore vendored
View File

@ -117,3 +117,8 @@ run/
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
.classpath
bin
.project
.settings

View File

@ -9,21 +9,23 @@ before_script:
build_test:
stage: deploy
script:
- gradle --build-cache assemble
- gradle --build-cache build publish -PpublicMaven -Ppipeline=$CI_PIPELINE_ID
- cp build/libs/* ./
- mv *-dev.jar tmp.zip
- rm *-maven.jar *-sources.jar *-sources-dev.jar
- mv *-dev.jar dev.zip
- mv *.jar latest.jar
- mv tmp.zip latest-dev.jar
- mv dev.zip latest-dev.jar
artifacts:
paths:
- build/libs
- latest.jar
- latest-dev.jar
only:
- master
deploy:
stage: deploy
when: manual
script:
- gradle --build-cache publishModrinth curseforge
- gradle --build-cache publish modrinth curseforge -PpublicMaven
only:
- master

View File

@ -1,43 +1,300 @@
apply from: "https://gitlab.com/-/snippets/2121059/raw/master/jfbase.gradle"
import com.modrinth.minotaur.TaskModrinthUpload
repositories {
maven {
name = 'TerraformersMC'
url = 'https://maven.terraformersmc.com/'
plugins {
id "java"
id "idea"
id "eclipse"
id "java-library"
id "maven-publish"
id "fabric-loom" version "0.10-SNAPSHOT" apply false
id "com.matthewprenger.cursegradle" version "1.4.0"
id "com.modrinth.minotaur" version "1.1.0"
}
def ENV = System.getenv()
ext.isPublicMaven = project.hasProperty('publicMaven')
static Node getOrCreateNode(Node node, String name) {
Node dependencies = null
for(Node n : node) {
if(name == n.name()) {
dependencies = n
break
}
}
if(dependencies == null) {
dependencies = node.appendNode(name)
}
return dependencies
}
def moduleDependencies(project, List<String> depNames) {
def deps = depNames.iterator().collect { project.dependencies.project(path: ":$it", configuration: 'dev') }
project.dependencies {
deps.each {
api it
}
}
project.publishing {
publications {
mavenJava(MavenPublication) {
pom.withXml {
def depsNode = getOrCreateNode(asNode(), "dependencies")
deps.each {
def depNode = depsNode.appendNode("dependency")
depNode.appendNode("groupId", it.group)
depNode.appendNode("artifactId", it.name)
depNode.appendNode("version", it.version)
depNode.appendNode("scope", "compile")
}
}
}
}
}
}
allprojects {
apply plugin: "java-library"
apply plugin: "maven-publish"
apply plugin: "fabric-loom"
tasks.withType(JavaCompile).configureEach {
it.options.release = 16
}
group = "io.gitlab.jfronny.libjf"
version = "$project.mod_version" + (project.hasProperty('pipeline') ? "+" + project.getProperty('pipeline') : "")
sourceSets {
testmod {
compileClasspath += main.compileClasspath
runtimeClasspath += main.runtimeClasspath
}
}
loom {
runs {
testmodClient {
client()
ideConfigGenerated project.rootProject == project
name = "Testmod Client"
source sourceSets.testmod
}
testmodServer {
server()
ideConfigGenerated project.rootProject == project
name = "Testmod Server"
source sourceSets.testmod
}
}
}
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}"
modRuntime modCompileOnly("com.terraformersmc:modmenu:2.0.14")
include modImplementation(fabricApi.module("fabric-tag-extensions-v0", "${project.fabric_version}"))
include modImplementation(fabricApi.module("fabric-resource-loader-v0", "${project.fabric_version}"))
}
configurations {
dev
}
loom {
shareRemapCaches = true
}
repositories {
maven {
name = 'TerraformersMC'
url = 'https://maven.terraformersmc.com/'
}
mavenLocal()
}
jar {
archiveClassifier = "dev"
}
afterEvaluate {
remapJar {
input = file("${project.buildDir}/libs/$archivesBaseName-${project.version}-dev.jar")
archiveFileName = "${archivesBaseName}-${project.version}.jar"
}
artifacts {
dev file: file("${project.buildDir}/libs/$archivesBaseName-${project.version}-dev.jar"), type: "jar", builtBy: jar
}
processResources {
inputs.property "version", project.version
filesMatching("fabric.mod.json") {
expand "version": project.version
}
}
}
task sourcesJar(type: Jar, dependsOn: classes) {
archiveClassifier = "sources"
from sourceSets.main.allSource
}
tasks.withType(AbstractArchiveTask) {
preserveFileTimestamps = false
reproducibleFileOrder = true
}
}
subprojects {
dependencies {
testmodImplementation sourceSets.main.output
}
publishing {
publications {
mavenJava(MavenPublication) {
afterEvaluate {
artifact(remapJar) {
builtBy remapJar
}
artifact(sourcesJar) {
builtBy remapSourcesJar
}
}
}
}
setupRepositories(repositories)
}
loom.disableDeprecatedPomGeneration(publishing.publications.mavenJava)
javadoc.enabled = false
afterEvaluate {
genSources.enabled = false
unpickJar.enabled = false
}
}
task remapMavenJar(type: net.fabricmc.loom.task.RemapJarTask, dependsOn: jar) {
input = jar.archiveFile
archiveFileName = "${archivesBaseName}-${project.version}-maven.jar"
addNestedDependencies = false
}
build.dependsOn remapMavenJar
publishing {
publications {
mavenJava(MavenPublication) {
artifact(remapMavenJar) {
builtBy remapMavenJar
}
artifact(sourcesJar) {
builtBy remapSourcesJar
}
pom.withXml {
def depsNode = getOrCreateNode(asNode(), "dependencies")
subprojects.each {
def depNode = depsNode.appendNode("dependency")
depNode.appendNode("groupId", it.group)
depNode.appendNode("artifactId", it.name)
depNode.appendNode("version", it.version)
depNode.appendNode("scope", "compile")
}
}
}
}
setupRepositories(repositories)
}
loom.disableDeprecatedPomGeneration(publishing.publications.mavenJava)
void setupRepositories(RepositoryHandler repositories) {
//repositories.mavenLocal() // uncomment for testing
def ENV = System.getenv()
if (ext.isPublicMaven) {
repositories.maven {
url = "https://gitlab.com/api/v4/projects/25805200/packages/maven"
name = "gitlab"
credentials(HttpHeaderCredentials) {
name = "Job-Token"
value = ENV.CI_JOB_TOKEN
}
authentication {
header(HttpHeaderAuthentication)
}
}
}
repositories.mavenLocal()
}
subprojects.each { remapJar.dependsOn("${it.path}:remapJar") }
sourceSets {
testmod {
compileClasspath += main.compileClasspath + main.output
runtimeClasspath += main.runtimeClasspath + main.output
}
testmod
}
def devOnlyModules = [
"libjf-devutil-v0"
]
dependencies {
//to change the versions see the gradle.properties file
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}"
afterEvaluate {
subprojects.each {
api project(path: ":${it.name}", configuration: "dev")
include modImplementation(fabricApi.module("fabric-tag-extensions-v0", "0.40.1+1.17"))
include modImplementation(fabricApi.module("fabric-resource-loader-v0", "0.40.1+1.17"))
modImplementation "com.terraformersmc:modmenu:2.0.10"
}
if (!(it.name in devOnlyModules)) {
include it
}
loom {
runs {
testmodClient {
client()
ideConfigGenerated project.rootProject == project
name = "Testmod Client"
source sourceSets.testmod
}
testmodServer {
server()
ideConfigGenerated project.rootProject == project
name = "Testmod Server"
source sourceSets.testmod
testmodImplementation it.sourceSets.testmod.output
}
}
}
task modrinth(type: TaskModrinthUpload, dependsOn: remapJar) {
onlyIf {
ENV.MODRINTH_API_TOKEN
}
token = ENV.MODRINTH_API_TOKEN
projectId = "WKwQAwke"
versionNumber = version
versionName = "[${project.minecraft_version}] ${project.mod_version}"
uploadFile = remapJar
addGameVersion("${project.minecraft_version}")
addLoader('fabric')
}
curseforge {
if (ENV.CURSEFORGE_API_TOKEN) {
apiKey = ENV.CURSEFORGE_API_TOKEN
} else {
println "No CURSEFORGE_API_TOKEN specified"
}
project {
id = "482600"
releaseType = 'release'
addGameVersion "Fabric"
addGameVersion "${project.minecraft_version}"
changelog = ""
mainArtifact(file("${project.buildDir}/libs/${archivesBaseName}-${version}.jar"))
mainArtifact.displayName = "[${project.minecraft_version}] ${project.mod_version}"
afterEvaluate {
uploadTask.dependsOn(remapJar)
}
}
options {
forgeGradleIntegration = false
}
}

View File

@ -4,15 +4,10 @@ org.gradle.jvmargs=-Xmx1G
# check these on https://fabricmc.net/versions.html
minecraft_version=1.17.1
yarn_mappings=build.61
loader_version=0.11.6
loader_version=0.12.1
# Mod Properties
mod_version=1.2.3
mod_version=2.0
maven_group=io.gitlab.jfronny
archives_base_name=libjf
modrinth_id=WKwQAwke
modrinth_required_dependencies=
modrinth_optional_dependencies=
curseforge_id=482600
curseforge_required_dependencies=
curseforge_optional_dependencies=
fabric_version=0.40.6+1.17

Binary file not shown.

View File

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

269
gradlew vendored
View File

@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -17,67 +17,101 @@
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# 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
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${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"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# 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
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
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"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
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
@ -106,80 +140,95 @@ 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" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
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, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# 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"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

4
libjf-base/build.gradle Normal file
View File

@ -0,0 +1,4 @@
archivesBaseName = "libjf-base"
dependencies {
}

View File

@ -0,0 +1,25 @@
package io.gitlab.jfronny.libjf;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.gitlab.jfronny.libjf.gson.HiddenAnnotationExclusionStrategy;
import net.fabricmc.loader.api.FabricLoader;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.lang.reflect.Modifier;
public class LibJf {
private LibJf() {
}
public static final String MOD_ID = "libjf";
public static final Logger LOGGER = LogManager.getLogger(MOD_ID);
public static final Gson GSON = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
.excludeFieldsWithModifiers(Modifier.PRIVATE)
.addSerializationExclusionStrategy(new HiddenAnnotationExclusionStrategy())
.setPrettyPrinting()
.create();
// Only ever set through the libjf-unsafe-v0 testmod or user code since it causes logspam
public static boolean DEV = false;
}

View File

@ -0,0 +1,39 @@
package io.gitlab.jfronny.libjf;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
public class ResourcePath {
private final ResourceType type;
private final Identifier id;
public ResourcePath(ResourceType type, Identifier id) {
this.type = type;
this.id = id;
}
public ResourcePath(String name) throws IllegalStateException {
String[] s1 = name.split("/", 3);
if (s1.length != 3) {
throw new IllegalStateException("Could not split path string into resource type and ID due to insufficient length: " + name);
}
type = switch (s1[0]) {
case "assets" -> ResourceType.CLIENT_RESOURCES;
case "data" -> ResourceType.SERVER_DATA;
default -> throw new IllegalStateException("Unexpected value for resource type: " + s1[0] + " in: " + name);
};
id = new Identifier(s1[1], s1[2]);
}
public Identifier getId() {
return id;
}
public ResourceType getType() {
return type;
}
public String getName() {
return String.format("%s/%s/%s", type.getDirectory(), id.getNamespace(), id.getPath());
}
}

View File

@ -4,7 +4,9 @@ import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
public class HiddenAnnotationExclusionStrategy implements ExclusionStrategy {
public boolean shouldSkipClass(Class<?> clazz) { return false; }
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return fieldAttributes.getAnnotation(GsonHidden.class) != null;
}

View File

@ -0,0 +1,5 @@
package io.gitlab.jfronny.libjf.interfaces;
public interface ThrowingRunnable<TEx extends Throwable> {
void run() throws TEx;
}

View File

@ -0,0 +1,5 @@
package io.gitlab.jfronny.libjf.interfaces;
public interface ThrowingSupplier<T, TEx extends Throwable> {
T get() throws TEx;
}

View File

@ -0,0 +1,25 @@
{
"schemaVersion": 1,
"id": "libjf-base",
"name": "LibJF Base",
"version": "${version}",
"environment": "*",
"license": "MIT",
"contact": {
"website": "https://jfronny.gitlab.io",
"repo": "https://gitlab.com/jfmods/libjf"
},
"authors": [
"JFronny"
],
"depends": {
"fabricloader": ">=0.12.0",
"minecraft": "*"
},
"custom": {
"modmenu": {
"parent": "libjf",
"badges": ["library"]
}
}
}

View File

@ -0,0 +1,6 @@
{
"schemaVersion": 1,
"id": "libjf-base-testmod",
"version": "1.0",
"environment": "*"
}

View File

@ -0,0 +1,5 @@
archivesBaseName = "libjf-config-v0"
dependencies {
moduleDependencies(project, ["libjf-base", "libjf-unsafe-v0"])
}

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.libjf.config;
package io.gitlab.jfronny.libjf.config.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;

View File

@ -0,0 +1,4 @@
package io.gitlab.jfronny.libjf.config.api;
public interface JfConfig {
}

View File

@ -0,0 +1,50 @@
package io.gitlab.jfronny.libjf.config.impl;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.Entry;
import net.fabricmc.loader.api.FabricLoader;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
/** Based on https://github.com/TeamMidnightDust/MidnightLib which is based on https://github.com/Minenash/TinyConfig
* Credits to TeamMidnightDust and Minenash */
public class Config {
public final List<EntryInfo> entries = new ArrayList<>();
public Path path;
public final String modid;
public final Class<?> configClass;
public Config(String modid, Class<?> config) {
this.modid = modid;
configClass = config;
path = FabricLoader.getInstance().getConfigDir().resolve(modid + ".json");
for (Field field : config.getFields()) {
EntryInfo info = new EntryInfo();
info.field = field;
if (field.isAnnotationPresent(Entry.class))
try {
info.defaultValue = field.get(null);
} catch (IllegalAccessException ignored) {}
entries.add(info);
}
try {
LibJf.GSON.fromJson(Files.newBufferedReader(path), config); }
catch (Exception e) { write(); }
}
public void write() {
path = FabricLoader.getInstance().getConfigDir().resolve(modid + ".json");
try {
if (!Files.exists(path)) Files.createFile(path);
Files.write(path, LibJf.GSON.toJson(configClass.getDeclaredConstructor().newInstance()).getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,29 @@
package io.gitlab.jfronny.libjf.config.impl;
import com.google.common.collect.ImmutableMap;
import java.util.HashMap;
import java.util.Map;
public class ConfigHolder {
private ConfigHolder() {
}
private static final Map<String, Config> configs = new HashMap<>();
public static void registerConfig(String modId, Class<?> config) {
if (!isRegistered(config))
configs.put(modId, new Config(modId, config));
}
public static Map<String, Config> getConfigs() {
return ImmutableMap.copyOf(configs);
}
public static boolean isRegistered(Class<?> config) {
for (Config value : configs.values()) {
if (value.configClass.equals(config))
return true;
}
return false;
}
}

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.libjf.config;
package io.gitlab.jfronny.libjf.config.impl;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.text.Text;

View File

@ -1,9 +1,9 @@
package io.gitlab.jfronny.libjf.config;
package io.gitlab.jfronny.libjf.config.impl;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import io.gitlab.jfronny.libjf.Libjf;
import io.gitlab.jfronny.libjf.config.gui.TinyConfigScreen;
import io.gitlab.jfronny.libjf.config.impl.gui.TinyConfigScreen;
import io.gitlab.jfronny.libjf.LibJf;
import java.util.HashMap;
import java.util.Map;
@ -12,8 +12,8 @@ public class ModMenu implements ModMenuApi {
@Override
public Map<String, ConfigScreenFactory<?>> getProvidedConfigScreenFactories() {
Map<String, ConfigScreenFactory<?>> factories = new HashMap<>();
for (Map.Entry<String, Config> entry : Libjf.getConfigs().entrySet()) {
if (!Libjf.MOD_ID.equals(entry.getKey()))
for (Map.Entry<String, Config> entry : ConfigHolder.getConfigs().entrySet()) {
if (!LibJf.MOD_ID.equals(entry.getKey()))
factories.put(entry.getKey(), buildFactory(entry.getValue()));
}
return factories;

View File

@ -0,0 +1,20 @@
package io.gitlab.jfronny.libjf.config.impl.entry;
import io.gitlab.jfronny.libjf.config.impl.Config;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolder;
import io.gitlab.jfronny.libjf.config.impl.EntryInfo;
import io.gitlab.jfronny.libjf.config.api.Entry;
import io.gitlab.jfronny.libjf.config.impl.gui.EntryInfoWidgetBuilder;
import io.gitlab.jfronny.libjf.gson.GsonHidden;
import io.gitlab.jfronny.libjf.LibJf;
import net.fabricmc.api.ClientModInitializer;
public class JfConfigClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
for (Config config : ConfigHolder.getConfigs().values()) {
LibJf.LOGGER.info("Registring config UI for " + config.modid);
EntryInfoWidgetBuilder.initConfig(config);
}
}
}

View File

@ -0,0 +1,18 @@
package io.gitlab.jfronny.libjf.config.impl.entry;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.JfConfig;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
public class JfConfigSafe implements PreLaunchEntrypoint {
@Override
public void onPreLaunch() {
for (EntrypointContainer<JfConfig> config : FabricLoader.getInstance().getEntrypointContainers(LibJf.MOD_ID + ":config", JfConfig.class)) {
ConfigHolder.registerConfig(config.getProvider().getMetadata().getId(), config.getEntrypoint().getClass());
LibJf.LOGGER.info("Registering config for " + config.getProvider().getMetadata().getId());
}
}
}

View File

@ -0,0 +1,18 @@
package io.gitlab.jfronny.libjf.config.impl.entry;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.JfConfig;
import io.gitlab.jfronny.libjf.unsafe.DynamicEntry;
import io.gitlab.jfronny.libjf.unsafe.UltraEarlyInit;
public class JfConfigUnsafe implements UltraEarlyInit {
@Override
public void init() {
DynamicEntry.execute(LibJf.MOD_ID + ":config", JfConfig.class, s -> {
ConfigHolder.registerConfig(s.modId(), s.instance().getClass());
LibJf.LOGGER.info("Registering config for " + s.modId());
});
LibJf.LOGGER.info("Finished LibJF config entrypoint");
}
}

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.libjf.config.gui;
package io.gitlab.jfronny.libjf.config.impl.gui;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;

View File

@ -1,77 +1,62 @@
package io.gitlab.jfronny.libjf.config;
package io.gitlab.jfronny.libjf.config.impl.gui;
import io.gitlab.jfronny.libjf.Libjf;
import io.gitlab.jfronny.libjf.config.api.Entry;
import io.gitlab.jfronny.libjf.config.impl.Config;
import io.gitlab.jfronny.libjf.config.impl.EntryInfo;
import io.gitlab.jfronny.libjf.gson.GsonHidden;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.gui.widget.*;
import net.minecraft.text.*;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.Formatting;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;
/** Based on https://github.com/TeamMidnightDust/MidnightLib which is based on https://github.com/Minenash/TinyConfig
* Credits to TeamMidnightDust and Minenash */
@SuppressWarnings("unchecked")
public class Config {
@Environment(EnvType.CLIENT)
public class EntryInfoWidgetBuilder {
private static final Pattern INTEGER_ONLY = Pattern.compile("(-?[0-9]*)");
private static final Pattern DECIMAL_ONLY = Pattern.compile("-?([\\d]+\\.?[\\d]*|[\\d]*\\.?[\\d]+|\\.)");
public final List<EntryInfo> entries = new ArrayList<>();
public Path path;
public final String modid;
public final Class<?> configClass;
public Config(String modid, Class<?> config) {
this.modid = modid;
configClass = config;
path = FabricLoader.getInstance().getConfigDir().resolve(modid + ".json");
for (Field field : config.getFields()) {
EntryInfo info = new EntryInfo();
info.field = field;
if (field.isAnnotationPresent(Entry.class))
public static void initConfig(Config config) {
for (EntryInfo info : config.entries) {
if (info.field.isAnnotationPresent(Entry.class) || info.field.isAnnotationPresent(GsonHidden.class))
try {
info.defaultValue = field.get(null);
} catch (IllegalAccessException ignored) {}
entries.add(info);
initEntry(config, info);
} catch (Exception ignored) {
}
}
try {
Libjf.GSON.fromJson(Files.newBufferedReader(path), config); }
catch (Exception e) { write(); }
}
@Environment(EnvType.CLIENT)
public void initClient(EntryInfo info) {
if (!(info.field.isAnnotationPresent(Entry.class) || info.field.isAnnotationPresent(GsonHidden.class))) return;
private static void initEntry(Config config, EntryInfo info) {
if (!(info.field.isAnnotationPresent(io.gitlab.jfronny.libjf.config.api.Entry.class) || info.field.isAnnotationPresent(GsonHidden.class))) return;
Class<?> type = info.field.getType();
Entry e = info.field.getAnnotation(Entry.class);
io.gitlab.jfronny.libjf.config.api.Entry e = info.field.getAnnotation(Entry.class);
info.width = e != null ? e.width() : 0;
if (e == null) return;
if (type == int.class) textField(info, Integer::parseInt, INTEGER_ONLY, e.min(), e.max(), true);
else if (type == double.class) textField(info, Double::parseDouble, DECIMAL_ONLY, e.min(), e.max(),false);
else if (type == String.class) textField(info, String::length, null, Math.min(e.min(),0), Math.max(e.max(),1),true);
if (type == int.class) textField(config, info, Integer::parseInt, INTEGER_ONLY, e.min(), e.max(), true);
else if (type == double.class) textField(config, info, Double::parseDouble, DECIMAL_ONLY, e.min(), e.max(),false);
else if (type == String.class) textField(config, info, String::length, null, Math.min(e.min(),0), Math.max(e.max(),1),true);
else if (type == boolean.class) {
Function<Object,Text> func = value -> new LiteralText((Boolean) value ? "True" : "False").formatted((Boolean) value ? Formatting.GREEN : Formatting.RED);
Function<Object, Text> func = value -> new LiteralText((Boolean) value ? "True" : "False").formatted((Boolean) value ? Formatting.GREEN : Formatting.RED);
info.widget = new AbstractMap.SimpleEntry<ButtonWidget.PressAction, Function<Object, Text>>(button -> {
info.value = !(Boolean) info.value;
button.setMessage(func.apply(info.value));
}, func);
} else if (type.isEnum()) {
List<?> values = Arrays.asList(info.field.getType().getEnumConstants());
Function<Object,Text> func = value -> new TranslatableText(modid + ".jfconfig." + "enum." + type.getSimpleName() + "." + info.value.toString());
info.widget = new AbstractMap.SimpleEntry<ButtonWidget.PressAction, Function<Object,Text>>( button -> {
Function<Object,Text> func = value -> new TranslatableText(config.modid + ".jfconfig." + "enum." + type.getSimpleName() + "." + info.value.toString());
info.widget = new AbstractMap.SimpleEntry<ButtonWidget.PressAction, Function<Object, Text>>(button -> {
int index = values.indexOf(info.value) + 1;
info.value = values.get(index >= values.size()? 0 : index);
button.setMessage(func.apply(info.value));
@ -85,7 +70,7 @@ public class Config {
}
}
private void textField(EntryInfo info, Function<String,Number> f, Pattern pattern, double min, double max, boolean cast) {
private static void textField(Config config, EntryInfo info, Function<String,Number> f, Pattern pattern, double min, double max, boolean cast) {
boolean isNumber = pattern != null;
info.widget = (BiFunction<TextFieldWidget, ButtonWidget, Predicate<String>>) (t, b) -> s -> {
s = s.trim();
@ -107,7 +92,7 @@ public class Config {
info.tempValue = s;
t.setEditableColor(inLimits? 0xFFFFFFFF : 0xFFFF7777);
info.inLimits = inLimits;
b.active = entries.stream().allMatch(e -> e.inLimits);
b.active = config.entries.stream().allMatch(e -> e.inLimits);
if (inLimits)
info.value = isNumber? value : s;
@ -115,14 +100,4 @@ public class Config {
return true;
};
}
public void write() {
path = FabricLoader.getInstance().getConfigDir().resolve(modid + ".json");
try {
if (!Files.exists(path)) Files.createFile(path);
Files.write(path, Libjf.GSON.toJson(configClass.getDeclaredConstructor().newInstance()).getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.libjf.config.gui;
package io.gitlab.jfronny.libjf.config.impl.gui;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;

View File

@ -1,14 +1,11 @@
package io.gitlab.jfronny.libjf.config.gui;
package io.gitlab.jfronny.libjf.config.impl.gui;
import io.gitlab.jfronny.libjf.Libjf;
import io.gitlab.jfronny.libjf.config.Config;
import io.gitlab.jfronny.libjf.config.Entry;
import io.gitlab.jfronny.libjf.config.EntryInfo;
import io.gitlab.jfronny.libjf.config.gui.ButtonEntry;
import io.gitlab.jfronny.libjf.config.gui.MidnightConfigListWidget;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.impl.Config;
import io.gitlab.jfronny.libjf.config.api.Entry;
import io.gitlab.jfronny.libjf.config.impl.EntryInfo;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.ScreenTexts;
import net.minecraft.client.gui.widget.ButtonWidget;
@ -57,7 +54,7 @@ public class TinyConfigScreen extends Screen {
this.addDrawableChild(new ButtonWidget(this.width / 2 - 154, this.height - 28, 150, 20, ScreenTexts.CANCEL, button -> {
try {
Libjf.GSON.fromJson(Files.newBufferedReader(config.path), config.configClass); }
LibJf.GSON.fromJson(Files.newBufferedReader(config.path), config.configClass); }
catch (Exception e) { config.write(); }
for (EntryInfo info : config.entries) {

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"id": "libjf-config-v0",
"name": "LibJF Config",
"version": "${version}",
"authors": [
"JFronny"
],
"contact": {
"website": "https://jfronny.gitlab.io",
"repo": "https://gitlab.com/jfmods/libjf"
},
"license": "MIT",
"environment": "*",
"entrypoints": {
"modmenu": [
"io.gitlab.jfronny.libjf.config.impl.ModMenu"
],
"client": [
"io.gitlab.jfronny.libjf.config.impl.entry.JfConfigClient"
],
"libjf:preEarly": [
"io.gitlab.jfronny.libjf.config.impl.entry.JfConfigUnsafe"
],
"preLaunch": [
"io.gitlab.jfronny.libjf.config.impl.entry.JfConfigSafe"
]
},
"depends": {
"fabricloader": ">=0.12.0",
"minecraft": "*",
"libjf-base": "${version}",
"libjf-unsafe-v0": "${version}"
},
"custom": {
"modmenu": {
"badges": ["library"],
"parent": "libjf"
}
}
}

View File

@ -1,17 +1,19 @@
package io.gitlab.jfronny.libjf.test;
package io.gitlab.jfronny.libjf.config.test;
import io.gitlab.jfronny.libjf.config.Entry;
import io.gitlab.jfronny.libjf.config.JfConfig;
import io.gitlab.jfronny.libjf.config.api.JfConfig;
import io.gitlab.jfronny.libjf.config.api.Entry;
import io.gitlab.jfronny.libjf.gson.GsonHidden;
public class TestMod implements JfConfig {
public class TestConfig implements JfConfig {
@Entry public static boolean disablePacks = false;
@Entry public static int intTest = 20;
@Entry public static double decimalTest = 20;
@Entry public static String dieStr = "lolz";
@Entry @GsonHidden public static String guiOnlyStr = "lolz";
@Entry @GsonHidden
public static String guiOnlyStr = "lolz";
public static String gsonOnlyStr = "lolz";
@Entry public static Test enumTest = Test.Test;
@Entry
public static Test enumTest = Test.Test;
public enum Test {
Test, ER

View File

@ -0,0 +1,11 @@
{
"libjf-config-v0-testmod.jfconfig.title": "JfConfig example",
"libjf-config-v0-testmod.jfconfig.disablePacks": "Disable resource packs",
"libjf-config-v0-testmod.jfconfig.intTest": "Int Test",
"libjf-config-v0-testmod.jfconfig.decimalTest": "Decimal Test",
"libjf-config-v0-testmod.jfconfig.dieStr": "String Test",
"libjf-config-v0-testmod.jfconfig.enumTest": "Enum Test",
"libjf-config-v0-testmod.jfconfig.enumTest.tooltip": "Enum Test Tooltip",
"libjf-config-v0-testmod.jfconfig.enum.Test.Test": "Test",
"libjf-config-v0-testmod.jfconfig.enum.Test.ER": "ER"
}

View File

@ -0,0 +1,11 @@
{
"schemaVersion": 1,
"id": "libjf-config-v0-testmod",
"version": "1.0",
"environment": "*",
"entrypoints": {
"libjf:config": [
"io.gitlab.jfronny.libjf.config.test.TestConfig"
]
}
}

View File

@ -0,0 +1,5 @@
archivesBaseName = "libjf-data-manipulation-v0"
dependencies {
moduleDependencies(project, ["libjf-base", "libjf-unsafe-v0"])
}

View File

@ -0,0 +1,35 @@
package io.gitlab.jfronny.libjf.data.manipulation.api;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import java.util.HashSet;
import java.util.Set;
@SuppressWarnings("unused")
public class RecipeUtil {
private static final Set<Identifier> REMOVAL_BY_ID = new HashSet<>();
private static final Set<ItemStack> RECIPES_FOR_REMOVAL = new HashSet<>();
public static void removeRecipe(Identifier identifier) {
REMOVAL_BY_ID.add(identifier);
}
public static void removeRecipeFor(Item product) {
RECIPES_FOR_REMOVAL.add(product.getDefaultStack());
}
public static boolean isIdBlocked(Identifier identifier) {
return REMOVAL_BY_ID.contains(identifier);
}
public static boolean isOutputBlocked(ItemStack product) {
for (ItemStack stack : RECIPES_FOR_REMOVAL) {
if (product.isItemEqual(stack)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,84 @@
package io.gitlab.jfronny.libjf.data.manipulation.api;
import io.gitlab.jfronny.libjf.data.manipulation.impl.ResourcePackHook;
import io.gitlab.jfronny.libjf.interfaces.ThrowingRunnable;
import io.gitlab.jfronny.libjf.interfaces.ThrowingSupplier;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.function.Predicate;
public class UserResourceEvents {
public static <TVal, TEx extends Throwable> TVal disable(ThrowingSupplier<TVal, TEx> then) throws TEx {
try {
ResourcePackHook.setDisabled(true);
return then.get();
}
finally {
ResourcePackHook.setDisabled(false);
}
}
public static <TEx extends Throwable> void disable(ThrowingRunnable<TEx> then) throws TEx {
try {
ResourcePackHook.setDisabled(true);
then.run();
} finally {
ResourcePackHook.setDisabled(false);
}
}
public static final Event<Contains> CONTAINS = EventFactory.createArrayBacked(Contains.class,
(listeners) -> (type, id, previous, pack) -> {
for (Contains listener : listeners) {
previous = listener.contains(type, id, previous, pack);
}
return previous;
});
public static final Event<FindResource> FIND_RESOURCE = EventFactory.createArrayBacked(FindResource.class,
(listeners) -> ((type, namespace, prefix, maxDepth, pathFilter, previous, pack) -> {
for (FindResource listener : listeners) {
previous = listener.findResources(type, namespace, prefix, maxDepth, pathFilter, previous, pack);
}
return previous;
}));
public static final Event<Open> OPEN = EventFactory.createArrayBacked(Open.class,
(listeners) -> ((type, id, previous, pack) -> {
for (Open listener : listeners) {
previous = listener.open(type, id, previous, pack);
}
return previous;
}));
public static final Event<OpenRoot> OPEN_ROOT = EventFactory.createArrayBacked(OpenRoot.class,
(listeners) -> ((fileName, previous, pack) -> {
for (OpenRoot listener : listeners) {
previous = listener.openRoot(fileName, previous, pack);
}
return previous;
}));
public interface Contains {
boolean contains(ResourceType type, Identifier id, boolean previous, ResourcePack pack);
}
public interface FindResource {
Collection<Identifier> findResources(ResourceType type, String namespace, String prefix, int maxDepth, Predicate<String> pathFilter, Collection<Identifier> previous, ResourcePack pack);
}
public interface Open {
InputStream open(ResourceType type, Identifier id, InputStream previous, ResourcePack pack) throws IOException;
}
public interface OpenRoot {
InputStream openRoot(String fileName, InputStream previous, ResourcePack pack) throws IOException;
}
}

View File

@ -0,0 +1,66 @@
package io.gitlab.jfronny.libjf.data.manipulation.impl;
import io.gitlab.jfronny.libjf.ResourcePath;
import io.gitlab.jfronny.libjf.data.manipulation.api.UserResourceEvents;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.function.Predicate;
@SuppressWarnings("unused")
public class ResourcePackHook {
private static boolean disabled = false;
public static void setDisabled(boolean disabled) {
ResourcePackHook.disabled = disabled;
}
public static boolean isDisabled() {
return disabled;
}
public static boolean hookContains(boolean value, ResourcePack pack, ResourceType type, Identifier id) {
return disabled ? value : UserResourceEvents.CONTAINS.invoker().contains(type, id, value, pack);
}
public static InputStream hookOpen(InputStream value, ResourcePack pack, ResourceType type, Identifier id) throws IOException {
if (disabled) return value;
InputStream is = UserResourceEvents.OPEN.invoker().open(type, id, value, pack);
if (is == null)
throw new FileNotFoundException(new ResourcePath(type, id).getName() + "CN");
return is;
}
public static InputStream hookOpenEx(IOException ex, ResourcePack pack, ResourceType type, Identifier id) throws IOException {
if (disabled) throw ex;
try {
return hookOpen(null, pack, type, id);
}
catch (Throwable t) {
throw ex;
}
}
public static Collection<Identifier> hookFindResources(Collection<Identifier> value, ResourcePack pack, ResourceType type, String namespace, String prefix, int maxDepth, Predicate<String> pathFilter) {
return disabled ? value : UserResourceEvents.FIND_RESOURCE.invoker().findResources(type, namespace, prefix, maxDepth, pathFilter, value, pack);
}
public static InputStream hookOpenRoot(InputStream value, ResourcePack pack, String fileName) throws IOException {
if (disabled) return value;
InputStream is = value;
is = UserResourceEvents.OPEN_ROOT.invoker().openRoot(fileName, is, pack);
if (is == null)
throw new FileNotFoundException(fileName);
return is;
}
public static InputStream hookOpenRootEx(IOException ex, ResourcePack pack, String fileName) throws IOException {
if (disabled) throw ex;
try {
return hookOpenRoot(null, pack, fileName);
}
catch (Throwable t) {
throw ex;
}
}
}

View File

@ -0,0 +1,58 @@
package io.gitlab.jfronny.libjf.data.manipulation.impl;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmConfig;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.PatchUtil;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.modification.MethodModificationPatch;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.targeting.InterfaceImplTargetPatch;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.MethodNode;
import java.io.IOException;
import java.io.InputStream;
import java.util.Set;
public class ResourcePackHookPatch implements AsmConfig {
private static final String TARGET_CLASS_INTERMEDIARY = "net.minecraft.class_3262";
private static final String HOOK_IMPLEMENTATION = Type.getInternalName(ResourcePackHook.class);
@Override
public Set<String> skipClasses() {
return Set.of(PatchUtil.getRemapped(TARGET_CLASS_INTERMEDIARY),
"io.gitlab.jfronny.libjf.data.WrappedPack", //TODO remove for 1.18
"io.gitlab.jfronny.libjf.data.wrappedPackImpl.WrappedResourcePack",
"io.gitlab.jfronny.libjf.data.wrappedPackImpl.SafeWrappedResourcePack");
}
@Override
public Set<Patch> getPatches() {
// https://maven.fabricmc.net/docs/yarn-21w38a+build.9/net/minecraft/resource/ResourcePack.html
return Set.of(new InterfaceImplTargetPatch(TARGET_CLASS_INTERMEDIARY, new MethodModificationPatch(TARGET_CLASS_INTERMEDIARY, Set.of(
// open root
new MethodModificationPatch.MethodDescriptorPatch("method_14410", "(Ljava/lang/String;)Ljava/io/InputStream;", method -> {
catchEx(method, "hookOpenRootEx", new String[]{"java/lang/String"});
hookReturn(method, "hookOpenRoot", "Ljava/io/InputStream;", new String[]{"java/lang/String"});
}),
// open
new MethodModificationPatch.MethodDescriptorPatch("method_14405", "(Lnet/minecraft/class_3264;Lnet/minecraft/class_2960;)Ljava/io/InputStream;", method -> {
catchEx(method, "hookOpenEx", new String[]{"net/minecraft/class_3264", "net/minecraft/class_2960"});
hookReturn(method, "hookOpen", "Ljava/io/InputStream;", new String[]{"net/minecraft/class_3264", "net/minecraft/class_2960"});
}),
// find resource
new MethodModificationPatch.MethodDescriptorPatch("method_14408", "(Lnet/minecraft/class_3264;Ljava/lang/String;Ljava/lang/String;ILjava/util/function/Predicate;)Ljava/util/Collection;", method -> {
hookReturn(method, "hookFindResources", "Ljava/util/Collection;", new String[]{"net/minecraft/class_3264", "java/lang/String", "java/lang/String", "I", "java/util/function/Predicate"});
}),
// contains
new MethodModificationPatch.MethodDescriptorPatch("method_14411", "(Lnet/minecraft/class_3264;Lnet/minecraft/class_2960;)Z", method -> {
hookReturn(method, "hookContains", "Z", new String[]{"net/minecraft/class_3264", "net/minecraft/class_2960"});
})
))));
}
private void hookReturn(MethodNode method, String targetMethod, String targetType, String[] extraParamTypes) {
PatchUtil.redirectReturn(method, TARGET_CLASS_INTERMEDIARY, HOOK_IMPLEMENTATION, targetMethod, targetType, extraParamTypes);
}
private void catchEx(MethodNode method, String targetMethod, String[] extraParamTypes) {
PatchUtil.redirectExceptions(method, TARGET_CLASS_INTERMEDIARY, HOOK_IMPLEMENTATION, Type.getInternalName(IOException.class), Type.getInternalName(InputStream.class), targetMethod, extraParamTypes);
}
}

View File

@ -1,10 +1,9 @@
package io.gitlab.jfronny.libjf.mixin;
package io.gitlab.jfronny.libjf.data.manipulation.mixin;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonObject;
import io.gitlab.jfronny.libjf.Libjf;
import io.gitlab.jfronny.libjf.data.RecipeUtil;
import net.minecraft.item.ItemStack;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.data.manipulation.api.RecipeUtil;
import net.minecraft.recipe.Recipe;
import net.minecraft.recipe.RecipeManager;
import net.minecraft.util.Identifier;
@ -19,15 +18,15 @@ import java.util.Map;
@Mixin(RecipeManager.class)
public class RecipeManagerMixin {
@ModifyVariable(method = "apply", at = @At(value = "INVOKE_ASSIGN", target = "Ljava/util/Set;iterator()Ljava/util/Iterator;", ordinal = 0, remap = false))
@ModifyVariable(method = "apply(Ljava/util/Map;Lnet/minecraft/resource/ResourceManager;Lnet/minecraft/util/profiler/Profiler;)V", at = @At(value = "INVOKE_ASSIGN", target = "Ljava/util/Set;iterator()Ljava/util/Iterator;", ordinal = 0, remap = false))
private Iterator<Map.Entry<Identifier, JsonObject>> filterIterator(Iterator<Map.Entry<Identifier, JsonObject>> iterator) {
ArrayList<Map.Entry<Identifier, JsonObject>> replacement = new ArrayList<>();
while(iterator.hasNext()) {
Map.Entry<Identifier, JsonObject> cur = iterator.next();
Identifier recipeId = cur.getKey();
if (RecipeUtil.getIdentifiersForRemoval().contains(recipeId.toString())) {
Libjf.LOGGER.info("Blocking recipe by identifier: " + recipeId);
if (RecipeUtil.isIdBlocked(recipeId)) {
LibJf.LOGGER.info("Blocking recipe by identifier: " + recipeId);
} else {
replacement.add(cur);
}
@ -36,16 +35,14 @@ public class RecipeManagerMixin {
return replacement.iterator();
}
@Redirect(method = "apply", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableMap$Builder;put(Ljava/lang/Object;Ljava/lang/Object;)Lcom/google/common/collect/ImmutableMap$Builder;", remap = false))
@Redirect(method = "apply(Ljava/util/Map;Lnet/minecraft/resource/ResourceManager;Lnet/minecraft/util/profiler/Profiler;)V", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableMap$Builder;put(Ljava/lang/Object;Ljava/lang/Object;)Lcom/google/common/collect/ImmutableMap$Builder;", remap = false))
private ImmutableMap.Builder<Identifier, Recipe<?>> onPutRecipe(ImmutableMap.Builder<Identifier, Recipe<?>> builder, Object key, Object value) {
Identifier id = (Identifier) key;
Recipe<?> recipe = (Recipe<?>) value;
for (ItemStack stack : RecipeUtil.getRecipesForRemoval()) {
if (recipe.getOutput().isItemEqual(stack)) {
Libjf.LOGGER.info("Blocked recipe by predicate: " + recipe.getId());
return builder;
}
if (RecipeUtil.isOutputBlocked(recipe.getOutput())) {
LibJf.LOGGER.info("Blocked recipe by predicate: " + recipe.getId());
return builder;
}
return builder.put(id, recipe);

View File

@ -0,0 +1,33 @@
{
"schemaVersion": 1,
"id": "libjf-data-manipulation-v0",
"name": "LibJF Data Manipulation",
"version": "${version}",
"authors": [
"JFronny"
],
"contact": {
"website": "https://jfronny.gitlab.io",
"repo": "https://gitlab.com/jfmods/libjf"
},
"license": "MIT",
"environment": "*",
"mixins": ["libjf-data-manipulation-v0.mixins.json"],
"entrypoints": {
"libjf:asm": [
"io.gitlab.jfronny.libjf.data.manipulation.impl.ResourcePackHookPatch"
]
},
"depends": {
"fabricloader": ">=0.12.0",
"minecraft": "*",
"libjf-base": "${version}",
"libjf-unsafe-v0": "${version}"
},
"custom": {
"modmenu": {
"parent": "libjf",
"badges": ["library"]
}
}
}

View File

@ -0,0 +1,14 @@
{
"required": true,
"minVersion": "0.8",
"package": "io.gitlab.jfronny.libjf.data.manipulation.mixin",
"compatibilityLevel": "JAVA_16",
"mixins": [
"RecipeManagerMixin"
],
"client": [
],
"injectors": {
"defaultRequire": 1
}
}

View File

@ -0,0 +1,28 @@
package io.gitlab.jfronny.libjf.data.manipulation.test;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.data.manipulation.api.RecipeUtil;
import io.gitlab.jfronny.libjf.data.manipulation.api.UserResourceEvents;
import net.fabricmc.api.ModInitializer;
import net.minecraft.item.Items;
import net.minecraft.resource.DirectoryResourcePack;
public class TestEntrypoint implements ModInitializer {
@Override
public void onInitialize() {
// This should prevent resource packs from doing anything if my hooks are working and
UserResourceEvents.OPEN.register((type, id, previous, pack) -> {
if (pack instanceof DirectoryResourcePack) {
LibJf.LOGGER.info(pack.getName() + " opened " + type.name() + "/" + id.toString());
}
return previous;
});
UserResourceEvents.CONTAINS.register((type, id, previous, pack) -> {
if (pack instanceof DirectoryResourcePack) {
return false;
}
return previous;
});
RecipeUtil.removeRecipeFor(Items.DIAMOND_SWORD);
}
}

View File

@ -0,0 +1,11 @@
{
"schemaVersion": 1,
"id": "libjf-data-manipulation-v0-testmod",
"version": "1.0",
"environment": "*",
"entrypoints": {
"main": [
"io.gitlab.jfronny.libjf.data.manipulation.test.TestEntrypoint"
]
}
}

View File

@ -0,0 +1,5 @@
archivesBaseName = "libjf-data-v0"
dependencies {
moduleDependencies(project, ["libjf-base"])
}

View File

@ -0,0 +1,12 @@
package io.gitlab.jfronny.libjf.data;
import io.gitlab.jfronny.libjf.LibJf;
import net.fabricmc.fabric.api.tag.TagFactory;
import net.minecraft.item.Item;
import net.minecraft.tag.Tag;
import net.minecraft.util.Identifier;
public class Tags {
public static final Tag<Item> SHULKER_ILLEGAL = TagFactory.ITEM.create(new Identifier(LibJf.MOD_ID, "shulker_boxes_illegal"));
public static final Tag<Item> OVERPOWERED = TagFactory.ITEM.create(new Identifier(LibJf.MOD_ID, "overpowered"));
}

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.libjf.mixin;
package io.gitlab.jfronny.libjf.data.mixin;
import io.gitlab.jfronny.libjf.data.Tags;
import net.minecraft.entity.Entity;

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.libjf.mixin;
package io.gitlab.jfronny.libjf.data.mixin;
import io.gitlab.jfronny.libjf.data.Tags;
import net.minecraft.block.entity.ShulkerBoxBlockEntity;

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.libjf.mixin;
package io.gitlab.jfronny.libjf.data.mixin;
import io.gitlab.jfronny.libjf.data.Tags;
import net.minecraft.item.ItemStack;

View File

@ -0,0 +1,27 @@
{
"schemaVersion": 1,
"id": "libjf-data-v0",
"name": "LibJF Data",
"version": "${version}",
"authors": [
"JFronny"
],
"contact": {
"website": "https://jfronny.gitlab.io",
"repo": "https://gitlab.com/jfmods/libjf"
},
"license": "MIT",
"environment": "*",
"mixins": ["libjf-data-v0.mixins.json"],
"depends": {
"fabricloader": ">=0.12.0",
"minecraft": "*",
"libjf-base": "${version}"
},
"custom": {
"modmenu": {
"parent": "libjf",
"badges": ["library"]
}
}
}

View File

@ -1,12 +1,10 @@
{
"required": true,
"minVersion": "0.8",
"package": "io.gitlab.jfronny.libjf.mixin",
"package": "io.gitlab.jfronny.libjf.data.mixin",
"compatibilityLevel": "JAVA_16",
"mixins": [
"EntityMixin",
"RecipeManagerMixin",
"ReloadableResourceManagerImplMixin",
"ShulkerBoxBlockEntityMixin",
"ShulkerBoxSlotMixin"
],

View File

@ -0,0 +1,9 @@
{
"replace": false,
"values": [
"minecraft:netherite_helmet",
"minecraft:netherite_chestplate",
"minecraft:netherite_leggings",
"minecraft:netherite_boots"
]
}

View File

@ -0,0 +1,6 @@
{
"schemaVersion": 1,
"id": "libjf-data-v0-testmod",
"version": "1.0",
"environment": "*"
}

View File

@ -0,0 +1,5 @@
archivesBaseName = "libjf-devutil-v0"
dependencies {
moduleDependencies(project, ["libjf-base"])
}

View File

@ -0,0 +1,11 @@
package io.gitlab.jfronny.libjf.devutil;
import net.fabricmc.api.ModInitializer;
import net.minecraft.SharedConstants;
public class DevUtilMain implements ModInitializer {
@Override
public void onInitialize() {
SharedConstants.isDevelopment = true;
}
}

View File

@ -0,0 +1,111 @@
package io.gitlab.jfronny.libjf.devutil;
import com.mojang.authlib.minecraft.TelemetryEvent;
import com.mojang.authlib.minecraft.TelemetryPropertyContainer;
import com.mojang.authlib.minecraft.TelemetrySession;
import com.mojang.authlib.minecraft.UserApiService;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
public class NoOpUserApi implements UserApiService {
@Override
public boolean serversAllowed() {
return true;
}
@Override
public boolean realmsAllowed() {
return false;
}
@Override
public boolean chatAllowed() {
return true;
}
@Override
public boolean telemetryAllowed() {
return false;
}
@Override
public boolean isBlockedPlayer(UUID playerID) {
return false;
}
@Override
public void refreshBlockList() {
}
@Override
public TelemetrySession newTelemetrySession(Executor executor) {
return new TelemetrySession() {
@Override
public boolean isEnabled() {
return false;
}
@Override
public TelemetryPropertyContainer globalProperties() {
return new TelemetryPropertyContainer() {
@Override
public void addProperty(String id, String value) {
// ignored
}
@Override
public void addProperty(String id, int value) {
// ignored
}
@Override
public void addProperty(String id, boolean value) {
// ignored
}
@Override
public void addNullProperty(String id) {
// ignored
}
};
}
@Override
public void eventSetupFunction(Consumer<TelemetryPropertyContainer> eventSetupFunction) {
// ignored
}
@Override
public TelemetryEvent createNewEvent(String type) {
return new TelemetryEvent() {
@Override
public void send() {
// ignored
}
@Override
public void addProperty(String id, String value) {
// ignored
}
@Override
public void addProperty(String id, int value) {
// ignored
}
@Override
public void addProperty(String id, boolean value) {
// ignored
}
@Override
public void addNullProperty(String id) {
// ignored
}
};
}
};
}
}

View File

@ -0,0 +1,21 @@
package io.gitlab.jfronny.libjf.devutil.mixin;
import com.mojang.authlib.minecraft.UserApiService;
import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
import io.gitlab.jfronny.libjf.devutil.NoOpUserApi;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.RunArgs;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@Mixin(MinecraftClient.class)
public class MinecraftClientMixin {
/**
* @author JFronny
* @reason The social interactions service will not work in dev envs and only produces log spam
*/
@Overwrite
private UserApiService createUserApiService(YggdrasilAuthenticationService yggdrasilAuthenticationService, RunArgs runArgs) {
return new NoOpUserApi();
}
}

View File

@ -0,0 +1,32 @@
{
"schemaVersion": 1,
"id": "libjf-devutil-v0",
"name": "LibJF Development Util",
"version": "${version}",
"authors": [
"JFronny"
],
"contact": {
"website": "https://jfronny.gitlab.io",
"repo": "https://gitlab.com/jfmods/libjf"
},
"license": "MIT",
"environment": "*",
"entrypoints": {
"main": [
"io.gitlab.jfronny.libjf.devutil.DevUtilMain"
]
},
"mixins": ["libjf-devutil-v0.mixins.json"],
"depends": {
"fabricloader": ">=0.12.0",
"minecraft": "*",
"libjf-base": "${version}"
},
"custom": {
"modmenu": {
"parent": "libjf",
"badges": ["library"]
}
}
}

View File

@ -0,0 +1,14 @@
{
"required": true,
"minVersion": "0.8",
"package": "io.gitlab.jfronny.libjf.devutil.mixin",
"compatibilityLevel": "JAVA_16",
"mixins": [
"MinecraftClientMixin"
],
"client": [
],
"injectors": {
"defaultRequire": 1
}
}

View File

@ -0,0 +1,5 @@
archivesBaseName = "libjf-unsafe-v0"
dependencies {
moduleDependencies(project, ["libjf-base"])
}

View File

@ -1,13 +1,13 @@
package io.gitlab.jfronny.libjf.entry;
package io.gitlab.jfronny.libjf.unsafe;
import it.unimi.dsi.fastutil.objects.ReferenceArrayList;
import net.fabricmc.loader.ModContainer;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.metadata.EntrypointMetadata;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.impl.ModContainerImpl;
import net.fabricmc.loader.impl.metadata.EntrypointMetadata;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
@ -46,9 +46,8 @@ public class DynamicEntry {
private static ReferenceArrayList<EntrypointContainer> getEntrypointTargets(final String entrypoint) {
final ReferenceArrayList<EntrypointContainer> entrypoints = ReferenceArrayList.wrap(new EntrypointContainer[1], 0);
for (final ModContainer mod : (Collection<ModContainer>) (Object) FabricLoader.getInstance().getAllMods()) {
final List<EntrypointMetadata> modEntrypoints = mod.getInfo().getEntrypoints(entrypoint);
for (final ModContainer mod : FabricLoader.getInstance().getAllMods()) {
final List<EntrypointMetadata> modEntrypoints = ((ModContainerImpl)mod).getInfo().getEntrypoints(entrypoint);
if (modEntrypoints != null) {
for (final EntrypointMetadata metadata : modEntrypoints) {

View File

@ -0,0 +1,15 @@
package io.gitlab.jfronny.libjf.unsafe;
import io.gitlab.jfronny.libjf.LibJf;
import net.fabricmc.loader.api.LanguageAdapter;
public class JfLanguageAdapter implements LanguageAdapter {
@Override
public native <T> T create(net.fabricmc.loader.api.ModContainer mod, String value, Class<T> type);
static {
DynamicEntry.execute(LibJf.MOD_ID + ":preEarly", UltraEarlyInit.class, s -> s.instance().init());
DynamicEntry.execute(LibJf.MOD_ID + ":early", UltraEarlyInit.class, s -> s.instance().init());
LibJf.LOGGER.info("LibJF unsafe init completed");
}
}

View File

@ -0,0 +1,81 @@
package io.gitlab.jfronny.libjf.unsafe;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmConfig;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmTransformer;
import io.gitlab.jfronny.libjf.unsafe.asm.BakedAsmConfig;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import org.spongepowered.asm.mixin.transformer.IMixinTransformer;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class MixinPlugin implements IMixinConfigPlugin {
@Override
public void onLoad(String mixinPackage) {
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> classLoaderClass = classLoader.getClass();
Field delegateField = classLoaderClass.getDeclaredField("delegate");
delegateField.setAccessible(true);
Object delegate = delegateField.get(classLoader);
Class<?> delegateClass = delegate.getClass();
Field mixinTransformerField = delegateClass.getDeclaredField("mixinTransformer");
mixinTransformerField.setAccessible(true);
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
AsmTransformer.INSTANCE = (AsmTransformer) unsafe.allocateInstance(AsmTransformer.class);
AsmTransformer.INSTANCE.delegate = (IMixinTransformer) mixinTransformerField.get(delegate);
AsmTransformer.INSTANCE.asmConfigs = new HashSet<>();
DynamicEntry.execute(LibJf.MOD_ID + ":asm", AsmConfig.class, s -> {
LibJf.LOGGER.info("Discovered LibJF asm plugin in " + s.modId());
AsmTransformer.INSTANCE.asmConfigs.add(new BakedAsmConfig(s.instance()));
});
mixinTransformerField.set(delegate, AsmTransformer.INSTANCE);
} catch (NoSuchFieldException | IllegalAccessException | InstantiationException e) {
LibJf.LOGGER.error("Could not initialize LibJF ASM", e);
}
}
@Override
public String getRefMapperConfig() {
return null;
}
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
return false;
}
@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
}
@Override
public List<String> getMixins() {
return null;
}
@Override
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
}
@Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
}
}

View File

@ -0,0 +1,5 @@
package io.gitlab.jfronny.libjf.unsafe;
public interface UltraEarlyInit {
void init();
}

View File

@ -0,0 +1,10 @@
package io.gitlab.jfronny.libjf.unsafe.asm;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import java.util.Set;
public interface AsmConfig {
Set<String> skipClasses();
Set<Patch> getPatches();
}

View File

@ -0,0 +1,126 @@
package io.gitlab.jfronny.libjf.unsafe.asm;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.MappingResolver;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.MixinEnvironment;
import org.spongepowered.asm.mixin.transformer.IMixinTransformer;
import org.spongepowered.asm.mixin.transformer.ext.IExtensionRegistry;
import org.spongepowered.asm.transformers.MixinClassWriter;
import java.util.List;
import java.util.Set;
public class AsmTransformer implements IMixinTransformer {
public static AsmTransformer INSTANCE;
public static final MappingResolver MAPPING_RESOLVER = FabricLoader.getInstance().getMappingResolver();
public static final String INTERMEDIARY = "intermediary";
public IMixinTransformer delegate;
public Set<AsmConfig> asmConfigs;
private AsmConfig currentConfig = null;
@Override
public void audit(MixinEnvironment environment) {
delegate.audit(environment);
}
@Override
public List<String> reload(String mixinClass, ClassNode classNode) {
return delegate.reload(mixinClass, classNode);
}
@Override
public boolean computeFramesForClass(MixinEnvironment environment, String name, ClassNode classNode) {
return delegate.computeFramesForClass(environment, name, classNode);
}
@Override
public byte[] transformClassBytes(String name, String transformedName, byte[] classBytes) {
classBytes = delegate.transformClassBytes(name, transformedName, classBytes);
if (classBytes == null || name == null)
return classBytes;
if (isClassUnmoddable(name, null)) {
if (LibJf.DEV) LibJf.LOGGER.info("Skipping " + name);
return classBytes;
}
ClassNode klazz = new ClassNode();
ClassReader reader = new ClassReader(classBytes);
reader.accept(klazz, ClassReader.EXPAND_FRAMES); //ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG
//if ((klazz.access & Opcodes.ACC_INTERFACE) != 0) {
// return classBytes;
//}
for (AsmConfig config : asmConfigs) {
currentConfig = config;
if (!isClassUnmoddable(name, config)) {
for (Patch patch : config.getPatches()) {
patch.apply(klazz);
}
}
}
currentConfig = null;
ClassWriter writer = new MixinClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
try {
klazz.accept(writer);
}
catch (NullPointerException t) {
LibJf.LOGGER.error("Could not transform " + transformedName, t);
return null;
}
classBytes = writer.toByteArray();
//MixinEnvironment.getCurrentEnvironment();
return classBytes;
}
@Override
public byte[] transformClass(MixinEnvironment environment, String name, byte[] classBytes) {
LibJf.LOGGER.error("transformClass called");
return delegate.transformClass(environment, name, classBytes);
}
@Override
public boolean transformClass(MixinEnvironment environment, String name, ClassNode classNode) {
LibJf.LOGGER.error("transformClass called");
return delegate.transformClass(environment, name, classNode);
}
@Override
public byte[] generateClass(MixinEnvironment environment, String name) {
LibJf.LOGGER.error("generateClass called");
return delegate.generateClass(environment, name);
}
@Override
public boolean generateClass(MixinEnvironment environment, String name, ClassNode classNode) {
LibJf.LOGGER.error("generateClass called");
return delegate.generateClass(environment, name, classNode);
}
@Override
public IExtensionRegistry getExtensions() {
return delegate.getExtensions();
}
public static boolean isClassUnmoddable(String className, AsmConfig config) {
if (className.replace('/', '.').startsWith("org.objectweb.asm")
//|| className.startsWith("net.fabricmc.loader")
//|| className.startsWith("io.gitlab.jfronny.libjf.unsafe.asm")
)
return true;
if (config == null) return false;
Set<String> classes = config.skipClasses();
if (classes == null) return false;
return classes.contains(MAPPING_RESOLVER.unmapClassName(INTERMEDIARY, className));
}
public AsmConfig getCurrentConfig() {
return currentConfig;
}
}

View File

@ -0,0 +1,24 @@
package io.gitlab.jfronny.libjf.unsafe.asm;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import java.util.Set;
public class BakedAsmConfig implements AsmConfig {
private final Set<String> skipClasses;
private final Set<Patch> patches;
public BakedAsmConfig(AsmConfig config) {
skipClasses = config.skipClasses();
patches = config.getPatches();
}
@Override
public Set<String> skipClasses() {
return skipClasses;
}
@Override
public Set<Patch> getPatches() {
return patches;
}
}

View File

@ -0,0 +1,7 @@
package io.gitlab.jfronny.libjf.unsafe.asm.patch;
import org.objectweb.asm.tree.MethodNode;
public interface MethodPatch {
void apply(MethodNode method);
}

View File

@ -0,0 +1,7 @@
package io.gitlab.jfronny.libjf.unsafe.asm.patch;
import org.objectweb.asm.tree.ClassNode;
public interface Patch {
void apply(ClassNode klazz);
}

View File

@ -0,0 +1,67 @@
package io.gitlab.jfronny.libjf.unsafe.asm.patch;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmTransformer;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;
public class PatchUtil {
public static String getRemappedInternal(String className) {
return getRemapped(className.replace('/', '.')).replace('.', '/');
}
public static String getRemapped(String className) {
return AsmTransformer.MAPPING_RESOLVER.mapClassName(AsmTransformer.INTERMEDIARY, className);
}
public static void redirectReturn(MethodNode method, String targetClass, String hookClass, String targetMethod, String targetType, String[] extraParamTypes) {
for (AbstractInsnNode node : method.instructions) {
if (node.getOpcode() == Opcodes.ARETURN
|| node.getOpcode() == Opcodes.IRETURN
|| node.getOpcode() == Opcodes.DRETURN
|| node.getOpcode() == Opcodes.FRETURN
|| node.getOpcode() == Opcodes.LRETURN) {
method.instructions.insertBefore(node, PatchUtil.buildParamPassingInvoker(targetClass, hookClass, targetMethod, targetType, targetType, extraParamTypes));
}
}
}
public static void redirectExceptions(MethodNode method, String targetClass, String hookClass, String exceptionType, String returnTypeNormal, String targetMethod, String[] extraParamTypes) {
LabelNode start = new LabelNode();
LabelNode end = new LabelNode();
LabelNode handler = new LabelNode();
method.instructions.insertBefore(method.instructions.getFirst(), start);
method.instructions.insertBefore(method.instructions.getLast(), end);
method.instructions.add(handler);
method.instructions.add(PatchUtil.buildParamPassingInvoker(targetClass,
hookClass,
targetMethod,
"L" + getRemappedInternal(exceptionType) + ";",
"L" + getRemappedInternal(returnTypeNormal) + ";",
extraParamTypes));
method.instructions.add(new InsnNode(Opcodes.ARETURN));
method.tryCatchBlocks.add(new TryCatchBlockNode(start, end, handler, exceptionType));
}
public static InsnList buildParamPassingInvoker(String targetClass, String hookClass, String targetMethod, String inType, String outType, String[] extraParamTypes) {
InsnList instructions = new InsnList();
instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
StringBuilder descriptor = new StringBuilder("(").append(inType);
descriptor.append('L').append(getRemappedInternal(targetClass)).append(';');
for (int i = 0; i < extraParamTypes.length; i++) {
instructions.add(new VarInsnNode(switch (extraParamTypes[i]) {
case "I" -> Opcodes.ILOAD;
case "D" -> Opcodes.DLOAD;
case "F" -> Opcodes.FLOAD;
case "L" -> Opcodes.LLOAD;
default -> Opcodes.ALOAD;
}, i + 1));
if (extraParamTypes[i].length() == 1)
descriptor.append(extraParamTypes[i]);
else
descriptor.append('L').append(PatchUtil.getRemappedInternal(extraParamTypes[i])).append(';');
}
descriptor.append(")").append(outType);
instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, hookClass, targetMethod, descriptor.toString()));
return instructions;
}
}

View File

@ -0,0 +1,23 @@
package io.gitlab.jfronny.libjf.unsafe.asm.patch.method;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.MethodPatch;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodNode;
public class MethodReplacementPatch implements MethodPatch {
private final InsnList instructions;
public MethodReplacementPatch(InsnList instructions) {
this.instructions = instructions;
}
@Override
public void apply(MethodNode method) {
method.instructions.clear();
InsnList clone = new InsnList();
for (AbstractInsnNode instruction : instructions) {
clone.add(instruction);
}
method.instructions.insert(clone);
}
}

View File

@ -0,0 +1,42 @@
package io.gitlab.jfronny.libjf.unsafe.asm.patch.modification;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmTransformer;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.MethodPatch;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class MethodModificationPatch implements Patch {
private final Map<String, MethodPatch> patches = new LinkedHashMap<>();
public MethodModificationPatch(String targetMethodOwner, Set<MethodDescriptorPatch> patches) {
for (MethodDescriptorPatch patch : patches) {
this.patches.put(
AsmTransformer.MAPPING_RESOLVER.mapMethodName(AsmTransformer.INTERMEDIARY, targetMethodOwner, patch.methodName, patch.methodDescriptor),
patch.patch
);
}
for (String s : this.patches.keySet()) {
if (LibJf.DEV) LibJf.LOGGER.info("Registered patch for " + s);
}
}
@Override
public void apply(ClassNode klazz) {
for (MethodNode method : klazz.methods) {
if (patches.containsKey(method.name)) {
if (LibJf.DEV) LibJf.LOGGER.info("Patching " + method.name);
patches.get(method.name).apply(method);
}
}
}
public static record MethodDescriptorPatch(String methodName, String methodDescriptor, MethodPatch patch) {
}
}

View File

@ -0,0 +1,107 @@
package io.gitlab.jfronny.libjf.unsafe.asm.patch.targeting;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmTransformer;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class InterfaceImplTargetPatch implements Patch {
public static final Map<String, Set<String>> INTERFACES = new HashMap<>();
private final String targetInterface;
private final Patch methodPatch;
public InterfaceImplTargetPatch(String targetInterfaceIntermediary, Patch methodPatch) {
this.targetInterface = AsmTransformer.MAPPING_RESOLVER.mapClassName(AsmTransformer.INTERMEDIARY, targetInterfaceIntermediary).replace('.', '/');
this.methodPatch = methodPatch;
}
@Override
public void apply(ClassNode klazz) {
scanInterfaces(klazz);
if (getUpper(klazz.name).contains(targetInterface)) {
if (LibJf.DEV) LibJf.LOGGER.info("Found " + klazz.name + " implementing " + targetInterface);
methodPatch.apply(klazz);
}
}
private static void scanInterfaces(ClassNode klazz) {
if (INTERFACES.containsKey(klazz.name)) return;
INTERFACES.put(klazz.name, new HashSet<>());
for (String anInterface : klazz.interfaces) {
INTERFACES.get(klazz.name).add(anInterface);
}
if (klazz.superName != null) {
INTERFACES.get(klazz.name).add(klazz.superName);
}
INTERFACES.put(klazz.name, Set.copyOf(INTERFACES.get(klazz.name)));
for (String s : INTERFACES.get(klazz.name)) {
String n = s.replace('/', '.');
if (AsmTransformer.isClassUnmoddable(n, AsmTransformer.INSTANCE.getCurrentConfig()))
continue;
try {
InterfaceImplTargetPatch.class.getClassLoader().loadClass(n);
} catch (Throwable e) {
throw new RuntimeException("Could not load super class " + s + " of " + klazz.name, e);
}
}
}
private static void scanInterfaces(Class<?> klazz) {
String n = Type.getInternalName(klazz);
if (INTERFACES.containsKey(n)) return;
INTERFACES.put(n, new HashSet<>());
for (Class<?> anInterface : klazz.getInterfaces()) {
INTERFACES.get(n).add(Type.getInternalName(anInterface));
}
Class<?> superC = klazz.getSuperclass();
if (superC != null) {
INTERFACES.get(n).add(Type.getInternalName(superC));
}
INTERFACES.put(n, Set.copyOf(INTERFACES.get(n)));
for (String s : INTERFACES.get(n)) {
String nn = s.replace('/', '.');
if (AsmTransformer.isClassUnmoddable(nn, AsmTransformer.INSTANCE.getCurrentConfig()))
continue;
try {
scanInterfaces(InterfaceImplTargetPatch.class.getClassLoader().loadClass(nn));
} catch (Throwable e) {
throw new RuntimeException("Could not load super class " + s + " of " + n, e);
}
}
}
public static Set<String> getUpper(String className) {
Set<String> s = INTERFACES.get(className);
if (s == null) {
if (!className.startsWith("java/")
&& !className.startsWith("com/mojang/")
&& !className.startsWith("net/minecraft/")
&& !className.startsWith("jdk/")
&& !className.startsWith("it/unimi/dsi/fastutil/")
&& !className.startsWith("com/google/")
) {
if (LibJf.DEV) LibJf.LOGGER.info("Non-default class not considered for interface scanning: " + className);
INTERFACES.put(className, Set.of());
return Set.of();
}
try {
scanInterfaces(Class.forName(className.replace('/', '.')));
s = INTERFACES.get(className);
} catch (ClassNotFoundException e) {
LibJf.LOGGER.error("Could not get base for " + className, e);
return Set.of();
}
}
s = new HashSet<>(s);
for (String s1 : s.toArray(new String[0])) {
s.addAll(getUpper(s1));
}
return s;
}
}

View File

@ -0,0 +1,30 @@
{
"schemaVersion": 1,
"id": "libjf-unsafe-v0",
"name": "LibJF Unsafe",
"version": "${version}",
"authors": [
"JFronny"
],
"contact": {
"website": "https://jfronny.gitlab.io",
"repo": "https://gitlab.com/jfmods/libjf"
},
"license": "MIT",
"environment": "*",
"languageAdapters": {
"libjf": "io.gitlab.jfronny.libjf.unsafe.JfLanguageAdapter"
},
"mixins": ["libjf-unsafe-v0.mixins.json"],
"depends": {
"fabricloader": ">=0.12.0",
"minecraft": "*",
"libjf-base": "${version}"
},
"custom": {
"modmenu": {
"parent": "libjf",
"badges": ["library"]
}
}
}

View File

@ -0,0 +1,14 @@
{
"required": true,
"minVersion": "0.8",
"package": "io.gitlab.jfronny.libjf.data.mixin",
"compatibilityLevel": "JAVA_16",
"plugin": "io.gitlab.jfronny.libjf.unsafe.MixinPlugin",
"mixins": [
],
"client": [
],
"injectors": {
"defaultRequire": 1
}
}

View File

@ -0,0 +1,45 @@
package io.gitlab.jfronny.libjf.unsafe.test;
import com.mojang.blaze3d.systems.RenderSystem;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmConfig;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import net.minecraft.client.main.Main;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import java.util.Set;
public class AsmTest implements AsmConfig {
@Override
public Set<String> skipClasses() {
return null;
}
@Override
public Set<Patch> getPatches() {
return Set.of(klazz -> {
if (klazz.name.equals("com/mojang/blaze3d/systems/RenderSystem")) {
for (MethodNode method : klazz.methods) {
if (method.name.equals("initRenderThread")) {
method.instructions.insert(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(AsmTest.class), "logSuccess", "()V"));
}
}
}
});
}
public static void logSuccess() {
LibJf.LOGGER.info("Successfully ASMd into RenderSystem\n" +
":::'###:::::'######::'##::::'##:'########:'########::'######::'########:\n" +
"::'## ##:::'##... ##: ###::'###:... ##..:: ##.....::'##... ##:... ##..::\n" +
":'##:. ##:: ##:::..:: ####'####:::: ##:::: ##::::::: ##:::..::::: ##::::\n" +
"'##:::. ##:. ######:: ## ### ##:::: ##:::: ######:::. ######::::: ##::::\n" +
" #########::..... ##: ##. #: ##:::: ##:::: ##...:::::..... ##:::: ##::::\n" +
" ##.... ##:'##::: ##: ##:.:: ##:::: ##:::: ##:::::::'##::: ##:::: ##::::\n" +
" ##:::: ##:. ######:: ##:::: ##:::: ##:::: ########:. ######::::: ##::::\n" +
"..:::::..:::......:::..:::::..:::::..:::::........:::......::::::..:::::");
}
}

View File

@ -0,0 +1,19 @@
package io.gitlab.jfronny.libjf.unsafe.test;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.UltraEarlyInit;
public class UnsafeEntryTest implements UltraEarlyInit {
@Override
public void init() {
LibJf.LOGGER.info("Successfully executed code before that should be possible\n" +
"'||' '|' '|| . '||''''| '|| \n" +
" || | || .||. ... .. .... || . .... ... .. || .... ... \n" +
" || | || || ||' '' '' .|| ||''| '' .|| ||' '' || '|. | \n" +
" || | || || || .|' || || .|' || || || '|.| \n" +
" '|..' .||. '|.' .||. '|..'|' .||.....| '|..'|' .||. .||. '| \n" +
" .. | \n" +
" '' ");
LibJf.DEV = true;
}
}

View File

@ -0,0 +1,14 @@
{
"schemaVersion": 1,
"id": "libjf-unsafe-v0-testmod",
"version": "1.0",
"environment": "*",
"entrypoints": {
"libjf:asm": [
"io.gitlab.jfronny.libjf.unsafe.test.AsmTest"
],
"libjf:early": [
"io.gitlab.jfronny.libjf.unsafe.test.UnsafeEntryTest"
]
}
}

View File

@ -7,3 +7,13 @@ pluginManagement {
gradlePluginPortal()
}
}
rootProject.name = "libjf"
include 'libjf-base'
include 'libjf-config-v0'
include 'libjf-data-v0'
include 'libjf-data-manipulation-v0'
//include 'libjf-devutil-v0' //TODO re-enable for 1.18
include 'libjf-unsafe-v0'

View File

@ -1,41 +1,27 @@
package io.gitlab.jfronny.libjf;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.gitlab.jfronny.libjf.config.Config;
import io.gitlab.jfronny.libjf.gson.HiddenAnnotationExclusionStrategy;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolder;
import io.gitlab.jfronny.libjf.data.WrappedPack;
import io.gitlab.jfronny.libjf.data.manipulation.api.UserResourceEvents;
import net.fabricmc.api.ModInitializer;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
public class Libjf {
public static final String MOD_ID = "libjf";
public static final Logger LOGGER = LogManager.getLogger(MOD_ID);
public static final Gson GSON = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
.excludeFieldsWithModifiers(Modifier.PRIVATE)
.addSerializationExclusionStrategy(new HiddenAnnotationExclusionStrategy())
.setPrettyPrinting()
.create();
private static final Map<String, Config> configs = new HashMap<>();
@Deprecated
//TODO remove for 1.18
@Deprecated(forRemoval = true)
public class Libjf implements ModInitializer {
@Deprecated(forRemoval = true)
public static void registerConfig(String modId, Class<?> config) {
if (!isRegistered(config))
configs.put(modId, new Config(modId, config));
ConfigHolder.registerConfig(modId, config);
}
public static Map<String, Config> getConfigs() {
return ImmutableMap.copyOf(configs);
}
public static boolean isRegistered(Class<?> config) {
for (Config value : configs.values()) {
if (value.configClass.equals(config))
return true;
}
return false;
@Override
public void onInitialize() {
UserResourceEvents.CONTAINS.register((type, id, previous, pack) ->
io.gitlab.jfronny.libjf.data.UserResourceEvents.CONTAINS.invoker().contains(type, id, previous, WrappedPack.create(pack)));
UserResourceEvents.FIND_RESOURCE.register((type, namespace, prefix, maxDepth, pathFilter, previous, pack) ->
io.gitlab.jfronny.libjf.data.UserResourceEvents.FIND_RESOURCE.invoker().findResources(type, namespace, prefix, maxDepth, pathFilter, previous, WrappedPack.create(pack)));
UserResourceEvents.OPEN.register((type, id, previous, pack) ->
io.gitlab.jfronny.libjf.data.UserResourceEvents.OPEN.invoker().open(type, id, previous, WrappedPack.create(pack)));
UserResourceEvents.OPEN_ROOT.register((fileName, previous, pack) ->
io.gitlab.jfronny.libjf.data.UserResourceEvents.OPEN_ROOT.invoker().openRoot(fileName, previous, WrappedPack.create(pack)));
}
}

View File

@ -1,4 +1,5 @@
package io.gitlab.jfronny.libjf.config;
public interface JfConfig {
@Deprecated(forRemoval = true)
public interface JfConfig extends io.gitlab.jfronny.libjf.config.api.JfConfig {
}

View File

@ -2,28 +2,23 @@ package io.gitlab.jfronny.libjf.data;
import net.minecraft.item.ItemStack;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Deprecated(forRemoval = true)
public class RecipeUtil {
private final static Set<String> removalsByIdentifier = new HashSet<>();
private final static List<ItemStack> recipesForRemoval = new ArrayList<>();
public static void removeRecipe(String id) {
removalsByIdentifier.add(id);
RecipeUtil.removeRecipe(id);
}
public static void removeRecipeFor(ItemStack product) {
recipesForRemoval.add(product);
RecipeUtil.removeRecipeFor(product);
}
public static Iterable<ItemStack> getRecipesForRemoval() {
return recipesForRemoval;
return RecipeUtil.getRecipesForRemoval();
}
public static Set<String> getIdentifiersForRemoval() {
return removalsByIdentifier;
return RecipeUtil.getIdentifiersForRemoval();
}
}

View File

@ -3,42 +3,13 @@ package io.gitlab.jfronny.libjf.data;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
public class ResourcePath implements AutoCloseable {
private final ResourceType type;
private final Identifier id;
@Deprecated(forRemoval = true)
public class ResourcePath extends io.gitlab.jfronny.libjf.ResourcePath {
public ResourcePath(ResourceType type, Identifier id) {
this.type = type;
this.id = id;
super(type, id);
}
public ResourcePath(String name) throws IllegalStateException {
String[] s1 = name.split("/", 3);
if (s1.length != 3) {
throw new IllegalStateException("Could not split path string into resource type and ID due to insufficient length: " + name);
}
type = switch (s1[0]) {
case "assets" -> ResourceType.CLIENT_RESOURCES;
case "data" -> ResourceType.SERVER_DATA;
default -> throw new IllegalStateException("Unexpected value for resource type: " + s1[0] + " in: " + name);
};
id = new Identifier(s1[1], s1[2]);
}
public Identifier getId() {
return id;
}
public ResourceType getType() {
return type;
}
public String getName() {
return String.format("%s/%s/%s", type.getDirectory(), id.getNamespace(), id.getPath());
}
@Override
public void close() throws Exception {
super(name);
}
}

View File

@ -1,12 +0,0 @@
package io.gitlab.jfronny.libjf.data;
import net.fabricmc.fabric.api.tag.TagFactory;
import net.minecraft.item.Item;
import net.minecraft.tag.Tag;
import net.minecraft.util.Identifier;
import io.gitlab.jfronny.libjf.Libjf;
public class Tags {
public static Tag<Item> SHULKER_ILLEGAL = TagFactory.ITEM.create(new Identifier(Libjf.MOD_ID, "shulker_boxes_illegal"));
public static Tag<Item> OVERPOWERED = TagFactory.ITEM.create(new Identifier(Libjf.MOD_ID, "overpowered"));
}

View File

@ -10,6 +10,7 @@ import java.io.InputStream;
import java.util.Collection;
import java.util.function.Predicate;
@Deprecated(forRemoval = true)
public class UserResourceEvents {
public static final Event<Contains> CONTAINS = EventFactory.createArrayBacked(Contains.class,
(listeners) -> (type, id, previous, pack) -> {

View File

@ -3,7 +3,7 @@ package io.gitlab.jfronny.libjf.data;
import io.gitlab.jfronny.libjf.data.wrappedPackImpl.WrappedResourcePack;
import net.minecraft.resource.ResourcePack;
//This is a class for binary compatibility with mods using libjf
@Deprecated(forRemoval = true)
public abstract class WrappedPack implements ResourcePack {
public abstract ResourcePack getUnderlying();

View File

@ -1,45 +0,0 @@
package io.gitlab.jfronny.libjf.data.wrappedPackImpl;
import io.gitlab.jfronny.libjf.data.ResourcePath;
import io.gitlab.jfronny.libjf.data.UserResourceEvents;
import io.gitlab.jfronny.libjf.data.WrappedPack;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.function.Predicate;
public class EventCallImpl {
public static InputStream hookOpenRoot(WrappedPack pack, String fileName) throws IOException {
InputStream is = null;
IOException ex = null;
try {
is = pack.getUnderlying().openRoot(fileName);
}
catch (IOException ex1) {
ex = ex1;
}
is = UserResourceEvents.OPEN_ROOT.invoker().openRoot(fileName, is, pack);
if (is == null)
throw ex == null ? new FileNotFoundException(fileName) : ex;
return is;
}
public static InputStream hookOpen(WrappedPack pack, ResourceType type, Identifier id) throws IOException {
InputStream is = UserResourceEvents.OPEN.invoker().open(type, id, pack.getUnderlying().contains(type, id) ? pack.getUnderlying().open(type, id) : null, pack);
if (is == null)
throw new FileNotFoundException(new ResourcePath(type, id).getName());
return is;
}
public static Collection<Identifier> hookFindResources(WrappedPack pack, ResourceType type, String namespace, String prefix, int maxDepth, Predicate<String> pathFilter) {
return UserResourceEvents.FIND_RESOURCE.invoker().findResources(type, namespace, prefix, maxDepth, pathFilter, pack.getUnderlying().findResources(type, namespace, prefix, maxDepth, pathFilter), pack);
}
public static boolean hookContains(WrappedPack pack, ResourceType type, Identifier id) {
return UserResourceEvents.CONTAINS.invoker().contains(type, id, pack.getUnderlying().contains(type, id), pack);
}
}

View File

@ -0,0 +1,64 @@
package io.gitlab.jfronny.libjf.data.wrappedPackImpl;
import io.gitlab.jfronny.libjf.data.manipulation.api.UserResourceEvents;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceType;
import net.minecraft.resource.metadata.ResourceMetadataReader;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Set;
import java.util.function.Predicate;
@Deprecated(forRemoval = true)
public class SafeWrappedResourcePack implements ResourcePack {
ResourcePack pack;
public SafeWrappedResourcePack(ResourcePack pack) {
this.pack = pack;
}
@Nullable
@Override
public InputStream openRoot(String fileName) throws IOException {
return UserResourceEvents.disable(() -> pack.openRoot(fileName));
}
@Override
public InputStream open(ResourceType type, Identifier id) throws IOException {
return UserResourceEvents.disable(() -> pack.open(type, id));
}
@Override
public Collection<Identifier> findResources(ResourceType type, String namespace, String prefix, int maxDepth, Predicate<String> pathFilter) {
return UserResourceEvents.disable(() -> pack.findResources(type, namespace, prefix, maxDepth, pathFilter));
}
@Override
public boolean contains(ResourceType type, Identifier id) {
return UserResourceEvents.disable(() -> pack.contains(type, id));
}
@Override
public Set<String> getNamespaces(ResourceType type) {
return UserResourceEvents.disable(() -> pack.getNamespaces(type));
}
@Nullable
@Override
public <T> T parseMetadata(ResourceMetadataReader<T> metaReader) throws IOException {
return UserResourceEvents.disable(() -> pack.parseMetadata(metaReader));
}
@Override
public String getName() {
return UserResourceEvents.disable(() -> pack.getName());
}
@Override
public void close() {
UserResourceEvents.disable(() -> pack.close());
}
}

View File

@ -13,31 +13,39 @@ import java.util.Collection;
import java.util.Set;
import java.util.function.Predicate;
@Deprecated(forRemoval = true)
public class WrappedResourcePack extends WrappedPack {
ResourcePack pack;
SafeWrappedResourcePack safeWrappedResourcePack;
public WrappedResourcePack(ResourcePack pack) {
this.pack = pack;
safeWrappedResourcePack = new SafeWrappedResourcePack(pack);
}
@Override
public ResourcePack getUnderlying() {
return safeWrappedResourcePack;
}
@Nullable
@Override
public InputStream openRoot(String fileName) throws IOException {
return EventCallImpl.hookOpenRoot(this, fileName);
return pack.openRoot(fileName);
}
@Override
public InputStream open(ResourceType type, Identifier id) throws IOException {
return EventCallImpl.hookOpen(this, type, id);
return pack.open(type, id);
}
@Override
public Collection<Identifier> findResources(ResourceType type, String namespace, String prefix, int maxDepth, Predicate<String> pathFilter) {
return EventCallImpl.hookFindResources(this, type, namespace, prefix, maxDepth, pathFilter);
return pack.findResources(type, namespace, prefix, maxDepth, pathFilter);
}
@Override
public boolean contains(ResourceType type, Identifier id) {
return EventCallImpl.hookContains(this, type, id);
return pack.contains(type, id);
}
@Override
@ -60,8 +68,4 @@ public class WrappedResourcePack extends WrappedPack {
public void close() {
pack.close();
}
public ResourcePack getUnderlying() {
return pack;
}
}

View File

@ -1,18 +0,0 @@
package io.gitlab.jfronny.libjf.entry;
import io.gitlab.jfronny.libjf.Libjf;
import io.gitlab.jfronny.libjf.config.JfConfig;
import net.fabricmc.loader.api.LanguageAdapter;
public class JfLanguageAdapter implements LanguageAdapter {
@Override
public native <T> T create(net.fabricmc.loader.api.ModContainer mod, String value, Class<T> type);
static {
DynamicEntry.execute(Libjf.MOD_ID + ":config", JfConfig.class, s -> {
Libjf.registerConfig(s.modId(), s.instance().getClass());
Libjf.LOGGER.info("Registering config for " + s.modId());
});
DynamicEntry.execute(Libjf.MOD_ID + ":early", UltraEarlyInit.class, s -> s.instance().init());
}
}

View File

@ -1,24 +0,0 @@
package io.gitlab.jfronny.libjf.entry;
import io.gitlab.jfronny.libjf.Libjf;
import io.gitlab.jfronny.libjf.config.Config;
import io.gitlab.jfronny.libjf.config.Entry;
import io.gitlab.jfronny.libjf.config.EntryInfo;
import io.gitlab.jfronny.libjf.gson.GsonHidden;
import net.fabricmc.api.ClientModInitializer;
public class LibjfClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
for (Config config : Libjf.getConfigs().values()) {
Libjf.LOGGER.info("Registring config UI for " + config.modid);
for (EntryInfo info : config.entries) {
if (info.field.isAnnotationPresent(Entry.class) || info.field.isAnnotationPresent(GsonHidden.class))
try {
config.initClient(info);
} catch (Exception ignored) {
}
}
}
}
}

View File

@ -1,5 +1,4 @@
package io.gitlab.jfronny.libjf.entry;
public interface UltraEarlyInit {
void init();
public interface UltraEarlyInit extends io.gitlab.jfronny.libjf.unsafe.UltraEarlyInit {
}

View File

@ -1,21 +0,0 @@
package io.gitlab.jfronny.libjf.mixin;
import io.gitlab.jfronny.libjf.data.WrappedPack;
import net.fabricmc.fabric.impl.resource.loader.FabricModResourcePack;
import net.fabricmc.fabric.impl.resource.loader.ModNioResourcePack;
import net.minecraft.resource.ReloadableResourceManagerImpl;
import net.minecraft.resource.ResourcePack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
@Mixin(ReloadableResourceManagerImpl.class)
public class ReloadableResourceManagerImplMixin {
@ModifyVariable(method = "addPack(Lnet/minecraft/resource/ResourcePack;)V", at = @At("HEAD"), argsOnly = true, ordinal = 0)
private ResourcePack modifyPack(ResourcePack pack) {
if (pack instanceof WrappedPack || pack instanceof ModNioResourcePack || pack instanceof FabricModResourcePack) { //TODO use ASM for this to allow mod resource packs
return pack;
}
return WrappedPack.create(pack);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -15,22 +15,14 @@
"icon": "assets/libjf/icon.png",
"environment": "*",
"entrypoints": {
"client": [
"io.gitlab.jfronny.libjf.entry.LibjfClient"
],
"modmenu": [
"io.gitlab.jfronny.libjf.config.ModMenu"
]
"main": ["io.gitlab.jfronny.libjf.Libjf"]
},
"languageAdapters": {
"libjf": "io.gitlab.jfronny.libjf.entry.JfLanguageAdapter"
},
"mixins": [
"libjf.mixins.json"
],
"depends": {
"fabricloader": ">=0.11.3",
"minecraft": "*"
"fabricloader": ">=0.12.0",
"minecraft": "*",
"libjf-config-v0": "${version}",
"libjf-data-v0": "${version}",
"libjf-base": "${version}"
},
"custom": {
"modmenu": {

View File

@ -1,26 +0,0 @@
package io.gitlab.jfronny.libjf.test;
import io.gitlab.jfronny.libjf.data.UserResourceEvents;
import net.fabricmc.api.ModInitializer;
import net.minecraft.resource.AbstractFileResourcePack;
import java.io.FileNotFoundException;
public class Entrypoint implements ModInitializer {
@Override
public void onInitialize() {
// This should prevent resource packs from doing anything if my hooks are working and
UserResourceEvents.OPEN.register((type, id, previous, pack) -> {
if (TestMod.disablePacks && pack.getUnderlying() instanceof AbstractFileResourcePack) {
throw new FileNotFoundException();
}
return previous;
});
UserResourceEvents.CONTAINS.register((type, id, previous, pack) -> {
if (TestMod.disablePacks && pack.getUnderlying() instanceof AbstractFileResourcePack) {
return false;
}
return previous;
});
}
}

View File

@ -1,11 +0,0 @@
{
"libjf-testmod.jfconfig.title": "JfConfig example",
"libjf-testmod.jfconfig.disablePacks": "Disable resource packs",
"libjf-testmod.jfconfig.intTest": "Int Test",
"libjf-testmod.jfconfig.decimalTest": "Decimal Test",
"libjf-testmod.jfconfig.dieStr": "String Test",
"libjf-testmod.jfconfig.enumTest": "Enum Test",
"libjf-testmod.jfconfig.enumTest.tooltip": "Enum Test Tooltip",
"libjf-testmod.jfconfig.enum.Test.Test": "Test",
"libjf-testmod.jfconfig.enum.Test.ER": "ER"
}

Some files were not shown because too many files have changed in this diff Show More