-
Notifications
You must be signed in to change notification settings - Fork 4
How it works
SummerMC Developer edited this page May 28, 2026
·
2 revisions
Folia Phantom uses ASM 9.7 (bytecode manipulation library) to rewrite .class files inside plugin JARs, redirecting Bukkit API calls to Folia-compatible equivalents.
Input JAR
│
├── ① Remove signature files (.SF/.DSA/.RSA)
├── ② Add folia-supported: true to plugin.yml
├── ③ ForkJoinPool parallel class transformation
│ ├── ScanningClassVisitor (fast-fail)
│ └── Transformer chain (if patch needed)
├── ④ Bundle FoliaPatcher runtime classes
└── ⑤ Write output JAR
ScanningClassVisitor pre-scans each class before transformation. If none of the target APIs are found, the class is skipped entirely.
Detected API calls:
| Owner | Methods |
|---|---|
BukkitScheduler |
Any method |
BukkitRunnable |
Any method (including instance methods) |
Block |
setType / setBlockData / breakNaturally / applyBoneMeal
|
Bukkit |
createWorld |
Plugin |
getDefaultWorldGenerator |
Entity |
teleport / remove / setFireTicks / setVelocity
|
LivingEntity |
damage / setHealth / addPotionEffect
|
Player |
openInventory / closeInventory / kickPlayer / setGameMode
|
World |
spawn / dropItem / createExplosion / strikeLightning
|
| Original | Transformed |
|---|---|
block.setType(material) |
FoliaPatcher.safeSetType(block, material) |
block.setBlockData(data) |
FoliaPatcher.safeSetBlockData(block, data) |
block.breakNaturally(tool) |
FoliaPatcher.safeBreakNaturally(block, tool) |
block.applyBoneMeal(face) |
FoliaPatcher.safeApplyBoneMeal(block, face) |
Strategy: isOwningRegion(location) → RegionScheduler.run()
| Original | Transformed |
|---|---|
Bukkit.createWorld(creator) |
FoliaPatcher.createWorld(creator) |
World.spawn(loc, class) |
FoliaPatcher.safeSpawn(loc, class) |
World.dropItem(loc, item) |
FoliaPatcher.safeDropItem(loc, item) |
World.createExplosion(loc, power) |
FoliaPatcher.safeCreateExplosion(loc, power) |
World.strikeLightning(loc) |
FoliaPatcher.safeStrikeLightning(loc) |
Strategy: isOwningRegion(location) → RegionScheduler.run()
| Original | Transformed |
|---|---|
entity.teleport(location) |
FoliaPatcher.safeTeleport(entity, location) |
entity.remove() |
FoliaPatcher.safeRemove(entity) |
livingEntity.damage(amount) |
FoliaPatcher.safeDamage(entity, amount) |
livingEntity.setHealth(health) |
FoliaPatcher.safeSetHealth(entity, health) |
livingEntity.addPotionEffect(effect) |
FoliaPatcher.safeAddPotionEffect(entity, effect) |
Strategy: isOwningRegion(entity) → EntityScheduler.execute()
| Original | Transformed |
|---|---|
player.openInventory(inv) |
FoliaPatcher.safeOpenInventory(player, inv) |
player.closeInventory() |
FoliaPatcher.safeCloseInventory(player) |
player.kickPlayer(message) |
FoliaPatcher.safeKickPlayer(player, message) |
player.setGameMode(mode) |
FoliaPatcher.safeSetGameMode(player, mode) |
Strategy: isOwningRegion(player) → EntityScheduler.execute()
| Original | Transformed |
|---|---|
BukkitScheduler.runTask(plugin, task) |
FoliaPatcher.runTask(null, plugin, task) |
BukkitRunnable.runTaskLater(plugin, delay) |
FoliaPatcher.runTaskLater_onRunnable(runnable, plugin, delay) |
Routing: Location available → RegionScheduler | No location → GlobalRegionScheduler | Async → AsyncScheduler
The FoliaPatcher class is bundled into the output JAR and provides:
-
Thread ownership check:
isOwningRegion(Location)viaServer.isOwnedByCurrentRegion() -
Return-value operations:
CompletableFuturefor synchronous wait - Fallback: Direct execution when plugin reference is null (with warning)
-
plugin.yml: Adds
folia-supported: true -
Signature removal: Deletes
META-INF/*.SF,.DSA,.RSA -
Runtime bundle: Includes
FoliaPatcher.class,FoliaPatcher$FoliaBukkitTask.class,FoliaPatcher$FoliaChunkGenerator.class