Add Sponge Support

This commit is contained in:
Myles 2016-09-26 01:44:21 +01:00
parent a1fbca11f0
commit b371c14a27
30 changed files with 1570 additions and 16 deletions

View File

@ -1 +1,2 @@
Handle injector errors
PORT STUFF TO GUAVA :D (so we dont need to include commons)
Test on SpongeForge, only tested SpongeVanilla

View File

@ -16,12 +16,25 @@
<bukkitVersion>1.8.8-R0.1-SNAPSHOT</bukkitVersion>
</properties>
<build>
<resources>
<resource>
<targetPath>.</targetPath>
<filtering>true</filtering>
<directory>src/main/resources/</directory>
<includes>
<include>*</include>
</includes>
</resource>
</resources>
</build>
<dependencies>
<!-- Common Module -->
<dependency>
<groupId>us.myles</groupId>
<artifactId>viaversion-common</artifactId>
<version>${parent.version}</version>
<version>${project.parent.version}</version>
<scope>provided</scope>
</dependency>

View File

@ -19,7 +19,6 @@ public class BukkitViaBulkChunkTranslator extends BulkChunkTranslatorProvider {
static {
try {
// TODO: Abstract this ?
mapChunkBulkRef = new ReflectionUtil.ClassReflection(ReflectionUtil.nms("PacketPlayOutMapChunkBulk"));
mapChunkRef = new ReflectionUtil.ClassReflection(ReflectionUtil.nms("PacketPlayOutMapChunk"));
if (((ViaVersionPlugin) Via.getPlatform()).isSpigot()) {
@ -74,7 +73,7 @@ public class BukkitViaBulkChunkTranslator extends BulkChunkTranslatorProvider {
}
@Override
public boolean isEnabled() {
return true;
public boolean isFiltered(Class<?> packetClass) {
return packetClass.getName().endsWith("PacketPlayOutMapChunkBulk");
}
}

View File

@ -3,7 +3,7 @@ package us.myles.ViaVersion.listeners;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import us.myles.ViaVersion.api.ViaVersion;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.update.UpdateUtil;
public class UpdateListener implements Listener {
@ -11,7 +11,7 @@ public class UpdateListener implements Listener {
@EventHandler
public void onJoin(PlayerJoinEvent e) {
if (e.getPlayer().hasPermission("viaversion.update")
&& ViaVersion.getConfig().isCheckForUpdates()) {
&& Via.getConfig().isCheckForUpdates()) {
UpdateUtil.sendUpdateMessage(e.getPlayer().getUniqueId());
}
}

View File

@ -2,6 +2,7 @@ name: ViaVersion
main: us.myles.ViaVersion.ViaVersionPlugin
authors: [_MylesC, Matsv]
version: ${project.version}
description: Allow newer Minecraft versions to connect to an older server version.
load: postworld
loadbefore: [ProtocolLib, ProxyPipe, SpigotLib, SkinRestorer]
softdepend: [ProtocolSupport, PacketListenerApi]

View File

@ -11,6 +11,19 @@
<artifactId>viaversion-bungee</artifactId>
<build>
<resources>
<resource>
<targetPath>.</targetPath>
<filtering>true</filtering>
<directory>src/main/resources/</directory>
<includes>
<include>*</include>
</includes>
</resource>
</resources>
</build>
<dependencies>
<!-- BungeeCord -->
<dependency>

View File

@ -91,9 +91,8 @@ public class Protocol1_9TO1_8 extends Protocol {
@Override
public boolean isFiltered(Class packetClass) {
if (!Via.getManager().getProviders().get(BulkChunkTranslatorProvider.class).isEnabled())
return false;
return packetClass.getName().endsWith("PacketPlayOutMapChunkBulk");
return Via.getManager().getProviders().get(BulkChunkTranslatorProvider.class).isFiltered(packetClass);
}
@Override

View File

@ -11,7 +11,7 @@ public class BulkChunkTranslatorProvider implements Provider {
return Arrays.asList(packet);
}
public boolean isEnabled() {
public boolean isFiltered(Class<?> packet) {
return false;
}
}

View File

@ -18,7 +18,7 @@
<resource>
<targetPath>.</targetPath>
<filtering>false</filtering>
<directory>.</directory>
<directory>../</directory>
<includes>
<include>LICENSE</include>
</includes>
@ -57,6 +57,10 @@
<pattern>org.javassist</pattern>
<shadedPattern>us.myles.viaversion.libs.javassist</shadedPattern>
</relocation>
<relocation>
<pattern>org.apache</pattern>
<shadedPattern>us.myles.viaversion.libs.apache</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
@ -75,17 +79,22 @@
<dependency>
<groupId>us.myles</groupId>
<artifactId>viaversion-common</artifactId>
<version>${parent.version}</version>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>us.myles</groupId>
<artifactId>viaversion-bukkit</artifactId>
<version>${parent.version}</version>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>us.myles</groupId>
<artifactId>viaversion-bungee</artifactId>
<version>${parent.version}</version>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>us.myles</groupId>
<artifactId>viaversion-sponge</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>

View File

@ -19,6 +19,7 @@
<module>common</module>
<module>bukkit</module>
<module>bungee</module>
<module>sponge</module>
<module>jar</module>
</modules>
@ -104,7 +105,7 @@
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
<scope>provided</scope>
<scope>compile</scope>
</dependency>
<!-- ChatColour API -->

72
sponge/pom.xml Normal file
View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>viaversion-parent</artifactId>
<groupId>us.myles</groupId>
<version>1.0.0-ALPHA-modules</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>viaversion-sponge</artifactId>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>sponge</id>
<url>http://repo.spongepowered.org/maven</url>
</repository>
</repositories>
<build>
<resources>
<resource>
<targetPath>.</targetPath>
<filtering>true</filtering>
<directory>src/main/resources/</directory>
<includes>
<include>*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>templating-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<id>filter-src</id>
<goals>
<goal>filter-sources</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Common Module -->
<dependency>
<groupId>us.myles</groupId>
<artifactId>viaversion-common</artifactId>
<version>${project.parent.version}</version>
<scope>provided</scope>
</dependency>
<!-- Sponge API -->
<dependency>
<groupId>org.spongepowered</groupId>
<artifactId>spongeapi</artifactId>
<version>4.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,5 @@
package us.myles.ViaVersion.sponge;
public class VersionInfo {
public static final String VERSION = "${project.version}";
}

View File

@ -0,0 +1,163 @@
package us.myles.ViaVersion;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import org.spongepowered.api.Game;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.state.GameStartedServerEvent;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.plugin.PluginContainer;
import org.spongepowered.api.scheduler.SpongeExecutorService;
import org.spongepowered.api.text.serializer.TextSerializers;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.api.ViaAPI;
import us.myles.ViaVersion.api.ViaVersionConfig;
import us.myles.ViaVersion.api.command.ViaCommandSender;
import us.myles.ViaVersion.api.configuration.ConfigurationProvider;
import us.myles.ViaVersion.api.platform.ViaPlatform;
import us.myles.ViaVersion.sponge.*;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
@Plugin(id = "viaversion",
name = "ViaVersion",
version = VersionInfo.VERSION,
authors = {"_MylesC", "Matsv"},
description = "Allow newer Minecraft versions to connect to an older server version."
)
public class SpongePlugin implements ViaPlatform {
@Inject
private Game game;
@Inject
private PluginContainer container;
private SpongeExecutorService asyncExecutor;
private SpongeExecutorService syncExecutor;
private SpongeConfigAPI conf = new SpongeConfigAPI(this);
private SpongeViaAPI api = new SpongeViaAPI();
private Logger logger;
@Listener
public void onServerStart(GameStartedServerEvent event) {
// Setup Logger
logger = new LoggerWrapper(container.getLogger());
// Setup Plugin
syncExecutor = game.getScheduler().createSyncExecutor(this);
asyncExecutor = game.getScheduler().createAsyncExecutor(this);
SpongeCommandHandler commandHandler = new SpongeCommandHandler();
game.getCommandManager().register(this, commandHandler, Arrays.asList("viaversion", "viaver"));
// Init platform
Via.init(ViaManager.builder()
.platform(this)
.commandHandler(commandHandler)
.injector(new SpongeViaInjector())
.loader(new SpongeViaLoader(this))
.build());
// Inject!
Via.getManager().init();
}
@Override
public Logger getLogger() {
return logger;
}
@Override
public String getPlatformName() {
return "Sponge";
}
@Override
public String getPluginVersion() {
return container.getVersion().orElse("Unknown Version");
}
@Override
public int runAsync(Runnable runnable) {
asyncExecutor.execute(runnable);
return -1;
}
@Override
public int runSync(Runnable runnable) {
syncExecutor.execute(runnable);
return -1;
}
@Override
public int runRepeatingSync(Runnable runnable, Long ticks) {
Long time = ticks * 50L;
syncExecutor.scheduleAtFixedRate(runnable, time, time, TimeUnit.MILLISECONDS);
// use id?
return -1;
}
@Override
public void cancelTask(int taskId) {
// oh.
}
@Override
public ViaCommandSender[] getOnlinePlayers() {
ViaCommandSender[] array = new ViaCommandSender[game.getServer().getOnlinePlayers().size()];
int i = 0;
for (Player player : game.getServer().getOnlinePlayers()) {
array[i++] = new SpongeCommandSender(player);
}
return array;
}
@Override
public void sendMessage(UUID uuid, String message) {
for (Player player : game.getServer().getOnlinePlayers()) {
if (player.getUniqueId().equals(uuid))
player.sendMessage(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(message));
}
}
@Override
public boolean kickPlayer(UUID uuid, String message) {
for (Player player : game.getServer().getOnlinePlayers()) {
if (player.getUniqueId().equals(uuid)) {
player.kick(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(message));
return true;
}
}
return false;
}
@Override
public boolean isPluginEnabled() {
return true;
}
@Override
public ViaAPI getApi() {
return api;
}
@Override
public ViaVersionConfig getConf() {
return conf;
}
@Override
public ConfigurationProvider getConfigurationProvider() {
return conf;
}
@Override
public void onReload() {
// TODO: Warning?
}
@Override
public JsonObject getDump() {
return new JsonObject();
}
}

View File

@ -0,0 +1,125 @@
package us.myles.ViaVersion.sponge;
import org.slf4j.Logger;
import java.util.logging.Level;
import java.util.logging.LogRecord;
public class LoggerWrapper extends java.util.logging.Logger {
private final Logger base;
public LoggerWrapper(Logger logger) {
super("logger", null);
this.base = logger;
}
@Override
public void log(LogRecord record) {
log(record.getLevel(), record.getMessage());
}
@Override
public void log(Level level, String msg) {
if (level == Level.FINEST) {
base.trace(msg);
return;
}
if (level == Level.FINE) {
base.debug(msg);
return;
}
if (level == Level.WARNING) {
base.warn(msg);
return;
}
if (level == Level.SEVERE) {
base.error(msg);
return;
}
if (level == Level.INFO) {
base.info(msg);
return;
}
base.trace(msg);
return;
}
@Override
public void log(Level level, String msg, Object param1) {
if (level == Level.FINEST) {
base.trace(msg, param1);
return;
}
if (level == Level.FINE) {
base.debug(msg, param1);
return;
}
if (level == Level.WARNING) {
base.warn(msg, param1);
return;
}
if (level == Level.SEVERE) {
base.error(msg, param1);
return;
}
if (level == Level.INFO) {
base.info(msg, param1);
return;
}
base.trace(msg, param1);
return;
}
@Override
public void log(Level level, String msg, Object[] params) {
if (level == Level.FINEST) {
base.trace(msg, params);
return;
}
if (level == Level.FINE) {
base.debug(msg, params);
return;
}
if (level == Level.WARNING) {
base.warn(msg, params);
return;
}
if (level == Level.SEVERE) {
base.error(msg, params);
return;
}
if (level == Level.INFO) {
base.info(msg, params);
return;
}
base.trace(msg, params);
return;
}
@Override
public void log(Level level, String msg, Throwable params) {
if (level == Level.FINEST) {
base.trace(msg, params);
return;
}
if (level == Level.FINE) {
base.debug(msg, params);
return;
}
if (level == Level.WARNING) {
base.warn(msg, params);
return;
}
if (level == Level.SEVERE) {
base.error(msg, params);
return;
}
if (level == Level.INFO) {
base.info(msg, params);
return;
}
base.trace(msg, params);
return;
}
}

View File

@ -0,0 +1,35 @@
package us.myles.ViaVersion.sponge;
import lombok.Getter;
import org.spongepowered.api.entity.living.player.Player;
import us.myles.ViaVersion.api.boss.BossBar;
import us.myles.ViaVersion.api.boss.BossColor;
import us.myles.ViaVersion.api.boss.BossStyle;
import us.myles.ViaVersion.boss.CommonBoss;
@Getter
public class SpongeBossBar extends CommonBoss<Player> {
public SpongeBossBar(String title, float health, BossColor color, BossStyle style) {
super(title, health, color, style);
}
@Override
public BossBar addPlayer(Player player) {
addPlayer(player.getUniqueId());
return this;
}
@Override
public BossBar addPlayers(Player... players) {
for (Player p : players)
addPlayer(p);
return this;
}
@Override
public BossBar removePlayer(Player player) {
removePlayer(player.getUniqueId());
return this;
}
}

View File

@ -0,0 +1,47 @@
package us.myles.ViaVersion.sponge;
import org.spongepowered.api.command.CommandCallable;
import org.spongepowered.api.command.CommandException;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.text.Text;
import us.myles.ViaVersion.commands.ViaCommandHandler;
import java.util.List;
import java.util.Optional;
public class SpongeCommandHandler extends ViaCommandHandler implements CommandCallable {
@Override
public CommandResult process(CommandSource source, String arguments) throws CommandException {
String[] args = arguments.length() > 0 ? arguments.split(" ") : new String[0];
onCommand(new SpongeCommandSender(source), args);
return CommandResult.success();
}
@Override
public List<String> getSuggestions(CommandSource source, String arguments) throws CommandException {
String[] args = arguments.length() > 0 ? arguments.split(" ") : new String[0];
return onTabComplete(new SpongeCommandSender(source), args);
}
@Override
public boolean testPermission(CommandSource source) {
return source.hasPermission("viaversion.admin");
}
@Override
public Optional<? extends Text> getShortDescription(CommandSource source) {
return Optional.of(Text.of("Shows ViaVersion Version and more."));
}
@Override
public Optional<? extends Text> getHelp(CommandSource source) {
return Optional.empty();
}
@Override
public Text getUsage(CommandSource source) {
return Text.of("Usage /viaversion");
}
}

View File

@ -0,0 +1,39 @@
package us.myles.ViaVersion.sponge;
import lombok.AllArgsConstructor;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.text.serializer.TextSerializer;
import org.spongepowered.api.text.serializer.TextSerializers;
import us.myles.ViaVersion.api.command.ViaCommandSender;
import java.util.UUID;
@AllArgsConstructor
public class SpongeCommandSender implements ViaCommandSender {
private CommandSource source;
@Override
public boolean hasPermission(String permission) {
return source.hasPermission(permission);
}
@Override
public void sendMessage(String msg) {
source.sendMessage(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(msg));
}
@Override
public UUID getUUID() {
if (source instanceof Player) {
return ((Player) source).getUniqueId();
} else {
return UUID.fromString(getName());
}
}
@Override
public String getName() {
return source.getName();
}
}

View File

@ -0,0 +1,183 @@
package us.myles.ViaVersion.sponge;
import us.myles.ViaVersion.SpongePlugin;
import us.myles.ViaVersion.api.ViaVersionConfig;
import us.myles.ViaVersion.api.configuration.ConfigurationProvider;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SpongeConfigAPI implements ViaVersionConfig, ConfigurationProvider{
private final SpongePlugin spongePlugin;
public SpongeConfigAPI(SpongePlugin spongePlugin) {
this.spongePlugin = spongePlugin;
}
@Override
public boolean isCheckForUpdates() {
return false;
}
@Override
public boolean isPreventCollision() {
return false;
}
@Override
public boolean isNewEffectIndicator() {
return false;
}
@Override
public boolean isShowNewDeathMessages() {
return false;
}
@Override
public boolean isSuppressMetadataErrors() {
return false;
}
@Override
public boolean isShieldBlocking() {
return false;
}
@Override
public boolean isHologramPatch() {
return false;
}
@Override
public boolean isBossbarPatch() {
return false;
}
@Override
public boolean isBossbarAntiflicker() {
return false;
}
@Override
public boolean isUnknownEntitiesSuppressed() {
return false;
}
@Override
public double getHologramYOffset() {
return 0;
}
@Override
public boolean isAutoTeam() {
return false;
}
@Override
public boolean isBlockBreakPatch() {
return false;
}
@Override
public int getMaxPPS() {
return 0;
}
@Override
public String getMaxPPSKickMessage() {
return null;
}
@Override
public int getTrackingPeriod() {
return 0;
}
@Override
public int getWarningPPS() {
return 0;
}
@Override
public int getMaxWarnings() {
return 0;
}
@Override
public String getMaxWarningsKickMessage() {
return null;
}
@Override
public boolean isAntiXRay() {
return false;
}
@Override
public boolean isSendSupportedVersions() {
return false;
}
@Override
public boolean isStimulatePlayerTick() {
return false;
}
@Override
public boolean isItemCache() {
return false;
}
@Override
public boolean isNMSPlayerTicking() {
return false;
}
@Override
public boolean isReplacePistons() {
return false;
}
@Override
public int getPistonReplacementId() {
return 0;
}
@Override
public boolean isForceJsonTransform() {
return false;
}
@Override
public List<Integer> getBlockedProtocols() {
return Arrays.asList(0);
}
@Override
public String getBlockedDisconnectMsg() {
return "Boop";
}
@Override
public String getReloadDisconnectMsg() {
return "Beep";
}
@Override
public void set(String path, Object value) {
}
@Override
public void saveConfig() {
}
@Override
public Map<String, Object> getValues() {
return new HashMap<>();
}
}

View File

@ -0,0 +1,81 @@
package us.myles.ViaVersion.sponge;
import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import org.spongepowered.api.entity.living.player.Player;
import us.myles.ViaVersion.SpongePlugin;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.api.ViaAPI;
import us.myles.ViaVersion.api.boss.BossBar;
import us.myles.ViaVersion.api.boss.BossColor;
import us.myles.ViaVersion.api.boss.BossStyle;
import us.myles.ViaVersion.api.data.UserConnection;
import us.myles.ViaVersion.api.protocol.ProtocolRegistry;
import us.myles.ViaVersion.protocols.base.ProtocolInfo;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
public class SpongeViaAPI implements ViaAPI<Player> {
@Override
public int getPlayerVersion(@NonNull Player player) {
if (!isPorted(player.getUniqueId()))
return ProtocolRegistry.SERVER_PROTOCOL;
return getPortedPlayers().get(player.getUniqueId()).get(ProtocolInfo.class).getProtocolVersion();
}
@Override
public int getPlayerVersion(@NonNull UUID uuid) {
if (!isPorted(uuid))
return ProtocolRegistry.SERVER_PROTOCOL;
return getPortedPlayers().get(uuid).get(ProtocolInfo.class).getProtocolVersion();
}
@Override
public boolean isPorted(UUID playerUUID) {
return getPortedPlayers().containsKey(playerUUID);
}
@Override
public String getVersion() {
return Via.getPlatform().getPluginVersion();
}
@Override
public void sendRawPacket(UUID uuid, ByteBuf packet) throws IllegalArgumentException {
if (!isPorted(uuid)) throw new IllegalArgumentException("This player is not controlled by ViaVersion!");
UserConnection ci = getPortedPlayers().get(uuid);
ci.sendRawPacket(packet);
}
@Override
public void sendRawPacket(Player player, ByteBuf packet) throws IllegalArgumentException {
sendRawPacket(player.getUniqueId(), packet);
}
@Override
public BossBar createBossBar(String title, BossColor color, BossStyle style) {
return new SpongeBossBar(title, 1F, color, style);
}
@Override
public BossBar createBossBar(String title, float health, BossColor color, BossStyle style) {
return new SpongeBossBar(title, health, color, style);
}
@Override
public SortedSet<Integer> getSupportedVersions() {
SortedSet<Integer> outputSet = new TreeSet<>(ProtocolRegistry.getSupportedVersions());
outputSet.removeAll(Via.getPlatform().getConf().getBlockedProtocols());
return outputSet;
}
public Map<UUID, UserConnection> getPortedPlayers() {
return Via.getManager().getPortedPlayers();
}
}

View File

@ -0,0 +1,197 @@
package us.myles.ViaVersion.sponge;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import us.myles.ViaVersion.api.Pair;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.api.platform.ViaInjector;
import us.myles.ViaVersion.sponge.handlers.ViaVersionInitializer;
import us.myles.ViaVersion.sponge.util.ReflectionUtil;
import us.myles.ViaVersion.util.ListWrapper;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
public class SpongeViaInjector implements ViaInjector {
private List<ChannelFuture> injectedFutures = new ArrayList<>();
private List<Pair<Field, Object>> injectedLists = new ArrayList<>();
@Override
public void inject() throws Exception {
try {
Object connection = getServerConnection();
if (connection == null) {
throw new Exception("We failed to find the core component 'ServerConnection', please file an issue on our GitHub.");
}
for (Field field : connection.getClass().getDeclaredFields()) {
field.setAccessible(true);
final Object value = field.get(connection);
if (value instanceof List) {
// Inject the list
List wrapper = new ListWrapper((List) value) {
@Override
public synchronized void handleAdd(Object o) {
synchronized (this) {
if (o instanceof ChannelFuture) {
try {
injectChannelFuture((ChannelFuture) o);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
};
injectedLists.add(new Pair<>(field, connection));
field.set(connection, wrapper);
// Iterate through current list
synchronized (wrapper) {
for (Object o : (List) value) {
if (o instanceof ChannelFuture) {
injectChannelFuture((ChannelFuture) o);
} else {
break; // not the right list.
}
}
}
}
}
} catch (Exception e) {
Via.getPlatform().getLogger().severe("Unable to inject ViaVersion, please post these details on our GitHub and ensure you're using a compatible server version.");
throw e;
}
}
private void injectChannelFuture(ChannelFuture future) throws Exception {
try {
ChannelHandler bootstrapAcceptor = future.channel().pipeline().first();
try {
ChannelInitializer<SocketChannel> oldInit = ReflectionUtil.get(bootstrapAcceptor, "childHandler", ChannelInitializer.class);
ChannelInitializer newInit = new ViaVersionInitializer(oldInit);
ReflectionUtil.set(bootstrapAcceptor, "childHandler", newInit);
injectedFutures.add(future);
} catch (NoSuchFieldException e) {
throw new Exception("Unable to find core component 'childHandler', please check your plugins. issue: " + bootstrapAcceptor.getClass().getName());
}
} catch (Exception e) {
Via.getPlatform().getLogger().severe("We failed to inject ViaVersion, have you got late-bind enabled with something else?");
throw e;
}
}
@Override
public void uninject() {
// TODO: Uninject from players currently online
for (ChannelFuture future : injectedFutures) {
ChannelHandler bootstrapAcceptor = future.channel().pipeline().first();
try {
ChannelInitializer<SocketChannel> oldInit = ReflectionUtil.get(bootstrapAcceptor, "childHandler", ChannelInitializer.class);
if (oldInit instanceof ViaVersionInitializer) {
ReflectionUtil.set(bootstrapAcceptor, "childHandler", ((ViaVersionInitializer) oldInit).getOriginal());
}
} catch (Exception e) {
System.out.println("Failed to remove injection handler, reload won't work with connections, please reboot!");
}
}
injectedFutures.clear();
for (Pair<Field, Object> pair : injectedLists) {
try {
Object o = pair.getKey().get(pair.getValue());
if (o instanceof ListWrapper) {
pair.getKey().set(pair.getValue(), ((ListWrapper) o).getOriginalList());
}
} catch (IllegalAccessException e) {
System.out.println("Failed to remove injection, reload won't work with connections, please reboot!");
}
}
injectedLists.clear();
}
public static Object getServer() throws Exception {
Class<?> serverClazz = Class.forName("net.minecraft.server.MinecraftServer");
for (Method m : serverClazz.getDeclaredMethods()) {
if (m.getParameterCount() == 0) {
if ((m.getModifiers() & Modifier.STATIC) == Modifier.STATIC) {
if (m.getReturnType().equals(serverClazz)) {
return m.invoke(null);
}
}
}
}
throw new Exception("Could not find MinecraftServer static field!");
}
@Override
public int getServerProtocolVersion() throws Exception {
try {
Class<?> serverClazz = Class.forName("net.minecraft.server.MinecraftServer");
Object server = getServer();
Class<?> pingClazz = Class.forName("net.minecraft.network.ServerStatusResponse");
Object ping = null;
// Search for ping method
for (Field f : serverClazz.getDeclaredFields()) {
if (f.getType() != null) {
if (f.getType().getSimpleName().equals("ServerStatusResponse")) {
f.setAccessible(true);
ping = f.get(server);
}
}
}
if (ping != null) {
Object serverData = null;
for (Field f : pingClazz.getDeclaredFields()) {
if (f.getType() != null) {
if (f.getType().getSimpleName().endsWith("MinecraftProtocolVersionIdentifier")) {
f.setAccessible(true);
serverData = f.get(ping);
}
}
}
if (serverData != null) {
int protocolVersion = -1;
for (Field f : serverData.getClass().getDeclaredFields()) {
if (f.getType() != null) {
if (f.getType() == int.class) {
f.setAccessible(true);
protocolVersion = (int) f.get(serverData);
}
}
}
if (protocolVersion != -1) {
return protocolVersion;
}
}
}
} catch (Exception e) {
throw new Exception("Failed to get server", e);
}
throw new Exception("Failed to get server");
}
public static Object getServerConnection() throws Exception {
Class<?> serverClazz = Class.forName("net.minecraft.server.MinecraftServer");
Object server = getServer();
Object connection = null;
for (Method m : serverClazz.getDeclaredMethods()) {
if (m.getReturnType() != null) {
if (m.getReturnType().getSimpleName().equals("NetworkSystem")) {
if (m.getParameterTypes().length == 0) {
connection = m.invoke(server);
}
}
}
}
return connection;
}
}

View File

@ -0,0 +1,72 @@
package us.myles.ViaVersion.sponge;
import lombok.AllArgsConstructor;
import org.spongepowered.api.Sponge;
import us.myles.ViaVersion.SpongePlugin;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.api.platform.ViaPlatformLoader;
import us.myles.ViaVersion.protocols.protocol1_9to1_8.providers.BulkChunkTranslatorProvider;
import us.myles.ViaVersion.protocols.protocol1_9to1_8.providers.MovementTransmitterProvider;
import us.myles.ViaVersion.sponge.listeners.ClientLeaveListener;
import us.myles.ViaVersion.sponge.listeners.UpdateListener;
import us.myles.ViaVersion.sponge.providers.SpongeViaBulkChunkTranslator;
import us.myles.ViaVersion.sponge.providers.SpongeViaMovementTransmitter;
@AllArgsConstructor
public class SpongeViaLoader implements ViaPlatformLoader {
private SpongePlugin plugin;
@Override
public void load() {
// Update Listener
Sponge.getEventManager().registerListeners(plugin, new UpdateListener());
//
/* Base Protocol */
Sponge.getEventManager().registerListeners(plugin, new ClientLeaveListener());
// /* 1.9 client to 1.8 server */
//
// new ArmorListener(plugin).register();
// new CommandBlockListener(plugin).register();
// new DeathListener(plugin).register();
// new BlockListener(plugin).register();
//
// if (Bukkit.getVersion().toLowerCase().contains("paper") || Bukkit.getVersion().toLowerCase().contains("taco")) {
// plugin.getLogger().info("Enabling PaperSpigot/TacoSpigot patch: Fixes block placement.");
// new PaperPatch(plugin).register();
// }
// if (plugin.getConf().isItemCache()) {
// new HandItemCache().runTaskTimerAsynchronously(plugin, 2L, 2L); // Updates player's items :)
// HandItemCache.CACHE = true;
// }
//
// /* Providers */
Via.getManager().getProviders().use(BulkChunkTranslatorProvider.class, new SpongeViaBulkChunkTranslator());
Via.getManager().getProviders().use(MovementTransmitterProvider.class, new SpongeViaMovementTransmitter());
// Via.getManager().getProviders().use(HandItemProvider.class, new HandItemProvider() {
// @Override
// public Item getHandItem(final UserConnection info) {
// if (HandItemCache.CACHE) {
// return HandItemCache.getHandItem(info.get(ProtocolInfo.class).getUuid());
// } else {
// try {
// return Bukkit.getScheduler().callSyncMethod(Bukkit.getPluginManager().getPlugin("ViaVersion"), new Callable<Item>() {
// @Override
// public Item call() throws Exception {
// UUID playerUUID = info.get(ProtocolInfo.class).getUuid();
// if (Bukkit.getPlayer(playerUUID) != null) {
// return HandItemCache.convert(Bukkit.getPlayer(playerUUID).getItemInHand());
// }
// return null;
// }
// }).get(10, TimeUnit.SECONDS);
// } catch (Exception e) {
// System.out.println("Error fetching hand item: " + e.getClass().getName());
// if (Via.getManager().isDebug())
// e.printStackTrace();
// return null;
// }
// }
// }
// });
}
}

View File

@ -0,0 +1,90 @@
package us.myles.ViaVersion.sponge.handlers;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import us.myles.ViaVersion.api.PacketWrapper;
import us.myles.ViaVersion.api.data.UserConnection;
import us.myles.ViaVersion.api.type.Type;
import us.myles.ViaVersion.exception.CancelException;
import us.myles.ViaVersion.packets.Direction;
import us.myles.ViaVersion.protocols.base.ProtocolInfo;
import us.myles.ViaVersion.util.PipelineUtil;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
public class ViaDecodeHandler extends ByteToMessageDecoder {
private final ByteToMessageDecoder minecraftDecoder;
private final UserConnection info;
public ViaDecodeHandler(UserConnection info, ByteToMessageDecoder minecraftDecoder) {
this.info = info;
this.minecraftDecoder = minecraftDecoder;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf bytebuf, List<Object> list) throws Exception {
// use transformers
if (bytebuf.readableBytes() > 0) {
// Ignore if pending disconnect
if (info.isPendingDisconnect()) {
return;
}
// Increment received
boolean second = info.incrementReceived();
// Check PPS
// TODO implement pps
// if (second) {
// if (((ViaVersionPlugin) Via.getPlatform()).handlePPS(info))
// return;
// }
if (info.isActive()) {
// Handle ID
int id = Type.VAR_INT.read(bytebuf);
// Transform
ByteBuf newPacket = ctx.alloc().buffer();
try {
if (id == PacketWrapper.PASSTHROUGH_ID) {
newPacket.writeBytes(bytebuf);
} else {
PacketWrapper wrapper = new PacketWrapper(id, bytebuf, info);
ProtocolInfo protInfo = info.get(ProtocolInfo.class);
protInfo.getPipeline().transform(Direction.INCOMING, protInfo.getState(), wrapper);
wrapper.writeToBuffer(newPacket);
}
bytebuf.clear();
bytebuf = newPacket;
} catch (Exception e) {
// Clear Buffer
bytebuf.clear();
// Release Packet, be free!
newPacket.release();
throw e;
}
}
// call minecraft decoder
try {
list.addAll(PipelineUtil.callDecode(this.minecraftDecoder, ctx, bytebuf));
} catch (InvocationTargetException e) {
if (e.getCause() instanceof Exception) {
throw (Exception) e.getCause();
}
} finally {
if (info.isActive()) {
bytebuf.release();
}
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (PipelineUtil.containsCause(cause, CancelException.class)) return;
super.exceptionCaught(ctx, cause);
}
}

View File

@ -0,0 +1,72 @@
package us.myles.ViaVersion.sponge.handlers;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import us.myles.ViaVersion.api.PacketWrapper;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.api.data.UserConnection;
import us.myles.ViaVersion.api.type.Type;
import us.myles.ViaVersion.exception.CancelException;
import us.myles.ViaVersion.packets.Direction;
import us.myles.ViaVersion.protocols.base.ProtocolInfo;
import us.myles.ViaVersion.util.PipelineUtil;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class ViaEncodeHandler extends MessageToByteEncoder {
private final UserConnection info;
private final MessageToByteEncoder minecraftEncoder;
public ViaEncodeHandler(UserConnection info, MessageToByteEncoder minecraftEncoder) {
this.info = info;
this.minecraftEncoder = minecraftEncoder;
}
@Override
protected void encode(final ChannelHandlerContext ctx, Object o, final ByteBuf bytebuf) throws Exception {
// handle the packet type
if (!(o instanceof ByteBuf)) {
// call minecraft encoder
try {
PipelineUtil.callEncode(this.minecraftEncoder, ctx, o, bytebuf);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof Exception) {
throw (Exception) e.getCause();
}
}
}
if (bytebuf.readableBytes() == 0) {
throw new CancelException();
}
// Increment sent
info.incrementSent();
if (info.isActive()) {
// Handle ID
int id = Type.VAR_INT.read(bytebuf);
// Transform
ByteBuf oldPacket = bytebuf.copy();
bytebuf.clear();
try {
PacketWrapper wrapper = new PacketWrapper(id, oldPacket, info);
ProtocolInfo protInfo = info.get(ProtocolInfo.class);
protInfo.getPipeline().transform(Direction.OUTGOING, protInfo.getState(), wrapper);
wrapper.writeToBuffer(bytebuf);
} catch (Exception e) {
bytebuf.clear();
throw e;
} finally {
oldPacket.release();
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (PipelineUtil.containsCause(cause, CancelException.class)) return;
super.exceptionCaught(ctx, cause);
}
}

View File

@ -0,0 +1,35 @@
package us.myles.ViaVersion.sponge.handlers;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import us.myles.ViaVersion.api.data.UserConnection;
import us.myles.ViaVersion.protocols.base.ProtocolInfo;
import java.util.List;
public class ViaPacketHandler extends MessageToMessageEncoder {
private final UserConnection info;
public ViaPacketHandler(UserConnection info) {
this.info = info;
}
@Override
protected void encode(ChannelHandlerContext ctx, Object o, List list) throws Exception {
// Split chunks bulk packet up in to single chunks packets before it reached the encoder.
// This will prevent issues with several plugins and other protocol handlers due to the chunks being sent twice.
// It also sends the chunks in the right order possible resolving some issues with added chunks/block/entity data.
if (!(o instanceof ByteBuf)) {
info.setLastPacket(o);
/* This transformer is more for fixing issues which we find hard at packet level :) */
if (info.isActive()) {
if (info.get(ProtocolInfo.class).getPipeline().filter(o, list)) {
return;
}
}
}
list.add(o);
}
}

View File

@ -0,0 +1,48 @@
package us.myles.ViaVersion.sponge.handlers;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import us.myles.ViaVersion.api.data.UserConnection;
import us.myles.ViaVersion.api.protocol.ProtocolPipeline;
import java.lang.reflect.Method;
public class ViaVersionInitializer extends ChannelInitializer<SocketChannel> {
private final ChannelInitializer<SocketChannel> original;
private Method method;
public ViaVersionInitializer(ChannelInitializer<SocketChannel> oldInit) {
this.original = oldInit;
try {
this.method = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class);
this.method.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public ChannelInitializer<SocketChannel> getOriginal() {
return original;
}
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
UserConnection info = new UserConnection(socketChannel);
// init protocol
new ProtocolPipeline(info);
// Add originals
this.method.invoke(this.original, socketChannel);
// Add our transformers
MessageToByteEncoder encoder = new ViaEncodeHandler(info, (MessageToByteEncoder) socketChannel.pipeline().get("encoder"));
ByteToMessageDecoder decoder = new ViaDecodeHandler(info, (ByteToMessageDecoder) socketChannel.pipeline().get("decoder"));
ViaPacketHandler chunkHandler = new ViaPacketHandler(info);
socketChannel.pipeline().replace("encoder", "encoder", encoder);
socketChannel.pipeline().replace("decoder", "decoder", decoder);
socketChannel.pipeline().addAfter("packet_handler", "viaversion_packet_handler", chunkHandler);
}
}

View File

@ -0,0 +1,12 @@
package us.myles.ViaVersion.sponge.listeners;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.network.ClientConnectionEvent;
import us.myles.ViaVersion.api.Via;
public class ClientLeaveListener {
@Listener
public void onDisconnect(ClientConnectionEvent.Disconnect disconnect) {
Via.getManager().removePortedClient(disconnect.getTargetEntity().getUniqueId());
}
}

View File

@ -0,0 +1,16 @@
package us.myles.ViaVersion.sponge.listeners;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.network.ClientConnectionEvent;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.update.UpdateUtil;
public class UpdateListener {
@Listener
public void onJoin(ClientConnectionEvent.Join join) {
if (join.getTargetEntity().hasPermission("viaversion.update")
&& Via.getConfig().isCheckForUpdates()) {
UpdateUtil.sendUpdateMessage(join.getTargetEntity().getUniqueId());
}
}
}

View File

@ -0,0 +1,55 @@
package us.myles.ViaVersion.sponge.providers;
import com.google.common.collect.Lists;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.protocols.protocol1_9to1_8.providers.BulkChunkTranslatorProvider;
import us.myles.ViaVersion.protocols.protocol1_9to1_8.storage.ClientChunks;
import us.myles.ViaVersion.sponge.util.ReflectionUtil;
import java.util.List;
import java.util.logging.Level;
public class SpongeViaBulkChunkTranslator extends BulkChunkTranslatorProvider {
// Reflection
private static ReflectionUtil.ClassReflection mapChunkBulkRef;
private static ReflectionUtil.ClassReflection mapChunkRef;
static {
try {
mapChunkBulkRef = new ReflectionUtil.ClassReflection(Class.forName("net.minecraft.network.play.server.S26PacketMapChunkBulk"));
mapChunkRef = new ReflectionUtil.ClassReflection(Class.forName("net.minecraft.network.play.server.S21PacketChunkData"));
} catch (Exception e) {
Via.getPlatform().getLogger().log(Level.WARNING, "Failed to initialise chunks reflection", e);
}
}
@Override
public List<Object> transformMapChunkBulk(Object packet, ClientChunks clientChunks) {
List<Object> list = Lists.newArrayList();
try {
int[] xcoords = mapChunkBulkRef.getFieldValue("field_149266_a", packet, int[].class);
int[] zcoords = mapChunkBulkRef.getFieldValue("field_149264_b", packet, int[].class);
Object[] chunkMaps = mapChunkBulkRef.getFieldValue("field_179755_c", packet, Object[].class);
for (int i = 0; i < chunkMaps.length; i++) {
int x = xcoords[i];
int z = zcoords[i];
Object chunkMap = chunkMaps[i];
Object chunkPacket = mapChunkRef.newInstance();
mapChunkRef.setFieldValue("field_149284_a", chunkPacket, x);
mapChunkRef.setFieldValue("field_149282_b", chunkPacket, z);
mapChunkRef.setFieldValue("field_179758_c", chunkPacket, chunkMap);
mapChunkRef.setFieldValue("field_149279_g", chunkPacket, true); // Chunk bulk chunks are always ground-up
clientChunks.getBulkChunks().add(ClientChunks.toLong(x, z)); // Store for later
list.add(chunkPacket);
}
} catch (Exception e) {
Via.getPlatform().getLogger().log(Level.WARNING, "Failed to transform chunks bulk", e);
}
return list;
}
@Override
public boolean isFiltered(Class<?> packetClass) {
return packetClass.getName().endsWith("S26PacketMapChunkBulk");
}
}

View File

@ -0,0 +1,41 @@
package us.myles.ViaVersion.sponge.providers;
import us.myles.ViaVersion.protocols.protocol1_9to1_8.providers.MovementTransmitterProvider;
import java.lang.reflect.Field;
public class SpongeViaMovementTransmitter extends MovementTransmitterProvider {
// Used for packet mode
private Object idlePacket;
private Object idlePacket2;
public SpongeViaMovementTransmitter() {
Class<?> idlePacketClass;
try {
idlePacketClass = Class.forName("net.minecraft.network.play.client.C03PacketPlayer");
} catch (ClassNotFoundException e) {
throw new RuntimeException("Couldn't find idle packet, help!", e);
}
try {
idlePacket = idlePacketClass.newInstance();
idlePacket2 = idlePacketClass.newInstance();
Field flying = idlePacketClass.getDeclaredField("field_149474_g");
flying.setAccessible(true);
flying.set(idlePacket2, true);
} catch (NoSuchFieldException | InstantiationException | IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException("Couldn't make player idle packet, help!", e);
}
}
@Override
public Object getFlyingPacket() {
return idlePacket2;
}
@Override
public Object getGroundPacket() {
return idlePacket;
}
}

View File

@ -0,0 +1,130 @@
package us.myles.ViaVersion.sponge.util;
import com.google.common.collect.Maps;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
public class ReflectionUtil {
public static Object invokeStatic(Class<?> clazz, String method) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method m = clazz.getDeclaredMethod(method);
return m.invoke(null);
}
public static Object invoke(Object o, String method) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method m = o.getClass().getDeclaredMethod(method);
return m.invoke(o);
}
public static <T> T getStatic(Class<?> clazz, String f, Class<T> t) throws NoSuchFieldException, IllegalAccessException {
Field field = clazz.getDeclaredField(f);
field.setAccessible(true);
return (T) field.get(null);
}
public static <T> T getSuper(Object o, String f, Class<T> t) throws NoSuchFieldException, IllegalAccessException {
Field field = o.getClass().getSuperclass().getDeclaredField(f);
field.setAccessible(true);
return (T) field.get(o);
}
public static <T> T get(Object instance, Class<?> clazz, String f, Class<T> t) throws NoSuchFieldException, IllegalAccessException {
Field field = clazz.getDeclaredField(f);
field.setAccessible(true);
return (T) field.get(instance);
}
public static <T> T get(Object o, String f, Class<T> t) throws NoSuchFieldException, IllegalAccessException {
Field field = o.getClass().getDeclaredField(f);
field.setAccessible(true);
return (T) field.get(o);
}
public static <T> T getPublic(Object o, String f, Class<T> t) throws NoSuchFieldException, IllegalAccessException {
Field field = o.getClass().getField(f);
field.setAccessible(true);
return (T) field.get(o);
}
public static void set(Object o, String f, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = o.getClass().getDeclaredField(f);
field.setAccessible(true);
field.set(o, value);
}
public static final class ClassReflection {
private final Class<?> handle;
private final Map<String, Field> fields = Maps.newConcurrentMap();
private final Map<String, Method> methods = Maps.newConcurrentMap();
public ClassReflection(Class<?> handle) {
this(handle, true);
}
public ClassReflection(Class<?> handle, boolean recursive) {
this.handle = handle;
scanFields(handle, recursive);
scanMethods(handle, recursive);
}
private void scanFields(Class<?> host, boolean recursive) {
if (host.getSuperclass() != null && recursive) {
scanFields(host.getSuperclass(), true);
}
for (Field field : host.getDeclaredFields()) {
field.setAccessible(true);
fields.put(field.getName(), field);
}
}
private void scanMethods(Class<?> host, boolean recursive) {
if (host.getSuperclass() != null && recursive) {
scanMethods(host.getSuperclass(), true);
}
for (Method method : host.getDeclaredMethods()) {
method.setAccessible(true);
methods.put(method.getName(), method);
}
}
public Object newInstance() throws IllegalAccessException, InstantiationException {
return handle.newInstance();
}
public Field getField(String name) {
return fields.get(name);
}
public void setFieldValue(String fieldName, Object instance, Object value) throws IllegalAccessException {
getField(fieldName).set(instance, value);
}
public <T> T getFieldValue(String fieldName, Object instance, Class<T> type) throws IllegalAccessException {
return type.cast(getField(fieldName).get(instance));
}
public <T> T invokeMethod(Class<T> type, String methodName, Object instance, Object... args) throws InvocationTargetException, IllegalAccessException {
return type.cast(getMethod(methodName).invoke(instance, args));
}
public Method getMethod(String name) {
return methods.get(name);
}
public Collection<Field> getFields() {
return Collections.unmodifiableCollection(fields.values());
}
public Collection<Method> getMethods() {
return Collections.unmodifiableCollection(methods.values());
}
}
}