/** * Copyright 2010 by Benjamin J. Land (a.k.a. BenLand100) * * This file is part of the SMART Minimizing Autoing Resource Thing (SMART) * * SMART is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SMART is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with SMART. If not, see <http://www.gnu.org/licenses/>. */ package smart.updater; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.util.HashMap; import java.util.LinkedList; import java.util.Map.Entry; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.*; /** * Updater is the core of the updater. To add a new search, put its class name * in the SEARCHES field, and ensure its .class file is in the correct package. * Usage for this program is as follows: * * java smart.updater.Updater * Simply runs the updater on a freshly downloaded runescape.jar and * writes everything to STDOUT * * java smart.updater.Updater [outputfile] * Runs the updater on a freshly downloaded runescape.jar and generates a * SMART compatible pascal file in the provided file name, overwriting any * previous data. * * java smart.updater.Updater [downloadedjar] [outputfile] * Runs the updater on the specified jar file (without downloading a new) * runescape.jar, and generates a SMART compatible pascal file in the * provided file name, overwriting any previous data. * * @author benland100 */ public class Updater { /** * Searches to run go here. Must be in the smart.updater.searches package. * Searches are run in the order given, but may be deferred if required data * is not avaliable when run. */ public static final String[] SEARCHES = new String[]{ "LoginIndex", "BaseAnimableXY", "NPC", "NPCDefName", "NPCNode", "NPCIndexArray", "NPCNodes", "NPCCount", "GroundPlane", "RSInteractable", "Toolkit", "Viewport", "StandardDetail", "MinimapAngle", "Animation", "QueueXY", "Camera", "MenuOptionCount", "MenuXY", "MenuNodeList", "NodeListHead", "NodeNext", "MenuOption", "CharacterHeight", "NPCLevel", "LoopCycle", "LoopCycleStatus", "HPRatio", "Interacting", "PlayerName", "PlayerIndexArray", "GetPlayers", "PlayerDef" }; /** * See the class description for usage information. * * @param args */ public static void main(String[] args) { Updater updater; RSClient data; switch (args.length) { case 1: updater = new Updater(download("http://world" + Math.round(Math.random() * 169) + ".runescape.com/runescape.jar")); data = updater.runSearches(); System.out.println(data); saveFile(args[1],data.smartFile().getBytes()); case 2: updater = new Updater(readFile(args[0])); data = updater.runSearches(); System.out.println(data); saveFile(args[1],data.smartFile().getBytes()); break; case 0: default: updater = new Updater(download("http://world" + Math.round(Math.random() * 169) + ".runescape.com/runescape.jar")); data = updater.runSearches(); System.out.println(data); System.out.println(data.smartFile()); } } /** * Saves data to a file, erasing all previous information. * * @param file Path to file * @param data Data to save */ public static void saveFile(String file, byte[] data) { try { File f = new File(file); if (f.exists()) { f.delete(); } f.createNewFile(); FileOutputStream out = new FileOutputStream(f); out.write(data); out.close(); } catch (Exception e) { e.printStackTrace(); } } /** * Reads data from a file and returns the bytes * * @param file Path to file * @return Data read */ public static byte[] readFile(String file) { try { FileInputStream in = new FileInputStream(new File(file)); ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = in.read(buffer, 0, 1024)) > -1) { out.write(buffer, 0, len); } in.close(); out.close(); return out.toByteArray(); } catch (Exception e) { e.printStackTrace(); } return null; } /** * Reads data from a URL and returns the bytes * * @param address Valid URL * @return Data read */ public static byte[] download(String address) { ByteArrayOutputStream out = null; InputStream in = null; try { URL url = new URL(address); URLConnection urlc = url.openConnection(); urlc.setConnectTimeout(1000); urlc.setReadTimeout(1000); out = new ByteArrayOutputStream(); in = urlc.getInputStream(); byte[] buffer = new byte[1024]; int numRead; while ((numRead = in.read(buffer)) != -1) { out.write(buffer, 0, numRead); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (in != null) { in.close(); } if (out != null) { out.close(); } } catch (IOException e) { } } if (out != null) { return out.toByteArray(); } return null; } /** * HashMap of raw binary class data by class name, just in case. */ private HashMap<String, byte[]> rawclasses = new HashMap<String, byte[]>(); /** * HashMap of ClassGens by class name, for use in the Searches. */ private HashMap<String, ClassGen> javaclasses = new HashMap<String, ClassGen>(); /** * Creates an Updater instance by parsing the classes out of a byte[] from * a runescape.jar. This constructor does not do any bytecode searching, it * simply parses classes. * * @param jar byte[] from a runescape.jar */ public Updater(byte[] jar) { try { ZipInputStream in = new ZipInputStream(new ByteArrayInputStream(jar)); ZipEntry entry = in.getNextEntry(); byte[] buffer = new byte[1024]; while (entry != null) { String entryName = entry.getName(); if (entryName.endsWith(".class")) { ByteArrayOutputStream data = new ByteArrayOutputStream(); int len; while ((len = in.read(buffer, 0, 1024)) > -1) { data.write(buffer, 0, len); } rawclasses.put(entryName.replaceAll("\\\\", ".").replace(".class", ""), data.toByteArray()); data.close(); } in.closeEntry(); entry = in.getNextEntry(); } in.close(); } catch (Exception e) { throw new RuntimeException(e); } try { for (Entry<String, byte[]> entry : rawclasses.entrySet()) { ByteArrayInputStream in = new ByteArrayInputStream(entry.getValue()); ClassParser parser = new ClassParser(in, entry.getKey()); javaclasses.put(entry.getKey(), new ClassGen(parser.parse())); in.close(); } } catch (Exception e) { throw new RuntimeException(e); } } /** * Searches the client class for the version. Usually works fine. * * @return Client version */ public int getVersion() { ClassGen client = javaclasses.get("client"); if (client == null) return -1; for (Method m : client.getMethods()) { if (m.getName().equals("main")) { MethodGen gen = new MethodGen(m, client.getClassName(), client.getConstantPool()); InstructionList il = gen.getInstructionList(); InstructionHandle i = il.getStart(); do { if (i.getInstruction() instanceof SIPUSH) { int value = ((SIPUSH) i.getInstruction()).getValue().intValue(); if (value >= 400 && value <= 800 && value != 503 && value != 768) { return value; } } } while ((i = i.getNext()) != null); } } return -1; } /** * Runs all the specified searches deferring as necessary and prints out * some nice debug information. * * @return RSClient instance containing the hooks found. */ public RSClient runSearches() { long start = System.currentTimeMillis(); System.out.println("=== SMART Public Updater by BenLand100 ==="); System.out.println(); int version = getVersion(); if (version == -1) System.out.println("Warning: version not found, hooks may fail"); System.out.println("Preparing to run searches on:"); System.out.println(" * " + rawclasses.size() + " class files"); System.out.println(" * From client version: " + version); System.out.println(); RSClient data = new RSClient(version); LinkedList<Search> deferred = new LinkedList<Search>(); for (String searchName : SEARCHES) { Search search = null; try { search = (Search) Class.forName("smart.updater.searches." + searchName).newInstance(); System.out.print(searchName + "... "); switch (search.run(data, javaclasses)) { case Success: System.out.println(" completed successfully"); break; case Failure: System.out.println(" failed to find its hooks"); break; case MissingInfo: System.out.println(" needs to be deferred"); deferred.add(search); } } catch (Exception e) { e.printStackTrace(); } } int lastsize = deferred.size(); while (deferred.size() > 0) { System.out.println("Running deferred searches"); for (Search search : deferred) { System.out.print(search.getClass().getSimpleName() + "... "); switch (search.run(data, javaclasses)) { case Success: System.out.println(" completed after deferring"); deferred.remove(search); break; case Failure: System.out.println(" failed to find its hooks"); deferred.remove(search); break; case MissingInfo: System.out.println(" needs to be deferred again"); } } if (lastsize == deferred.size()) { break; } lastsize = deferred.size(); } for (Search search : deferred) { System.out.println(search.getClass().getSimpleName() + " failed to find its hooks"); } System.out.println(); System.out.println("Finished after " + (System.currentTimeMillis() - start) + "ms"); System.out.println(); return data; } }