Skip to content

How it works

SummerMC Developer edited this page May 28, 2026 · 2 revisions

⚙️ How It Works

Folia Phantom uses ASM 9.7 (bytecode manipulation library) to rewrite .class files inside plugin JARs, redirecting Bukkit API calls to Folia-compatible equivalents.

🏗️ Architecture

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

🔍 Fast-Fail Scanning

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

🔄 Transformer Chain (Apply Order)

1. ThreadSafetyTransformer — Block Operations

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()

2. WorldGenClassTransformer — World Operations

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()

3. EntitySchedulerTransformer — Entity Operations

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()

4. PlayerSafetyTransformer — Player / Inventory

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()

5. SchedulerClassTransformer — Scheduling

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

🧠 FoliaPatcher Runtime Bridge

The FoliaPatcher class is bundled into the output JAR and provides:

  • Thread ownership check: isOwningRegion(Location) via Server.isOwnedByCurrentRegion()
  • Return-value operations: CompletableFuture for synchronous wait
  • Fallback: Direct execution when plugin reference is null (with warning)

📦 JAR Post-Processing

  • 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

🏠 Home

Clone this wiki locally