@@ -168,6 +168,17 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- net.runelite:discord also has this -->
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
<version>4.5.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna-platform</artifactId>
|
||||
<version>4.5.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.runelite</groupId>
|
||||
|
||||
@@ -79,4 +79,15 @@ public interface WorldHopperConfig extends Config
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "ping",
|
||||
name = "Show world ping",
|
||||
description = "Shows ping to each game world",
|
||||
position = 4
|
||||
)
|
||||
default boolean ping()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
package net.runelite.client.plugins.worldhopper;
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ObjectArrays;
|
||||
import com.google.inject.Provides;
|
||||
@@ -37,6 +38,7 @@ import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -73,8 +75,10 @@ import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.input.KeyManager;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.plugins.worldhopper.ping.Ping;
|
||||
import net.runelite.client.ui.ClientToolbar;
|
||||
import net.runelite.client.ui.NavigationButton;
|
||||
import net.runelite.client.util.ExecutorServiceExceptionLogger;
|
||||
import net.runelite.client.util.HotkeyListener;
|
||||
import net.runelite.client.util.Text;
|
||||
import net.runelite.client.util.WorldUtil;
|
||||
@@ -92,6 +96,7 @@ import org.apache.commons.lang3.ArrayUtils;
|
||||
public class WorldHopperPlugin extends Plugin
|
||||
{
|
||||
private static final int WORLD_FETCH_TIMER = 10;
|
||||
private static final int WORLD_PING_TIMER = 10;
|
||||
private static final int REFRESH_THROTTLE = 60_000; // ms
|
||||
private static final int TICK_THROTTLE = (int) Duration.ofMinutes(10).toMillis();
|
||||
|
||||
@@ -126,6 +131,8 @@ public class WorldHopperPlugin extends Plugin
|
||||
@Inject
|
||||
private WorldHopperConfig config;
|
||||
|
||||
private final ScheduledExecutorService hopperExecutorService = new ExecutorServiceExceptionLogger(Executors.newSingleThreadScheduledExecutor());
|
||||
|
||||
private NavigationButton navButton;
|
||||
private WorldSwitcherPanel panel;
|
||||
|
||||
@@ -137,9 +144,10 @@ public class WorldHopperPlugin extends Plugin
|
||||
|
||||
private int favoriteWorld1, favoriteWorld2;
|
||||
|
||||
private ScheduledFuture<?> worldResultFuture;
|
||||
private ScheduledFuture<?> worldResultFuture, pingFuture;
|
||||
private WorldResult worldResult;
|
||||
private Instant lastFetch;
|
||||
private boolean firstRun;
|
||||
|
||||
private final HotkeyListener previousKeyListener = new HotkeyListener(() -> config.previousKey())
|
||||
{
|
||||
@@ -167,11 +175,11 @@ public class WorldHopperPlugin extends Plugin
|
||||
@Override
|
||||
protected void startUp() throws Exception
|
||||
{
|
||||
firstRun = true;
|
||||
|
||||
keyManager.registerKeyListener(previousKeyListener);
|
||||
keyManager.registerKeyListener(nextKeyListener);
|
||||
|
||||
worldResultFuture = executorService.scheduleAtFixedRate(this::tick, 0, WORLD_FETCH_TIMER, TimeUnit.MINUTES);
|
||||
|
||||
panel = new WorldSwitcherPanel(this);
|
||||
|
||||
final BufferedImage icon;
|
||||
@@ -191,11 +199,17 @@ public class WorldHopperPlugin extends Plugin
|
||||
{
|
||||
clientToolbar.addNavigation(navButton);
|
||||
}
|
||||
|
||||
worldResultFuture = executorService.scheduleAtFixedRate(this::tick, 0, WORLD_FETCH_TIMER, TimeUnit.MINUTES);
|
||||
pingFuture = hopperExecutorService.scheduleAtFixedRate(this::pingWorlds, WORLD_PING_TIMER, WORLD_PING_TIMER, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown() throws Exception
|
||||
{
|
||||
pingFuture.cancel(true);
|
||||
pingFuture = null;
|
||||
|
||||
keyManager.unregisterKeyListener(previousKeyListener);
|
||||
keyManager.unregisterKeyListener(nextKeyListener);
|
||||
|
||||
@@ -205,20 +219,37 @@ public class WorldHopperPlugin extends Plugin
|
||||
lastFetch = null;
|
||||
|
||||
clientToolbar.removeNavigation(navButton);
|
||||
|
||||
hopperExecutorService.shutdown();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onConfigChanged(final ConfigChanged event)
|
||||
{
|
||||
if (event.getGroup().equals(WorldHopperConfig.GROUP) && event.getKey().equals("showSidebar"))
|
||||
if (event.getGroup().equals(WorldHopperConfig.GROUP))
|
||||
{
|
||||
if (config.showSidebar())
|
||||
switch (event.getKey())
|
||||
{
|
||||
clientToolbar.addNavigation(navButton);
|
||||
}
|
||||
else
|
||||
{
|
||||
clientToolbar.removeNavigation(navButton);
|
||||
case "showSidebar":
|
||||
if (config.showSidebar())
|
||||
{
|
||||
clientToolbar.addNavigation(navButton);
|
||||
}
|
||||
else
|
||||
{
|
||||
clientToolbar.removeNavigation(navButton);
|
||||
}
|
||||
break;
|
||||
case "ping":
|
||||
if (config.ping())
|
||||
{
|
||||
SwingUtilities.invokeLater(() -> panel.showPing());
|
||||
}
|
||||
else
|
||||
{
|
||||
SwingUtilities.invokeLater(() -> panel.hidePing());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,6 +431,13 @@ public class WorldHopperPlugin extends Plugin
|
||||
}
|
||||
|
||||
fetchWorlds();
|
||||
|
||||
// Ping worlds once at startup
|
||||
if (firstRun)
|
||||
{
|
||||
firstRun = false;
|
||||
hopperExecutorService.execute(this::pingWorlds);
|
||||
}
|
||||
}
|
||||
|
||||
void refresh()
|
||||
@@ -690,4 +728,24 @@ public class WorldHopperPlugin extends Plugin
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void pingWorlds()
|
||||
{
|
||||
if (worldResult == null || !config.showSidebar() || !config.ping())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
|
||||
for (World world : worldResult.getWorlds())
|
||||
{
|
||||
int ping = Ping.ping(world);
|
||||
SwingUtilities.invokeLater(() -> panel.updatePing(world, ping));
|
||||
}
|
||||
|
||||
stopwatch.stop();
|
||||
|
||||
log.debug("Done pinging worlds in {}", stopwatch.elapsed());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.ui.DynamicGridLayout;
|
||||
@@ -49,12 +48,14 @@ class WorldSwitcherPanel extends PluginPanel
|
||||
|
||||
private static final int WORLD_COLUMN_WIDTH = 60;
|
||||
private static final int PLAYERS_COLUMN_WIDTH = 40;
|
||||
private static final int PING_COLUMN_WIDTH = 47;
|
||||
|
||||
private final JPanel listContainer = new JPanel();
|
||||
|
||||
private WorldTableHeader worldHeader;
|
||||
private WorldTableHeader playersHeader;
|
||||
private WorldTableHeader activityHeader;
|
||||
private WorldTableHeader pingHeader;
|
||||
|
||||
private WorldOrder orderIndex = WorldOrder.WORLD;
|
||||
private boolean ascendingOrder = true;
|
||||
@@ -112,12 +113,48 @@ class WorldSwitcherPanel extends PluginPanel
|
||||
}
|
||||
}
|
||||
|
||||
void updatePing(World world, int ping)
|
||||
{
|
||||
for (WorldTableRow worldTableRow : rows)
|
||||
{
|
||||
if (worldTableRow.getWorld() == world)
|
||||
{
|
||||
worldTableRow.setPing(ping);
|
||||
|
||||
// If the panel is sorted by ping, re-sort it
|
||||
if (orderIndex == WorldOrder.PING)
|
||||
{
|
||||
updateList();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void hidePing()
|
||||
{
|
||||
for (WorldTableRow worldTableRow : rows)
|
||||
{
|
||||
worldTableRow.hidePing();
|
||||
}
|
||||
}
|
||||
|
||||
void showPing()
|
||||
{
|
||||
for (WorldTableRow worldTableRow : rows)
|
||||
{
|
||||
worldTableRow.showPing();
|
||||
}
|
||||
}
|
||||
|
||||
void updateList()
|
||||
{
|
||||
rows.sort((r1, r2) ->
|
||||
{
|
||||
switch (orderIndex)
|
||||
{
|
||||
case PING:
|
||||
return Integer.compare(r1.getPing(), r2.getPing()) * (ascendingOrder ? 1 : -1);
|
||||
case WORLD:
|
||||
return Integer.compare(r1.getWorld().getId(), r2.getWorld().getId()) * (ascendingOrder ? 1 : -1);
|
||||
case PLAYERS:
|
||||
@@ -126,7 +163,6 @@ class WorldSwitcherPanel extends PluginPanel
|
||||
return r1.getWorld().getActivity().compareTo(r2.getWorld().getActivity()) * -1 * (ascendingOrder ? 1 : -1);
|
||||
default:
|
||||
return 0;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@@ -191,12 +227,16 @@ class WorldSwitcherPanel extends PluginPanel
|
||||
|
||||
private void orderBy(WorldOrder order)
|
||||
{
|
||||
pingHeader.highlight(false, ascendingOrder);
|
||||
worldHeader.highlight(false, ascendingOrder);
|
||||
playersHeader.highlight(false, ascendingOrder);
|
||||
activityHeader.highlight(false, ascendingOrder);
|
||||
|
||||
switch (order)
|
||||
{
|
||||
case PING:
|
||||
pingHeader.highlight(true, ascendingOrder);
|
||||
break;
|
||||
case WORLD:
|
||||
worldHeader.highlight(true, ascendingOrder);
|
||||
break;
|
||||
@@ -219,6 +259,23 @@ class WorldSwitcherPanel extends PluginPanel
|
||||
{
|
||||
JPanel header = new JPanel(new BorderLayout());
|
||||
JPanel leftSide = new JPanel(new BorderLayout());
|
||||
JPanel rightSide = new JPanel(new BorderLayout());
|
||||
|
||||
pingHeader = new WorldTableHeader("Ping", orderIndex == WorldOrder.PING, ascendingOrder, plugin::refresh);
|
||||
pingHeader.setPreferredSize(new Dimension(PING_COLUMN_WIDTH, 0));
|
||||
pingHeader.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mousePressed(MouseEvent mouseEvent)
|
||||
{
|
||||
if (SwingUtilities.isRightMouseButton(mouseEvent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
ascendingOrder = orderIndex != WorldOrder.PING || !ascendingOrder;
|
||||
orderBy(WorldOrder.PING);
|
||||
}
|
||||
});
|
||||
|
||||
worldHeader = new WorldTableHeader("World", orderIndex == WorldOrder.WORLD, ascendingOrder, plugin::refresh);
|
||||
worldHeader.setPreferredSize(new Dimension(WORLD_COLUMN_WIDTH, 0));
|
||||
@@ -253,7 +310,6 @@ class WorldSwitcherPanel extends PluginPanel
|
||||
});
|
||||
|
||||
activityHeader = new WorldTableHeader("Activity", orderIndex == WorldOrder.ACTIVITY, ascendingOrder, plugin::refresh);
|
||||
activityHeader.setBorder(new EmptyBorder(3, 5, 3, 5));
|
||||
activityHeader.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
@@ -269,10 +325,13 @@ class WorldSwitcherPanel extends PluginPanel
|
||||
});
|
||||
|
||||
leftSide.add(worldHeader, BorderLayout.WEST);
|
||||
leftSide.add(playersHeader, BorderLayout.EAST);
|
||||
leftSide.add(playersHeader, BorderLayout.CENTER);
|
||||
|
||||
rightSide.add(activityHeader, BorderLayout.CENTER);
|
||||
rightSide.add(pingHeader, BorderLayout.EAST);
|
||||
|
||||
header.add(leftSide, BorderLayout.WEST);
|
||||
header.add(activityHeader, BorderLayout.CENTER);
|
||||
header.add(rightSide, BorderLayout.CENTER);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class WorldTableHeader extends JPanel
|
||||
setLayout(new BorderLayout(5, 0));
|
||||
setBorder(new CompoundBorder(
|
||||
BorderFactory.createMatteBorder(0, 0, 0, 1, ColorScheme.MEDIUM_GRAY_COLOR),
|
||||
new EmptyBorder(0, 5, 0, 5)));
|
||||
new EmptyBorder(0, 5, 0, 2)));
|
||||
setBackground(ColorScheme.SCROLL_TRACK_COLOR);
|
||||
|
||||
addMouseListener(new MouseAdapter()
|
||||
|
||||
@@ -54,6 +54,7 @@ class WorldTableRow extends JPanel
|
||||
|
||||
private static final int WORLD_COLUMN_WIDTH = 60;
|
||||
private static final int PLAYERS_COLUMN_WIDTH = 40;
|
||||
private static final int PING_COLUMN_WIDTH = 35;
|
||||
|
||||
private static final Color CURRENT_WORLD = new Color(66, 227, 17);
|
||||
private static final Color UNAVAILABLE_WORLD = Color.GRAY.darker().darker();
|
||||
@@ -75,6 +76,7 @@ class WorldTableRow extends JPanel
|
||||
private JLabel worldField;
|
||||
private JLabel playerCountField;
|
||||
private JLabel activityField;
|
||||
private JLabel pingField;
|
||||
private BiConsumer<World, Boolean> onFavorite;
|
||||
|
||||
@Getter
|
||||
@@ -83,6 +85,8 @@ class WorldTableRow extends JPanel
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private int updatedPlayerCount;
|
||||
|
||||
private int ping;
|
||||
|
||||
private Color lastBackground;
|
||||
private boolean current;
|
||||
|
||||
@@ -151,12 +155,18 @@ class WorldTableRow extends JPanel
|
||||
setComponentPopupMenu(popupMenu);
|
||||
|
||||
JPanel leftSide = new JPanel(new BorderLayout());
|
||||
JPanel rightSide = new JPanel(new BorderLayout());
|
||||
leftSide.setOpaque(false);
|
||||
rightSide.setOpaque(false);
|
||||
|
||||
JPanel worldField = buildWorldField();
|
||||
worldField.setPreferredSize(new Dimension(WORLD_COLUMN_WIDTH, 0));
|
||||
worldField.setOpaque(false);
|
||||
|
||||
JPanel pingField = buildPingField();
|
||||
pingField.setPreferredSize(new Dimension(PING_COLUMN_WIDTH, 0));
|
||||
pingField.setOpaque(false);
|
||||
|
||||
JPanel playersField = buildPlayersField();
|
||||
playersField.setPreferredSize(new Dimension(PLAYERS_COLUMN_WIDTH, 0));
|
||||
playersField.setOpaque(false);
|
||||
@@ -168,10 +178,12 @@ class WorldTableRow extends JPanel
|
||||
recolour(current);
|
||||
|
||||
leftSide.add(worldField, BorderLayout.WEST);
|
||||
leftSide.add(playersField, BorderLayout.EAST);
|
||||
leftSide.add(playersField, BorderLayout.CENTER);
|
||||
rightSide.add(activityField, BorderLayout.CENTER);
|
||||
rightSide.add(pingField, BorderLayout.EAST);
|
||||
|
||||
add(leftSide, BorderLayout.WEST);
|
||||
add(activityField, BorderLayout.CENTER);
|
||||
add(rightSide, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
void setFavoriteMenu(boolean favorite)
|
||||
@@ -199,9 +211,31 @@ class WorldTableRow extends JPanel
|
||||
playerCountField.setText(String.valueOf(playerCount));
|
||||
}
|
||||
|
||||
void setPing(int ping)
|
||||
{
|
||||
this.ping = ping;
|
||||
pingField.setText(ping <= 0 ? "-" : Integer.toString(ping));
|
||||
}
|
||||
|
||||
void hidePing()
|
||||
{
|
||||
pingField.setText("-");
|
||||
}
|
||||
|
||||
void showPing()
|
||||
{
|
||||
setPing(ping); // to update pingField
|
||||
}
|
||||
|
||||
int getPing()
|
||||
{
|
||||
return ping;
|
||||
}
|
||||
|
||||
public void recolour(boolean current)
|
||||
{
|
||||
playerCountField.setForeground(current ? CURRENT_WORLD : Color.WHITE);
|
||||
pingField.setForeground(current ? CURRENT_WORLD : Color.WHITE);
|
||||
|
||||
if (current)
|
||||
{
|
||||
@@ -244,6 +278,19 @@ class WorldTableRow extends JPanel
|
||||
return column;
|
||||
}
|
||||
|
||||
private JPanel buildPingField()
|
||||
{
|
||||
JPanel column = new JPanel(new BorderLayout());
|
||||
column.setBorder(new EmptyBorder(0, 5, 0, 5));
|
||||
|
||||
pingField = new JLabel("-");
|
||||
pingField.setFont(FontManager.getRunescapeSmallFont());
|
||||
|
||||
column.add(pingField, BorderLayout.EAST);
|
||||
|
||||
return column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the activity list field (containing that world's activity/theme).
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.client.plugins.worldhopper.ping;
|
||||
|
||||
import com.sun.jna.Library;
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
interface IPHlpAPI extends Library
|
||||
{
|
||||
IPHlpAPI INSTANCE = Native.loadLibrary("IPHlpAPI", IPHlpAPI.class);
|
||||
|
||||
Pointer IcmpCreateFile();
|
||||
|
||||
boolean IcmpCloseHandle(Pointer handle);
|
||||
|
||||
int IcmpSendEcho(Pointer IcmpHandle, int DestinationAddress, Pointer RequestData, short RequestSize, Pointer RequestOptions, IcmpEchoReply ReplyBuffer, int ReplySize, int Timeout);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.client.plugins.worldhopper.ping;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
import com.sun.jna.Structure;
|
||||
import com.sun.jna.platform.win32.WinDef;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class IcmpEchoReply extends Structure
|
||||
{
|
||||
private static final int IP_OPTION_INFO_SIZE = 1 + 1 + 1 + 1 + (Pointer.SIZE == 8 ? 12 : 4); // on 64bit vms add 4 byte padding
|
||||
public static final int SIZE = 4 + 4 + 4 + 2 + 2 + Pointer.SIZE + IP_OPTION_INFO_SIZE;
|
||||
|
||||
public WinDef.ULONG address;
|
||||
public WinDef.ULONG status;
|
||||
public WinDef.ULONG roundTripTime;
|
||||
public WinDef.USHORT dataSize;
|
||||
public WinDef.USHORT reserved;
|
||||
public WinDef.PVOID data;
|
||||
public WinDef.UCHAR ttl;
|
||||
public WinDef.UCHAR tos;
|
||||
public WinDef.UCHAR flags;
|
||||
public WinDef.UCHAR optionsSize;
|
||||
public WinDef.PVOID optionsData;
|
||||
|
||||
IcmpEchoReply(Pointer p)
|
||||
{
|
||||
super(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder()
|
||||
{
|
||||
return Arrays.asList("address", "status", "roundTripTime", "dataSize", "reserved", "data", "ttl", "tos", "flags", "optionsSize", "optionsData");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.client.plugins.worldhopper.ping;
|
||||
|
||||
import com.sun.jna.Memory;
|
||||
import com.sun.jna.Pointer;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.client.util.OSType;
|
||||
import net.runelite.http.api.worlds.World;
|
||||
|
||||
@Slf4j
|
||||
public class Ping
|
||||
{
|
||||
private static final String RUNELITE_PING = "RuneLitePing";
|
||||
|
||||
private static final int TIMEOUT = 2000;
|
||||
private static final int PORT = 43594;
|
||||
|
||||
public static int ping(World world)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (OSType.getOSType())
|
||||
{
|
||||
case Windows:
|
||||
return windowsPing(world);
|
||||
default:
|
||||
return tcpPing(world);
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
log.warn("error pinging", ex);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static int windowsPing(World world) throws UnknownHostException
|
||||
{
|
||||
IPHlpAPI ipHlpAPI = IPHlpAPI.INSTANCE;
|
||||
Pointer ptr = ipHlpAPI.IcmpCreateFile();
|
||||
InetAddress inetAddress = InetAddress.getByName(world.getAddress());
|
||||
byte[] address = inetAddress.getAddress();
|
||||
String dataStr = RUNELITE_PING;
|
||||
int dataLength = dataStr.length() + 1;
|
||||
Pointer data = new Memory(dataLength);
|
||||
data.setString(0L, dataStr);
|
||||
IcmpEchoReply icmpEchoReply = new IcmpEchoReply(new Memory(IcmpEchoReply.SIZE + dataLength));
|
||||
assert icmpEchoReply.size() == IcmpEchoReply.SIZE;
|
||||
int packed = (address[0] & 0xff) | ((address[1] & 0xff) << 8) | ((address[2] & 0xff) << 16) | ((address[3] & 0xff) << 24);
|
||||
int ret = ipHlpAPI.IcmpSendEcho(ptr, packed, data, (short) (dataLength), Pointer.NULL, icmpEchoReply, IcmpEchoReply.SIZE + dataLength, TIMEOUT);
|
||||
if (ret != 1)
|
||||
{
|
||||
ipHlpAPI.IcmpCloseHandle(ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rtt = Math.toIntExact(icmpEchoReply.roundTripTime.longValue());
|
||||
ipHlpAPI.IcmpCloseHandle(ptr);
|
||||
|
||||
return rtt;
|
||||
}
|
||||
|
||||
private static int tcpPing(World world) throws IOException
|
||||
{
|
||||
try (Socket socket = new Socket())
|
||||
{
|
||||
socket.setSoTimeout(TIMEOUT);
|
||||
InetAddress inetAddress = InetAddress.getByName(world.getAddress());
|
||||
long start = System.nanoTime();
|
||||
socket.connect(new InetSocketAddress(inetAddress, PORT));
|
||||
long end = System.nanoTime();
|
||||
return (int) ((end - start) / 1000000L);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user