diff --git a/http-api/src/main/java/net/runelite/http/api/RuneliteAPI.java b/http-api/src/main/java/net/runelite/http/api/RuneliteAPI.java
index 35a90a6e75..6cd6da7c1e 100644
--- a/http-api/src/main/java/net/runelite/http/api/RuneliteAPI.java
+++ b/http-api/src/main/java/net/runelite/http/api/RuneliteAPI.java
@@ -37,6 +37,7 @@ public class RuneliteAPI
private static final String BASE = "https://api.runelite.net/runelite-";
private static final Properties properties = new Properties();
private static String version;
+ private static int rsVersion;
static
{
@@ -46,6 +47,7 @@ public class RuneliteAPI
properties.load(in);
version = properties.getProperty("runelite.version");
+ rsVersion = Integer.parseInt(properties.getProperty("rs.version"));
}
catch (IOException ex)
{
@@ -68,4 +70,9 @@ public class RuneliteAPI
RuneliteAPI.version = version;
}
+ public static int getRsVersion()
+ {
+ return rsVersion;
+ }
+
}
diff --git a/http-api/src/main/resources/runelite.properties b/http-api/src/main/resources/runelite.properties
index b26062dfdf..51d6be8768 100644
--- a/http-api/src/main/resources/runelite.properties
+++ b/http-api/src/main/resources/runelite.properties
@@ -1 +1,2 @@
-runelite.version=${project.version}
\ No newline at end of file
+runelite.version=${project.version}
+rs.version=${rs.version}
\ No newline at end of file
diff --git a/http-service/pom.xml b/http-service/pom.xml
index 9f2817f96f..69c2219786 100644
--- a/http-service/pom.xml
+++ b/http-service/pom.xml
@@ -58,6 +58,11 @@
commons-csv
1.4
+
+ com.google.guava
+ guava
+ 21.0
+
org.slf4j
diff --git a/http-service/src/main/java/net/runelite/http/service/Service.java b/http-service/src/main/java/net/runelite/http/service/Service.java
index 57d17d6ba7..d7193f1527 100644
--- a/http-service/src/main/java/net/runelite/http/service/Service.java
+++ b/http-service/src/main/java/net/runelite/http/service/Service.java
@@ -30,6 +30,7 @@ import javax.naming.NamingException;
import javax.sql.DataSource;
import net.runelite.http.api.RuneliteAPI;
import net.runelite.http.service.hiscore.HiscoreService;
+import net.runelite.http.service.updatecheck.UpdateCheckService;
import net.runelite.http.service.worlds.WorldsService;
import net.runelite.http.service.xtea.XteaService;
import org.slf4j.Logger;
@@ -46,6 +47,7 @@ public class Service implements SparkApplication
private final JsonTransformer transformer = new JsonTransformer();
private HiscoreService hiscores = new HiscoreService();
+ private UpdateCheckService updateCheck = new UpdateCheckService(this);
private WorldsService worlds = new WorldsService();
private XteaService xtea = new XteaService(this);
@@ -57,6 +59,7 @@ public class Service implements SparkApplication
xtea.init();
get("/version", (request, response) -> RuneliteAPI.getVersion());
+ get("/update-check", updateCheck::check, transformer);
get("/hiscore", (request, response) -> hiscores.lookup(request.queryParams("username")), transformer);
get("/worlds", (request, response) -> worlds.listWorlds(), transformer);
post("/xtea", xtea::submit);
@@ -96,6 +99,36 @@ public class Service implements SparkApplication
this.hiscores = hiscores;
}
+ public UpdateCheckService getUpdateCheck()
+ {
+ return updateCheck;
+ }
+
+ public void setUpdateCheck(UpdateCheckService updateCheck)
+ {
+ this.updateCheck = updateCheck;
+ }
+
+ public WorldsService getWorlds()
+ {
+ return worlds;
+ }
+
+ public void setWorlds(WorldsService worlds)
+ {
+ this.worlds = worlds;
+ }
+
+ public XteaService getXtea()
+ {
+ return xtea;
+ }
+
+ public void setXtea(XteaService xtea)
+ {
+ this.xtea = xtea;
+ }
+
public DataSource getDataSource()
{
return dataSource;
diff --git a/http-service/src/main/java/net/runelite/http/service/updatecheck/UpdateCheckService.java b/http-service/src/main/java/net/runelite/http/service/updatecheck/UpdateCheckService.java
new file mode 100644
index 0000000000..1c75be2d6a
--- /dev/null
+++ b/http-service/src/main/java/net/runelite/http/service/updatecheck/UpdateCheckService.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2017, Adam
+ * 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.http.service.updatecheck;
+
+import com.google.common.base.Suppliers;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.URISyntaxException;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import net.runelite.http.api.RuneliteAPI;
+import net.runelite.http.api.worlds.WorldResult;
+import net.runelite.http.service.Service;
+import net.runelite.http.service.worlds.WorldsService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import spark.Request;
+import spark.Response;
+
+public class UpdateCheckService
+{
+ private static final Logger logger = LoggerFactory.getLogger(UpdateCheckService.class);
+
+ private static final int PORT = 43594;
+ private static final int WORLD_OFFSET = 300;
+ private static final byte HANDSHAKE_TYPE = 15;
+
+ private static final int RESPONSE_OK = 0;
+ private static final int RESPONSE_OUTDATED = 6;
+
+ private final Service service;
+ private final Supplier updateAvailable = Suppliers.memoizeWithExpiration(this::checkUpdate, 1, TimeUnit.MINUTES);
+
+ public UpdateCheckService(Service service)
+ {
+ this.service = service;
+ }
+
+ public Boolean check(Request request, Response response)
+ {
+ return updateAvailable.get();
+ }
+
+ private boolean checkUpdate()
+ {
+ int nextWorld = randomWorld();
+ if (nextWorld == -1)
+ {
+ return false;
+ }
+
+ InetAddress address;
+ try
+ {
+ String url = "oldschool" + nextWorld + ".runescape.com";
+ address = InetAddress.getByName(url);
+ }
+ catch (UnknownHostException ex)
+ {
+ logger.warn(null, ex);
+ return false;
+ }
+
+ try (Socket socket = new Socket(address, PORT))
+ {
+ ByteBuffer buffer = ByteBuffer.allocate(5);
+ buffer.put(HANDSHAKE_TYPE);
+ buffer.putInt(RuneliteAPI.getRsVersion());
+
+ InputStream is = socket.getInputStream();
+ OutputStream os = socket.getOutputStream();
+ os.write(buffer.array());
+
+ int reply = is.read();
+
+ if (reply == RESPONSE_OUTDATED)
+ {
+ return true;
+ }
+
+ if (reply != RESPONSE_OK)
+ {
+ logger.debug("Non-ok response for handshake: {}", reply);
+ }
+ }
+ catch (IOException ex)
+ {
+ logger.warn(null, ex);
+ }
+
+ return false; // no update
+ }
+
+ private int randomWorld()
+ {
+ WorldsService worldsService = service.getWorlds();
+
+ try
+ {
+ WorldResult worlds = worldsService.listWorlds();
+ int size = worlds.getWorlds().size();
+ Random rand = new Random();
+ int worldNumber = worlds.getWorlds().get(rand.nextInt(size)).getId();
+ return worldNumber - WORLD_OFFSET;
+ }
+ catch (IOException | URISyntaxException ex)
+ {
+ logger.warn(null, ex);
+ return -1;
+ }
+ }
+}