Skip to content

Commit c4ee9d0

Browse files
authored
Add McVersionRemapper for new Paper version format support (#340)
2 parents 4f676de + 1020f02 commit c4ee9d0

3 files changed

Lines changed: 158 additions & 1 deletion

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
77
javaVersion=25
88
mcVersion=26.1.1
99
group=dev.slne.surf.api
10-
version=3.9.3
10+
version=3.9.4
1111
relocationPrefix=dev.slne.surf.api.libs
1212
snapshot=false

surf-api-paper/surf-api-paper-server/src/main/kotlin/dev/slne/surf/api/paper/server/inventory/framework/InventoryLoader.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import me.devnatan.inventoryframework.ViewFrame
55

66
object InventoryLoader {
77
init {
8+
McVersionRemapper.remap()
89
InventoryViewRemapper.remap()
910
CloseContextRemapper.remap()
1011
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package dev.slne.surf.api.paper.server.inventory.framework
2+
3+
import net.bytebuddy.ByteBuddy
4+
import net.bytebuddy.asm.AsmVisitorWrapper
5+
import net.bytebuddy.description.field.FieldDescription
6+
import net.bytebuddy.description.field.FieldList
7+
import net.bytebuddy.description.method.MethodList
8+
import net.bytebuddy.description.type.TypeDescription
9+
import net.bytebuddy.dynamic.ClassFileLocator
10+
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy
11+
import net.bytebuddy.implementation.Implementation
12+
import net.bytebuddy.jar.asm.ClassVisitor
13+
import net.bytebuddy.jar.asm.MethodVisitor
14+
import net.bytebuddy.jar.asm.Opcodes
15+
import net.bytebuddy.pool.TypePool
16+
import net.bytebuddy.utility.OpenedClassReader
17+
import org.bukkit.Bukkit
18+
19+
/**
20+
* Remaps inventory-framework's McVersion parser to support Paper's new version format:
21+
*
22+
* 26.1.2.build.23-alpha
23+
*
24+
* inventory-framework 3.7.1 detects whether a patch version exists via a private method named
25+
* `countColons`, which actually counts dots and expects exactly 3 dots for versions like:
26+
*
27+
* 1.21.11-R0.1-SNAPSHOT
28+
*
29+
* With the new Paper format, `26.1.2.build.23-alpha` contains 4 dots, so IF incorrectly parses it
30+
* as `26.1` instead of `26.1.2`.
31+
*
32+
* This remapper replaces only the private static `countColons(String): Int` method with a parser
33+
* that returns `3` whenever the Minecraft version part has a numeric patch component.
34+
*/
35+
object McVersionRemapper {
36+
37+
private const val MC_VERSION_INTERNAL =
38+
"dev/slne/surf/api/libs/devnatan/inventoryframework/runtime/thirdparty/McVersion"
39+
40+
private const val REMAPPER_INTERNAL =
41+
"dev/slne/surf/api/paper/server/inventory/framework/McVersionRemapper"
42+
43+
fun isRemapNecessary(): Boolean {
44+
return try {
45+
val version = Bukkit.getBukkitVersion()
46+
version.contains(".build.") || hasNumericPatchVersionPart(version)
47+
} catch (_: Throwable) {
48+
false
49+
}
50+
}
51+
52+
fun remap() {
53+
if (!isRemapNecessary()) return
54+
55+
val locator = ClassFileLocator.ForClassLoader.of(javaClass.classLoader)
56+
val typePool = TypePool.Default.of(locator)
57+
val typeDescription = typePool.describe(MC_VERSION_INTERNAL.replace("/", ".")).resolve()
58+
59+
ByteBuddy()
60+
.redefine<Any>(typeDescription, locator)
61+
.visit(McVersionClassVisitorWrapper())
62+
.make()
63+
.load(javaClass.classLoader, ClassLoadingStrategy.Default.INJECTION)
64+
}
65+
66+
/**
67+
* Replacement for inventory-framework's private static `countColons(String): Int`.
68+
*
69+
* Important: The original code checks `countColons(version) == 3` before parsing
70+
* `version.split(".")[2]` as the patch number.
71+
*
72+
* Therefore this method deliberately returns:
73+
* - `3` if the Minecraft version part has a numeric patch, e.g. `1.21.11` or `26.1.2`
74+
* - `2` for patchless Paper build versions, e.g. `26.1.build.23-alpha`
75+
* - the original dot count for legacy/non-build versions
76+
*/
77+
@JvmStatic
78+
@Suppress("unused")
79+
fun countVersionDotsForPatchDetection(version: String): Int {
80+
if (version.contains(".build.")) {
81+
val mcVersionPart = version.substringBefore(".build.")
82+
return if (hasNumericPatchVersionPart(mcVersionPart)) 3 else 2
83+
}
84+
85+
return if (hasNumericPatchVersionPart(version)) {
86+
3
87+
} else {
88+
version.count { it == '.' }
89+
}
90+
}
91+
92+
private fun hasNumericPatchVersionPart(version: String): Boolean {
93+
val mcVersionPart = version.substringBefore('-')
94+
val parts = mcVersionPart.split('.')
95+
96+
return parts.size >= 3 &&
97+
parts[0].isNotEmpty() &&
98+
parts[1].isNotEmpty() &&
99+
parts[2].isNotEmpty() &&
100+
parts[0].all(Char::isDigit) &&
101+
parts[1].all(Char::isDigit) &&
102+
parts[2].all(Char::isDigit)
103+
}
104+
105+
private class McVersionClassVisitorWrapper : AsmVisitorWrapper {
106+
override fun mergeWriter(flags: Int): Int = flags
107+
override fun mergeReader(flags: Int): Int = flags
108+
109+
override fun wrap(
110+
instrumentedType: TypeDescription,
111+
classVisitor: ClassVisitor,
112+
implementationContext: Implementation.Context,
113+
typePool: TypePool,
114+
fields: FieldList<FieldDescription.InDefinedShape?>,
115+
methods: MethodList<*>,
116+
writerFlags: Int,
117+
readerFlags: Int
118+
): ClassVisitor {
119+
return McVersionClassVisitor(classVisitor)
120+
}
121+
}
122+
123+
private class McVersionClassVisitor(
124+
visitor: ClassVisitor
125+
) : ClassVisitor(OpenedClassReader.ASM_API, visitor) {
126+
127+
override fun visitMethod(
128+
access: Int,
129+
name: String,
130+
descriptor: String,
131+
signature: String?,
132+
exceptions: Array<String>?
133+
): MethodVisitor? {
134+
if (name == "countColons" && descriptor == "(Ljava/lang/String;)I") {
135+
val mv = super.visitMethod(access, name, descriptor, signature, exceptions)
136+
137+
mv.visitCode()
138+
mv.visitVarInsn(Opcodes.ALOAD, 0)
139+
mv.visitMethodInsn(
140+
Opcodes.INVOKESTATIC,
141+
REMAPPER_INTERNAL,
142+
"countVersionDotsForPatchDetection",
143+
"(Ljava/lang/String;)I",
144+
false
145+
)
146+
mv.visitInsn(Opcodes.IRETURN)
147+
mv.visitMaxs(1, 1)
148+
mv.visitEnd()
149+
150+
return null
151+
}
152+
153+
return super.visitMethod(access, name, descriptor, signature, exceptions)
154+
}
155+
}
156+
}

0 commit comments

Comments
 (0)