Solved "Faking" the number of online players

Discussion in 'Plugin Development' started by JRL1004, Dec 30, 2013.

Thread Status:
Not open for further replies.
  1. Offline

    JRL1004

    So, I am working on a plugin for this thread and all is going well except for one thing. I am having a hard time getting the server to send data saying that there are only X player's online despite the true number of players that there are. I have gotten here and gone through all the options for the event but none of them allow me to set how many player's show up. Here is what I have (but it's not much) :
    Code:java
    1. public class SecondaryEventHandler implements Listener {
    2.  
    3. @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
    4. public void onPing(ServerListPingEvent event) {
    5. // What should I put here?
    6. }
    7. }

    Also, my event(s) are properly registered.

    EDIT: Linked the thread for those who want a better idea.
     
  2. Offline

    Wizehh

    Code:java
    1. event.setMaxPlayers(1);
     
  3. Offline

    JRL1004

    Wizehh That only changes the (displayed) maximum number of players. I want to fake the actual amount of players. For example, if five players are online, I would like to be able to make is say 120 are online even though there are only five.
     
  4. Offline

    ArthurMaker

    I don't think it is possible. Maybe with packets, but I don't know which.
     
  5. Offline

    Wizehh

    JRL1004
    I know it's possible to set the max player (display only) number to whatever, as I've done it before.
     
  6. Offline

    JRL1004

    ArthurMaker I know it is possible as I have seen it done before on a server I went on with a friend (I no longer know what the server is so I can't give an example). I have a feeling this will require packets but I am only able to send those when I can use CraftPlayer#getHandle().playerConnection.sendPacket() and the ServerListPingEvent (if that is the one I need) does not have a player I can hook into the connection of.
     
  7. Offline

    ArthurMaker

    JRL1004 I think it will be complicated, because I don't think it is just an Event and/or a simple packet... I'll search for more information here... Brb.

    JRL1004
    I found this plugin: http://dev.bukkit.org/bukkit-plugins/fakeplayers/

    And I checked its code with a program that I use for study plugins and, dude, it is more complicated that it seems - really, really, really more complicated. You will need more than packets.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 6, 2016
  8. Offline

    JRL1004

    ArthurMaker I went through the BungeeCord source on GitHub to see how they got the online player count to work and I got lost so I hope this link proves more helpful. Thanks.

    ArthurMaker Okay, I've gone through the source of what you sent me and located the one class that is actually needed. Unfortunately, most of this plugin relies on ProtocolLib and that makes it much more difficult to use as an example (do to it using unavailable and external functions/methods).

    EDIT: If anyone else can help me out here I would appreciate it

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 6, 2016
  9. Offline

    xTrollxDudex

    JRL1004
    Correction:
    PlayerConnection#sendPacket(Packet)
    Back to the topic:
    PHP:
    for(int i 0<= (120 Bukkit.getServer().getOnlinePlayers().length); i++) {
        
    Player[] online Bukkit.getServer().getOnlinePlayers();
        
    online[online.length 1] = new Player {
            
    // override methods
        
    }
    }
     
  10. Offline

    Minecrell

    Modifying it using ProtocolLib is actually really simple:
    Code:java
    1. ProtocolLibrary.getProtocolManager().addPacketListener(
    2. new PacketAdapter(PacketAdapter.params(this, PacketType.Status.Server.OUT_SERVER_INFO).optionAsync()) {
    3. @Override
    4. public void onPacketSending(PacketEvent event) {
    5. WrappedServerPing serverPing = event.getPacket().getServerPings().read(0);
    6. serverPing.setPlayersOnline(999);
    7. serverPing.setPlayersMaximum(1000);
    8. }
    9. });

    However, in this case I would probably go for an different version, as it may fit better for hiding all players:
    Code:java
    1. public class ServerListPacketAdapter extends PacketAdapter {
    2. private static Class<?> SERVER_PING = MinecraftReflection.getServerPingClass();
    3. private static FieldAccessor PLAYERS = Accessors.getFieldAccessor(SERVER_PING, MinecraftReflection.getServerPingPlayerSampleClass(), true);
    4.  
    5. public ServerListPacketAdapter(Plugin plugin) {
    6. super(PacketAdapter.params(plugin, PacketType.Status.Server.OUT_SERVER_INFO).optionAsync());
    7. }
    8.  
    9. @Override
    10. public void onPacketSending(PacketEvent event) {
    11. PLAYERS.set(event.getPacket().getServerPings().read(0).getHandle(), null);
    12. }
    13. }

    Will be displayed as:

    [​IMG]

    Edit: Make sure removing optionAsync() if you need to use the Bukkit API in the packet handler.
     
  11. Offline

    xize

    Minecrell
    you could do the same without Reflection by setting maxPlayers to setMaxPlayers(-1); for the ??? :p
    JRL1004
    honest it is possible with packets but I used ProtocolLib same as Minecrell posted.
     
  12. Offline

    Minecrell

    Yeah, that was working before 1.7... But when I tested it this morning it displayed -1 in the server list instead of ???... :(
     
  13. Offline

    JRL1004

    I'm not sure how to go about doing that. I know I can take the length and increment it to a value that I want but I am not sure if it will send the information to the client saying the player count I want.

    Minecrell I'm trying to avoid using ProtocolLib but I will use that if it is needed.

    xize How would I do this using packets (if you know how)?
     
  14. Offline

    Minecrell

    I don't think it would be easy without ProtocolLib, as you would need to inject a custom packet handler, so you can change the player amount before the packet is sent to the client. ProtocolLib will save you from updating your plugin on every Minecraft update, doing it yourself requires you to update your plugin for every Minecraft update.
    Actually, you could use something like Comphenix TinyProtocol class, but that class is only working for ingame packets, and not for the server list packet.
     
  15. Offline

    xTrollxDudex

    JRL1004
    Actually you're not updating the online player array for the client, it's for the whole server.
     
  16. Offline

    Wizehh

    Just use Protocol Lib. I'm using it & it works perfectly.
     
  17. Offline

    xTrollxDudex

    JRL1004
    2 Options:
    • http://wiki.vg/Server_List_Ping Use ProtocolLib to intercept the ping and change the json string
    • Add a few objects to one of the lists in MinecraftServer, let me find it.

    Edit: Got it. It's the "t" field which is private, use reflection.
    PHP:
    Field t null;
    try {
        
    MinecraftServer.class.getDeclaredField("t");
        
    t.setAccessible(true);
    } catch(
    Exception x) {
        
    x.printStackTrace();
    }
    MinecraftServer instance = ((CraftServerBukkit.getServer()).getServer();
    t.set(instance, new ListWrapper(instance));

    private class 
    ListWrapper extends PlayerList {
        public 
    ListWrapper(MinecraftServer s) {
            
    super(s);
        }

        @
    Override
        
    public int getPlayerCount() {
            return 
    120;
        }
    }
    Note that I may have forgotten to catch a few exception. This should work but is untestsed.
     
  18. Offline

    Comphenix

    Thanks to Minecrell making me aware of this needed feature, ProtocolLib 3.1.1-SNAPSHOT now supports toggling the player count/maximum on and off:
    Code:java
    1. ProtocolLibrary.getProtocolManager().addPacketListener(
    2. new PacketAdapter(PacketAdapter.params(this, PacketType.Status.Server.OUT_SERVER_INFO).optionAsync()) {
    3. @Override
    4. public void onPacketSending(PacketEvent event) {
    5. WrappedServerPing ping = event.getPacket().getServerPings().read(0);
    6. ping.setPlayersVisible(false);
    7. }
    8. });

    Of course, this is not live on BukkitDev yet, so I still recommend using the old reflection-based method.

    Also, I seriously doubt xTrollxDudex proposal can ever work. But even if it did, you'd still need to add support for plugin reloading and server reloads, especially if you ever intend to release your plugin publicly.

    There are two major problems with extending PlayerList, and this hack in particular:
    1. It should extend DedicatedPlayerList, not PlayerList. But that's only a minor issue.
    2. The PlayerList constructor executes a lot of initialization code, including the construction of CraftServer, which instantly throws an exception:
      Code:
      java.lang.UnsupportedOperationException: Cannot redefine singleton Server
          at org.bukkit.Bukkit.setServer(Bukkit.java:63) ~[craftbukkit-1.7.2-R0.3-20131225.043511-4.jar:git-Bukkit-1.7.2-R0.2-3-g530fcb7-b2978jnks]
    3. Replacing the PlayerList also replaces, you guessed it, the list of players. So you'll have to copy over the players from the old list (and axillary data) if you ever want to support plugin reloading.
    It's possible to construct an instance of an object without invoking the constructor, but that's JVM specific and crazy. It won't gain you any favors from the BukkitDev reviewers, that's for sure.
     
    Garris0n and Minecrell like this.
  19. Offline

    xTrollxDudex

    Comphenix
    Looks like I'm not going to be extending DedicatedPlayerList after all :p

    Anyways, after a bit of testing, this is what I got:
    Show Spoiler
    proof.png

    I'm working avoiding the ticking entity crash, here is the current code:
    PHP:
    package com.gmail.woodyc40.test;
     
    import net.minecraft.server.v1_7_R1.EntityPlayer;
    import net.minecraft.server.v1_7_R1.MinecraftServer;
    import net.minecraft.server.v1_7_R1.PlayerInteractManager;
    import net.minecraft.util.com.mojang.authlib.GameProfile;
    import org.bukkit.Bukkit;
    import org.bukkit.craftbukkit.v1_7_R1.CraftServer;
    import org.bukkit.craftbukkit.v1_7_R1.CraftWorld;
     
    import java.lang.reflect.Field;
    import java.util.List;
     
    public class 
    PlayerListHandler {
     
        public 
    void setPlayerCount() {
            
    Field t null;
            try {
                
    MinecraftServer.class.getDeclaredField("t");
                
    t.setAccessible(true);
            } catch(
    Exception x) {
                
    x.printStackTrace();
            }
            
    MinecraftServer instance = ((CraftServerBukkit.getServer()).getServer();
            
    assert t != null;
            try {
                
    Object playerList t.get(instance);
                
    Field f playerList.getClass().getSuperclass().getDeclaredField("players");
     
                List<
    EntityPlayer> list = (List<EntityPlayer>) f.get(playerList);
                for(
    int i 0<= 120i++) {
                    list.
    add(new EntityPlayer(
                            
    instance,
                            ((
    CraftWorldBukkit.getServer().getWorld("world")).getHandle(),
                            new 
    GameProfile("69""test"),
                            new 
    PlayerInteractManager(((CraftWorldBukkit.getServer().getWorld("world")).getHandle()))
                    );
                }
            } catch (
    IllegalAccessException NoSuchFieldException e) {
                
    e.printStackTrace();
            }
        }
     
    }
    I'm setting this in the onEnable like so:
    PHP:
    package com.gmail.woodyc40.test;
     
    import org.bukkit.plugin.java.JavaPlugin;
     
    public class 
    Test extends JavaPlugin {
     
        @
    Override
        
    public void onEnable() {
            new 
    PlayerListHandler().setPlayerCount();
        }
     
    }
    I removed some math since Bukkit.getServer().getOnlinePlayers().length throws me a NPE if there aren't any players online.

    Edit:
    DONE!
    Updated code:
    PHP:
    package com.gmail.woodyc40.test;
     
    import net.minecraft.server.v1_7_R1.*;
    import net.minecraft.util.com.mojang.authlib.GameProfile;
    import org.bukkit.Bukkit;
    import org.bukkit.craftbukkit.v1_7_R1.CraftServer;
    import org.bukkit.craftbukkit.v1_7_R1.CraftWorld;
    import org.bukkit.craftbukkit.v1_7_R1.entity.CraftPlayer;
     
    import java.lang.reflect.Field;
    import java.util.List;
     
    public class 
    PlayerListHandler {
     
        public 
    void setPlayerCount() {
            
    Field t null;
            try {
                
    MinecraftServer.class.getDeclaredField("t");
                
    t.setAccessible(true);
            } catch(
    Exception x) {
                
    x.printStackTrace();
            }
            
    MinecraftServer instance = ((CraftServerBukkit.getServer()).getServer();
            
    assert t != null;
            try {
                
    Object playerList t.get(instance);
                
    Field f playerList.getClass().getSuperclass().getDeclaredField("players");
     
                List<
    EntityPlayer> list = (List<EntityPlayer>) f.get(playerList);
                for(
    int i 0<= 120i++) {
                    
    CustomPlayer cp = new CustomPlayer(new PlayerInteractManager(((CraftWorldBukkit.getServer().getWorld("world")).getHandle()));
                    list.
    add(cp);
                }
            } catch (
    IllegalAccessException NoSuchFieldException e) {
                
    e.printStackTrace();
            }
        }
     
        public class 
    CustomPlayer extends EntityPlayer {
     
            public 
    CustomPlayer(PlayerInteractManager manager) {
                
    super(((CraftServerBukkit.getServer()).getServer(),
                        ((
    CraftWorldBukkit.getServer().getWorld("world")).getHandle(),
                        new 
    GameProfile("69""test"),
                        
    manager);
     
                
    manager.b(EnumGamemode.SURVIVAL);
                
    playerConnection = new CustomPC(serverthis);
            }
     
            public class 
    CustomPC extends PlayerConnection {
                public 
    CustomPC(MinecraftServer serverEntityPlayer p) {
                    
    super(server, new NetworkManager(false), p);
                }
                @
    Override
                
    public CraftPlayer getPlayer() {
                    return new 
    CraftPlayer((CraftServerBukkit.getServer(), player); // Fake player prevents spout NPEs
                
    }
            }
        }
     
    }
    Snap:
    proof.png

    One issue is that there's a random "test" at the top of the PlayerList, I'll try to kick him later and see what happens.

    I tried to remove him using PacketPlayOutPlayerInfo but it's not working for some reason.
     
    GrandmaJam likes this.
  20. Offline

    JRL1004

    xTrollxDudex Okay, Thanks. I'll use an adapted form of your code (mostly changing the 120 to a random integer and then finding a way to remove "test" from the list). If I can't get that to work then Comphenix 's ProtocolLib plugin should be more than needed. One of the reasons I have not added it as a dependency is because I don't like the idea of my plugins needing more than bukkit/craftbukkit to run.

    So, I was looking at how you did this and saw this part:
    PHP:
    new GameProfile("69""test")
    by setting "test" which I now assume is the player name, would it make them not appear in the list if I set it to null? I'm going to test it.
     
  21. Offline

    BillyGalbreath

    This sentence made me laugh. ^_^ ProtocolLib IS using packets...
     
  22. Offline

    xTrollxDudex

    JRL1004
    Yes, that should be the player name, it's used for a few thigs I suspect, watch out for NPE's
     
  23. Offline

    xize

    BillyGalbreath very true I kinda make a double sentence there oops XD
     
  24. Offline

    JRL1004

    xTrollxDudex So, setting the name to null causes a ClassCastException (not sure why) but if you change the name to a special character list you can essentially hide the name. What I did to make it seem a bit nicer is I made it represent a config value. Now the people using the plugin can put the name of their server and it will just show up as the server name being the random four bar connection. Also, after a bit of testing I found that the Fake players are actually real players according to the server so they take up actual slots, to work around this I made the plugin iterate through the "t" field's array and clear all components that are instanceof CustomPlayer (and it works quite well).
     
  25. Offline

    xTrollxDudex

    JRL1004
    Maybe it thinks null isn't a string :p
    I'm surprised this got solved within the first page.
     
Thread Status:
Not open for further replies.

Share This Page