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; + } + } +}