Merge pull request #9651 from abextm/bypass-loadbalancer

runelite-client: Bypass Jagex load balancer if we can't connect
This commit is contained in:
Adam
2019-08-14 18:44:37 -04:00
committed by GitHub
4 changed files with 180 additions and 53 deletions

View File

@@ -29,6 +29,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import net.runelite.http.api.RuneLiteAPI;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
@@ -40,10 +41,16 @@ class ClientConfigLoader
private static final String CONFIG_URL = "http://oldschool.runescape.com/jav_config.ws";
static RSConfig fetch() throws IOException
static RSConfig fetch(String host) throws IOException
{
HttpUrl url = HttpUrl.parse(CONFIG_URL);
if (host != null)
{
url = url.newBuilder().host(host).build();
}
final Request request = new Request.Builder()
.url(CONFIG_URL)
.url(url)
.build();
final RSConfig config = new RSConfig();

View File

@@ -38,7 +38,6 @@ import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
@@ -58,6 +57,7 @@ import static net.runelite.client.rs.ClientUpdateCheckMode.VANILLA;
import net.runelite.client.ui.FatalErrorDialog;
import net.runelite.client.ui.SplashScreen;
import net.runelite.http.api.RuneLiteAPI;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.compress.compressors.CompressorException;
@@ -65,6 +65,8 @@ import org.apache.commons.compress.compressors.CompressorException;
@Slf4j
public class ClientLoader implements Supplier<Applet>
{
private static final int NUM_ATTEMPTS = 6;
private ClientUpdateCheckMode updateCheckMode;
private Object client = null;
@@ -98,77 +100,118 @@ public class ClientLoader implements Supplier<Applet>
try
{
SplashScreen.stage(0, null, "Fetching applet viewer config");
RSConfig config = ClientConfigLoader.fetch();
HostSupplier hostSupplier = new HostSupplier();
String host = null;
RSConfig config;
for (int attempt = 0; ; attempt++)
{
try
{
config = ClientConfigLoader.fetch(host);
break;
}
catch (IOException e)
{
log.info("Failed to get jav_config from host \"{}\" ({})", host, e.getMessage());
if (attempt >= NUM_ATTEMPTS)
{
throw e;
}
host = hostSupplier.get();
}
}
Map<String, byte[]> zipFile = new HashMap<>();
{
Certificate[] jagexCertificateChain = getJagexCertificateChain();
String codebase = config.getCodeBase();
String initialJar = config.getInitialJar();
URL url = new URL(codebase + initialJar);
Request request = new Request.Builder()
.url(url)
.build();
HttpUrl url = HttpUrl.parse(codebase + initialJar);
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
for (int attempt = 0; ; attempt++)
{
int length = (int) response.body().contentLength();
if (length < 0)
{
length = 3 * 1024 * 1024;
}
final int flength = length;
InputStream istream = new FilterInputStream(response.body().byteStream())
{
private int read = 0;
zipFile.clear();
@Override
public int read(byte[] b, int off, int len) throws IOException
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
{
int length = (int) response.body().contentLength();
if (length < 0)
{
int thisRead = super.read(b, off, len);
this.read += thisRead;
SplashScreen.stage(.05, .35, null, "Downloading Old School RuneScape", this.read, flength, true);
return thisRead;
length = 3 * 1024 * 1024;
}
};
JarInputStream jis = new JarInputStream(istream);
byte[] tmp = new byte[4096];
ByteArrayOutputStream buffer = new ByteArrayOutputStream(756 * 1024);
for (; ; )
{
JarEntry metadata = jis.getNextJarEntry();
if (metadata == null)
final int flength = length;
InputStream istream = new FilterInputStream(response.body().byteStream())
{
break;
}
private int read = 0;
buffer.reset();
@Override
public int read(byte[] b, int off, int len) throws IOException
{
int thisRead = super.read(b, off, len);
this.read += thisRead;
SplashScreen.stage(.05, .35, null, "Downloading Old School RuneScape", this.read, flength, true);
return thisRead;
}
};
JarInputStream jis = new JarInputStream(istream);
byte[] tmp = new byte[4096];
ByteArrayOutputStream buffer = new ByteArrayOutputStream(756 * 1024);
for (; ; )
{
int n = jis.read(tmp);
if (n <= -1)
JarEntry metadata = jis.getNextJarEntry();
if (metadata == null)
{
break;
}
buffer.write(tmp, 0, n);
}
if (!Arrays.equals(metadata.getCertificates(), jagexCertificateChain))
buffer.reset();
for (; ; )
{
int n = jis.read(tmp);
if (n <= -1)
{
break;
}
buffer.write(tmp, 0, n);
}
if (!Arrays.equals(metadata.getCertificates(), jagexCertificateChain))
{
if (metadata.getName().startsWith("META-INF/"))
{
// META-INF/JAGEXLTD.SF and META-INF/JAGEXLTD.RSA are not signed, but we don't need
// anything in META-INF anyway.
continue;
}
else
{
throw new VerificationException("Unable to verify jar entry: " + metadata.getName());
}
}
zipFile.put(metadata.getName(), buffer.toByteArray());
}
break;
}
catch (IOException e)
{
log.info("Failed to download gamepack from \"{}\" ({})", url, e.getMessage());
if (attempt >= NUM_ATTEMPTS)
{
if (metadata.getName().startsWith("META-INF/"))
{
// META-INF/JAGEXLTD.SF and META-INF/JAGEXLTD.RSA are not signed, but we don't need
// anything in META-INF anyway.
continue;
}
else
{
throw new VerificationException("Unable to verify jar entry: " + metadata.getName());
}
throw e;
}
zipFile.put(metadata.getName(), buffer.toByteArray());
url = url.newBuilder().host(hostSupplier.get()).build();
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2019 Abex
* 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.rs;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import net.runelite.http.api.worlds.WorldClient;
@Slf4j
class HostSupplier implements Supplier<String>
{
private final Random random = new Random(System.nanoTime());
private Queue<String> hosts = new ArrayDeque<>();
@Override
public String get()
{
if (!hosts.isEmpty())
{
return hosts.poll();
}
try
{
List<String> newHosts = new WorldClient()
.lookupWorlds()
.getWorlds()
.stream()
.map(i -> i.getAddress())
.collect(Collectors.toList());
Collections.shuffle(newHosts, random);
hosts.addAll(newHosts.subList(0, 16));
}
catch (IOException e)
{
log.warn("Unable to retrieve world list", e);
}
while (hosts.size() < 2)
{
hosts.add("oldschool" + (random.nextInt(50) + 1) + ".runescape.COM");
}
return hosts.poll();
}
}

View File

@@ -37,7 +37,7 @@ public class ClientConfigLoaderTest
@Test
public void test() throws IOException
{
final RSConfig config = ClientConfigLoader.fetch();
final RSConfig config = ClientConfigLoader.fetch(null);
for (String key : config.getClassLoaderProperties().keySet())
{