diff --git a/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java b/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java index 56ba0799d2..d77c9da4ee 100644 --- a/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java +++ b/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java @@ -33,38 +33,61 @@ import com.google.gson.Gson; import io.sigpipe.jbsdiff.InvalidHeaderException; import io.sigpipe.jbsdiff.Patch; import java.applet.Applet; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.lang.reflect.Field; import java.net.URL; +import java.net.URLClassLoader; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; -import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.logging.Logger; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; + +import javassist.ClassPool; +import javassist.NotFoundException; import lombok.extern.slf4j.Slf4j; import static net.runelite.client.rs.ClientUpdateCheckMode.AUTO; import static net.runelite.client.rs.ClientUpdateCheckMode.NONE; import static net.runelite.client.rs.ClientUpdateCheckMode.VANILLA; + +import net.runelite.client.RuneLite; +import net.runelite.client.rs.bytecode.ByteCodeUtils; +import net.runelite.client.rs.bytecode.ByteCodePatcher; +import net.runelite.client.rs.bytecode.Hooks; import net.runelite.http.api.RuneLiteAPI; import okhttp3.Request; import okhttp3.Response; import org.apache.commons.compress.compressors.CompressorException; +import org.xeustechnologies.jcl.JarClassLoader; @Slf4j @Singleton public class ClientLoader { + public static File hooksFile = new File(RuneLite.RUNELITE_DIR+"/hooks-"+ RuneLiteAPI.getVersion() +"-.json"); private final ClientConfigLoader clientConfigLoader; private ClientUpdateCheckMode updateCheckMode; + private JarOutputStream target; @Inject private ClientLoader( @@ -84,6 +107,11 @@ public class ClientLoader try { + File injectedClientFile = ByteCodeUtils.injectedClientFile; + File hijackedClientFile = ByteCodeUtils.hijackedClientFile; + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + target = new JarOutputStream(new FileOutputStream(injectedClientFile), manifest); RSConfig config = clientConfigLoader.fetch(); Map zipFile = new HashMap<>(); @@ -98,7 +126,9 @@ public class ClientLoader try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) { - JarInputStream jis = new JarInputStream(response.body().byteStream()); + JarInputStream jis; + + jis = new JarInputStream(response.body().byteStream()); byte[] tmp = new byte[4096]; ByteArrayOutputStream buffer = new ByteArrayOutputStream(756 * 1024); @@ -121,81 +151,125 @@ public class ClientLoader buffer.write(tmp, 0, n); } - if (!Arrays.equals(metadata.getCertificates(), jagexCertificateChain)) - { - if (metadata.getName().startsWith("META-INF/")) - { - // META-INF/JAGEXLTD.SF and META-INF/JAGEXLTD.RSA are not signed, but we don't need - // anything in META-INF anyway. - continue; - } - else - { - throw new VerificationException("Unable to verify jar entry: " + metadata.getName()); - } - } - zipFile.put(metadata.getName(), buffer.toByteArray()); } } } - if (updateCheckMode == AUTO) - { - Map hashes; - try (InputStream is = ClientLoader.class.getResourceAsStream("/patch/hashes.json")) + if (updateCheckMode == AUTO) { - hashes = new Gson().fromJson(new InputStreamReader(is), new TypeToken>() + Map hashes; + try (InputStream is = ClientLoader.class.getResourceAsStream("/patch/hashes.json")) { - }.getType()); - } - - for (Map.Entry file : hashes.entrySet()) - { - byte[] bytes = zipFile.get(file.getKey()); - - String ourHash = null; - if (bytes != null) - { - ourHash = Hashing.sha512().hashBytes(bytes).toString(); - } - - if (!file.getValue().equals(ourHash)) - { - log.debug("{} had a hash mismatch; falling back to vanilla. {} != {}", file.getKey(), file.getValue(), ourHash); - log.info("Client is outdated!"); - updateCheckMode = VANILLA; - break; - } - } - } - - if (updateCheckMode == AUTO) - { - ByteArrayOutputStream patchOs = new ByteArrayOutputStream(756 * 1024); - int patchCount = 0; - - for (Map.Entry file : zipFile.entrySet()) - { - byte[] bytes; - try (InputStream is = ClientLoader.class.getResourceAsStream("/patch/" + file.getKey() + ".bs")) - { - if (is == null) + hashes = new Gson().fromJson(new InputStreamReader(is), new TypeToken>() { - continue; + }.getType()); + } + + for (Map.Entry file : hashes.entrySet()) + { + byte[] bytes = zipFile.get(file.getKey()); + + String ourHash = null; + if (bytes != null) + { + ourHash = Hashing.sha512().hashBytes(bytes).toString(); } - bytes = ByteStreams.toByteArray(is); + if (!file.getValue().equals(ourHash)) + { + if (hijackedClientFile.exists()) { + Logger.getAnonymousLogger().warning("[RuneLit] Hash checking / Client patching skipped due to hijacked client."); + updateCheckMode = VANILLA; + break; + } else { + log.info("{} had a hash mismatch; falling back to vanilla. {} != {}", file.getKey(), file.getValue(), ourHash); + log.info("Client is outdated!"); + updateCheckMode = VANILLA; + break; + } + } } - - patchOs.reset(); - Patch.patch(file.getValue(), bytes, patchOs); - file.setValue(patchOs.toByteArray()); - - ++patchCount; } - log.debug("Patched {} classes", patchCount); + if (updateCheckMode == AUTO) + { + ByteArrayOutputStream patchOs = new ByteArrayOutputStream(756 * 1024); + int patchCount = 0; + + for (Map.Entry file : zipFile.entrySet()) + { + byte[] bytes; + try (InputStream is = ClientLoader.class.getResourceAsStream("/patch/" + file.getKey() + ".bs")) + { + if (is == null) + { + continue; + } + + bytes = ByteStreams.toByteArray(is); + } + + patchOs.reset(); + Patch.patch(file.getValue(), bytes, patchOs); + file.setValue(patchOs.toByteArray()); + + ++patchCount; + + if (!file.getKey().startsWith("META")) { + add(file.getValue(), file.getKey(), target); + } + } + if (target!=null) + target.close(); + + log.info("Patched {} classes", patchCount); + } + if (hooksFile.exists()) { + ByteCodePatcher.classPool = new ClassPool(true); + ByteCodePatcher.classPool.appendClassPath(RuneLite.RUNELITE_DIR+"/injectedClient-"+ RuneLiteAPI.getVersion() +"-.jar"); + Gson gson = new Gson(); + Hooks hooks = gson.fromJson(new BufferedReader(new FileReader(hooksFile)), Hooks.class); + + if (hooks.clientInstance.equals("")|| + hooks.projectileClass.equals("") || + hooks.actorClass.equals("")) { + System.out.println("[RuneLit] Bad hooks, re-scraping."); + ByteCodePatcher.clientInstance = getClientInstance(ByteCodeUtils.injectedClientFile.getPath()); + ByteCodePatcher.findHooks(injectedClientFile.getPath()); + } else { + ByteCodePatcher.clientInstance = hooks.clientInstance; + ByteCodePatcher.applyHooks(ByteCodeUtils.injectedClientFile, hooks); + System.out.println("[RuneLit] Loaded hooks"); + } + + } else { + System.out.println("[RuneLit] Hooks file not found, scraping hooks."); + ByteCodePatcher.clientInstance = getClientInstance(ByteCodeUtils.injectedClientFile.getPath()); + ByteCodePatcher.findHooks(injectedClientFile.getPath()); + } + + Map zipFile2 = new HashMap<>(); + JarInputStream jis = new JarInputStream(new FileInputStream(hijackedClientFile)); + + byte[] tmp = new byte[4096]; + ByteArrayOutputStream buffer = new ByteArrayOutputStream(756 * 1024); + for (; ; ) { + JarEntry metadata = jis.getNextJarEntry(); + if (metadata == null) { + break; + } + + buffer.reset(); + for (; ; ) { + int n = jis.read(tmp); + if (n <= -1) { + break; + } + buffer.write(tmp, 0, n); + } + + zipFile2.put(metadata.getName(), buffer.toByteArray()); } String initialClass = config.getInitialClass(); @@ -206,7 +280,7 @@ public class ClientLoader protected Class findClass(String name) throws ClassNotFoundException { String path = name.replace('.', '/').concat(".class"); - byte[] data = zipFile.get(path); + byte[] data = zipFile2.get(path); if (data == null) { throw new ClassNotFoundException(name); @@ -223,8 +297,7 @@ public class ClientLoader return rs; } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException - | CompressorException | InvalidHeaderException | CertificateException | VerificationException - | SecurityException e) + | CompressorException | InvalidHeaderException | CertificateException | SecurityException e) { if (e instanceof ClassNotFoundException) { @@ -235,6 +308,22 @@ public class ClientLoader log.error("Error loading RS!", e); return null; + } catch (NotFoundException e) { + e.printStackTrace(); + } + return null; + } + + private void add(byte[] bytes, String entryName ,JarOutputStream target) throws IOException { + BufferedInputStream in = null; + try { + JarEntry entry = new JarEntry(entryName); + target.putNextEntry(entry); + target.write(bytes); + target.closeEntry(); + } finally { + if (in != null) + in.close(); } } @@ -244,4 +333,63 @@ public class ClientLoader Collection certificates = certificateFactory.generateCertificates(ClientLoader.class.getResourceAsStream("jagex.crt")); return certificates.toArray(new Certificate[certificates.size()]); } + + public static String getClientInstance(String jarFile) { + JarClassLoader jcl = new JarClassLoader(); + try { + ClassPool classPool = new ClassPool(true); + classPool.appendClassPath(RuneLite.RUNELITE_DIR+"/injectedClient-"+ RuneLiteAPI.getVersion() +"-.jar"); + } catch (NotFoundException e) { + e.printStackTrace(); + } + + try { + jcl.add(new FileInputStream(jarFile)); + try (JarInputStream in = new JarInputStream(new BufferedInputStream(new FileInputStream(jarFile)))) { + JarEntry entry; + while ((entry = in.getNextJarEntry()) != null) { + if (entry.getName().endsWith(".class")) { + File temp = new File(jarFile); + ClassLoader cl = ClassLoader.getSystemClassLoader(); + try { + URLClassLoader child = new URLClassLoader( + new URL[] {temp.toURI().toURL()}, + cl + ); + try { + Class classToLoad = Class.forName(entry.getName().replace(".class", ""), false, child); + JarClassLoader jcl2 = new JarClassLoader(); + try { + jcl2.add(new FileInputStream(ByteCodeUtils.injectedClientFile)); + Field[] fields = classToLoad.getDeclaredFields(); + for (Field f : fields) { + try { + if (f.getType().getName()=="client") { + ByteCodePatcher.hooks.clientInstance = classToLoad.getName()+"."+f.getName(); + System.out.println("[RuneLit] Found client instance at "+classToLoad.getName()+"."+f.getName()); + return classToLoad.getName()+"."+f.getName(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + } catch (Exception e) { + e.printStackTrace(); + System.out.println("Class not found: "+entry.getName()); + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/rs/bytecode/ByteCodePatcher.java b/runelite-client/src/main/java/net/runelite/client/rs/bytecode/ByteCodePatcher.java new file mode 100644 index 0000000000..2e7b740ee9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/rs/bytecode/ByteCodePatcher.java @@ -0,0 +1,160 @@ +package net.runelite.client.rs.bytecode; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; +import net.runelite.client.RuneLite; +import net.runelite.client.rs.ClientLoader; +import net.runelite.client.rs.bytecode.transformers.ActorTransform; +import net.runelite.client.rs.bytecode.transformers.ProjectileTransform; +import net.runelite.http.api.RuneLiteAPI; +import org.xeustechnologies.jcl.JarClassLoader; + +import java.io.BufferedInputStream; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; + +public class ByteCodePatcher { + + public static List modifiedClasses = new ArrayList<>(); + public static Hooks hooks = new Hooks(); + public static String clientInstance; + public static JarClassLoader jcl = new JarClassLoader(); + public static ClassPool classPool = null; + public static ClassLoader cl = ClassLoader.getSystemClassLoader(); + + public static void applyHooks(File jf, Hooks hooks) { + try { + URLClassLoader child = new URLClassLoader( + new URL[] {jf.toURI().toURL()}, + cl + ); + try { + Class actorClass = Class.forName(hooks.actorClass, false, child); + transformActor(actorClass); + Class projectileClass = Class.forName(hooks.projectileClass, false, child); + transformProjectile(projectileClass); + ByteCodeUtils.updateHijackedJar(); + } catch (Exception e) { + e.printStackTrace(); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void findHooks(String jf) { + try { + classPool = new ClassPool(true); + classPool.appendClassPath(RuneLite.RUNELITE_DIR+"/injectedClient-"+ RuneLiteAPI.getVersion() +"-.jar"); + } catch (NotFoundException e) { + e.printStackTrace(); + } + + try { + jcl.add(new FileInputStream(jf)); + try (JarInputStream in = new JarInputStream(new BufferedInputStream(new FileInputStream(jf)))) { + JarEntry entry; + while ((entry = in.getNextJarEntry()) != null) { + if (entry.getName().endsWith(".class")) { + checkClasses(new File(jf), entry); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + try { + Writer writer = new FileWriter(ClientLoader.hooksFile); + gson.toJson(hooks, writer); + writer.flush(); + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + ByteCodeUtils.updateHijackedJar(); + } + + public static void checkClasses(File jf, JarEntry entry) { + try { + URLClassLoader child = new URLClassLoader( + new URL[] {jf.toURI().toURL()}, + cl + ); + try { + Class classToLoad = Class.forName(entry.getName().replace(".class", ""), false, child); + checkActor(classToLoad); + checkProjectile(classToLoad); + } catch (Exception e) { + e.printStackTrace(); + } + + } catch (Exception e) { + e.printStackTrace(); + System.out.println("Class not found: "+entry.getName()); + } + } + + public static void checkActor(Class current) { + try { + Method method = current.getDeclaredMethod("setCombatInfo", new Class[] { int.class, int.class, int.class, int.class, int.class, int.class }); + if (method!=null) { + hooks.actorClass = current.getName(); + System.out.println("[RuneLit] Transforming Actor at class: "+current.getName()); + ActorTransform at = new ActorTransform(); + at.modify(current); + } + } catch (NoSuchMethodException e) { + //e.printStackTrace(); + } catch (NoClassDefFoundError e) { + //e.printStackTrace(); + } + } + + public static void transformActor(Class current) { + System.out.println("[RuneLit] Transforming Actor at class: "+current.getName()); + ActorTransform at = new ActorTransform(); + at.modify(current); + } + + public static void checkProjectile(Class current) { + try { + Method method = current.getDeclaredMethod("projectileMoved", new Class[] { int.class, int.class, int.class, int.class}); + if (method!=null) { + hooks.projectileClass = current.getName(); + System.out.println("[RuneLit] Transforming Projectile at class: "+current.getName()); + ProjectileTransform pt = new ProjectileTransform(); + pt.modify(current); + } + } catch (NoSuchMethodException e) { + //e.printStackTrace(); + } catch (NoClassDefFoundError e) { + //e.printStackTrace(); + } + } + + public static void transformProjectile(Class current) { + System.out.println("[RuneLit] Transforming Projectile at class: "+current.getName()); + ProjectileTransform pt = new ProjectileTransform(); + pt.modify(current); + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/rs/bytecode/ByteCodeUtils.java b/runelite-client/src/main/java/net/runelite/client/rs/bytecode/ByteCodeUtils.java new file mode 100644 index 0000000000..646d595982 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/rs/bytecode/ByteCodeUtils.java @@ -0,0 +1,117 @@ +package net.runelite.client.rs.bytecode; + +import javassist.CtClass; +import net.runelite.client.RuneLite; +import net.runelite.http.api.RuneLiteAPI; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class ByteCodeUtils { + //TODO: Write method to delete old revision injected clients. + public static File injectedClientFile = new File(RuneLite.RUNELITE_DIR+"/injectedClient-"+ RuneLiteAPI.getVersion() +"-.jar"); + public static File hijackedClientFile = new File(RuneLite.RUNELITE_DIR+"/hijackedClient-"+ RuneLiteAPI.getVersion() +"-.jar"); + + public static JarOutputStream target; + + public static void updateHijackedJar() { + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + try { + target = new JarOutputStream(new FileOutputStream(hijackedClientFile), manifest); + } catch (IOException e) { + e.printStackTrace(); + } + try { + List classesToSkip = new ArrayList<>(); + for (CtClass ct : ByteCodePatcher.modifiedClasses) { + classesToSkip.add(ct.getName()); + } + + JarFile original = new JarFile(injectedClientFile); + Enumeration entries = original.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + boolean skip = false; + for (CtClass ct : ByteCodePatcher.modifiedClasses) { + if ((ct.getName()+".class").equals(entry.getName())) { + skip = true; + } + } + if (!skip) + add(entry); + } + + for (CtClass ct : ByteCodePatcher.modifiedClasses) { + add(ct); + } + + target.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void add(CtClass ct) { + try { + JarEntry newEntry = new JarEntry(ct.getName()+".class"); + target.putNextEntry(newEntry); + target.write(ct.toBytecode()); + target.closeEntry(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void add(JarEntry entry) throws IOException { + try { + if (!entry.getName().startsWith("META")&&!entry.getName().equals("")) { + target.putNextEntry(entry); + target.write(getBytesFromZipFile(entry.getName())); + target.closeEntry(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static byte[] getBytesFromZipFile(String entryName) { + ZipFile zipFile; + try { + zipFile = new ZipFile(injectedClientFile); + Enumeration entries = zipFile.entries(); + + while(entries.hasMoreElements()){ + ZipEntry entry = entries.nextElement(); + if (entry.getName().equals(entryName)) { + InputStream stream = zipFile.getInputStream(entry); + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[1024]; + while ((nRead = stream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + buffer.flush(); + return buffer.toByteArray(); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/rs/bytecode/Hooks.java b/runelite-client/src/main/java/net/runelite/client/rs/bytecode/Hooks.java new file mode 100644 index 0000000000..a67e1bdf08 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/rs/bytecode/Hooks.java @@ -0,0 +1,11 @@ +package net.runelite.client.rs.bytecode; + +public class Hooks { + + public String clientInstance = ""; + public String actorClass = ""; + public String projectileClass = ""; + + public Hooks() { + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/rs/bytecode/transformers/ActorTransform.java b/runelite-client/src/main/java/net/runelite/client/rs/bytecode/transformers/ActorTransform.java new file mode 100644 index 0000000000..37b4777c56 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/rs/bytecode/transformers/ActorTransform.java @@ -0,0 +1,61 @@ +package net.runelite.client.rs.bytecode.transformers; + +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.CtNewMethod; +import javassist.NotFoundException; +import net.runelite.client.rs.bytecode.ByteCodePatcher; +import net.runelite.client.rs.bytecode.ByteCodeUtils; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; + +public class ActorTransform { + public CtClass ct = null; + + + public void modify(Class actor) { + try { + ct = ByteCodePatcher.classPool.get(actor.getName()); + transformGetAnimation(); + transformAnimationChanged(); + ByteCodePatcher.modifiedClasses.add(ct); + } catch (NotFoundException e) { + e.printStackTrace(); + } + } + + public void transformGetAnimation() { + try { + CtMethod protectedAnimation = ct.getDeclaredMethod("1protect$getRsAnimation"); + ct.removeMethod(protectedAnimation); + protectedAnimation.setName("getRsAnimation"); + ct.addMethod(protectedAnimation); + CtMethod getAnimation = ct.getDeclaredMethod("getAnimation"); + ct.removeMethod(getAnimation); + getAnimation = CtNewMethod.make("public int getAnimation() { return this.getRsAnimation(); }",ct); + ct.addMethod(getAnimation); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + public void transformAnimationChanged() { + try { + CtMethod getAnimationChanged = ct.getDeclaredMethod("animationChanged", new CtClass[]{CtClass.intType}); + ct.removeMethod(getAnimationChanged); + getAnimationChanged = CtNewMethod.make("public void animationChanged(int n) { " + + " net.runelite.api.events.AnimationChanged animationChanged = new net.runelite.api.events.AnimationChanged();" + + " animationChanged.setActor((net.runelite.api.Actor)this);" + + " "+ByteCodePatcher.clientInstance+".getCallbacks().post((java.lang.Object)animationChanged); }",ct); + ct.addMethod(getAnimationChanged); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/rs/bytecode/transformers/ProjectileTransform.java b/runelite-client/src/main/java/net/runelite/client/rs/bytecode/transformers/ProjectileTransform.java new file mode 100644 index 0000000000..9cdae79a77 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/rs/bytecode/transformers/ProjectileTransform.java @@ -0,0 +1,40 @@ +package net.runelite.client.rs.bytecode.transformers; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.CtNewMethod; +import net.runelite.client.rs.bytecode.ByteCodePatcher; +import net.runelite.client.rs.bytecode.ByteCodeUtils; + +public class ProjectileTransform { + public CtClass ct = null; + + public void modify(Class projectile) { + try { + ct = ByteCodePatcher.classPool.get(projectile.getName()); + transformProjectileMoved(); + ByteCodePatcher.modifiedClasses.add(ct); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void transformProjectileMoved() { + CtMethod getAnimation; + try { + getAnimation = ct.getDeclaredMethod("projectileMoved", new CtClass[]{CtClass.intType, CtClass.intType, CtClass.intType, CtClass.intType}); + ct.removeMethod(getAnimation); + getAnimation = CtNewMethod.make("public void projectileMoved(int n, int n2, int n3, int n4) { " + + " int n5 = this.getId();" + + " net.runelite.api.coords.LocalPoint localPoint = new net.runelite.api.coords.LocalPoint(n, n2);" + + " net.runelite.api.events.ProjectileMoved projectileMoved = new net.runelite.api.events.ProjectileMoved();" + + " projectileMoved.setProjectile(this);" + + " projectileMoved.setPosition(localPoint);" + + " projectileMoved.setZ(n3);" + + " "+ByteCodePatcher.clientInstance+".getCallbacks().post(projectileMoved); }",ct); + ct.addMethod(getAnimation); + } catch (Exception e) { + e.printStackTrace(); + } + } +}