SRL Archive - General

Java: How To Create Your Own Bot Interface


Brandon

2013-05-19 23:07:41

How To Create Your Own Bot Interface



1. What you need to know.
2. Setting Up A Project.
3. Creating a configuration.
4. Creating a boot loader.
5. Creating a Manifest.
6. Creating Utility functions.
7. Loading the client.
8. Creating the Frame/Implementing the Client.
9. Overriding the Canvas & Creating a PaintListener..
10. Drawing using our PaintListener/PaintObserver..
11. Attached Tutorial Files




What you need to know.
Arrays, Instances, References, Logic.
Various Programming Patterns: Subclassing, Observer Pattern, BootLoading, Multi-Casting/Chaining, Singleton.

Setting Up A Project.

For this tutorial, I will not be using command-line. I downloaded and chose a simple IDE with a simple and easy to use interface. This IDE is IntelliJ (Community Edition) which can be downloaded here: https://www.jetbrains.com/idea/download/

You can use whatever IDE you like but for the sake of this tutorial and boot loaders, I recommend IntelliJ.


Part One (Creating the project base):

  1. Click the file menu, hit New -> Project.
  2. Select Java from the left menu.
  3. Hit Next.
  4. Give your project a name (Ex: BotTutorial).
  5. Hit done.


The below images are the results of the above steps:

http://i.imgur.com/Bv2wDHa.png
http://i.imgur.com/CS3Vwv5.png


  1. Right-Click on the src folder. Hit New -> Package.
  2. Name your package (without quote): "eos".
  3. Right-Click on the src folder. Hit New -> Package.
  4. Name your package (without quotes): "java.awt".
  5. Right-Click on the eos package. Hit New -> Class.
  6. Name your class "Main".
  7. Add the below code to your class.


package eos;

/**
* Created by Brandon on 2015-12-20.
*/
public class Main {
public static void main(String[] args) {

}
}


The below images are the results of the above steps:

http://i.imgur.com/0NKfx30.png
http://i.imgur.com/XCXtxTk.png
http://i.imgur.com/8HM7w0O.png
http://i.imgur.com/CxH2n0i.png
http://i.imgur.com/HiIO0Tj.png
http://i.imgur.com/ULjlLeZ.png







Creating a configuration.
A configuration is a script in Intelli-J that tells it which class is the main class (which class should be ran first).

  1. Click the button with the arrow pointing DOWN at the top right of your IDE. This is the configuration button.
  2. Hit Edit Configurations…
  3. Hit the plus sign at the top left corner of your IDE.
  4. Choose Application.
  5. Give your configuration a name (without quotes): "Main".
  6. Specify your main class (without quotes): "eos.Main".
  7. Specify the following VM Options (if you do not know where your Jar is located, look at your Working Directory on the same screen): -Xbootclasspath/p:"/Users/YourUserNameHere/Desktop/BotTutorial/out/artifacts/Eos/Eos.jar"
  8. Hit Apply.
  9. Hit Done.




The below images are the results of the above steps:

http://i.imgur.com/i0QhndW.png
http://i.imgur.com/dXY860r.png
http://i.imgur.com/FomWekk.png









Creating a boot loader.
For those of you that do not know what a boot loader is, it is a set of instructions/code that is ran before the actual application's main gets run. We need this because we want our bot to be double-clickable (double click the jar and it runs). We don't want to have to input command line arguments using cmd/terminal in order to run it every time.


  1. Right-Click the eos package. Hit New -> Class.
  2. Enter "BootLoader" as the class name.
  3. Paste the following code for the boot loader.


package eos;

import java.io.IOException;
import java.net.URLDecoder;

/**
* Created by Brandon on 2015-12-20.
*/
public class BootLoader {
private static final String BOOT_FLAGS = "-Xbootclasspath/p:";


public static void main(String[] args) throws IOException {
String path = BootLoader.class.getProtectionDomain().getCodeSource().getLocation().getPath();
path = URLDecoder.decode(path, "UTF-8").replaceAll("\\\\", "/");

StringBuilder commands = new StringBuilder(0xFF);
String mainName = Main.class.getCanonicalName();
boolean windows = System.getProperty("os.name").contains("Win");

if (windows) {
commands.append("javaw");
} else {
commands.append("java");
}

commands.append(' ').append(BOOT_FLAGS);
commands.append('"').append(path).append("\"");
commands.append(" -cp ").append('"').append(".").append('"').append(' ').append(mainName);

for (String arg : args) {
commands.append(' ').append('"').append(arg).append('"');
}

Process application = null;
Runtime runtime = Runtime.getRuntime();

if (windows) {
application = runtime.exec(commands.toString());
}
else {
application = runtime.exec(new String[]{ "/bin/sh", "-c", commands.toString() });
}
}
}


The above code creates a JVM, runs your application with "XBootClassPath/p:" commands and arguments the original JVM dies off leaving the bot JVM running. This allows us to override internal classes without using command line and keeps our jar double-clickable.








Creating a Manifest.
A manifest is the part of the jar file configuration that tells the JVM what classes to run when the jar is double clicked or ran without explicit main arguments.

In our case, we want our boot loader to run first! Then the boot loader will run main on a separate JVM and then die off, leaving only the Main JVM running.


  1. Click the file menu -> Project Structure.
  2. Choose Artifacts from the left menu.
  3. Hit the plus sign and choose Jar -> From Modules With Dependencies.
  4. Specify the main class (without quotes): "eos.BootLoader".
  5. Hit OK.
  6. Put a checkmark in "Build On Make".
  7. Change the name of the Artifact to "Eos" (without quotes).
  8. Hit Apply.
  9. Hit OK.


The below images are a result of the above steps:

http://i.imgur.com/c05ooIJ.png
http://i.imgur.com/0a3gviu.png
http://i.imgur.com/1shmw3R.png








Creating Utility/Helper classes.

For this part of the tutorial, we will be creating a socket class and a Utilities class.

  1. Right-Click the eos package. Create a new package called "utility" (without quotes).
  2. Right-Click the utility package. Create a new class called "Util" (without quotes).
  3. Copy paste the below code into the Util class.



package eos.utility;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;

/**
* Created by Brandon on 2015-12-20.
*/
public class Util {
private static final Random random = new Random();
private static Util instance = new Util();
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-hh-mm-ss");

private Util() {
}

public static Util getInstance() {
return instance;
}

public static int random(int Min, int Max) {
return random.nextInt(Math.abs(Max - Min)) + Min;
}

public static void sleep(int Time) {
try {
Thread.sleep(Time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public static void sleepRandom(int Min, int Max) {
sleep(random(Min, Max));
}

private static Date getDate() {
return Calendar.getInstance().getTime();
}

public static Image loadResourceImage(String ResourcePath) {
try {
return ImageIO.read(instance.getClass().getResource(ResourcePath));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

public static ImageIcon loadIcon(String url) {
try {
return new ImageIcon(new URL(url));
} catch(Exception e) {
e.printStackTrace();
}
return null;
}
}


The above class is our utilities class that does various things for us. It is a singleton class meaning that there is only ever ONE instance of the class. It cannot be allocated by the user of the class. The class allows us to load images and resource images, generate random numbers, sleep for a certain amount of time, and sleep for a random amount of time.



Next is our socket class for downloading the RS config file & whatever else we need to download (if any).

  1. Right click the utility package. Hit New -> Class.
  2. Name the class (without quotes): "HTTPSocket" and place the following code into the newly created class.


package eos.utility;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;

/**
* Created by Brandon on 2015-12-20.
*/
public class HTTPSocket {

private URL url;

public HTTPSocket(String Address) throws IOException {
url = new URL(Address);
}

private void setProperties(URLConnection connection, String userAgent) {
connection.addRequestProperty("Protocol", "HTTP/1.1");
connection.addRequestProperty("Connection", "keep-alive");
connection.addRequestProperty("Keep-Alive", "300");
if (userAgent != null) {
connection.addRequestProperty("User-Agent", userAgent);
} else {
connection.addRequestProperty("User-Agent", "Mozilla/5.0 (" + System.getProperty("os.name") + " " + System.getProperty("os.version") + ") Java/" + System.getProperty("java.version"));
}
}

public String getPage(String userAgent) {
return request(null, userAgent, false);
}

public String request(String data, String userAgent, boolean post) {
try {
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
setProperties(connection, userAgent);
connection.setRequestMethod(post ? "POST" : "GET");
connection.setUseCaches(false);
connection.setDoInput(true);

if (post) {
connection.addRequestProperty("Content-Length", String.valueOf(data.length()));
connection.setDoOutput(true);
try (OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream())) {
writer.write(data);
writer.flush();
}
}

String Line;
StringBuilder Builder = new StringBuilder();
try (BufferedReader Reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
while ((Line = Reader.readLine()) != null) {
Builder.append(Line).append("\n");
}
}
connection.disconnect();
return Builder.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}


This class allows us to download web pages, config files, etc..








Loading the client.

The RS client can be loaded using 2 different techniques. The first technique is to load it like SMART or like the browser. This requires you to parse the HTML with a Regex. The second way to load it is like the Official RS client via the "jav_config.ws" file.

For the sake of this tutorial, I will be showing you how to load it like the Official RS Client. If you'd like to know how to load it like the browser does, you can PM me or check the source SMART's source code.


  1. Right-Click the eos package.
  2. Hit New -> Class.
  3. Name the class ClientApplet.
  4. Paste the below code into the file.




package eos;

import eos.utility.HTTPSocket;

import javax.swing.*;
import java.applet.Applet;
import java.applet.AppletContext;
import java.applet.AppletStub;
import java.awt.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;

/**
* Created by Brandon on 2015-12-20.
*/
public class ClientApplet extends JPanel implements AppletStub {

private static final long serialVersionUID = 5627929030422355843L;
private final HashMap<String, String> parameters = new HashMap<>();

private Applet applet;
private URLClassLoader loader;
private URL codebase;
private URL documentbase;

public ClientApplet(String world, int width, int height) {
try {
this.setLayout(new BorderLayout(0, 0));
HTTPSocket socket = new HTTPSocket(world + "/jav_config.ws");
String lines[] = socket.getPage(null).replaceAll("param=|msg=", "").split("\r|\n|\r\n"); //param=|msg=(.*?)\r|\n|\r\n

for (String line : lines) {
if (line.length() > 0) {
int idx = line.indexOf("=");
parameters.put(line.substring(0, idx), line.substring(idx + 1));
}
}

codebase = documentbase = new URL(parameters.get("codebase"));
loader = new URLClassLoader(new URL[]{new URL(codebase + parameters.get("initial_jar"))});
applet = (Applet)loader.loadClass(parameters.get("initial_class").replace(".class", "")).newInstance();
applet.setStub(this);
applet.setPreferredSize(new Dimension(width, height));
this.add(applet, BorderLayout.CENTER);
} catch(Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(null, "Error Loading.. Please Check Your Internet Connection.", "Error Loading..", JOptionPane.ERROR_MESSAGE);
}
}

public void start() {
if (applet != null) {
applet.init();
applet.start();

while(applet.getComponentCount() < 1) {
try {
Thread.sleep(10);
} catch(Exception e) {

}
}
}
}

public void stop() {
if (applet != null) {
applet.stop();
applet.destroy();
applet = null;
}
}

public Applet getApplet() {
return applet;
}

public ClassLoader getClassLoader() {
return loader;
}

@Override
public boolean isActive() {
return true;
}

@Override
public URL getDocumentBase() {
return documentbase;
}

@Override
public URL getCodeBase() {
return codebase;
}

@Override
public String getParameter(String name) {
return parameters.get(name);
}

@Override
public AppletContext getAppletContext() {
return applet.getAppletContext();
}

@Override
public void appletResize(int width, int height) {
applet.setSize(width, height);
}
}


The above class actually loads the RS applet. It is capable of loading both OSRS and RS3. How does it work? When the Official RS client loads the applet, it gets the applet parameters from the following URL(s):

http://runescape.com/jav_config.ws

OR

http://oldschool.runescape.com/jav_config.ws



The result is a configuration file that looks like:


title=RuneScape
adverturl=http://www.runescape.com/bare_advert.ws
codebase=http://world1.runescape.com/
storebase=0
initial_jar=gamepackeRaq70Zx95k7978wYJvrdwQ7MorebrDI_6623136.jar
initial_class=Rs2Applet.class
viewerversion=124
win_sub_version=1
mac_sub_version=2
other_sub_version=2
browsercontrol_win_x86_jar=browsercontrol_0_-1928975093.jar
browsercontrol_win_amd64_jar=browsercontrol_1_1674545273.jar
termsurl=http://www.jagex.com/g=runescape/terms/terms.ws
privacyurl=http://www.jagex.com/g=runescape/privacy/privacy.ws
download=1513674
window_preferredwidth=1024
window_preferredheight=768
advert_height=96
applet_minwidth=765
applet_minheight=540
applet_maxwidth=3840
applet_maxheight=2160
msg=lang0=English
msg=lang1=Deutsch
msg=lang2=Français
msg=lang3=Português
msg=err_verify_bc64=Unable to verify browsercontrol64
msg=err_verify_bc=Unable to verify browsercontrol
msg=err_load_bc=Unable to load browsercontrol
msg=err_create_advertising=Unable to create advertising
msg=err_create_target=Unable to create target applet
msg=copy_paste_url=Please copy and paste the following URL into your web browser
msg=tandc=This game is copyright © 1999 - 2015 Jagex Ltd.\Use of this game is subject to our ["http://www.runescape.com/terms/terms.ws"Terms and Conditions] and ["http://www.runescape.com/privacy/privacy.ws"Privacy Policy].
msg=options=Options
msg=language=Language
msg=changes_on_restart=Your changes will take effect when you next start this program.
msg=loading_app_resources=Loading application resources
msg=loading_app=Loading application
msg=err_save_file=Error saving file
msg=err_downloading=Error downloading
msg=ok=OK
msg=cancel=Cancel
msg=message=Message
msg=information=Information
msg=err_get_file=Error getting file
msg=new_version=Update available! You can now launch the client directly from the RuneScape website, and chat timestamps work properly.\nGet the new version here: http://www.runescape.com/download
msg=new_version_linktext=Download Update
msg=new_version_link=http://www.runescape.com/download
param=13=true
param=35=false
param=29=443
param=21=-1007872315
param=20=
param=12=.runescape.com
param=52=443
param=23=lobby44.runescape.com
param=37=0
param=31=false
param=40=1
param=39=0
param=19=
param=3=0
param=6=33533
param=16=wwGlrZHF5gKN6D3mDdihco3oPeYN2KFybL9hUUFqOvk
param=45=eRaq70Zx95k7978wYJvrdwQ7MorebrDI
param=7=0
param=22=0
param=2=DEB8EF942367613913E5A4FD89E2CCFB4E6AFAFA94A0B5B049FBC3FAB530F4FF5EF63E4DAD5C45AA
param=43=
param=32=false
param=17=false
param=4=halign=true|valign=true|image=rs_logo.gif,0,-43|rotatingimage=rs3_loading_spinner.gif,0,47,9.6|progress=true,Verdana,13,0xFFFFFF,0,51
param=44=
param=28=43594
param=41=true
param=38=1757695916
param=46=443
param=33=1143
param=11=false
param=5=http://world1.runescape.com
param=48=0
param=1=80
param=24=false
param=18=43594
param=27=http://services.runescape.com/m=gamelogspecs/clientstats?data=
param=-1=t6xUGAxBP6eU3HBFKr2Dig
param=42=0
param=15=0
param=47=content.runescape.com
param=50=443
param=0=LdncX3BzNDKE5WuJeQ9alA
param=34=1200
param=36=false
param=25=38
param=8=1088940781
param=30=http://world1.runescape.com
param=51=0
param=10=43594
param=14=content.runescape.com


Now the file is separated by \r\n|\r|\n new line format. Each "param" is passed to the applet. Each "msg" is a localized string that can be used to display an error, an option, etc.. However, we don't care about msg's. We only care for params.


There are a few other parameters we need to load the applet and that would be:


codebase=http://world1.runescape.com/
initial_jar=gamepackg5QzAjWsDRm4MGtjZbXmyI7pl-psD5zq_798252.jar
initial_class=Rs2Applet.class

Our ClientApplet class loads the jar specified at the above codebase + initial_jar and loads the main class specified by initial_class.

This is the way the Official Client loads the applet.

Our start method starts the applet and waits until the Canvas is ready. Our stop method destroys the applet and frees all allocated resources.









Creating the Frame/Implementing the Client.

Now that we have every thing we need to load the client/applet. We need to be able to display it on screen. To do this, we need to create a window/frame and place the applet inside it.

  1. Go to your Main file.
  2. Copy the following code into the file.



package eos;

import eos.utility.Util;

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
* Created by Brandon on 2015-12-20.
*/
public class Main {

private static JLabel splash = new JLabel(Util.loadIcon("http://www.runescape.com/img/game/splash.gif"));

private static void displayFrame(String world, int width, int height) {
JFrame frame = new JFrame("Eos");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);

if (splash != null) {
frame.add(splash);
frame.pack();
frame.setVisible(true);
}

centerFrame(frame);

ClientApplet applet = new ClientApplet(world, width, height);
frame.add(applet);
applet.start();
if (splash != null) {
frame.remove(splash);
}
frame.revalidate();
frame.pack();

centerFrame(frame);

frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
frame.setVisible(false);
applet.stop();
frame.dispose();
super.windowClosed(e);
}
});
}


private static void centerFrame(JFrame frame) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
int cX = (toolkit.getScreenSize().width / 2) - (frame.getWidth() / 2);
int cY = (toolkit.getScreenSize().height / 2) - (frame.getHeight() / 2);
frame.setMinimumSize(frame.getSize());
frame.setLocation(cX, cY);
frame.setVisible(true);
frame.setResizable(true);
}

public static void notifyCanvasReady(Canvas canvas) {
}

public static void main(String[] args) {
displayFrame("http://runescape.com", 800, 600);
}
}


Now if we hit the green play button, we can see that we have successfully loaded the RS3 client. We can specify the OSRS url to load OSRS if we wanted. No difference in code :)


Now let's break it all down. The first function creates a JFrame (window). We add our splash screen/loading icon to the window and immediately display it. We centre the window on our screen because we're professional.

Next we load the applet (our splash screen will still be showing until the applet has started). We loaded the applet by creating a ClientApplet. We remove our splash screen and add the applet to the frame. We then re-centre the frame on our screen.

Finally, we add a window listener that destroys the applet when the user presses the "exit" button. This is because we need to manually start and stop the applet and once the applet has been stopped, we hide our frame and do cleanup. The JVM will then die right after and voila we are complete.

The client successfully loads and can be closed at any time!









Overriding the Canvas & Creating a PaintListener.

This is what we're all here for. We have successfully created a client but we want to be able to draw on it. We want to be able to tell it to do stuff. We want to listen to events! We have successfully created a boot loader that loads our custom canvas..

Oh but wait? We haven't created a canvas yet! Without a custom canvas, we cannot draw on the Applet because it is double buffered. This means that the client uses two buffers. One it draws on in the background and another it displays on screen. It swaps these two buffers constantly to avoid flickering on the screen. Thus if we draw on it, our drawing will disappear.


Anyway, let's override the java.awt.Canvas class with our own implementation. First we need to create a PaintListener class that allows us to draw on the Canvas that we will be creating.


  1. Right-Click the eos package.
  2. Hit New -> Package.
  3. Name it "listeners" (without quotes).
  4. Right-Click the listeners package.
  5. Hit New -> Class.
  6. Name the class: "PaintListener" (without quotes).
  7. Copy paste the following code into the newly created PaintListener file.



package eos.listeners;

import java.awt.*;
import java.util.EventListener;

/**
* Created by Brandon on 2015-12-20.
*/
public interface PaintListener extends EventListener {
public void onPaint(Graphics g);
}


This class is an interface/observer/listener that will be added to our canvas. The canvas will call the onPaint(Graphics g) function to tell us to draw our custom drawings/art onto the client.

This is called the observer pattern. Why? Because we are observing whenever the Canvas draws.

Next we need to create our Canvas.


  1. Right-Click the java.awt package. (Please note this is NOT the eos package!).
  2. Hit New -> Class.
  3. Name your class "Canvas" (without quotes).
  4. Copy the following code into the canvas class.



package java.awt;

import eos.Main;
import eos.listeners.PaintListener;

import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleRole;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.peer.CanvasPeer;

/**
* Created by Brandon on 2015-12-20.
*/
public class Canvas extends Component implements Accessible {
private static final long serialVersionUID = -2284879212465893870L;
private static int nameCounter = 0;
private static final String base = "canvas";

private BufferedImage debugImage;
private transient PaintListener paintListener;

public Canvas() {
Main.notifyCanvasReady(this);
}

public Canvas(GraphicsConfiguration config) {
this();
setGraphicsConfiguration(config);
}

public void addPaintListener(PaintListener listener) {
paintListener = (PaintListener)AWTEventMulticaster.addInternal(paintListener, listener);
}

@Override
public Graphics getGraphics() {
if (debugImage == null || debugImage.getWidth() != getWidth() || debugImage.getHeight() != getHeight()) {
debugImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
}

Graphics g = debugImage.getGraphics();

if (paintListener != null) {
paintListener.onPaint(g);
}

super.getGraphics().drawImage(debugImage, 0, 0, null);

return g;
}

@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
}

@Override
void setGraphicsConfiguration(GraphicsConfiguration gc) {
synchronized(getTreeLock()) {
CanvasPeer peer = (CanvasPeer)getPeer();
if (peer != null) {
gc = peer.getAppropriateGraphicsConfiguration(gc);
}
super.setGraphicsConfiguration(gc);
}
}

@Override
String constructComponentName() {
synchronized (Canvas.class) {
return base + nameCounter++;
}
}

@Override
public void addNotify() {
synchronized (getTreeLock()) {
if (peer == null)
peer = getToolkit().createCanvas(this);
super.addNotify();
}
}

@Override
public void paint(Graphics g) {
g.clearRect(0, 0, width, height);
}

@Override
public void update(Graphics g) {
g.clearRect(0, 0, width, height);
paint(g);
}

@Override
boolean postsOldMouseEvents() {
return true;
}

@Override
public void createBufferStrategy(int numBuffers) {
super.createBufferStrategy(numBuffers);
}

@Override
public void createBufferStrategy(int numBuffers, BufferCapabilities caps) throws AWTException {
super.createBufferStrategy(numBuffers, caps);
}

@Override
public BufferStrategy getBufferStrategy() {
return super.getBufferStrategy();
}

@Override
public AccessibleContext getAccessibleContext() {
if (accessibleContext == null) {
accessibleContext = new AccessibleAWTCanvas();
}
return accessibleContext;
}

protected class AccessibleAWTCanvas extends AccessibleAWTComponent {
private static final long serialVersionUID = -6325592262103146699L;

@Override
public AccessibleRole getAccessibleRole() {
return AccessibleRole.CANVAS;
}
}
}



Now we have a Canvas that uses our PaintListener. This canvas is the same code as the Official Java.AWT.Canvas class. The only difference is that we've added a few noticeable things to it.


We have add a buffer to the canvas so we can draw on and we have added a PaintListener property:


private BufferedImage debugImage;
private transient PaintListener paintListener;


Next we have added a function to addPaintListeners to our canvas..


public void addPaintListener(PaintListener listener) {
paintListener = (PaintListener)AWTEventMulticaster.addInternal(paintListener, listener);
}


Now you're probably wondering what in the hell is a Multicaster. The multi-caster is a CHAIN of listeners. How does it work?

First you supply it the listener that you have (your one listener property). Then you supply it the listener that you want to ADD to the chain of observers. Finally, the value that is returned is the original head of the listener chain.

It's pretty much a linked list of observers.

This way, you keep track of ONE listener and whenever an event occurs, you notify the observer. The multi-caster will automatically notify ALL observers in the chain.

For example:


private LinkedList<EventListener> listeners;

public void addInternal(EventListener a, EventListener b) {
if (a == null) {
listeners.add(b);
return b;
}

listeners.add(b);
return a;
}

Now lets say you did:


a = addInternal(a, b);
a = addInternal(a, c);
a = addInternal(a, d);


//Your chain would look like:

//a -> b -> c -> d.

//If you did:

a.notify();



//It would translate to (notifying all observers in the observer chain):

EventListener e = a;

while (e.peek() != null) {
e.notifyObserver();
e = e.next();
}


This means you can add as many paint listeners as you like and each of them can perform different drawings. Usually you only need one paint listener, but whatever floats your boat..


Next we modified the Canvas constructor:


public Canvas() {
Main.notifyCanvasReady(this);
}

This notifies our main class that we are ready to proceed. A canvas has been created and needs us to add listeners/observers to it :).


Finally, we have added/overrode one last method in the client:


@Override
public Graphics getGraphics() {
if (debugImage == null || debugImage.getWidth() != getWidth() || debugImage.getHeight() != getHeight()) {
debugImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
}

Graphics g = debugImage.getGraphics();

if (paintListener != null) {
paintListener.onPaint(g);
}

super.getGraphics().drawImage(debugImage, 0, 0, null);
return g;
}


The client calls this function to draw on the canvas. We need to notify our observers and also give them a proper graphics context that they can use for drawing on.


Voila, we have now completed our PaintListener observer.








Drawing using our PaintListener/PaintObserver.

To draw on our Canvas, we need to add a listener/observer to the canvas. To do this, we modify the addPaintListeners function in the Main file as follows:


public static void notifyCanvasReady(Canvas canvas) {
canvas.addPaintListener((PaintListener) g -> {
g.setColor(Color.white);
g.drawString("HELLO WORLD", 50, 50);
});
}


For those of you that are NOT familiar with the Java Lambda syntax, the above function is the same as:


public static void notifyCanvasReady(Canvas canvas) {
canvas.addPaintListener(new PaintListener(Graphics g) {
g.setColor(Color.white);
g.drawString("HELLO WORLD", 50, 50);
});
}

What this does is that it adds a paintListener to the Canvas which is component 0 on the applet. That's right, the canvas is the very first component of the applet.

We add a listener to it that draws "HELLO WORLD" in the white colour at Point(50, 50).


The results of your intuition is:

http://i.imgur.com/oEx1qEk.jpg

rj

2013-05-19 23:12:00

Awww we gotta actually write the code :( this will be useful one day when I'm not a total noob at java

Brandon

2013-05-19 23:33:13

From: Officer Barbrady
Awww we gotta actually write the code :( this will be useful one day when I'm not a total noob at java


LOL! Hey.. No fair.. I had to write it lol.. Meh I attached file files at the end of the tutorial if you're lazy :l

rj

2013-05-19 23:34:10

From: Brandon
LOL! Hey.. No fair.. I had to write it lol.. Meh I attached file files at the end of the tutorial if you're lazy :l


I'll might aswell write it have to learn it eventually

Flight

2013-05-20 00:07:19

This is a wonderful tutorial Brandon, really thorough in the explanations. I hope to learn quite a bit after reading this over. Thank you very much for this!

[XoL]

2013-05-20 00:42:34

Great tutorial man!

NKN

2013-05-20 07:11:20

Thanks for the tutorial man. :) Helps a lot.

Now lets see if I can pull NKNBot out of the dirt.

J J

2013-05-22 04:30:43

Great tutorial, Brandon. I've been rather busy the last few weeks and haven't worked on my own client for atleast 2 months. The last time I worked on it I was working on the layout and trying to get one JTabbedPane working. It did actually work.. sort of but when selecting something from my JMenuBar it would go under the JTabbedPane due to light weight and heavy weight components..

The ClientPool is really interesting because I wasn't sure how to implement the drawing functions properly when working with multiple tabs. I will need to rewrite a lot to properly be able to add new clients when the user wants more tabs. It's interesting though, I'll work on it in a few weeks when the 4th semester is done and my summer holiday starts I guess.. :)

Do you have any experience with a JToolBar in combination with a JTabbedPane holding the applets?

Brandon

2013-05-22 12:29:48

From: J J
Great tutorial, Brandon. I've been rather busy the last few weeks and haven't worked on my own client for atleast 2 months. The last time I worked on it I was working on the layout and trying to get one JTabbedPane working. It did actually work.. sort of but when selecting something from my JMenuBar it would go under the JTabbedPane due to light weight and heavy weight components..

The ClientPool is really interesting because I wasn't sure how to implement the drawing functions properly when working with multiple tabs. I will need to rewrite a lot to properly be able to add new clients when the user wants more tabs. It's interesting though, I'll work on it in a few weeks when the 4th semester is done and my summer holiday starts I guess.. :)

Do you have any experience with a JToolBar in combination with a JTabbedPane holding the applets?



Yup. My own bot has a toolbar. Also the Smart I've been working on also has a toolbar with a good amount of buttons and functionality.

See here: http://villavu.com/forum/showthread.php?t=102735

J J

2013-05-24 11:15:52

From: Brandon
Yup. My own bot has a toolbar. Also the Smart I've been working on also has a toolbar with a good amount of buttons and functionality.

See here: http://villavu.com/forum/showthread.php?t=102735

Oh yeah, I'll check the source out, haven't done that yet.
I've been spending most of my time on two university projects for this semester.. actually so much that I have 18 hours on the four man group project where you needed to make about 10 :P So this weekend I will be spending some time on RuneScape related stuff again.

My goal is to make a multi tabbed loader that supportes different 'types' of RuneScape.

Have a welcome screen where you can select
RuneScape classic, RuneScape or RuneScape 2007 in a Combobox.
Then another combobox where you can select the language and that listens to the other choice. So if RuneScape is selected you can select an option, otherwise it is disabled.

Once you press on new tab it should show that same interface again and have support to load english, german clients in different tabs without problems..

Nice challenge. Will also need to make use of ClientPools

EDIT: Made a nice start today. Learned more about Tabs. I can now hit the + button to create a new tab with a custom panel attached. Tomorrow I'll work on the actual loading part for English RuneScape with a Client Pool.. :P


Brandon

2013-05-24 17:02:27

From: J J
Oh yeah, I'll check the source out, haven't done that yet.
I've been spending most of my time on two university projects for this semester.. actually so much that I have 18 hours on the four man group project where you needed to make about 10 :P So this weekend I will be spending some time on RuneScape related stuff again.

My goal is to make a multi tabbed loader that supportes different 'types' of RuneScape.

Have a welcome screen where you can select
RuneScape classic, RuneScape or RuneScape 2007 in a Combobox.
Then another combobox where you can select the language and that listens to the other choice. So if RuneScape is selected you can select an option, otherwise it is disabled.

Once you press on new tab it should show that same interface again and have support to load english, german clients in different tabs without problems..

Nice challenge. Will also need to make use of ClientPools

EDIT: Made a nice start today. Learned more about Tabs. I can now hit the + button to create a new tab with a custom panel attached. Tomorrow I'll work on the actual loading part for English RuneScape with a Client Pool.. :P



Looking nice! As soon as you finish your code and stuff, then you can pretty up the UI and stuff. I can see it now. That's a good start!

J J

2013-05-25 14:00:35

From: Brandon
Looking nice! As soon as you finish your code and stuff, then you can pretty up the UI and stuff. I can see it now. That's a good start!

I got the loading with tabs to work, except for replacing the Canvas. It just doesn't seem to work.
My .bat: java -Xmx1024m -Xbootclasspath/p:"Canvas.jar" -jar Loader.jar
I exported the Canvas as a normal .jar file.

The error:

C:\Users\JanJaap\Desktop>java -Xmx1024m -Xbootclasspath/p:"Canvas.jar" -jar Load
er.jar
Start button pressed!
Chosen version: NORMAL, chosen language: ENGLISH
Creating client
End of ClientApplet
Starting applet
Waiting for canvas @ StartPanel
adding client @ StartPanel
Adding client! [StartPanel]
Adding client applet
[Canvas] getGraphics()
Client == null: null
Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: org/obdur
o/loader/utils/ClientPool
at java.awt.Canvas.getGraphics(Canvas.java:47)
at sun.awt.RepaintArea.paint(Unknown Source)
at sun.awt.windows.WComponentPeer.handleEvent(Unknown Source)
at java.awt.Component.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.awt.EventQueue.access$200(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Sour
ce)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Sour
ce)
at java.awt.EventQueue$4.run(Unknown Source)
at java.awt.EventQueue$4.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Sour
ce)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)
[Canvas] getGraphics()
Client == null: null
Exception in thread "" java.lang.NoClassDefFoundError: org/obduro/loader/utils/C
lientPool
at java.awt.Canvas.getGraphics(Canvas.java:47)
at jo.i(jo.java:108)
at jn.run(jn.java:93)
at java.lang.Thread.run(Unknown Source)
[Canvas] getGraphics()
Client == null: null
Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: org/obdur
o/loader/utils/ClientPool
at java.awt.Canvas.getGraphics(Canvas.java:47)
at sun.awt.RepaintArea.paint(Unknown Source)
at sun.awt.windows.WComponentPeer.handleEvent(Unknown Source)
at java.awt.Component.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.awt.EventQueue.access$200(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Sour
ce)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Sour
ce)
at java.awt.EventQueue$4.run(Unknown Source)
at java.awt.EventQueue$4.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Sour
ce)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)[C
anvas] getGraphics()

Client == null: null at java.awt.EventDispatchThread.pumpEvents(Unknown Sourc
e)

at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)
Error: java.awt.Canvas.getGraphics:47 alc.y:35 ym.s:92 dl.t:89 ep.i:135 am.fx:18
76 acu.fe:1850 ng.fj:1843 da.j:564 r.i:240 client.ar:1377 nk.m:582 nk.o:558 nk.r
un:515 java.lang.Thread.run | java.lang.NoClassDefFoundError: org/obduro/loader/
utils/ClientPool | 0,0,0,0 0,0,0 0 0 2 1,1 0 0 0 0 0 0 911 15 0 MRBQmhx4tVpYeDb
wMzzCJcPuXzF5Yy3b
error_game_crash


If I use your arguments java -jar Loader.jar -Xbootclasspath/p:"Loader.jar it won't replace the canvas.

EDIT: Forgot a " in the args, now I have
java -jar Loader.jar -Xbootclasspath/p:"Loader.jar"
Still not replacing the Canvas though :(

And the Canvas class relies on ClientPool and some others so I assume you really have to xboot with yourself

EDIT 2: Yay, I got it working after doing some investigation… ;)

Correct arg:

java -Xbootclasspath/p:Loader.jar -jar Loader.jar


When you click on new tab you get this screen

Then when you click on start it loads. Multiple tabs do also work properly :)

Now to make it close properly by destructing the applet. Also need to override the JFrame closing that it destroys all applets.

EDIT 3:
Can you give more information about multi threading for tabs? I'm just using one thread right now. I have worked with multiple threads on Android but not with actual Java.

Brandon

2013-05-25 18:54:44

From: J J

EDIT 3:
Can you give more information about multi threading for tabs? I'm just using one thread right now. I have worked with multiple threads on Android but not with actual Java.



Glad you got the Canvas working. The reason for the error in the first place is because you exported it to another jar. If you export it, then you need to export all dependencies too.. aka client pool and all its dependencies and so on and so forth. Else, place the canvas in the same jar and x-boot with yourself like you discovered :D


For the threading what I did was I have two functions for adding a Tab to the JTabbedPane.

One just adds it which forces all other clients to wait until the applet initialized. The next function uses a thread but a synchronized one that synchronizes on JTabbedPane.

The reason is because JTabbedPane and most components can only process events ONE at a time. You can't shove a shit ton of events from multiple threads at the same time without giving it time to update the UI.


So to destroy an Applet/Tab so that you don't get stupid exceptions or crash or lag and lower CPU usage for each one destroyed, what I did was:

  • First Remove the Tab so users don't destroy the same tab twice if they're lagging or w/e.
  • Second Remove the Tab from the EventBlocker. This queue blocks events to the canvas so its only right we stop blocking from a canvas that's about to be destroyed.
  • Third, Remove the Applet from the JPanel (loader)
  • Stop and Destroy the applet.
  • Remove the Clients from the client pool.
  • System GC or just null the client/applet reference.

Thus:


public void addClient(Client client) { //Just in case we don't care about multiple clients/tabs..
this.removeSplashScreen();

synchronized (this.TabPane) {
this.addTab(TabPane, "Tab-ID: [" + this.getFreeTabID() + "]", client.getLoader()); //Add the LOADER to the Tab. You can add the client but loader is easier to handle. Its a JPanel.
this.TabPane.setSelectedIndex(this.TabPane.getTabCount() - 1);
this.updateUI(); //Update the TabUI quickly.

if (this.TabPane.getTabCount() == 1) { //If its our first time adding a client, meh.. center the window and pack.
this.pack();
this.setSize(this.getWidth(), this.getHeight()); //Because I removed the splashScreen so client needs resizing..
this.centerWindow();
}
}
}


public void addTabbedClient(final Client client) { //Used for multiple tabs where each tab has its own thread so as to not affect the other tabs :)
new Thread(new Runnable() {
@Override
public void run() {
Frame.this.addClient(client);

//Process other stuff such as Waiting on Listeners to register upon addition if need be..
//Only waits for listeners IF the Loader has "started" NOT added as that'd LOCK all threads..
}
}).start();
}


//Cleaning up is a bit tricky but I did it like this:

public void removeClient(final ClientApplet loader) { //Given a loader, remove we need to synchronize the JTabbedPane thread so as to not remove all clients at the EXACT same time. That will lock it up.
if (loader == null || loader.getApplet() == null || loader.getApplet().getComponentCount() < 1) {
return;
}

synchronized (this.TabPane) { //Lock only the JTabbedPane.
this.TabPane.remove(loader); //Remove the loader and unlock it so other threads can do the same. We remove the tab first because you don't want users destructing one tab TWICE!
}

try {
new Thread(new Runnable() { //If we are only closing One tab, we want to close it via a thread so as to not lag the rest of the clients while the UI updates itself.
@Override
public void run() {
Client client = null;
synchronized (clients) { //If you need to synchronize your clientpool so that multiple threads don't delete from it at the same time then do so otherwise ignore this line.
if (loader.getApplet() != null && loader.getApplet().getComponentCount() > 0) { //If our loader is not already destroyed and has a valid canvas (if it has a canvas, that means RS has loaded)
EventHandler.remove(loader.getApplet().getComponent(0)); //Remove Our ComponentBlocking.. Basically No point of blocking input on a canvas that's being destroyed.
client = clients.get(loader.getApplet().getComponent(0).getClass().getClassLoader().hashCode());
clients.remove(loader.getApplet().getComponent(0).getClass().getClassLoader().hashCode()); //Remove the client from the ClientPool.
}
}

loader.remove(loader.getApplet()); //This is the tricky part. It must be in order! First we need to remove the applet from the JPanel using this line.
loader.getApplet().destroy(); //Next we destroy the applet.
loader.destruct(); //This function calls applet.stop() followed by applet.destroy(). Then it sets applet to null and closes the ClassLoader.
Main.decreaseTabCount(); //Meh needed for Smart JNI.
if (client != null) {
client.destruct(); //Needed for Smart JNI which destroyed the MemoryMap File and cleans up the stupid SMART.PID files.
client = null; //Allow garbage collection to do its thing if need be. No references left :)
}
}
}).start();
} catch (Exception Ex) {
Main.StackTrace(Ex);
}
}

private void removeAllClients() { //YOU DO NOT WANT TO THREAD THIS! Why? Because your application can close before the thread has time to complete its job.
synchronized (this.TabPane) { //No need for this but just in case someone used it wrong and spawned two frames in the same client.
this.TabPane.removeAll(); //First Remove ALL TABS!.
}

synchronized (clients) {
for (Client client : clients.values()) { //Loop through all clients and destroy them.
if (client != null && client.getLoader() != null && client.getCanvas() != null) {
EventHandler.remove(client.getCanvas()); //Unblock input from that Tab.
//clients.remove(client.getCanvas().getClass().getClassLoader().hashCode());
client.getLoader().remove(client.getApplet()); //remove the applet from the JPanel.
client.getApplet().destroy(); //Destruct the applet.
client.destruct(); //destruct the loader.
Main.decreaseTabCount();
}
}
clients.clear(); //Clear our client pool all at once.
}
System.gc(); //Let garbage collection do its thing if need be rather than setting all null to invoke it.
}


//YOU DO NOT WANT TO THREAD THIS! Why? Because your application can close before the thread has time to complete its job.
public void destruct() { //Added this to Frame.onWindowClosing so that the frame first goes invisible then destructs the clients. It stops users from trying to destruct TWICE by double clicking the exit button..
Frame.this.setVisible(false);
Frame.this.removeAllClients();
Main.cleanUp();
System.exit(0);
}

bob_dole

2013-05-25 20:57:33

When you finish making the oldschool client with tabs could you post the finished product?

An oldschool client with tabs would be awesome. thanks

NKN

2013-05-25 22:07:27

@Brandon

XBootClassPath is to set your canvas as the superclass of theirs, right?

What if you got an instance of their canvas class, and edited the bytecode to redirect it to yours.

Would this eliminate the need for Xbootclasspath?

Brandon

2013-05-25 23:00:11

From: NKN
@Brandon

XBootClassPath is to set your canvas as the superclass of theirs, right?

What if you got an instance of their canvas class, and edited the bytecode to redirect it to yours.

Would this eliminate the need for Xbootclasspath?


This works as well. You can also tell if they are inheriting from your canvas class by setting yours to final. You will notice that it throws an error because they can't inherit from it. So yeah. Injecting your own canvas into the client is an alternative to X-booting.

NKN

2013-05-25 23:49:35

From: Brandon
This works as well. You can also tell if they are inheriting from your canvas class by setting yours to final. You will notice that it throws an error because they can't inherit from it. So yeah. Injecting your own canvas into the client is an alternative to X-booting.


Alright cool. Trying to get it so you can run the jar file, and not have to use a .bat file.

Shatterhand

2014-01-01 17:53:38

Thanks alot for this! :)

Can someone help me with OSRS parameters? I get this error:
java.lang.ClassNotFoundException: client

These are the strings I get with regex:
Archive = gamepack_2653253.jar
JarLocation = [url]http://oldschool1.runescape.com//gamepack_2653253.jar[/url]
Code = client

Brandon

2014-01-01 18:50:40

From: Shatterhand
Thanks alot for this! :)

Can someone help me with OSRS parameters? I get this error:
java.lang.ClassNotFoundException: client

These are the strings I get with regex:
Archive = gamepack_2653253.jar
JarLocation = [url]http://oldschool1.runescape.com//gamepack_2653253.jar[/url]
Code = client


:o forgot I even had a tutorial on this :S


The // in your url is preventing it from finding the right jar.

The jarLocation should be: http://oldschool1.runescape.com/gamepack_2653253.jar

Your applet will load after that. This basically means that you have a url ending in a slash. Remove the trailing slash and it'll be all good.


Oh and if you want some copy-pasta then the following should still work (hopefully):

ClientApplet.java

package PackageForNKN;

import java.applet.*;
import java.awt.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public final class ClientApplet extends JPanel implements AppletStub {

private static final long serialVersionUID = 5627929030422355843L;
private Applet applet = null;
private URLClassLoader ClassLoader = null;
private URL codeBase = null, documentBase = null;
private HashMap<String, String> parameters = new HashMap<>();
private static final Pattern codeRegex = Pattern.compile("code=(.*?) ");
private static final Pattern archiveRegex = Pattern.compile("archive=(.*?) ");
private static final Pattern parameterRegex = Pattern.compile("<param name=\"([^\\s]+)\"\\s+value=\"([^>]*)\">");

public ClientApplet(String root, String params, int Width, int Height) {
try {
this.setLayout(new BorderLayout(0, 0));
String pageSource = downloadPage(new URL(root + params), null);
Matcher codeMatcher = codeRegex.matcher(pageSource);
Matcher archiveMatcher = archiveRegex.matcher(pageSource);

if (codeMatcher.find() && archiveMatcher.find()) {
String archive = archiveMatcher.group(1);
String jarLocation = root + "/" + archive;
String code = codeMatcher.group(1).replaceAll(".class", "");
Matcher parameterMatcher = parameterRegex.matcher(pageSource);
this.codeBase = new URL(jarLocation);
this.documentBase = new URL(root);

while (parameterMatcher.find()) {
this.parameters.put(parameterMatcher.group(1), parameterMatcher.group(2));
}

this.ClassLoader = new URLClassLoader(new URL[]{new URL(jarLocation)});
this.applet = (Applet) ClassLoader.loadClass(code).newInstance();
this.applet.setStub(this);
this.applet.setPreferredSize(new Dimension(Width, Height));
this.add(this.applet, BorderLayout.CENTER);
}
} catch (MalformedURLException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(null, "Error Loading.. Please Check Your Internet Connection.", "Error Loading..", JOptionPane.ERROR_MESSAGE);
}
}

public ClassLoader getClassLoader() {
return this.ClassLoader;
}

public String postRequest(URL Address, String UserAgent, HashMap<String, String> data) {
StringBuilder parameterBuilder = new StringBuilder();
Iterator<Entry<String, String>> it = data.entrySet().iterator();
int i = 0;

while(it.hasNext() && i < data.entrySet().size() - 1) {
Entry<String, String> e = it.next();
parameterBuilder.append(e.getKey()).append("=").append(e.getValue()).append("&");
++i;
}
Entry<String, String> entry = it.next();
parameterBuilder.append(entry.getKey()).append("=").append(entry.getValue());

String parameterURL = parameterBuilder.toString();
if (parameterURL == null || parameterURL.trim().isEmpty()) {
return null;
}

try {
URLConnection Connection = Address.openConnection();
Connection.addRequestProperty("Protocol", "HTTP/1.1");
Connection.addRequestProperty("Connection", "keep-alive");
Connection.addRequestProperty("Keep-Alive", "300");

if (UserAgent != null) {
Connection.addRequestProperty("User-Agent", UserAgent);
} else {
Connection.addRequestProperty("User-Agent", "Mozilla/5.0 (" + System.getProperty("os.name") + " " + System.getProperty("os.version") + ") Java/" + System.getProperty("java.version"));
}

Connection.setDoOutput(true);


try (OutputStreamWriter writer = new OutputStreamWriter(Connection.getOutputStream())) {
writer.write(parameterURL);
writer.flush();
String Line;
StringBuilder Builder = new StringBuilder();
try (BufferedReader Reader = new BufferedReader(new InputStreamReader(Connection.getInputStream()))) {
while ((Line = Reader.readLine()) != null) {
Builder.append(Line).append("\n");
}
}
return Builder.toString();
}
} catch(Exception e) {
e.printStackTrace();
}
return null;
}

public String downloadPage(URL Address, String UserAgent) {
try {
URLConnection Connection = Address.openConnection();
Connection.addRequestProperty("Protocol", "HTTP/1.1");
Connection.addRequestProperty("Connection", "keep-alive");
Connection.addRequestProperty("Keep-Alive", "300");
if (UserAgent != null) {
Connection.addRequestProperty("User-Agent", UserAgent);
} else {
Connection.addRequestProperty("User-Agent", "Mozilla/5.0 (" + System.getProperty("os.name") + " " + System.getProperty("os.version") + ") Java/" + System.getProperty("java.version"));
}

String Line;
StringBuilder Builder = new StringBuilder();
try (BufferedReader Reader = new BufferedReader(new InputStreamReader(Connection.getInputStream()))) {
while ((Line = Reader.readLine()) != null) {
Builder.append(Line).append("\n");
}
}
return Builder.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

public Applet getApplet() {
return this.applet;
}

public void start() {
if (this.applet != null) {
this.applet.init();
}

if (this.applet != null) {
this.applet.start();
}
}

public void destruct() {
if (this.applet != null) {
this.remove(this.applet);
this.applet.stop();
this.applet.destroy();
this.applet = null;
}
}

@Override
public boolean isActive() {
return true;
}

@Override
public URL getDocumentBase() {
return this.documentBase;
}

@Override
public URL getCodeBase() {
return this.codeBase;
}

@Override
public String getParameter(String name) {
return this.parameters.get(name);
}

@Override
public AppletContext getAppletContext() {
return null;
}

@Override
public void appletResize(int width, int height) {
}
}

Main.java:

package PackageForNKN;

public class Main {
public static void main(String[] args) {
new ClientApplet("http://oldschool1.runescape.com", "", 765, 503);
}
}

Shatterhand

2014-01-02 07:04:47

From: Brandon
:o forgot I even had a tutorial on this :S


The // in your url is preventing it from finding the right jar.

The jarLocation should be: http://oldschool1.runescape.com/gamepack_2653253.jar

Your applet will load after that. This basically means that you have a url ending in a slash. Remove the trailing slash and it'll be all good.


Oh and if you want some copy-pasta then the following should still work (hopefully):

ClientApplet.java

package PackageForNKN;

import java.applet.*;
import java.awt.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public final class ClientApplet extends JPanel implements AppletStub {

private static final long serialVersionUID = 5627929030422355843L;
private Applet applet = null;
private URLClassLoader ClassLoader = null;
private URL codeBase = null, documentBase = null;
private HashMap<String, String> parameters = new HashMap<>();
private static final Pattern codeRegex = Pattern.compile("code=(.*?) ");
private static final Pattern archiveRegex = Pattern.compile("archive=(.*?) ");
private static final Pattern parameterRegex = Pattern.compile("<param name=\"([^\\s]+)\"\\s+value=\"([^>]*)\">");

public ClientApplet(String root, String params, int Width, int Height) {
try {
this.setLayout(new BorderLayout(0, 0));
String pageSource = downloadPage(new URL(root + params), null);
Matcher codeMatcher = codeRegex.matcher(pageSource);
Matcher archiveMatcher = archiveRegex.matcher(pageSource);

if (codeMatcher.find() && archiveMatcher.find()) {
String archive = archiveMatcher.group(1);
String jarLocation = root + "/" + archive;
String code = codeMatcher.group(1).replaceAll(".class", "");
Matcher parameterMatcher = parameterRegex.matcher(pageSource);
this.codeBase = new URL(jarLocation);
this.documentBase = new URL(root);

while (parameterMatcher.find()) {
this.parameters.put(parameterMatcher.group(1), parameterMatcher.group(2));
}

this.ClassLoader = new URLClassLoader(new URL[]{new URL(jarLocation)});
this.applet = (Applet) ClassLoader.loadClass(code).newInstance();
this.applet.setStub(this);
this.applet.setPreferredSize(new Dimension(Width, Height));
this.add(this.applet, BorderLayout.CENTER);
}
} catch (MalformedURLException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(null, "Error Loading.. Please Check Your Internet Connection.", "Error Loading..", JOptionPane.ERROR_MESSAGE);
}
}

public ClassLoader getClassLoader() {
return this.ClassLoader;
}

public String postRequest(URL Address, String UserAgent, HashMap<String, String> data) {
StringBuilder parameterBuilder = new StringBuilder();
Iterator<Entry<String, String>> it = data.entrySet().iterator();
int i = 0;

while(it.hasNext() && i < data.entrySet().size() - 1) {
Entry<String, String> e = it.next();
parameterBuilder.append(e.getKey()).append("=").append(e.getValue()).append("&");
++i;
}
Entry<String, String> entry = it.next();
parameterBuilder.append(entry.getKey()).append("=").append(entry.getValue());

String parameterURL = parameterBuilder.toString();
if (parameterURL == null || parameterURL.trim().isEmpty()) {
return null;
}

try {
URLConnection Connection = Address.openConnection();
Connection.addRequestProperty("Protocol", "HTTP/1.1");
Connection.addRequestProperty("Connection", "keep-alive");
Connection.addRequestProperty("Keep-Alive", "300");

if (UserAgent != null) {
Connection.addRequestProperty("User-Agent", UserAgent);
} else {
Connection.addRequestProperty("User-Agent", "Mozilla/5.0 (" + System.getProperty("os.name") + " " + System.getProperty("os.version") + ") Java/" + System.getProperty("java.version"));
}

Connection.setDoOutput(true);


try (OutputStreamWriter writer = new OutputStreamWriter(Connection.getOutputStream())) {
writer.write(parameterURL);
writer.flush();
String Line;
StringBuilder Builder = new StringBuilder();
try (BufferedReader Reader = new BufferedReader(new InputStreamReader(Connection.getInputStream()))) {
while ((Line = Reader.readLine()) != null) {
Builder.append(Line).append("\n");
}
}
return Builder.toString();
}
} catch(Exception e) {
e.printStackTrace();
}
return null;
}

public String downloadPage(URL Address, String UserAgent) {
try {
URLConnection Connection = Address.openConnection();
Connection.addRequestProperty("Protocol", "HTTP/1.1");
Connection.addRequestProperty("Connection", "keep-alive");
Connection.addRequestProperty("Keep-Alive", "300");
if (UserAgent != null) {
Connection.addRequestProperty("User-Agent", UserAgent);
} else {
Connection.addRequestProperty("User-Agent", "Mozilla/5.0 (" + System.getProperty("os.name") + " " + System.getProperty("os.version") + ") Java/" + System.getProperty("java.version"));
}

String Line;
StringBuilder Builder = new StringBuilder();
try (BufferedReader Reader = new BufferedReader(new InputStreamReader(Connection.getInputStream()))) {
while ((Line = Reader.readLine()) != null) {
Builder.append(Line).append("\n");
}
}
return Builder.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

public Applet getApplet() {
return this.applet;
}

public void start() {
if (this.applet != null) {
this.applet.init();
}

if (this.applet != null) {
this.applet.start();
}
}

public void destruct() {
if (this.applet != null) {
this.remove(this.applet);
this.applet.stop();
this.applet.destroy();
this.applet = null;
}
}

@Override
public boolean isActive() {
return true;
}

@Override
public URL getDocumentBase() {
return this.documentBase;
}

@Override
public URL getCodeBase() {
return this.codeBase;
}

@Override
public String getParameter(String name) {
return this.parameters.get(name);
}

@Override
public AppletContext getAppletContext() {
return null;
}

@Override
public void appletResize(int width, int height) {
}
}

Main.java:

package PackageForNKN;

public class Main {
public static void main(String[] args) {
new ClientApplet("http://oldschool1.runescape.com", "", 765, 503);
}
}

You were right about the double "//", but you pasted wrong code here. :)

Changed this line and it works now. Thanks!
String jarLocation = root + archive;

vwxz

2014-01-02 08:53:00

Thanks for posting this! I was really interested in this idea for another game.

How would you modify these instructions for a game such as Yohoho Puzzle Pirates? YPP is a pure-Java game. Its loader is a signed Java applet which then launches a large JFrame with (what appear to be, from the appearance of the scrollbars etc.) lots of Swing components inside comprising the main game GUI. How would you obtain a reference to the JFrame owned by the applet? What about its children?

Also, could you explain how to send fake input to the game, please?

footballjds

2014-01-02 09:49:36

THANK YOU BRANDON! I'm trying to write my own JAVA Color Bot

Brandon

2014-01-02 11:30:55

From: vwxz
Thanks for posting this! I was really interested in this idea for another game.

How would you modify these instructions for a game such as Yohoho Puzzle Pirates? YPP is a pure-Java game. Its loader is a signed Java applet which then launches a large JFrame with (what appear to be, from the appearance of the scrollbars etc.) lots of Swing components inside comprising the main game GUI. How would you obtain a reference to the JFrame owned by the applet? What about its children?

Also, could you explain how to send fake input to the game, please?


Oh geez.. this game is extremely hard to hook. Trust me I tried hooking their OpenGL code through a wrapper, its impossible. You can only inject into that game and even then, it's hard too because they link to their libraries statically.. That means it's already linked as soon as the process even starts. I had to create the process in suspended mode, re-write their stupid tables and then resume the thread.. None of which was done in Java.

MAYBE using a loader would be different and far easier.

As for your other questions, the information to load an applet is in the OP. You can create a JFrame and then place the applet within the frame. If the game uses a canvas then all you do is make it use yours as shown in the OP as well. This would allow you to get colours from it.

If it uses a Component instead of a canvas, then you'll either have to replace that component with your own, or grab the colours from the Graphics2D.



Puzzle pirates loads its own JFrame though. All its data is stored in /users/appdata/puzzlepirates. Something like that.
If using the steam client, it uses OpenGL otherwise it uses Graphics2D and can be used through Simba. I never tried making a loader for it.

Shatterhand

2014-01-02 13:16:04

How do you set up Keylistener or Keybindings for the applet?

Keylistener doesnt work at all. When I add it to the applet, nothing happens when I fire an event. When I add it the the Frame, nothing happens when the focus is on the applet, even thought after setFocusable(false) on the applet or the ClientApplet.

Keybindings only works with WHEN_IN_FOCUSED_WINDOW and when focus is on another JComponent. I tried playing with the focus (disabling, enabling, requesting) but I cant find the solution.

NKN

2014-01-02 13:45:19

From: Shatterhand
How do you set up Keylistener or Keybindings for the applet?

Keylistener doesnt work at all. When I add it to the applet, nothing happens when I fire an event. When I add it the the Frame, nothing happens when the focus is on the applet, even thought after setFocusable(false) on the applet or the ClientApplet.

Keybindings only works with WHEN_IN_FOCUSED_WINDOW and when focus is on another JComponent. I tried playing with the focus (disabling, enabling, requesting) but I cant find the solution.


I think you'll have to override the applets default keylisteners.
This is what I did for PulseBot

package org.pulsebot.injection.input;

import org.pulsebot.injection.generic.RSClient;

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Arrays;

/**
* Created with IntelliJ IDEA.
* User: NKN
* Date: 6/18/13
* Time: 9:28 PM
*/
public class PulseKeyListeners implements KeyListener {
private ArrayList<KeyListener> keyListeners = null;
private Canvas canvas = null;
private RSClient rsClient = null;
private Boolean InputEnabled = true;

public PulseKeyListeners(Canvas canvas, RSClient rsClient) {
this.construct(canvas, rsClient);
}

public void construct(Canvas canvas, RSClient client) {
this.canvas = canvas;
this.rsClient = rsClient;
this.keyListeners = new ArrayList<>();
this.keyListeners.addAll(Arrays.asList(canvas.getKeyListeners()));
removeAllListeners();
}

public void updateRSClientCanvas(Canvas canvas, RSClient RSClient) {
this.restoreAllListeners();
this.construct(canvas, RSClient);
}

private void removeAllListeners() {
for (KeyListener Listener : canvas.getKeyListeners()) {
canvas.removeKeyListener(Listener);
}
canvas.addKeyListener(this);
}

private void restoreAllListeners() {
canvas.removeKeyListener(this);

for (KeyListener Listener : keyListeners) {
canvas.addKeyListener(Listener);
}

}

@Override
public void keyTyped(KeyEvent e) {
if (e.getSource() == rsClient.getApplet()) {
e.setSource(canvas);
for (KeyListener Listener : keyListeners) {
Listener.keyTyped(e);
}
} else if (InputEnabled) {
for (KeyListener Listener : keyListeners) {
Listener.keyTyped(e);
}
}
}

@Override
public void keyPressed(KeyEvent e) {
if (e.getSource() == rsClient.getApplet()) {
e.setSource(canvas);

for (KeyListener Listener : keyListeners) {
Listener.keyPressed(e);
}
} else if (InputEnabled) {
for (KeyListener Listener : keyListeners) {
Listener.keyPressed(e);
}
}
}

@Override
public void keyReleased(KeyEvent e) {
if (e.getSource() == rsClient.getApplet()) {
e.setSource(canvas);

for (KeyListener Listener : keyListeners) {
Listener.keyReleased(e);
}
} else if (InputEnabled) {
for (KeyListener Listener : keyListeners) {
Listener.keyReleased(e);
}
}
}
}

Brandon

2014-01-02 14:33:18

From: Shatterhand
How do you set up Keylistener or Keybindings for the applet?

Keylistener doesnt work at all. When I add it to the applet, nothing happens when I fire an event. When I add it the the Frame, nothing happens when the focus is on the applet, even thought after setFocusable(false) on the applet or the ClientApplet.

Keybindings only works with WHEN_IN_FOCUSED_WINDOW and when focus is on another JComponent. I tried playing with the focus (disabling, enabling, requesting) but I cant find the solution.



This tutorial only went so far as creating a GUI and loading the applet. It did not go into creating an entire bot with colour grabbing and event listening.. I say @NKN; should write that tutorial.

Also, why not look at Smart's source? Fairly easy to follow IMO. Or if Pulsebot is opensource, can always take a quick look at that.

What @NKN; said is what you need to do. I did however, upload the source of an old client I made last year, this morning:

KeyListener:
https://github.com/Brandon-T/Acid-Loader/blob/master/Acid/src/acid/events/AcidKeyListener.java

All listeners:
https://github.com/Brandon-T/Acid-Loader/tree/master/Acid/src/acid/events

kazzero

2014-01-12 17:18:29

thanks, found this rather useful.

lii

2014-01-13 15:17:12

Hey guys I know this code is specifically directed to Runescape, but could it be modified for other games, say Yahoo! Puzzle Pirates or would some basic aspects of the above need to be changed?

Kevin

2014-01-13 15:35:18

From: lii
Hey guys I know this code is specifically directed to Runescape, but could it be modified for other games, say Yahoo! Puzzle Pirates or would some basic aspects of the above need to be changed?


Well, it should work for anything with an applet with some basic aspects (like location of applet) being changed. Also keep in mind this just lets you draw any other app within your own custom frame - it does not cover other details such as event listeners/color grabbing.

lii

2014-01-13 22:20:55

oh hmm and how about a Java client game not an applet? From reading thru the program again it seems to only work with applets, will the same concept work with client games or not? If not is there a tutorial for building a reflection client for a java client?

StickToTheScript

2014-01-14 13:06:16

Looks great Brandon! I am going to give this a shot! Look forward to it! Thanks!

Shatterhand

2014-06-24 13:26:45

Started working on my client again. Managed to make keylisteners work (screenshot button, replaced F keys).

Now im trying to add reflection. I decided to get the player's name first as an example.

I looked into the simba hooks and reflection functions. (http://osrreflection.googlecode.com/git/Core/Hooks.simba).

function R_GetPlayerName: String;
var
_Player, _PlayerName: Integer;
begin
_Player := SmartGetFieldObject(SmartCurrentTarget, 0, client_player);
_PlayerName := SmartGetFieldObject(SmartCurrentTarget, _Player, Player_name);
Result := R_GetJavaString(_PlayerName, 512);
SmartFreeObject(SmartCurrentTarget, _PlayerName);
SmartFreeObject(SmartCurrentTarget, _Player);
end;
First it gets an object "client_player" (z.hs). Then it gets the object's field "Player_name" (b). So the full path is z.hs.b.
I checked it with decompiler too:

So first I tried to get z.hs with reflection. Failed. (java.lang.NoSuchFieldException: z.hs).

Class cls = client.getApplet().getClass();

Field f = cls.getDeclaredField("z.hs");

So then I tried to find z first, then find its field hs. Failed.
Class cls = client.getApplet().getClass();

Field f = cls.getDeclaredField("z");
f.setAccessible(true);
Object o = f.get(cls);

for (Field ff : o.getClass().getDeclaredFields()){
ff.setAccessible(true);
System.out.println("DeclaredField: " + ff.getName());

}
z's declared fields:
DeclaredField: MIN_VALUE
DeclaredField: MAX_VALUE
DeclaredField: TYPE
DeclaredField: digits
DeclaredField: DigitTens
DeclaredField: DigitOnes
DeclaredField: sizeTable
DeclaredField: value
DeclaredField: SIZE
DeclaredField: serialVersionUID
DeclaredField: $assertionsDisabled

I am clueless now, can somebody help me please? :)

Brandon

2014-06-24 23:39:42

From: Shatterhand

I am clueless now, can somebody help me please? :)




ClassLoader loader = applet.getClassLoader();
Field player = loader.loadClass("z").getDeclaredField("hs");
player.setAccessible(true);

Field name = loader.loadClass("x").getDeclaredField("b");
name.setAccessible(true);

System.out.println((String)name.get(player.get(null)));

Shatterhand

2014-06-25 03:42:11

From: Brandon

ClassLoader loader = applet.getClassLoader();
Field player = loader.loadClass("z").getDeclaredField("hs");
player.setAccessible(true);

Field name = loader.loadClass("x").getDeclaredField("b");
name.setAccessible(true);

System.out.println((String)name.get(player.get(null)));

That makes sense now, thank you! :)

computerichy

2014-06-30 18:26:29

From: NKN
Alright cool. Trying to get it so you can run the jar file, and not have to use a .bat file.

Hey, so you're not the only one attempting this. Did you ever come to finding a solution to overriding the canvas? I would be grateful if you lead me in the right direction to make this possible, thanks.

NKN

2014-06-30 18:41:24

From: computerichy
Hey, so you're not the only one attempting this. Did you ever come to finding a solution to overriding the canvas? I would be grateful if you lead me in the right direction to make this possible, thanks.


Well I did it via injection.

Analyzed the class through bytecode then changed the super of it to my custom canvas, instead of Java's canvas.

Brandon

2014-06-30 20:07:43

From: computerichy
Hey, so you're not the only one attempting this. Did you ever come to finding a solution to overriding the canvas? I would be grateful if you lead me in the right direction to make this possible, thanks.



In my colour bot, I use this.. Add it to your project and make this the main class of your jar file (in the manifest). After doing that, change the one line below to the "Main" class that loads your bot.. The key to it is to have two mains. The first main will create a JVM instance that runs the second main. The first main will then die off after launching the second one unless you keep it communicating with the second one (in this case, there is no need to do that).

This is the first main (similar to the one I actually use.. it will work):

package eos;

import java.io.IOException;
import java.net.URLDecoder;

/**
* Created by Brandon on 2014-06-28.
*/
public class Bootloader {
private static final String BOOT_FLAGS = "-Xbootclasspath/p:";


public static void main(String[] args) throws IOException {

String path = Bootloader.class.getProtectionDomain().getCodeSource().getLocation().getPath();
path = URLDecoder.decode(path, "UTF-8").replaceAll("\\\\", "/");

String mainName = Main.class.getCanonicalName(); //change this "Main" to your actual main class.

StringBuilder commands = new StringBuilder();
commands.append("javaw ").append(BOOT_FLAGS);
commands.append('"').append("MyBot.jar").append('"'); //jar file containing your custom canvas..
commands.append(" -cp ").append('"').append("MyBot.jar").append('"').append(' ').append(mainName);

Runtime.getRuntime().exec(commands.toString());
}
}

Shatterhand

2014-07-02 18:37:30

From: Brandon
In my colour bot, I use this.. Add it to your project and make this the main class of your jar file (in the manifest). After doing that, change the one line below to the "Main" class that loads your bot.. The key to it is to have two mains. The first main will create a JVM instance that runs the second main. The first main will then die off after launching the second one unless you keep it communicating with the second one (in this case, there is no need to do that).

This is the first main (similar to the one I actually use.. it will work):

package eos;

import java.io.IOException;
import java.net.URLDecoder;

/**
* Created by Brandon on 2014-06-28.
*/
public class Bootloader {
private static final String BOOT_FLAGS = "-Xbootclasspath/p:";


public static void main(String[] args) throws IOException {

String path = Bootloader.class.getProtectionDomain().getCodeSource().getLocation().getPath();
path = URLDecoder.decode(path, "UTF-8").replaceAll("\\\\", "/");

String mainName = Main.class.getCanonicalName(); //change this "Main" to your actual main class.

StringBuilder commands = new StringBuilder();
commands.append("javaw ").append(BOOT_FLAGS);
commands.append('"').append("MyBot.jar").append('"'); //jar file containing your custom canvas..
commands.append(" -cp ").append('"').append("MyBot.jar").append('"').append(' ').append(mainName);

Runtime.getRuntime().exec(commands.toString());
}
}

This is exactly what I did too. Tricky. :D

EDIT: The only problem with this so far is that I need to build it every time I want to run again.

computerichy

2014-07-03 12:11:18

From: Brandon
In my colour bot, I use this.. Add it to your project and make this the main class of your jar file (in the manifest). After doing that, change the one line below to the "Main" class that loads your bot.. The key to it is to have two mains. The first main will create a JVM instance that runs the second main. The first main will then die off after launching the second one unless you keep it communicating with the second one (in this case, there is no need to do that).

This is the first main (similar to the one I actually use.. it will work):

package eos;

import java.io.IOException;
import java.net.URLDecoder;

/**
* Created by Brandon on 2014-06-28.
*/
public class Bootloader {
private static final String BOOT_FLAGS = "-Xbootclasspath/p:";


public static void main(String[] args) throws IOException {

String path = Bootloader.class.getProtectionDomain().getCodeSource().getLocation().getPath();
path = URLDecoder.decode(path, "UTF-8").replaceAll("\\\\", "/");

String mainName = Main.class.getCanonicalName(); //change this "Main" to your actual main class.

StringBuilder commands = new StringBuilder();
commands.append("javaw ").append(BOOT_FLAGS);
commands.append('"').append("MyBot.jar").append('"'); //jar file containing your custom canvas..
commands.append(" -cp ").append('"').append("MyBot.jar").append('"').append(' ').append(mainName);

Runtime.getRuntime().exec(commands.toString());
}
}



Thank you very much for your help Brandon and NKN, this is the concept I had to go about automatically passing arguments in runtime.
I originally thought I would have to create an external jar bootloader which executed the interface with the arguments, but this makes it a whole lot better :P

EDIT: With this bootloader intact, am I also able to load runtime libraries from a relative directory, e.g. '/lib' without causing conflicts?

Brandon

2014-07-03 13:16:32

From: computerichy
Thank you very much for your help Brandon and NKN, this is the concept I had to go about automatically passing arguments in runtime.
I originally thought I would have to create an external jar bootloader which executed the interface with the arguments, but this makes it a whole lot better :P

EDIT: With this bootloader intact, am I also able to load runtime libraries from a relative directory, e.g. '/lib' without causing conflicts?


It does not affect your jar. All it does is run the jar with the XBoot arguments as if you were to do it yourself via command line or batch. Your jar has to do the rest.


Think of it like this:

*User double clicks jar*
Bootloader runs (JVM1 starts).
*Bootloader creates a process/jvm instance and runs jar->Main (JVM2 starts)*
jar->Main runs and shows the gui, loads the applet, does everything as it normally would
*Bootloader stops running (JVM1 dies)*
jar->Main continues to run as it normally would (JVM2 continues).

All in all, you do everything like you normally would. Load your relative libraries, etc.. etc..


Full example:


Bootloader.java (DO NOT load your /lib files here.. Load them in any other file except this one):

package eos;

import java.io.IOException;
import java.net.URLDecoder;

/**
* Created by Brandon on 2014-06-28.
*/
public class Bootloader {
private static final String BOOT_FLAGS = "-Xbootclasspath/p:";


public static void main(String[] args) throws IOException {

String path = Bootloader.class.getProtectionDomain().getCodeSource().getLocation().getPath();
path = URLDecoder.decode(path, "UTF-8").replaceAll("\\\\", "/");

String mainName = Main.class.getCanonicalName(); //change this "Main" to your actual main class.

StringBuilder commands = new StringBuilder();
commands.append("javaw ").append(BOOT_FLAGS);
commands.append('"').append("MyBot.jar").append('"'); //jar file containing your custom canvas..
commands.append(" -cp ").append('"').append("MyBot.jar").append('"').append(' ').append(mainName);

Runtime.getRuntime().exec(commands.toString());
}
}

Main.java

package eos;

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ResourceBundle;

public class Main {

void init(String world, int width, int height) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("/Resources/Eos.fxml"), ResourceBundle.getBundle("Resources.Main"));

JFrame frame = new JFrame("Eos");
JFXPanel panel = new JFXPanel();
panel.setScene(new Scene(root, 0, 0));

root.getChildrenUnmodifiable().stream().filter(n -> n.getId().equals("toolBar")).forEach(n -> {
new ClientApplet(frame, world, width, height, n); //add your applet to a frame. JFrame or SwingNode.. Either one..
});
}

public static void main(String[] args) {
new JFXPanel();
Platform.runLater(() -> {
try {
new Main().init("http://runescape.com", 800, 600);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}


Then in the manifest it'd look like:


Manifest-Version: 1.0
Main-Class: eos.Bootloader
Boot-Class-Path: eos

Notice that the manifest's loader is set to "BootLoader". This way, when the user double clicks the jar, bootloader is ran FIRST. THEN main is ran. Also notice that if you DO NOT want to use the BootLoader class then you can set "Boot-Class-Path" in the manifest itself. I haven't tested BootClassPath in manifest yet but it should work too. Currently I just use the BootLoader class.

computerichy

2014-07-03 13:31:32

From: Brandon
It does not affect your jar. All it does is run the jar with the XBoot arguments as if you were to do it yourself via command line or batch. Your jar has to do the rest.


Think of it like this:

*User double clicks jar*
Bootloader runs (JVM1 starts).
*Bootloader creates a process/jvm instance and runs jar->Main (JVM2 starts)*
jar->Main runs and shows the gui, loads the applet, does everything as it normally would
*Bootloader stops running (JVM1 dies)*
jar->Main continues to run as it normally would (JVM2 continues).

All in all, you do everything like you normally would. Load your relative libraries, etc.. etc..


Full example:


Bootloader.java:

package eos;

import java.io.IOException;
import java.net.URLDecoder;

/**
* Created by Brandon on 2014-06-28.
*/
public class Bootloader {
private static final String BOOT_FLAGS = "-Xbootclasspath/p:";


public static void main(String[] args) throws IOException {

String path = Bootloader.class.getProtectionDomain().getCodeSource().getLocation().getPath();
path = URLDecoder.decode(path, "UTF-8").replaceAll("\\\\", "/");

String mainName = Main.class.getCanonicalName(); //change this "Main" to your actual main class.

StringBuilder commands = new StringBuilder();
commands.append("javaw ").append(BOOT_FLAGS);
commands.append('"').append("MyBot.jar").append('"'); //jar file containing your custom canvas..
commands.append(" -cp ").append('"').append("MyBot.jar").append('"').append(' ').append(mainName);

Runtime.getRuntime().exec(commands.toString());
}
}

Main.java

package eos;

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ResourceBundle;

public class Main {

void init(String world, int width, int height) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("/Resources/Eos.fxml"), ResourceBundle.getBundle("Resources.Main"));

JFrame frame = new JFrame("Eos");
JFXPanel panel = new JFXPanel();
panel.setLayout(new BorderLayout(0, 0));
panel.setScene(new Scene(root, 0, 0));
frame.add(panel, BorderLayout.NORTH);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setResizable(false);

root.getChildrenUnmodifiable().stream().filter(n -> n.getId().equals("toolBar")).forEach(n -> {
int menuWidth = (int)n.getBoundsInParent().getMinX() + (int)n.getBoundsInParent().getWidth();
int menuHeight = (int)n.getBoundsInParent().getMinY() + (int)n.getBoundsInParent().getHeight();

ClientApplet applet = new ClientApplet(world, width, height);
applet.initializeListeners();

panel.setPreferredSize(new Dimension(menuWidth, menuHeight));
frame.add(applet, BorderLayout.CENTER);

applet.start();
frame.revalidate();
frame.pack();

frame.setMinimumSize(frame.getSize());
frame.setVisible(true);
frame.setResizable(true);

frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
frame.setVisible(false);
applet.stop();
}
});
});
}

public static void main(String[] args) {
new JFXPanel();
Platform.runLater(() -> {
try {
new Main().init("http://runescape.com", 800, 600);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}


Then in the manifest it'd look like:


Manifest-Version: 1.0
Main-Class: eos.Bootloader
Boot-Class-Path: eos

Notice that the manifest's loader is set to "BootLoader". This way, when the user double clicks the jar, bootloader is ran FIRST. THEN main is ran. Also notice that if you DO NOT want to use the BootLoader class then you can set "Boot-Class-Path" in the manifest itself. I haven't tested BootClassPath in manifest yet but it should work too. Currently I just use the BootLoader class.


Alright, that just about clarifies everything I need to finish the core of the client. Thanks to your support, this is the progress I've made so far:

Next up, the plugin – and API – system for the panel to the right.

computerichy

2014-07-07 17:24:45

From: Brandon
In my colour bot, I use this.. Add it to your project and make this the main class of your jar file (in the manifest). After doing that, change the one line below to the "Main" class that loads your bot.. The key to it is to have two mains. The first main will create a JVM instance that runs the second main. The first main will then die off after launching the second one unless you keep it communicating with the second one (in this case, there is no need to do that).

This is the first main (similar to the one I actually use.. it will work):

package eos;

import java.io.IOException;
import java.net.URLDecoder;

/**
* Created by Brandon on 2014-06-28.
*/
public class Bootloader {
private static final String BOOT_FLAGS = "-Xbootclasspath/p:";


public static void main(String[] args) throws IOException {

String path = Bootloader.class.getProtectionDomain().getCodeSource().getLocation().getPath();
path = URLDecoder.decode(path, "UTF-8").replaceAll("\\\\", "/");

String mainName = Main.class.getCanonicalName(); //change this "Main" to your actual main class.

StringBuilder commands = new StringBuilder();
commands.append("javaw ").append(BOOT_FLAGS);
commands.append('"').append("MyBot.jar").append('"'); //jar file containing your custom canvas..
commands.append(" -cp ").append('"').append("MyBot.jar").append('"').append(' ').append(mainName);

Runtime.getRuntime().exec(commands.toString());
}
}


Hmm, I've just discovered that this violates the Java's Runtime Environment binary code license; do you know of any alternative solutions to gaining the same effect of the overridden canvas?

PS: It's not a bot that I intend to create, instead I am creating a legit, useful client :p

NKN

2014-07-07 18:52:30

From: computerichy
Hmm, I've just discovered that this violates the Java's Runtime Environment binary code license; do you know of any alternative solutions to gaining the same effect of the overridden canvas?

PS: It's not a bot that I intend to create, instead I am creating a legit, useful client :p


Not sure if this breaks it, but here we go.



This is the class we replace, you can use your own or w/e.
package org.pulsebot.injection.generic;

import org.pulsebot.loader.Client;
import org.pulsebot.loader.utils.ClientPool;

import javax.accessibility.AccessibleRole;
import java.awt.*;
import java.awt.image.BufferStrategy;
import java.awt.peer.CanvasPeer;

/**
* @author SunJava
* @author Modified by: JJ
*/
public class RSCanvas extends Canvas {

private static final String base = "canvas";
private static int nameCounter = 0;
private static final long serialVersionUID = -2284879212465893870L;

private Client client = null;

public RSCanvas() {
super();
System.out.println("[Canvas] constructor called!");
}

public RSCanvas(GraphicsConfiguration config) {
this();
setGraphicsConfiguration(config);
}

void setGraphicsConfiguration(GraphicsConfiguration gc) {
synchronized (getTreeLock()) {
CanvasPeer peer = (CanvasPeer) getPeer();
if (peer != null) {
gc = peer.getAppropriateGraphicsConfiguration(gc);
}
setGraphicsConfiguration(gc);
}
}

@Override
public Graphics getGraphics() {
if (client == null) {
client = ClientPool.getClient(this);
}

if (client != null) {
return client.drawGraphics((Graphics2D) super.getGraphics());
}

return super.getGraphics();
}

@Override
public void setVisible(boolean b) {
// TODO: Modify
super.setVisible(b);
}

String constructComponentName() {
synchronized (Canvas.class) {
return base + nameCounter++;
}
}

boolean postsOldMouseEvents() {
return true;
}

public void createBufferStrategy(int numBuffers) {
super.createBufferStrategy(numBuffers);
}

public void createBufferStrategy(int numBuffers, BufferCapabilities caps) throws AWTException {
super.createBufferStrategy(numBuffers, caps);
}

public BufferStrategy getBufferStrategy() {
return super.getBufferStrategy();
}


protected class AccessibleAWTCanvas extends AccessibleAWTComponent {
private static final long serialVersionUID = -6325592262103146699L;

/**
* Get the role of this object.
*
* @return an instance of AccessibleRole describing the role of the
* object
* @see AccessibleRole
*/
public AccessibleRole getAccessibleRole() {
return AccessibleRole.CANVAS;
}

}
}



This is what I wrote to detect the class and send the modification command.
package org.pulsebot.injection.impl;

import org.objectweb.asm.tree.ClassNode;
import org.pulsebot.injection.generic.AbstractAnalyser;
import org.pulsebot.injection.utils.GenericUtils;

/**
* Created with IntelliJ IDEA.
* User: NKN
* Date: 6/2/13
* Time: 12:40 PM
*/
public class CanvasAnalyser extends AbstractAnalyser {

@Override
protected boolean canRun(ClassNode node) {
if (node.superName.contains("Canvas"))
return true;
return false;
}

@Override
protected String analyse(ClassNode node) {
System.out.println("Canvas is: " + node.superName);
GenericUtils.setSuper(node, "org/pulsebot/injection/generic/RSCanvas"); //Path to your canvas.
System.out.println("Canvas is now: " + node.superName);
System.out.println("Canvas successfully hacked.");
return "Canvas";
}

}

This is just what the prior class extends.

package org.pulsebot.injection.generic;

import org.objectweb.asm.tree.ClassNode;

/**
* Created with IntelliJ IDEA.
* User: Chris
* Date: 3/1/13
* Time: 11:28 PM
* To change this template use File | Settings | File Templates.
*/
public abstract class AbstractAnalyser {

public String run(ClassNode node) {
if (this.canRun(node))
return this.analyse(node);
return null;
}

protected abstract boolean canRun(ClassNode node);

protected abstract String analyse(ClassNode node);
}


And this is the method that actually renames it.
    public static void setSuper(ClassNode node, String superClass) {
String replacedSuper = "";
if (node.superName != "")
replacedSuper = node.superName;
if (replacedSuper != "") {
ListIterator<?> mli = node.methods.listIterator();
while (mli.hasNext()) {
MethodNode mn = (MethodNode) mli.next();
ListIterator<?> ili = mn.instructions.iterator();
while (ili.hasNext()) {
AbstractInsnNode ain = (AbstractInsnNode) ili.next();
if (ain.getOpcode() == Opcodes.INVOKESPECIAL) {
MethodInsnNode min = (MethodInsnNode) ain;
if (min.owner.equals(replacedSuper)) {
min.owner = superClass;
}
}
}
}
}
node.superName = superClass;
}



So that's the injection way. Probably could be done better, but ah well.
http://asm.ow2.org/download/index.html <— Bytecode library, hook it up with your project

computerichy

2014-07-08 15:43:51

From: NKN
Not sure if this breaks it, but here we go.



This is the class we replace, you can use your own or w/e.
package org.pulsebot.injection.generic;

import org.pulsebot.loader.Client;
import org.pulsebot.loader.utils.ClientPool;

import javax.accessibility.AccessibleRole;
import java.awt.*;
import java.awt.image.BufferStrategy;
import java.awt.peer.CanvasPeer;

/**
* @author SunJava
* @author Modified by: JJ
*/
public class RSCanvas extends Canvas {

private static final String base = "canvas";
private static int nameCounter = 0;
private static final long serialVersionUID = -2284879212465893870L;

private Client client = null;

public RSCanvas() {
super();
System.out.println("[Canvas] constructor called!");
}

public RSCanvas(GraphicsConfiguration config) {
this();
setGraphicsConfiguration(config);
}

void setGraphicsConfiguration(GraphicsConfiguration gc) {
synchronized (getTreeLock()) {
CanvasPeer peer = (CanvasPeer) getPeer();
if (peer != null) {
gc = peer.getAppropriateGraphicsConfiguration(gc);
}
setGraphicsConfiguration(gc);
}
}

@Override
public Graphics getGraphics() {
if (client == null) {
client = ClientPool.getClient(this);
}

if (client != null) {
return client.drawGraphics((Graphics2D) super.getGraphics());
}

return super.getGraphics();
}

@Override
public void setVisible(boolean b) {
// TODO: Modify
super.setVisible(b);
}

String constructComponentName() {
synchronized (Canvas.class) {
return base + nameCounter++;
}
}

boolean postsOldMouseEvents() {
return true;
}

public void createBufferStrategy(int numBuffers) {
super.createBufferStrategy(numBuffers);
}

public void createBufferStrategy(int numBuffers, BufferCapabilities caps) throws AWTException {
super.createBufferStrategy(numBuffers, caps);
}

public BufferStrategy getBufferStrategy() {
return super.getBufferStrategy();
}


protected class AccessibleAWTCanvas extends AccessibleAWTComponent {
private static final long serialVersionUID = -6325592262103146699L;

/**
* Get the role of this object.
*
* @return an instance of AccessibleRole describing the role of the
* object
* @see AccessibleRole
*/
public AccessibleRole getAccessibleRole() {
return AccessibleRole.CANVAS;
}

}
}



This is what I wrote to detect the class and send the modification command.
package org.pulsebot.injection.impl;

import org.objectweb.asm.tree.ClassNode;
import org.pulsebot.injection.generic.AbstractAnalyser;
import org.pulsebot.injection.utils.GenericUtils;

/**
* Created with IntelliJ IDEA.
* User: NKN
* Date: 6/2/13
* Time: 12:40 PM
*/
public class CanvasAnalyser extends AbstractAnalyser {

@Override
protected boolean canRun(ClassNode node) {
if (node.superName.contains("Canvas"))
return true;
return false;
}

@Override
protected String analyse(ClassNode node) {
System.out.println("Canvas is: " + node.superName);
GenericUtils.setSuper(node, "org/pulsebot/injection/generic/RSCanvas"); //Path to your canvas.
System.out.println("Canvas is now: " + node.superName);
System.out.println("Canvas successfully hacked.");
return "Canvas";
}

}

This is just what the prior class extends.

package org.pulsebot.injection.generic;

import org.objectweb.asm.tree.ClassNode;

/**
* Created with IntelliJ IDEA.
* User: Chris
* Date: 3/1/13
* Time: 11:28 PM
* To change this template use File | Settings | File Templates.
*/
public abstract class AbstractAnalyser {

public String run(ClassNode node) {
if (this.canRun(node))
return this.analyse(node);
return null;
}

protected abstract boolean canRun(ClassNode node);

protected abstract String analyse(ClassNode node);
}


And this is the method that actually renames it.
    public static void setSuper(ClassNode node, String superClass) {
String replacedSuper = "";
if (node.superName != "")
replacedSuper = node.superName;
if (replacedSuper != "") {
ListIterator<?> mli = node.methods.listIterator();
while (mli.hasNext()) {
MethodNode mn = (MethodNode) mli.next();
ListIterator<?> ili = mn.instructions.iterator();
while (ili.hasNext()) {
AbstractInsnNode ain = (AbstractInsnNode) ili.next();
if (ain.getOpcode() == Opcodes.INVOKESPECIAL) {
MethodInsnNode min = (MethodInsnNode) ain;
if (min.owner.equals(replacedSuper)) {
min.owner = superClass;
}
}
}
}
}
node.superName = superClass;
}



So that's the injection way. Probably could be done better, but ah well.
http://asm.ow2.org/download/index.html <— Bytecode library, hook it up with your project



Thanks, I'll look into this later and tell you how it goes. What do you think of the loader by the way?

computerichy

2014-09-13 13:06:26

@NKN; I have implemented the above, however I am not entirely sure how I go about calling the injection proccess (I'm assuming the analyser classes aren't automatically called). Is this some of the basic usage of ASM I am not familiar with? If so I am having difficulty finding suitable documentation/tutorials.

Brandon

2014-09-14 11:22:37

From: computerichy
@NKN; I have implemented the above, however I am not entirely sure how I go about calling the injection proccess (I'm assuming the analyser classes aren't automatically called). Is this some of the basic usage of ASM I am not familiar with? If so I am having difficulty finding suitable documentation/tutorials.


Just remember that with both methods you'll be copying the java canvas so.. Either way, you'll be doing some sort of violation of terms. The only way is to use visitors and inject code into the official canvas.

Again, SMART has been running with the method I mentioned for a long time now. Not sure what you're frightened of.

computerichy

2014-09-15 13:08:07

From: Brandon
Just remember that with both methods you'll be copying the java canvas so.. Either way, you'll be doing some sort of violation of terms. The only way is to use visitors and inject code into the official canvas.

Again, SMART has been running with the method I mentioned for a long time now. Not sure what you're frightened of.


Yes, I agree with what you're saying. I am happy to go forward with injecting the canvas. The initial reason I was concerned is because my intention is to create a legit client, avoiding any potential violations of laws/terms. I do doubt doing so causes any harm, and I'm happy to proceed with implementing the injector.

Anyway, as I said previously, I'm very new to the concept of Java bytecode and the manipulation of it. I'm unsure how to call the above methods that @NKN; kindly posted, could you lead me in the right direction? I like to ensure I learn rather than copy-pasting and coming across problems in the future.

Thanks.

Brandon

2014-09-15 13:59:29

From: computerichy
Yes, I agree with what you're saying. I am happy to go forward with injecting the canvas. The initial reason I was concerned is because my intention is to create a legit client, avoiding any potential violations of laws/terms. I do doubt doing so causes any harm, and I'm happy to proceed with implementing the injector.

Anyway, as I said previously, I'm very new to the concept of Java bytecode and the manipulation of it. I'm unsure how to call the above methods that @NKN; kindly posted, could you lead me in the right direction? I like to ensure I learn rather than copy-pasting and coming across problems in the future.

Thanks.


:S you're confusing me. What methods are you trying to call?

NKN gave you an example of a canvas class that he injects into the client. SetSuper just sets the official canvas' super class to his.

So now the RS canvas class inherits from his instead of the usual hierarchy. You call setSuper after loading the jar (not the applet). It means you need to read all the classes in the jar into classNodes and call analyser.canRun() on all of them. It will determine which class is the canvas and then you run setSuper on it to change the hierarchy.

He did not give you the code to load the jar into classNodes and explain how to use the above. I assume he wants you to figure out how and learn how. If he did intend that, then you'd have to read the docs or check out his updater/bot's source.

computerichy

2014-09-15 14:49:18

From: Brandon
:S you're confusing me. What methods are you trying to call?

NKN gave you an example of a canvas class that he injects into the client. SetSuper just sets the official canvas' super class to his.

So now the RS canvas class inherits from his instead of the usual hierarchy. You call setSuper after loading the jar (not the applet). It means you need to read all the classes in the jar into classNodes and call analyser.canRun() on all of them. It will determine which class is the canvas and then you run setSuper on it to change the hierarchy.

He did not give you the code to load the jar into classNodes and explain how to use the above. I assume he wants you to figure out how and learn how. If he did intend that, then you'd have to read the docs or check out his updater/bot's source.


Thank you for your quick response, these were the 2 ways of understanding I had in mind. I'm going to read the neccessary information from the docs and compare what I've learnt against NKN's source to make sure it's valid.

Thanks.

Shatterhand

2014-10-12 03:49:19

Can anyone help me with JMenuBar?

I tried adding the client JPanel to the frame or its contentpane too:
add(client.getLoader(), BorderLayout.CENTER);
getContentPane().add(client.getLoader(), BorderLayout.CENTER);

But the menu would get behind the JPanel.


EDIT: Solved. Applet and Canvas were AWT, heavy weight, and Swing is light weight, mixing these werent that good. This line solved it:
menu.getPopupMenu().setLightWeightPopupEnabled(false);

Brandon

2014-10-12 08:04:21

From: Shatterhand
Applet and Canvas were AWT, heavy weight, and Swing is light weight, mixing these werent that good. This line solved it:
menu.getPopupMenu().setLightWeightPopupEnabled(false);



If you ever decide to use JavaFX then you can position the menu-bar and applet like so:

void initJFX(String world, int width, int height) throws IOException {

//...
//...

root.getChildrenUnmodifiable().stream().filter(n -> n.getId().equals("menuToolBarPanel")).forEach(n -> {
int menuWidth = (int)n.getBoundsInParent().getMinX() + (int)n.getBoundsInParent().getWidth();
int menuHeight = (int)n.getBoundsInParent().getMinY() + (int)n.getBoundsInParent().getHeight();

ClientApplet applet = new ClientApplet(world, width, height);
panel.setPreferredSize(new Dimension(menuWidth, menuHeight));
frame.add(applet, BorderLayout.CENTER);

applet.start();
//...
//...
});
}

That would position the applet below the menu above the applet. You can't embed the RS applet in a swing-node. It just doesn't work so the above was the only solution I could come up with OTHER than messing with the properties and weights.

Shatterhand

2014-10-12 09:49:24

From: Brandon
If you ever decide to use JavaFX then you can position the menu-bar and applet like so:

void initJFX(String world, int width, int height) throws IOException {

//...
//...

root.getChildrenUnmodifiable().stream().filter(n -> n.getId().equals("menuToolBarPanel")).forEach(n -> {
int menuWidth = (int)n.getBoundsInParent().getMinX() + (int)n.getBoundsInParent().getWidth();
int menuHeight = (int)n.getBoundsInParent().getMinY() + (int)n.getBoundsInParent().getHeight();

ClientApplet applet = new ClientApplet(world, width, height);
panel.setPreferredSize(new Dimension(menuWidth, menuHeight));
frame.add(applet, BorderLayout.CENTER);

applet.start();
//...
//...
});
}

That would position the applet below the menu above the applet. You can't embed the RS applet in a swing-node. It just doesn't work so the above was the only solution I could come up with OTHER than messing with the properties and weights.

Does JavaFX solve this problem?
The applet would move up a little each time something is changed on the JFrame.

NKN

2014-10-12 10:32:32

From: Shatterhand
Does JavaFX solve this problem?
The applet would move up a little each time something is changed on the JFrame.


For some reason, adding this to PulseBots Canvas class fixed the jumping up and down


@Override
public void setLocation(int x, int y) {}

Shatterhand

2014-10-12 10:35:04

From: NKN
For some reason, adding this to PulseBots Canvas class fixed the jumping up and down


@Override
public void setLocation(int x, int y) {}

Oh wow that worked, tyvm! :D

computerichy

2014-10-20 21:26:02

From: Shatterhand
Oh wow that worked, tyvm! :D


Another solution I found for this was to set the applet's layout to null.

computerichy

2014-10-23 21:05:05

Thank you @Brandon; and @NKN; for your help regarding the canvas override. Now I've run in a slight issue relating the the dimensions of the canvas; Being a tabbed OS/RS3 loader, OS is of course a fixed size, however I have allowed for RS3 to be resized with the panel in which it's contained. The layouts are set to automatically adjust to the according version, and the applet resizes as expected.

In order for the canvas' dimensions to comply with the applets' resizing, I would be required to redefine the BufferedImage for game/paint buffer, correct?
I'm assuming the best approach would be to create a listener for applet resizing which redefines the client's BufferedImage according the the applet's size.
If this is, would it not cause any synchronization issues, as the Canvas' paintGraphics method could be invoked whilst the BufferedImage is being redefined.

NKN

2014-10-23 23:23:08

From: computerichy
Thank you @Brandon; and @NKN; for your help regarding the canvas override. Now I've run in a slight issue relating the the dimensions of the canvas; Being a tabbed OS/RS3 loader, OS is of course a fixed size, however I have allowed for RS3 to be resized with the panel in which it's contained. The layouts are set to automatically adjust to the according version, and the applet resizes as expected.

In order for the canvas' dimensions to comply with the applets' resizing, I would be required to redefine the BufferedImage for game/paint buffer, correct?
I'm assuming the best approach would be to create a listener for applet resizing which redefines the client's BufferedImage according the the applet's size.
If this is, would it not cause any synchronization issues, as the Canvas' paintGraphics method could be invoked whilst the BufferedImage is being redefined.



Can you humor me and post the code of your Canvas class and where you define the bufferedimages.

Brandon

2014-10-24 00:16:57

From: computerichy

In order for the canvas' dimensions to comply with the applets' resizing, I would be required to redefine the BufferedImage for game/paint buffer, correct?
I'm assuming the best approach would be to create a listener for applet resizing which redefines the client's BufferedImage according the the applet's size.
If this is, would it not cause any synchronization issues, as the Canvas' paintGraphics method could be invoked whilst the BufferedImage is being redefined.



1. Yes. You'd need to create a new BufferedImage that is the size as the new canvas dimensions. Not sure how you resized the canvas to be the same size as the panel it's in.. Did you explicitly call resize? Not sure that if a layout manager would work. I think that's what you're using.. Usually I just set my JPanel to borderlayout(0, 0) and place the applet in that.. it does its own resizing and withing the paint graphics, I renewed my buffer.. Could also call applet.resize();

2. I actually can't see it causing sync.. why would it?

Whenever I did the canvas paintGraphics I always checked the size first:


@Override
public Graphics getGraphics() {
//check for null.. bleh..
if (this.backBuffer.getWidth() != this.getWidth()......) {
//create new back buffer..
}

//whatever else..
}

Never had an issue with resizing or swapping out the back buffer for a new one.. Sometimes, if I prefer to keep one buffer throughout the entire game (for any game), I allocate screen_width x screen_height x BGRA and only use as much as client_width * client_height * BGRA.. the rest would be black if not used. This is good for shared-memory (sometimes) as it sucks re-allocating.. But you're using tabs.. so I wouldn't over-allocate then..

Zhendos

2014-12-21 10:54:15

The main question now is:
How to hook the OSRS client, get their hooks and make an API?

Beside that nice tutorial!

NKN

2014-12-21 11:14:33

From: Zhendos
The main question now is:
How to hook the OSRS client, get their hooks and make an API?

Beside that nice tutorial!


Download ASM.

Download OSRS client.

Use ASM to write an updater.

Dump hooks from updater.

Write API.

Brandon

2014-12-21 11:15:19

From: Zhendos
The main question now is:
How to hook the OSRS client, get their hooks and make an API?

Beside that nice tutorial!



Are you asking how to make an updater? Or are you asking just how to "hook" fields that you already have from somewhere?

https://villavu.com/forum/showthread.php?t=107479&p=1279724#post1279724


The idea is that once you know your fields, you do:

Field f = loader.loadClass("ClassName").getDeclaredField("FieldName");

You set the field accessible:

f.setAccessible(true);

and if the field is static you do: f.get*(null); but if its not, you do: f.get*(instance);


That's the gist of it.. See here for a better idea: http://www.powerbot.org/community/topic/748406-basic-reflection-tutorial/

Zhendos

2014-12-21 15:29:58

What this tutorial is doing is loading the OSRS client via URL.
What you said yes, I'm looking for some tutorials for making an OSRS loader and learning about bytecode for hooking the RuneScape client.
I've seen a RuneScape loader in GitHub but I don't understand most code and need some tutorials first to know how it's buikd uo.

I am currently doing ths tutorial with the RuneScape client without hooks and an RS loader.
Beside that I've a lot of experiece writing scripts on multiple bots.

Thanks for the reply and nice to see you're still active!

NKN

2014-12-21 15:41:28

From: Zhendos
What this tutorial is doing is loading the OSRS client via URL.
What you said yes, I'm looking for some tutorials for making an OSRS loader and learning about bytecode for hooking the RuneScape client.
I've seen a RuneScape loader in GitHub but I don't understand most code and need some tutorials first to know how it's buikd uo.

I am currently doing ths tutorial with the RuneScape client without hooks and an RS loader.
Beside that I've a lot of experiece writing scripts on multiple bots.

Thanks for the reply and nice to see you're still active!


You're probably going to need a lot more than just script writing knowledge before you can make an updater.

I'd advise learning about Java in itself if you haven't already, (More so than just API's from bots.)


Then if you're ready, learn about the JVM specification and java bytecode. Check out http://rs-hacking.com for more info AFTER you've done those two prior.

Zhendos

2014-12-21 15:56:51

I've beside writing scripts making some usefull things in java such for mathematic the ABC formula and things like this, making a runescape bot interface, looking at the code, how it's build up, edit things and look at results.

Thanks for your tips anyway.

Shatterhand

2014-12-23 20:14:59

From: Zhendos
I've beside writing scripts making some usefull things in java such for mathematic the ABC formula and things like this, making a runescape bot interface, looking at the code, how it's build up, edit things and look at results.

Thanks for your tips anyway.

At first I didnt understand much about reflection stuff and quit it for months but second time I got it. For these stuff you better check out the rs hacking forum as NKN said.

This tutorial gave me the start to make my own client which I have been maintaining for half a year and now I am making my own updater aswell. There is always something new to learn. Dont give up, good luck! :)

NKN

2014-12-24 02:49:39


Remember children, a singleton pool is a great pool to have.

Shatterhand

2014-12-24 10:28:04

I found a problem with my client and since I used this tutorial as a base for the keylistener of my client, I will ask for help here.

For some of my users canvas.getKeyListeners() returns with an empty array. Because of this the sendKeys() function wont work.

I couldnt find the source of this problem yet, some of them have win7, win8 and java 1.7, java 1.8.

I will include some code here:

public ClientKeyListener(Client client) {
this.client = client;
}

public void setCanvas(Canvas canvas) { //this is called after the constructor
this.canvas = canvas;
keyListeners = new ArrayList<>();
keyListeners.addAll(Arrays.asList(canvas.getKeyListeners()));
for (KeyListener listener : canvas.getKeyListeners()) {
canvas.removeKeyListener(listener);
}
canvas.addKeyListener(this);
System.out.println("keyListeners size: " + keyListeners.size()); //this will print out value 0 on some computers
}

public void keyTyped(KeyEvent e) {
System.out.println("keyTyped: " + e.getKeyChar() + " " + e.getKeyCode());

if (e.getSource() == client.getApplet()) {
e.setSource(canvas);
for (KeyListener listener : keyListeners) {
listener.keyTyped(e);
}
}
}

Thank you in advance, and Merry Xmas! :)

NKN

2014-12-24 11:16:20

From: Shatterhand
I found a problem with my client and since I used this tutorial as a base for the keylistener of my client, I will ask for help here.

For some of my users canvas.getKeyListeners() returns with an empty array. Because of this the sendKeys() function wont work.

I couldnt find the source of this problem yet, some of them have win7, win8 and java 1.7, java 1.8.

I will include some code here:

public ClientKeyListener(Client client) {
this.client = client;
}

public void setCanvas(Canvas canvas) { //this is called after the constructor
this.canvas = canvas;
keyListeners = new ArrayList<>();
keyListeners.addAll(Arrays.asList(canvas.getKeyListeners()));
for (KeyListener listener : canvas.getKeyListeners()) {
canvas.removeKeyListener(listener);
}
canvas.addKeyListener(this);
System.out.println("keyListeners size: " + keyListeners.size()); //this will print out value 0 on some computers
}

public void keyTyped(KeyEvent e) {
System.out.println("keyTyped: " + e.getKeyChar() + " " + e.getKeyCode());

if (e.getSource() == client.getApplet()) {
e.setSource(canvas);
for (KeyListener listener : keyListeners) {
listener.keyTyped(e);
}
}
}

Thank you in advance, and Merry Xmas! :)

Are you making sure the canvas isn't null before you run things on it?

Are you properly filling 'this' with listeners?
Check both.

Shatterhand

2014-12-24 13:31:42

From: NKN
Are you making sure the canvas isn't null before you run things on it?

Are you properly filling 'this' with listeners?
Check both.

Yes, there is a waiting loop for canvas getting ready.

I add this class for the Frame's contentPane. All this works for me, only doesnt work for some of my users.

Brandon

2014-12-24 15:20:22

From: Shatterhand
Yes, there is a waiting loop for canvas getting ready.

I add this class for the Frame's contentPane. All this works for me, only doesnt work for some of my users.


The custom canvas should call a function whenever it is changed/refreshed. This function will have to be quick and do all updating fast (fix listeners, update variables, etc). It will be synchronous with the canvas thread (don't thread the function).

Shatterhand

2014-12-24 15:47:45

From: Brandon
The custom canvas should call a function whenever it is changed/refreshed. This function will have to be quick and do all updating fast (fix listeners, update variables, etc). It will be synchronous with the canvas thread (don't thread the function).


Im sorry I dont understand you. Could you explain it please?

I dont do changes on the canvas, only when replacing the client in the frame, but then I will make a new ClientKeyListener and pass the client/canvas to it.

I dont understand why it is working for me right now, but for some friends it doesnt. :(

Shatterhand

2015-01-01 16:32:54

Solved my problem in the previous posts. On some computers (it happened on my laptop too) the game loaded up so slow that the time the Canvas stopped being null wasnt the same when the Canvas's keylisteners were created. Added an extra check for this and now it works fine.

hihihih565

2015-01-08 15:38:02

Nice tutorial, will be looking into it more soon.

Lotto320

2015-04-06 13:09:41

For some reason the drawn string doesn't appear after launching the example in your .zip file, am I doing something wrong?

The applet just simply looks like this http : // i.imgur.com/ aL94FuJ.png, without the "I hope you enjoy this tutorial so far. This example demonstrates"…

I feel like it has something to do with the Canvas not having been implemented correctly on my side, since debugging with a println in drawGraphics (Client.java) doesn't show anything.
EDIT: getGraphics() in Canvas.java doesn't print anything when adding a println either.

EDIT: Ahahaha, I read the last step thoroughly this time and realised that I need to xboot, oh my god.. I literally sat here for 2 hours trying to find out what was wrong..

oswald88

2019-11-25 23:43:31

it gives black screen when i try to load osrs. sometimes works but mostly gives blackscreen. I tried it in eclipse

slayr288

2021-08-20 05:11:51

bump