From a3a3569747143fdc95ef7084a25c655b7104e133 Mon Sep 17 00:00:00 2001 From: Coldsmiles Date: Tue, 6 Oct 2026 07:42:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=20Leaf=201.21.11=20=E5=B9=B6?= =?UTF-8?q?=E5=86=85=E7=BD=AE=20TabooLib=20=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 132 +++++++++++++++++- gradle.properties | 2 +- settings.gradle.kts | 10 +- .../common/BundledLibraryBootstrap.java | 129 +++++++++++++++++ .../taboolib/common/PrimitiveSettings.java | 100 +++++++++++++ .../xbaimiao/shoppro/util/ItemBuilder.kt | 59 +++++++- 6 files changed, 420 insertions(+), 12 deletions(-) create mode 100644 src/main/java/taboolib/common/BundledLibraryBootstrap.java create mode 100644 src/main/java/taboolib/common/PrimitiveSettings.java diff --git a/build.gradle.kts b/build.gradle.kts index 6544221..225e1e7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,7 @@ -import io.izzel.taboolib.gradle.* - plugins { `java-library` `maven-publish` - id("io.izzel.taboolib") version "2.0.11" + id("io.izzel.taboolib") version "2.0.2-community-1" id("org.jetbrains.kotlin.jvm") version "1.7.10" } @@ -23,12 +21,27 @@ taboolib { bukkitNodes["update-info"] = "支持1.12.2物品附加值" } env { - install(NMS_UTIL, UI) - install(EXPANSION_COMMAND_HELPER, BUKKIT_ALL) - install(DATABASE, CHAT, CONFIGURATION, LANG, NMS, KETHER) + // TabooLib 6.2 split the legacy bundles into versioned modules. The + // 6.2 platform contains the Bukkit 1.21 compatibility layer. + install( + "bukkit-ui", + "minecraft-command-helper", + "bukkit-hook", + "bukkit-util", + "bukkit-xseries", + "platform-bukkit", + "platform-bukkit-impl", + "database", + "minecraft-chat", + "basic-configuration", + "minecraft-i18n", + "minecraft-kether" + ) + + repoTabooLib = "https://maven.xbaimiao.com/repository/maven-public/" } version { - taboolib = "6.1.1-beta17" + taboolib = "6.2.0-beta33" } // relocate("kotlin", "libs.kotlin171") relocate("com.xbaimiao.ktor", "com.github.xbaimiao.shoppro.stat") @@ -40,6 +53,111 @@ repositories { maven("https://r.irepo.space/maven/") } +val bundledRuntime by configurations.creating + +val taboolibRuntimeModules = listOf( + "common", + "platform-bukkit", + "common-env", + "common-util", + "common-legacy-api", + "common-platform-api", + "bukkit-ui", + "minecraft-command-helper", + "bukkit-hook", + "bukkit-util", + "bukkit-xseries", + "platform-bukkit-impl", + "database", + "minecraft-chat", + "basic-configuration", + "minecraft-i18n", + "minecraft-kether" +) + +dependencies { + // These files are embedded as resources and installed before TabooLib's + // primitive loader runs, so a server can start without Maven access. + add("bundledRuntime", "me.lucko:jar-relocator:1.7") + add("bundledRuntime", "org.ow2.asm:asm:9.6") + add("bundledRuntime", "org.ow2.asm:asm-util:9.6") + add("bundledRuntime", "org.ow2.asm:asm-commons:9.6") + add("bundledRuntime", "org.tabooproject.reflex:reflex:1.1.7") + add("bundledRuntime", "org.tabooproject.reflex:analyser:1.1.7") + add("bundledRuntime", "org.jetbrains.kotlin:kotlin-stdlib:1.7.10") + add("bundledRuntime", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10") + add("bundledRuntime", "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3") + taboolibRuntimeModules.forEach { module -> + add("bundledRuntime", "io.izzel.taboolib:$module:6.2.0-beta33") + } +} + +val prepareBundledRuntime by tasks.registering { + val output = layout.buildDirectory.dir("generated/bundled-runtime") + outputs.dir(output) + doLast { + val root = output.get().asFile + root.deleteRecursively() + val resourceRoot = root.resolve("META-INF/taboolib/bundled") + val index = linkedMapOf() + configurations["bundledRuntime"].resolvedConfiguration.resolvedArtifacts.forEach { artifact -> + val module = artifact.moduleVersion.id + val relative = "${module.group.replace('.', '/')}/${module.name}/${module.version}/${module.name}-${module.version}.jar" + val destination = resourceRoot.resolve(relative) + destination.parentFile.mkdirs() + artifact.file.copyTo(destination, overwrite = true) + index[relative] = relative + + // The TabooLib build metadata in this project is generated by the + // Kotlin 1.7.10 Gradle plugin, while Reflex 1.1.7 resolves a newer + // compatible stdlib transitively. Install the resolved bytes at + // the exact paths PrimitiveSettings requests as well. + if (module.group == "org.jetbrains.kotlin" && + (module.name == "kotlin-stdlib" || module.name == "kotlin-stdlib-jdk8")) { + val requested = "${module.group.replace('.', '/')}/${module.name}/1.7.10/${module.name}-1.7.10.jar" + val requestedDestination = resourceRoot.resolve(requested) + requestedDestination.parentFile.mkdirs() + artifact.file.copyTo(requestedDestination, overwrite = true) + index[requested] = requested + val requestedPom = requested.removeSuffix(".jar") + ".pom" + val requestedPomFile = resourceRoot.resolve(requestedPom) + requestedPomFile.parentFile.mkdirs() + requestedPomFile.writeText( + "4.0.0" + + "${module.group}${module.name}" + + "1.7.10", + Charsets.UTF_8 + ) + index[requestedPom] = requestedPom + } + + // RuntimeEnvDependency checks POM + SHA-1 files for Kotlin and + // coroutine roots. A minimal local POM is sufficient because all + // requested root JARs are bundled explicitly. + if (module.group == "org.jetbrains.kotlin" || module.group == "org.jetbrains.kotlinx") { + val pomRelative = relative.removeSuffix(".jar") + ".pom" + val pom = resourceRoot.resolve(pomRelative) + pom.parentFile.mkdirs() + pom.writeText( + "4.0.0" + + "${module.group}${module.name}" + + "${module.version}", + Charsets.UTF_8 + ) + index[pomRelative] = pomRelative + } + } + val indexFile = root.resolve("META-INF/taboolib/bundled-libraries.properties") + indexFile.parentFile.mkdirs() + indexFile.writeText(index.entries.joinToString("\n") { (target, resource) -> "$target=$resource" } + "\n", Charsets.UTF_8) + } +} + +tasks.processResources { + dependsOn(prepareBundledRuntime) + from(prepareBundledRuntime) +} + dependencies { compileOnly("com.mojang:authlib:3.16.29") compileOnly("ink.ptms:nms-all:1.0.0") diff --git a/gradle.properties b/gradle.properties index 27ddfcb..39c1c1b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group=com.github.xbaimiao.shoppro -version=1.3.5-SNAPSHOT +version=1.3.6-SNAPSHOT diff --git a/settings.gradle.kts b/settings.gradle.kts index 4d301ca..2850fcc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,9 @@ -rootProject.name="ShopPro" \ No newline at end of file +pluginManagement { + repositories { + maven("https://maven.xbaimiao.com/repository/maven-public/") + gradlePluginPortal() + mavenCentral() + } +} + +rootProject.name = "ShopPro" diff --git a/src/main/java/taboolib/common/BundledLibraryBootstrap.java b/src/main/java/taboolib/common/BundledLibraryBootstrap.java new file mode 100644 index 0000000..dab3367 --- /dev/null +++ b/src/main/java/taboolib/common/BundledLibraryBootstrap.java @@ -0,0 +1,129 @@ +package taboolib.common; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.security.MessageDigest; +import java.util.Properties; + +/** + * Installs the libraries bundled by the build into TabooLib's normal cache + * before PrimitiveLoader starts resolving dependencies. + */ +public final class BundledLibraryBootstrap { + + private static final String INDEX = "META-INF/taboolib/bundled-libraries.properties"; + private static final String RESOURCE_PREFIX = "META-INF/taboolib/bundled/"; + + private BundledLibraryBootstrap() { + } + + public static void install() { + Properties index = new Properties(); + try (InputStream input = BundledLibraryBootstrap.class.getClassLoader().getResourceAsStream(INDEX)) { + if (input == null) { + return; + } + index.load(input); + } catch (IOException ignored) { + return; + } + + String libraryRoot = "libraries"; + try (InputStream input = BundledLibraryBootstrap.class.getClassLoader() + .getResourceAsStream("META-INF/taboolib/env.properties")) { + if (input != null) { + Properties env = new Properties(); + env.load(input); + libraryRoot = env.getProperty("file-libs", libraryRoot); + } + } catch (IOException ignored) { + // The generated defaults already use "libraries". + } + + File root = new File(libraryRoot); + for (String relativePath : index.stringPropertyNames()) { + String resourcePath = index.getProperty(relativePath); + if (resourcePath == null || resourcePath.isEmpty()) { + continue; + } + File target = new File(root, relativePath.replace('/', File.separatorChar)); + try { + boolean managedArtifact = relativePath.endsWith(".jar") || relativePath.endsWith(".pom"); + if (!target.isFile() || target.length() == 0L || (managedArtifact && !isSha1Valid(target))) { + File parent = target.getParentFile(); + if (parent != null && !parent.exists() && !parent.mkdirs() && !parent.isDirectory()) { + continue; + } + try (InputStream input = BundledLibraryBootstrap.class.getClassLoader() + .getResourceAsStream(RESOURCE_PREFIX + resourcePath); + OutputStream output = Files.newOutputStream(target.toPath())) { + if (input == null) { + continue; + } + byte[] buffer = new byte[8192]; + int read; + while ((read = input.read(buffer)) != -1) { + output.write(buffer, 0, read); + } + } + } + if (managedArtifact) { + writeSha1(target); + } + } catch (IOException ignored) { + // PrimitiveLoader will report a clear download/load error if a + // bundled file cannot be installed. + } + } + } + + private static void writeSha1(File file) throws IOException { + File sha1 = new File(file.getPath() + ".sha1"); + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + try (InputStream input = Files.newInputStream(file.toPath())) { + byte[] buffer = new byte[8192]; + int read; + while ((read = input.read(buffer)) != -1) { + digest.update(buffer, 0, read); + } + } + StringBuilder value = new StringBuilder(40); + for (byte part : digest.digest()) { + value.append(String.format("%02x", part & 0xff)); + } + Files.write(sha1.toPath(), value.toString().getBytes(java.nio.charset.StandardCharsets.US_ASCII)); + } catch (java.security.NoSuchAlgorithmException ex) { + throw new IOException("SHA-1 is unavailable", ex); + } + } + + private static boolean isSha1Valid(File file) { + File sha1 = new File(file.getPath() + ".sha1"); + if (!sha1.isFile() || sha1.length() == 0L) { + return false; + } + try { + String expected = new String(Files.readAllBytes(sha1.toPath()), java.nio.charset.StandardCharsets.US_ASCII) + .trim().toLowerCase(java.util.Locale.ROOT); + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + try (InputStream input = Files.newInputStream(file.toPath())) { + byte[] buffer = new byte[8192]; + int read; + while ((read = input.read(buffer)) != -1) { + digest.update(buffer, 0, read); + } + } + StringBuilder actual = new StringBuilder(40); + for (byte part : digest.digest()) { + actual.append(String.format("%02x", part & 0xff)); + } + return actual.toString().equals(expected); + } catch (Exception ignored) { + return false; + } + } +} diff --git a/src/main/java/taboolib/common/PrimitiveSettings.java b/src/main/java/taboolib/common/PrimitiveSettings.java new file mode 100644 index 0000000..ac23e14 --- /dev/null +++ b/src/main/java/taboolib/common/PrimitiveSettings.java @@ -0,0 +1,100 @@ +package taboolib.common; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.util.Properties; + +import static java.lang.System.getProperty; + +/** Runtime settings used by the embedded TabooLib primitive loader. */ +public class PrimitiveSettings { + + static { + // This runs before any PrimitiveLoader dependency check. + BundledLibraryBootstrap.install(); + } + + public static final String ID = "!taboolib".substring(1); + public static final Properties RUNTIME_PROPERTIES = getProperties("env", true); + public static final Properties VERSION_PROPERTIES = getProperties("version", false); + + public static final String KOTLIN_VERSION = VERSION_PROPERTIES.getProperty( + "!kotlin".substring(1), getProperty("taboolib.kotlin.stdlib", "1.8.22")); + public static final String KOTLIN_COROUTINES_VERSION = VERSION_PROPERTIES.getProperty( + "!kotlin-coroutines".substring(1), getProperty("taboolib.kotlin.coroutines", "1.7.3")); + public static final String TABOOLIB_VERSION = VERSION_PROPERTIES.getProperty( + ID, getProperty("taboolib.version", "skip")); + public static final boolean SKIP_KOTLIN_RELOCATE = VERSION_PROPERTIES.getProperty( + "skip-kotlin-relocate", getProperty("taboolib.skip-relocate.kotlin", "false")).equals("true"); + public static final boolean SKIP_TABOOLIB_RELOCATE = VERSION_PROPERTIES.getProperty( + "skip-taboolib-relocate", getProperty("taboolib.skip-relocate.self", "false")).equals("true"); + public static final boolean IS_DEV_MODE = TABOOLIB_VERSION.endsWith("-dev") + || getProperty("taboolib.dev", "false").equals("true"); + public static final boolean IS_DEBUG_MODE = IS_DEV_MODE + || RUNTIME_PROPERTIES.getProperty("debug", getProperty("taboolib.debug", "false")).equals("true"); + public static final boolean IS_FORCE_DOWNLOAD_IN_DEV_MODE = RUNTIME_PROPERTIES + .getProperty("force-download-in-dev", "true").equals("true"); + public static final String REPO_CENTRAL = RUNTIME_PROPERTIES.getProperty( + "repo-central", getProperty("taboolib.repo.central", "https://maven.aliyun.com/repository/central")); + public static final String REPO_TABOOLIB = RUNTIME_PROPERTIES.getProperty( + "repo-taboolib", getProperty("taboolib.repo.self", "https://maven.xbaimiao.com/repository/maven-public/")); + public static final String REPO_REFLEX = RUNTIME_PROPERTIES.getProperty( + "repo-reflex", getProperty("taboolib.repo.reflex", REPO_TABOOLIB)); + public static final String FILE_LIBS = RUNTIME_PROPERTIES.getProperty("file-libs", "libraries"); + public static final String FILE_ASSETS = RUNTIME_PROPERTIES.getProperty("file-assets", "assets"); + public static boolean IS_ISOLATED_MODE = RUNTIME_PROPERTIES + .getProperty("enable-isolated-classloader", "false").equals("true"); + public static final String[] INSTALL_MODULES = RUNTIME_PROPERTIES + .getProperty("module", getProperty("taboolib.module", "")).split(","); + + public static String formatVersion(String value) { + return value.replaceAll("[._-]", ""); + } + + public static String getRelocatedKotlinVersion() { + return "kotlin" + formatVersion(KOTLIN_VERSION); + } + + public static String getRelocatedKotlinCoroutinesVersion() { + return "kotlin" + formatVersion(KOTLIN_VERSION) + "x.coroutines" + + formatVersion(KOTLIN_COROUTINES_VERSION); + } + + private static Properties getProperties(String name, boolean allowGlobal) { + Properties properties = new Properties(); + URL url = PrimitiveSettings.class.getClassLoader() + .getResource("META-INF/taboolib/" + name + ".properties"); + if (url != null) { + try (InputStreamHolder ignored = new InputStreamHolder(url.openStream())) { + properties.load(ignored.input); + } catch (IOException ignored) { + } + } + if (allowGlobal) { + File globalFile = new File(name + ".properties"); + if (globalFile.isFile()) { + try (FileInputStream input = new FileInputStream(globalFile)) { + properties.load(input); + } catch (IOException ignored) { + } + } + } + return properties; + } + + /** Small closeable holder to keep the try-with-resources block Java 8 compatible. */ + private static final class InputStreamHolder implements AutoCloseable { + private final java.io.InputStream input; + + private InputStreamHolder(java.io.InputStream input) { + this.input = input; + } + + @Override + public void close() throws IOException { + input.close(); + } + } +} diff --git a/src/main/kotlin/com/github/xbaimiao/shoppro/util/ItemBuilder.kt b/src/main/kotlin/com/github/xbaimiao/shoppro/util/ItemBuilder.kt index 1d6f0da..6377344 100644 --- a/src/main/kotlin/com/github/xbaimiao/shoppro/util/ItemBuilder.kt +++ b/src/main/kotlin/com/github/xbaimiao/shoppro/util/ItemBuilder.kt @@ -2,6 +2,7 @@ package com.github.xbaimiao.shoppro.util import com.mojang.authlib.GameProfile import com.mojang.authlib.properties.Property +import org.bukkit.Bukkit import org.bukkit.ChatColor import org.bukkit.Color import org.bukkit.Material @@ -18,6 +19,8 @@ import taboolib.library.reflex.Reflex.Companion.setProperty import taboolib.library.xseries.XMaterial import taboolib.module.chat.colored import taboolib.platform.util.isAir +import java.net.URL +import java.nio.charset.StandardCharsets import java.util.* @@ -195,6 +198,58 @@ open class ItemBuilder { return null } + /** + * Bukkit 1.21 stores skull profiles as ResolvableProfile instead of the + * authlib GameProfile used by older CraftBukkit versions. Prefer the + * public PlayerProfile API when it is available and keep the reflective + * field fallback for older servers. + */ + private fun setSkullTexture(skull: SkullMeta, texture: SkullTexture) { + val applied = runCatching { + val url = textureUrl(texture.textures) ?: return@runCatching false + val createProfile = Bukkit::class.java.methods.firstOrNull { + it.name == "createPlayerProfile" && + it.parameterTypes.size == 1 && + it.parameterTypes[0] == UUID::class.java + } ?: return@runCatching false + val profile = createProfile.invoke(null, texture.uuid ?: UUID.randomUUID()) + val textures = profile.javaClass.methods.firstOrNull { + it.name == "getTextures" && it.parameterTypes.isEmpty() + }?.invoke(profile) ?: return@runCatching false + val setSkin = textures.javaClass.methods.firstOrNull { + it.name == "setSkin" && + it.parameterTypes.size == 1 && + it.parameterTypes[0] == URL::class.java + } ?: return@runCatching false + setSkin.invoke(textures, URL(url)) + val setProfile = skull.javaClass.methods.firstOrNull { + it.name == "setPlayerProfile" && + it.parameterTypes.size == 1 + } ?: return@runCatching false + setProfile.invoke(skull, profile) + true + }.getOrDefault(false) + + if (!applied) { + runCatching { + skull.setProperty("profile", GameProfile(texture.uuid, "test").also { + it.properties.put("textures", Property("textures", texture.textures)) + }) + } + } + } + + private fun textureUrl(value: String): String? { + if (value.startsWith("http://") || value.startsWith("https://")) { + return value + } + val decoded = runCatching { + String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8) + }.getOrNull() ?: return null + return Regex("\\\"url\\\"\\s*:\\s*\\\"([^\\\"]+)\\\"") + .find(decoded)?.groupValues?.get(1) + } + /** * 上色 */ @@ -255,9 +310,7 @@ open class ItemBuilder { itemMeta.owner = skullOwner } if (skullTexture != null) { - itemMeta.setProperty("profile", GameProfile(skullTexture!!.uuid, "test").also { - it.properties.put("textures", Property("textures", skullTexture!!.textures)) - }) + setSkullTexture(itemMeta, skullTexture!!) } } }