1+ package dev.slne.surf.surfapi.bukkit.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.*
13+ import net.bytebuddy.pool.TypePool
14+ import net.bytebuddy.utility.OpenedClassReader
15+
16+ /* *
17+ * Remaps the `CloseContext` class from the inventory-framework to accept `Object` instead of
18+ * `InventoryCloseEvent` as the close origin parameter.
19+ *
20+ * This is necessary because `IFInventoryListener.onPlayerQuit()` calls
21+ * `ElementFactory.createCloseContext(viewer, context, event)` where `event` is a
22+ * `PlayerQuitEvent`, **not** an `InventoryCloseEvent`. The original `BukkitElementFactory`
23+ * casts `closeOrigin` to `InventoryCloseEvent`, and the `CloseContext` constructor also
24+ * expects `InventoryCloseEvent`, causing a `ClassCastException` at runtime.
25+ *
26+ * This remapper rewrites the bytecode of both `BukkitElementFactory` and `CloseContext` so that:
27+ * 1. `BukkitElementFactory.createCloseContext()` no longer casts the origin to `InventoryCloseEvent`.
28+ * 2. `CloseContext`'s constructor accepts `Object` instead of `InventoryCloseEvent`.
29+ * 3. `CloseContext`'s `closeOrigin` field is typed as `Object` instead of `InventoryCloseEvent`.
30+ */
31+ object CloseContextRemapper {
32+
33+ private const val INVENTORY_CLOSE_EVENT_INTERNAL = " org/bukkit/event/inventory/InventoryCloseEvent"
34+ private const val OBJECT_INTERNAL = " java/lang/Object"
35+
36+ private const val CLOSE_CONTEXT_INTERNAL =
37+ " dev/slne/surf/surfapi/libs/devnatan/inventoryframework/context/CloseContext"
38+ private const val BUKKIT_ELEMENT_FACTORY_INTERNAL =
39+ " dev/slne/surf/surfapi/libs/devnatan/inventoryframework/internal/BukkitElementFactory"
40+
41+ fun remap () {
42+ remapCloseContext()
43+ remapBukkitElementFactory()
44+ }
45+
46+ /* *
47+ * Remaps `CloseContext`:
48+ * - Field `closeOrigin`: `InventoryCloseEvent` → `Object`
49+ * - Constructor descriptor: `(…, InventoryCloseEvent)` → `(…, Object)`
50+ * - Method bodies: removes `CHECKCAST InventoryCloseEvent` instructions
51+ */
52+ private fun remapCloseContext () {
53+ val locator = ClassFileLocator .ForClassLoader .of(javaClass.classLoader)
54+ val typePool = TypePool .Default .of(locator)
55+ val typeDescription = typePool.describe(CLOSE_CONTEXT_INTERNAL .replace(" /" , " ." )).resolve()
56+
57+ ByteBuddy ()
58+ .redefine<Any >(typeDescription, locator)
59+ .visit(CloseContextClassVisitorWrapper ())
60+ .make()
61+ .load(javaClass.classLoader, ClassLoadingStrategy .Default .INJECTION )
62+ }
63+
64+ private class CloseContextClassVisitorWrapper : AsmVisitorWrapper {
65+ override fun mergeWriter (flags : Int ): Int = flags
66+ override fun mergeReader (flags : Int ): Int = flags
67+
68+ override fun wrap (
69+ instrumentedType : TypeDescription ,
70+ classVisitor : ClassVisitor ,
71+ implementationContext : Implementation .Context ,
72+ typePool : TypePool ,
73+ fields : FieldList <FieldDescription .InDefinedShape ?>,
74+ methods : MethodList <* >,
75+ writerFlags : Int ,
76+ readerFlags : Int
77+ ): ClassVisitor {
78+ return CloseContextClassVisitor (classVisitor)
79+ }
80+ }
81+
82+ private class CloseContextClassVisitor (
83+ visitor : ClassVisitor
84+ ) : ClassVisitor(OpenedClassReader .ASM_API , visitor) {
85+
86+ override fun visitField (
87+ access : Int ,
88+ name : String ,
89+ descriptor : String ,
90+ signature : String? ,
91+ value : Any?
92+ ): FieldVisitor ? {
93+ // Change field type: InventoryCloseEvent -> Object
94+ val remappedDescriptor = remapDescriptor(descriptor)
95+ return super .visitField(access, name, remappedDescriptor, signature, value)
96+ }
97+
98+ override fun visitMethod (
99+ access : Int ,
100+ name : String ,
101+ descriptor : String ,
102+ signature : String? ,
103+ exceptions : Array <String >?
104+ ): MethodVisitor {
105+ // Change method descriptor: replace InventoryCloseEvent with Object
106+ val remappedDescriptor = remapDescriptor(descriptor)
107+ val mv = super .visitMethod(access, name, remappedDescriptor, signature, exceptions)
108+ return CloseContextMethodVisitor (mv)
109+ }
110+ }
111+
112+ private class CloseContextMethodVisitor (
113+ visitor : MethodVisitor
114+ ) : MethodVisitor(OpenedClassReader .ASM_API , visitor) {
115+
116+ override fun visitTypeInsn (opcode : Int , type : String ) {
117+ // Remove CHECKCAST to InventoryCloseEvent
118+ if (opcode == Opcodes .CHECKCAST && type == INVENTORY_CLOSE_EVENT_INTERNAL ) {
119+ return // skip the cast entirely
120+ }
121+ super .visitTypeInsn(opcode, type)
122+ }
123+
124+ override fun visitFieldInsn (opcode : Int , owner : String , name : String , descriptor : String ) {
125+ // Remap field access descriptors
126+ val remappedDescriptor = remapDescriptor(descriptor)
127+ super .visitFieldInsn(opcode, owner, name, remappedDescriptor)
128+ }
129+
130+ override fun visitMethodInsn (
131+ opcode : Int ,
132+ owner : String ,
133+ name : String ,
134+ descriptor : String ,
135+ isInterface : Boolean
136+ ) {
137+ // Remap method invocation descriptors (e.g. constructor calls within CloseContext)
138+ val remappedDescriptor = remapDescriptor(descriptor)
139+ super .visitMethodInsn(opcode, owner, name, remappedDescriptor, isInterface)
140+ }
141+ }
142+
143+ /* *
144+ * Remaps `BukkitElementFactory.createCloseContext()`:
145+ * - Removes the `CHECKCAST InventoryCloseEvent` instruction
146+ * - Rewrites the `CloseContext.<init>` invocation descriptor to use `Object`
147+ */
148+ private fun remapBukkitElementFactory () {
149+ val locator = ClassFileLocator .ForClassLoader .of(javaClass.classLoader)
150+ val typePool = TypePool .Default .of(locator)
151+ val typeDescription = typePool.describe(BUKKIT_ELEMENT_FACTORY_INTERNAL .replace(" /" , " ." )).resolve()
152+
153+ ByteBuddy ()
154+ .redefine<Any >(typeDescription, locator)
155+ .visit(BukkitElementFactoryClassVisitorWrapper ())
156+ .make()
157+ .load(javaClass.classLoader, ClassLoadingStrategy .Default .INJECTION )
158+ }
159+
160+ private class BukkitElementFactoryClassVisitorWrapper : AsmVisitorWrapper {
161+ override fun mergeWriter (flags : Int ): Int = flags
162+ override fun mergeReader (flags : Int ): Int = flags
163+
164+ override fun wrap (
165+ instrumentedType : TypeDescription ,
166+ classVisitor : ClassVisitor ,
167+ implementationContext : Implementation .Context ,
168+ typePool : TypePool ,
169+ fields : FieldList <FieldDescription .InDefinedShape ?>,
170+ methods : MethodList <* >,
171+ writerFlags : Int ,
172+ readerFlags : Int
173+ ): ClassVisitor {
174+ return BukkitElementFactoryClassVisitor (classVisitor)
175+ }
176+ }
177+
178+ private class BukkitElementFactoryClassVisitor (
179+ visitor : ClassVisitor
180+ ) : ClassVisitor(OpenedClassReader .ASM_API , visitor) {
181+
182+ override fun visitMethod (
183+ access : Int ,
184+ name : String ,
185+ descriptor : String ,
186+ signature : String? ,
187+ exceptions : Array <String >?
188+ ): MethodVisitor {
189+ val mv = super .visitMethod(access, name, descriptor, signature, exceptions)
190+
191+ // Only remap the createCloseContext method
192+ if (name == " createCloseContext" ) {
193+ return BukkitElementFactoryMethodVisitor (mv)
194+ }
195+
196+ return mv
197+ }
198+ }
199+
200+ private class BukkitElementFactoryMethodVisitor (
201+ visitor : MethodVisitor
202+ ) : MethodVisitor(OpenedClassReader .ASM_API , visitor) {
203+
204+ override fun visitTypeInsn (opcode : Int , type : String ) {
205+ // Remove CHECKCAST to InventoryCloseEvent
206+ if (opcode == Opcodes .CHECKCAST && type == INVENTORY_CLOSE_EVENT_INTERNAL ) {
207+ return // skip the cast entirely
208+ }
209+ super .visitTypeInsn(opcode, type)
210+ }
211+
212+ override fun visitMethodInsn (
213+ opcode : Int ,
214+ owner : String ,
215+ name : String ,
216+ descriptor : String ,
217+ isInterface : Boolean
218+ ) {
219+ // Remap the CloseContext constructor invocation descriptor
220+ if (owner == CLOSE_CONTEXT_INTERNAL && name == " <init>" ) {
221+ val remappedDescriptor = remapDescriptor(descriptor)
222+ super .visitMethodInsn(opcode, owner, name, remappedDescriptor, isInterface)
223+ } else {
224+ super .visitMethodInsn(opcode, owner, name, descriptor, isInterface)
225+ }
226+ }
227+ }
228+
229+ /* *
230+ * Replaces all occurrences of `InventoryCloseEvent` with `Object` in a type descriptor.
231+ */
232+ private fun remapDescriptor (descriptor : String ): String {
233+ val closeEventDescriptor = Type .getObjectType(INVENTORY_CLOSE_EVENT_INTERNAL ).descriptor
234+ val objectDescriptor = Type .getObjectType(OBJECT_INTERNAL ).descriptor
235+ return descriptor.replace(closeEventDescriptor, objectDescriptor)
236+ }
237+ }
0 commit comments