PDA

View Full Version : Improved RuneScape Classic Loader



Waffle
11-08-2011, 08:32 AM
Hey guys, a few days ago I found a program someone had made to load RSC directly (found here: http://ww2.sythe.org/showthread.php?p=9879730). This was real convenient and all for quick use, but I wanted more from it, so I upgraded it.

Source is included in the "source" directory, as well as the original compile.bat and run.bat.

Executable JAR included in main directory.

New program features include:
- File->Exit option just because that's standard
- World Switching menu (all three classic servers)
- Screenshots (creates directory automatically upon first use)

Hope you guys enjoy!

edit:
Up to date version has been attached.

Flight
11-08-2011, 08:52 AM
Oo I used to love working with RSC bots back in the day. I wish I had an account I'd surely look into this. Nice job. :)

_sw
11-08-2011, 11:05 AM
Very nice work. :)

I just noticed one thing: you've given every world pretty much the same parameters. IIRC, world 3 uses nodeid 5003 and world 1 uses nodeid 5001 and has a different servertype. If you're connecting to world 3 with nodeid 5002 it's potentially very easy for Jagex to detect you're not playing the game the "official" way.

Eventually I'll add a parser to my loader so that it loads the Javascript from Jagex's servers directly, which will hopefully eliminate that problem. :p

Waffle
11-08-2011, 02:19 PM
^ Could you go into a little more detail as to why that's a problem and potentially how to fix it?

I didn't do any of the actual Applet loading, that was all pre-existing. Everything I did pretty much didn't have anything to do with applet loading., i.e. the menus and taking screenshots.

_sw
11-08-2011, 03:17 PM
I made the loader using this file (http://classic2.runescape.com/plugin.js?param=o0,a0,s0). Notice that it's stored on World 2. Take a look at the parameters in that file and compare them to the parameters that the loader uses.

Jagex's Javascript:



document.write('<param name=nodeid value=5002>');
document.write('<param name=servertype value=1>');


The parameters used by the loader:



parameters.put("nodeid", "5002");
parameters.put("servertype", "1");


Now, let's take a look at the same Javascript file but from classic1.runescape.com instead of classic2.runescape.com:



document.write('<param name=nodeid value=5001>');
document.write('<param name=servertype value=3>');


Notice how the nodeid is 5001 and the servertype is 3. The loader, when you point it at classic1.runescape.com is still using servertype 1 and nodeid 5002. These commands are then passed on to the client. It's impossible to have a servertype of 1 and nodeid of 5002 when connecting to World 1 through the official classic client.

Instead of loading the same parameters for every single world, it is much safer to either parse the Javascript file or do something like this:



if (world == 1) {
parameters.put("nodeid", "5001");
parameters.put("servertype", "3");
} else if (world == 3) {
parameters.put("nodeid", "5003");
parameters.put("servertype", "1");
} else {
parameters.put("nodeid", "5002");
parameters.put("servertype", "1");
}

Waffle
11-08-2011, 04:00 PM
I noticed the unsigned URL has a reference to the server number as well. I went ahead and changed that, too. Here's the updated parameters code:



if (world.contains("1")
{
parameters.put("unsignedurl","http://www.runescape.com/classicapplet/classicgame.ws?f=1&j=1&a=1");
parameters.put("nodeid", "5001");
parameters.put("servertype", "1");
}
else if (world.contains("3")
{
parameters.put("unsignedurl","http://www.runescape.com/classicapplet/classicgame.ws?f=3&j=1&a=1");
parameters.put("nodeid", "5003");
parameters.put("servertype", "3");
}
else
{
parameters.put("unsignedurl","http://www.runescape.com/classicapplet/classicgame.ws?f=2&j=1&a=1");
parameters.put("nodeid", "5002");
parameters.put("servertype", "2");
}


I suppose I could create a method to do this based off a given world, just to cut down on repeat code. I'll work on that now.

edit:
Also, I was getting a NullPointerException, I guess because I didn't have "world" initialized until the main() method. If I set the world to 2 in the field declarations, will the parameters still set correctly when I switch worlds?

edit2:
Method for setting correct world parameters:


public static void setWorldParameters(String w)
{
parameters.put("unsignedurl","http://www.runescape.com/classicapplet/classicgame.ws?f=" + w + "&j=1&a=1");
parameters.put("nodeid", "500" + w);
parameters.put("servertype", w);
}


Then the updated static block:


static
{
parameters = new HashMap<String, String>();
setWorldParameters(world);
parameters.put("crashurl", "error_loader_crash.ws?a=0&j=1");
parameters.put("cabbase", "loader.cab");
parameters.put("java_arguments", "-Xmx96m -Dsun.java2d.noddraw=true");
parameters.put("country", "0");
parameters.put("haveie6", "0");
parameters.put("affid", "0");
parameters.put("js", "1");
parameters.put("objecttag", "0");
parameters.put("advertsuppressed", "1");
parameters.put("modewhat", "0");
parameters.put("modewhere", "0");
parameters.put("settings", "wwGlrZHF5gKN6D3mDdihco3oPeYN2KFybL9hUUFqOvk");
}


My question about initializing the world and setting it to 2 in the field declarations still remains though

_sw
11-08-2011, 04:14 PM
Yep, as long as it loads the parameters after the string 'world' is updated to a new number it should work fine. Test it by loading World 1, it should give an additional message on the "Click here to log in" screen (something like "You need a veterans account to access this world"). If it does, it's working correctly.

Waffle
11-08-2011, 04:21 PM
Yep, as long as it loads the parameters after the string 'world' is updated to a new number it should work fine. Test it by loading World 1, it should give an additional message on the "Click here to log in" screen (something like "You need a veterans account to access this world"). If it does, it's working correctly.

It was doing that when I was loading the incorrect parameters though.

Does the code I have now update parameters upon switching worlds? I don't really know how to tell.

_sw
11-08-2011, 04:27 PM
The right client is how it should look when loading World 1:

http://i.imgur.com/JyZuv.png

Waffle
11-08-2011, 04:28 PM
Mine doesn't even display a members' message. Uh oh :(

edit:
Now it says Veteran account is needed on the default startup, even though it's loading world 2. Lol..

_sw
11-08-2011, 04:38 PM
Haha. I'll add the nodeid, unsignedurl and servertype changes to your version of the loader. Will get back to you. :tongue:

Waffle
11-08-2011, 04:44 PM
Actually, I snooped around in all 3 servers' page source codes, and looked at the parameters for all three.

I was assuming all three parameters were different for every world, but they aren't. Some are the same for worlds 2 and 3, which makes sense now that I think about it because they don't require a veteran account whereas world 1 does.

Also, the settings string has changed. Recommend I update that?

edit:
Servers 2 and 3 have server type 1. Server 1 has server type 3. /facepalm Jagex for making sense

edit2:
So here we go:


public static void setWorldParameters(String w)
{
parameters.put("unsignedurl","http://www.runescape.com/classicapplet/classicgame.ws?f=" + w + "&j=1&a=1");
parameters.put("nodeid", "500" + w);

if (w.contains("1"))
parameters.put("servertype", "3");
else
parameters.put("servertype", "1");
}

_sw
11-08-2011, 04:47 PM
Edit: Okay, it's very strange. No matter what with your loader - I always either get JUST the Veteran's message no matter what world I select, or never get it even if World 1 is selected.

I've modified my loader (very roughly) to have multiple world support. It uses arguments, so to play in World 1 you run it as "start javaw -Xmx128m RSCLoader 1" etc. It seems to work okay. Presumably the problem is that it's not loading the params correctly when switching worlds.

http://pastebin.com/fwkTy29U

Waffle
11-08-2011, 04:53 PM
Now I am getting the members' message as normal, but World 1 doesn't display the Veteran message.

Does this mean parameters are not updating upon switching worlds?

edit:
Duh, stupid me. Problem fixed. I called the setWorldParameters() method in the switchWorld() method.

Works like a charm now. I'll add the updated program to the first post as well as this one.

_sw
11-08-2011, 05:03 PM
Great. :biggrin:

Speaking of the "settings" parameter... I have no idea what it does. It seems to change completely randomly. Having two completely different IP addresses and computers load the same settings would definitely be suspicious, which is why it's infinitely safer to parse the plugin.js file for parameters. That's what all of the public bots made since the 2009 update have done. APOS, Reflectionist, ARS, Pro-Bot, RSCBot, etc.

Waffle
11-08-2011, 05:23 PM
^ Then let's do that. Recommended approach? I was thinking just using a BufferedReader or something similar to what someone would use to grab HiScores Lite data

_sw
11-08-2011, 05:30 PM
Take a look at how Reflectionist does it:

http://rscc.dyndns.info/rsc/SourceCode/Reflectionist.rar

(src/org/meatstick/reflectionist/loader)

JarLoader.java
RSApplet.java
RSAppletStub.java

Reflection in Reflectionist hasn't worked for about a year, but the loader part still works perfectly.

Waffle
11-08-2011, 06:08 PM
^ You're going to have to explain to me where it's grabbing the settings because I looked over it for about 5 mins and couldn't figure it out. I have to go to class from now until about 3 hours from now, so I will be mostly unable to reply and work on my program til then.

Thanks for your help so far, btw. My end goal is to create something similar to SMART for RSC that still works with SCAR. But I still have lots of step-by-step learning to do before I get there.

_sw
11-08-2011, 06:19 PM
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package org.meatstick.reflectionist.web;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
*
* @author Lukas
*/
public class WebCrawler {

public WebCrawler() {
}

public static Map getParameters() {
try {
BufferedReader br = openCon("http://classic2.runescape.com/plugin.js?param=o0,a0,s0");
HashMap map = new HashMap();
String s;
while((s = br.readLine()) != null) {
if(!s.contains("param name") || !s.contains("value")) {
continue;
}
s = s.replace("(", "");
s = s.replace(")", "");
s = s.replaceAll("document.write", "").replaceAll("\"", "").replaceAll("'", "").replaceAll("<", "").replaceAll(">", "").replaceAll(";", "");
String param_name = null;
String value = null;
if(s.indexOf("param name") != -1 && s.indexOf("value") != -1) {
if(s.contains("unsignedurl")) {
System.out.println(s);
}
param_name = s.trim().substring("param_name=".length(), s.indexOf("value") - 2);
s = s.substring(s.indexOf(param_name) + (param_name.length() + 1), s.length());

value = s.trim().substring("value=".length(), s.length());
if(param_name.contains("unsignedurl")) {
System.out.println("val"+value);
System.out.println("prm"+param_name);
}
map.put(param_name, value);
}
}
if(map.containsKey("haveie6")) {
map.remove("haveie6");
}
map.put("haveie6", "0");
return map;
} catch (Exception ex) {
Logger.getLogger(WebCrawler.class.getName()).log(L evel.SEVERE, null, ex);
System.exit(0);
}
return null;
}

public static BufferedReader openCon(String url) throws Exception{
return new BufferedReader(
new InputStreamReader(
new URL(url).openStream()));
}


}

Waffle
11-08-2011, 08:58 PM
Wow. Exactly what I had in mind!

Waffle
11-09-2011, 12:05 AM
The WebCrawler() class you posted set some parameters with strings that were a character or two too short. I fixed that, and made it set the "haveie6" parameter only once at the end of the while loop.



public static void parseForSettings(String w)
{
System.out.println("\n"); //For debugging use
try
{
URL url = new URL("http://classic" + w + ".runescape.com/plugin.js?param=o0,a0,s0");
BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));

String raw;

while((raw = br.readLine()) != null)
{
if(!raw.contains("param name") || !raw.contains("value") || raw.contains("haveie6"))
continue;

raw = raw.replace("(", "");
raw = raw.replace(")", "");
raw = raw.replaceAll("document.write", "").replaceAll("\"", "").replaceAll("'", "").replaceAll("<", "").replaceAll(">", "").replaceAll(";", "");

String param_name = null;
String value = null;

if(raw.indexOf("param name") != -1 && raw.indexOf("value") != -1)
{
if (raw.contains("error_loader_crash") || raw.contains("http://www.runescape.com/classicapplet/")) //This is where the issue was, we needed to take a substring one character shorter for the crashurl and unsigned applet parameters
param_name = raw.trim().substring("param_name=".length(), raw.indexOf("value") - 3);
else
param_name = raw.trim().substring("param_name=".length(), raw.indexOf("value") - 2);

raw = raw.substring(raw.indexOf(param_name) + (param_name.length() + 1), raw.length());

value = raw.trim().substring("value=".length(), raw.length());

System.out.println(param_name + ", " + value); //For debugging use (this is how I knew some names weren't correct)
parameters.put(param_name, value);
}
}
parameters.put("haveie6", "0"); //Set this parameter once as opposed to every time the while loop executes..
System.out.println("haveie6, 0");
} catch (Exception ex)
{
throw new RuntimeException(ex);
}
System.out.println("\n"); //For debugging use
}