Implement dynamic remapping for InventoryView methods#229
Implement dynamic remapping for InventoryView methods#229ammodev merged 1 commit intoversion/1.21.11from
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a runtime ByteBuddy-based bytecode remapping hook to improve Bukkit InventoryView compatibility (when InventoryView is an interface at runtime) and wires it into the inventory framework initialization, alongside a patch version bump.
Changes:
- Introduces
InventoryViewRemapperto redefine a shaded InventoryFramework runtime class and rewriteINVOKEVIRTUAL→INVOKEINTERFACEcalls targetingorg.bukkit.inventory.InventoryView. - Triggers the remapping during
InventoryLoaderinitialization. - Bumps project version to
1.21.11-2.59.4.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/inventory/framework/InventoryViewRemapper.kt | Adds ByteBuddy/ASM-based runtime class redefinition to patch InventoryView callsites. |
| surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/inventory/framework/InventoryLoader.kt | Ensures remapping runs automatically as part of inventory framework setup. |
| gradle.properties | Patch version bump. |
| fun remap() { | ||
| if (!isRemapNecessary()) return | ||
|
|
||
| val locator = ClassFileLocator.ForClassLoader.of(javaClass.classLoader) | ||
| val typePool = TypePool.Default.of(locator) | ||
| val typeDescription = typePool.describe(INVENTORY_UPDATE_INTERNAL.replace("/", ".")).resolve() | ||
|
|
||
| ByteBuddy() | ||
| .redefine<Any>(typeDescription, locator) | ||
| .visit(InventoryViewClassVisitorWrapper()) | ||
| .make() | ||
| .load(javaClass.classLoader, ClassLoadingStrategy.Default.INJECTION) | ||
| } |
There was a problem hiding this comment.
remap() can throw (e.g., TypePool#resolve() if the class name changes / isn't present, or load(...) if injection fails). Because this runs during plugin startup, any uncaught exception here will fail inventory framework initialization (or even plugin load). Consider wrapping the resolve/redefine/load sequence in a try/catch and logging a warning/severe message so the plugin can continue (ideally with a clear indication that InventoryView compatibility patching failed).
|
|
||
| object InventoryViewRemapper { | ||
| private const val INVENTORY_VIEW_INTERNAL = "org/bukkit/inventory/InventoryView" | ||
| private const val INVENTORY_UPDATE_INTERNAL = | ||
| "dev/slne/surf/surfapi/libs/devnatan/inventoryframework/runtime/thirdparty/InventoryUpdate" |
There was a problem hiding this comment.
INVENTORY_UPDATE_INTERNAL hard-codes the relocated internal class name, which is tightly coupled to the Shadow relocation configuration (relocationPrefix + relocate("me.devnatan.inventoryframework", ...)). This is easy to break if the relocation prefix or library package structure changes. Consider deriving this name from an actual type reference (if available) or centralizing the relocated prefix/string in one place so build config and runtime remapping can’t drift.
| object InventoryViewRemapper { | |
| private const val INVENTORY_VIEW_INTERNAL = "org/bukkit/inventory/InventoryView" | |
| private const val INVENTORY_UPDATE_INTERNAL = | |
| "dev/slne/surf/surfapi/libs/devnatan/inventoryframework/runtime/thirdparty/InventoryUpdate" | |
| import dev.slne.surf.surfapi.libs.devnatan.inventoryframework.runtime.thirdparty.InventoryUpdate | |
| object InventoryViewRemapper { | |
| private val INVENTORY_VIEW_INTERNAL: String = InventoryView::class.java.name.replace('.', '/') | |
| private val INVENTORY_UPDATE_INTERNAL: String = | |
| InventoryUpdate::class.java.name.replace('.', '/') |
|
|
||
| object InventoryLoader { | ||
| init { | ||
| InventoryViewRemapper.remap() |
There was a problem hiding this comment.
Calling InventoryViewRemapper.remap() in the object initializer means any exception during remapping will prevent InventoryLoader from initializing (and then break InventoryLoader.load() / enable() calls). Even if remap() is made more defensive, it’s safer to ensure failures here are contained (e.g., handle/log exceptions inside remap() or around this call) so inventory framework startup doesn’t hard-fail.
| object InventoryLoader { | |
| init { | |
| InventoryViewRemapper.remap() | |
| import java.util.logging.Level | |
| object InventoryLoader { | |
| init { | |
| try { | |
| InventoryViewRemapper.remap() | |
| } catch (t: Throwable) { | |
| plugin.logger.log( | |
| Level.SEVERE, | |
| "Failed to remap inventory views during InventoryLoader initialization. " + | |
| "Inventory framework will continue to load, but some inventories may not behave as expected.", | |
| t | |
| ) | |
| } |
This pull request introduces a new runtime class remapping mechanism to address compatibility issues with
InventoryViewin the Bukkit server inventory framework, and ensures this remapping is triggered during inventory framework initialization. It also bumps the project version.InventoryView compatibility improvements:
InventoryViewRemapperobject inInventoryViewRemapper.ktthat uses ByteBuddy to dynamically remap method calls fromINVOKEVIRTUALtoINVOKEINTERFACEforInventoryView, ensuring compatibility whenInventoryViewis an interface.InventoryLoaderto invokeInventoryViewRemapper.remap()during initialization, so remapping occurs automatically when the inventory framework is loaded.Project metadata:
gradle.propertiesfrom1.21.11-2.59.3to1.21.11-2.59.4.