.
diff --git a/assets/cache/config17.jag b/assets/cache/config17.jag
new file mode 100644
index 0000000..40b0850
Binary files /dev/null and b/assets/cache/config17.jag differ
diff --git a/assets/cache/entity3.jag b/assets/cache/entity3.jag
new file mode 100644
index 0000000..e39c544
Binary files /dev/null and b/assets/cache/entity3.jag differ
diff --git a/assets/cache/jagex.jag b/assets/cache/jagex.jag
new file mode 100644
index 0000000..16f4922
Binary files /dev/null and b/assets/cache/jagex.jag differ
diff --git a/assets/cache/maps14.jag b/assets/cache/maps14.jag
new file mode 100644
index 0000000..60d5b5e
Binary files /dev/null and b/assets/cache/maps14.jag differ
diff --git a/assets/cache/media12.jag b/assets/cache/media12.jag
new file mode 100644
index 0000000..f7ebecb
Binary files /dev/null and b/assets/cache/media12.jag differ
diff --git a/assets/cache/models6.jag b/assets/cache/models6.jag
new file mode 100644
index 0000000..dc2aa0b
Binary files /dev/null and b/assets/cache/models6.jag differ
diff --git a/assets/cache/textures5.jag b/assets/cache/textures5.jag
new file mode 100644
index 0000000..bef6199
Binary files /dev/null and b/assets/cache/textures5.jag differ
diff --git a/assets/mudclient38-recreated.jar b/assets/mudclient38-recreated.jar
new file mode 100644
index 0000000..7bbd000
Binary files /dev/null and b/assets/mudclient38-recreated.jar differ
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..b7f358a
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/activation.jar b/lib/activation.jar
new file mode 100644
index 0000000..55fd714
Binary files /dev/null and b/lib/activation.jar differ
diff --git a/lib/asm-5.0.4.jar b/lib/asm-5.0.4.jar
new file mode 100644
index 0000000..611b5b7
Binary files /dev/null and b/lib/asm-5.0.4.jar differ
diff --git a/lib/asm-tree-5.0.4.jar b/lib/asm-tree-5.0.4.jar
new file mode 100644
index 0000000..c39316e
Binary files /dev/null and b/lib/asm-tree-5.0.4.jar differ
diff --git a/lib/asm-util-5.0.4.jar b/lib/asm-util-5.0.4.jar
new file mode 100644
index 0000000..2828c1f
Binary files /dev/null and b/lib/asm-util-5.0.4.jar differ
diff --git a/lib/commons-compress-1.18.jar b/lib/commons-compress-1.18.jar
new file mode 100644
index 0000000..e401046
Binary files /dev/null and b/lib/commons-compress-1.18.jar differ
diff --git a/lib/hamcrest-core-1.3.jar b/lib/hamcrest-core-1.3.jar
new file mode 100644
index 0000000..9d5fe16
Binary files /dev/null and b/lib/hamcrest-core-1.3.jar differ
diff --git a/lib/jansi-1.18.jar b/lib/jansi-1.18.jar
new file mode 100644
index 0000000..a7be6db
Binary files /dev/null and b/lib/jansi-1.18.jar differ
diff --git a/lib/json-20201115.jar b/lib/json-20201115.jar
new file mode 100644
index 0000000..a9eab7a
Binary files /dev/null and b/lib/json-20201115.jar differ
diff --git a/lib/junit-4.12.jar b/lib/junit-4.12.jar
new file mode 100644
index 0000000..3a7fc26
Binary files /dev/null and b/lib/junit-4.12.jar differ
diff --git a/lib/sqlite-jdbc-3.8.11.2.jar b/lib/sqlite-jdbc-3.8.11.2.jar
new file mode 100644
index 0000000..9c5334b
Binary files /dev/null and b/lib/sqlite-jdbc-3.8.11.2.jar differ
diff --git a/src/Client/JClassLoader.java b/src/Client/JClassLoader.java
new file mode 100644
index 0000000..bb3a3f7
--- /dev/null
+++ b/src/Client/JClassLoader.java
@@ -0,0 +1,83 @@
+/**
+ * rsctimes
+ *
+ * This file is part of rsctimes.
+ *
+ *
rsctimes 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.
+ *
+ *
rsctimes 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 rsctimes. If not,
+ * see .
+ *
+ *
Authors: see
+ */
+package Client;
+
+import java.io.ByteArrayOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+/** Deals with fetching, loading, and patching a modified RSC jar. */
+public class JClassLoader extends ClassLoader {
+
+ /** Stores class names and the corresponding class byte data */
+ private Map m_classData = new HashMap<>();
+
+ /**
+ * Fetches the game jar and loads and patches the classes
+ *
+ * @param jarURL The URL of the jar to be loaded and patched
+ * @return If no exceptions occurred
+ */
+ public boolean fetch(String jarURL) {
+ Logger.Info("Fetching Jar: " + jarURL);
+
+ try {
+ JarInputStream in = new JarInputStream(Launcher.getResourceAsStream(jarURL));
+ Launcher.getInstance().setProgress(1, 1);
+
+ JarEntry entry;
+ while ((entry = in.getNextJarEntry()) != null) {
+ // Check if file is needed
+ String name = entry.getName();
+
+ // Read class to byte array
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ byte[] data = new byte[1024];
+ int readSize;
+ while ((readSize = in.read(data, 0, data.length)) != -1) bOut.write(data, 0, readSize);
+ byte[] classData = bOut.toByteArray();
+ bOut.close();
+
+ if (name.endsWith(".class")) {
+ Logger.Info("Loading file: " + name);
+ Launcher.getInstance().setStatus("Loading " + name + "...");
+
+ name = name.replace(".class", "").replace('/', '.');
+ classData = JClassPatcher.getInstance().patch(classData);
+ m_classData.put(name, classData);
+ }
+ }
+ in.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public final Class> findClass(String name) {
+ byte[] data = m_classData.get(name);
+ if (data == null) return null;
+
+ return defineClass(name, data, 0, data.length);
+ }
+}
diff --git a/src/Client/JClassPatcher.java b/src/Client/JClassPatcher.java
new file mode 100644
index 0000000..397f59e
--- /dev/null
+++ b/src/Client/JClassPatcher.java
@@ -0,0 +1,366 @@
+/**
+ * rsctimes
+ *
+ * This file is part of rsctimes.
+ *
+ *
rsctimes 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.
+ *
+ *
rsctimes 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 rsctimes. If not,
+ * see .
+ *
+ *
Authors: see
+ */
+package Client;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldInsnNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.InsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.VarInsnNode;
+import org.objectweb.asm.util.Printer;
+import org.objectweb.asm.util.Textifier;
+import org.objectweb.asm.util.TraceMethodVisitor;
+
+public class JClassPatcher {
+
+ // Singleton
+ private static JClassPatcher instance = null;
+
+ public static List ExceptionSignatures = new ArrayList();
+ public static List InstructionBytecode = new ArrayList();
+
+ private Printer printer = new Textifier();
+ private TraceMethodVisitor mp = new TraceMethodVisitor(printer);
+
+ private JClassPatcher() {
+ // Empty private constructor to prevent extra instances from being created.
+ }
+
+ public byte[] patch(byte data[]) {
+ ClassReader reader = new ClassReader(data);
+ ClassNode node = new ClassNode();
+ reader.accept(node, ClassReader.SKIP_DEBUG);
+
+ /*if(node.name.equals("ua"))
+ {
+ patchRenderer(node);
+ }
+ else if(node.name.equals("lb"))
+ {
+ patchCamera(node);
+ }
+ else */ if (node.name.equals("jagex/client/k")) {
+ patchApplet(node);
+ } else if (node.name.equals("mudclient")) {
+ patchClient(node);
+ } else if (node.name.equals("jagex/client/d")) {
+ patchFrame(node);
+ } else if (node.name.equals("jagex/o")) {
+ patchUtility(node);
+ }
+
+ patchGeneric(node);
+
+ dumpClass(node);
+
+ ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+ node.accept(writer);
+ return writer.toByteArray();
+ }
+
+ private void patchGeneric(ClassNode node) {
+ Iterator methodNodeList = node.methods.iterator();
+
+ while (methodNodeList.hasNext()) {
+ MethodNode methodNode = methodNodeList.next();
+
+ // General byte patch
+ Iterator insnNodeList = methodNode.instructions.iterator();
+
+ hookClassVariable(
+ methodNode,
+ "jagex/client/e",
+ "ad",
+ "I",
+ "Game/Client",
+ "connection_port",
+ "I",
+ true,
+ true);
+ }
+ }
+
+ private void patchApplet(ClassNode node) {
+ Logger.Info("Patching applet (" + node.name + ".class)");
+
+ Iterator methodNodeList = node.methods.iterator();
+ while (methodNodeList.hasNext()) {
+ MethodNode methodNode = methodNodeList.next();
+
+ if (methodNode.name.equals("run") && methodNode.desc.equals("()V")) {
+ Iterator insnNodeList = methodNode.instructions.iterator();
+ while (insnNodeList.hasNext()) {
+ AbstractInsnNode insnNode = insnNodeList.next();
+ AbstractInsnNode startNode;
+
+ if (insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL
+ && ((MethodInsnNode) insnNode).name.equals("showStatus")) {
+ startNode = insnNode;
+ methodNode.instructions.insertBefore(insnNode, new InsnNode(Opcodes.POP));
+ methodNode.instructions.insertBefore(insnNode, new InsnNode(Opcodes.POP));
+ methodNode.instructions.remove(startNode);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private void patchFrame(ClassNode node) {
+ Logger.Info("Patching frame (" + node.name + ".class)");
+
+ Iterator methodNodeList = node.methods.iterator();
+ while (methodNodeList.hasNext()) {
+ MethodNode methodNode = methodNodeList.next();
+ }
+ }
+
+ private void patchClient(ClassNode node) {
+ Logger.Info("Patching client (" + node.name + ".class)");
+
+ Iterator methodNodeList = node.methods.iterator();
+ while (methodNodeList.hasNext()) {
+ MethodNode methodNode = methodNodeList.next();
+
+ if (methodNode.name.equals("yi") && methodNode.desc.equals("()V")) {
+ Iterator insnNodeList = methodNode.instructions.iterator();
+ while (insnNodeList.hasNext()) {
+ AbstractInsnNode insnNode = insnNodeList.next();
+
+ // Client.init patch
+ AbstractInsnNode findNode = methodNode.instructions.getFirst();
+ methodNode.instructions.insertBefore(findNode, new VarInsnNode(Opcodes.ALOAD, 0));
+ methodNode.instructions.insertBefore(
+ findNode,
+ new FieldInsnNode(
+ Opcodes.PUTSTATIC, "Game/Client", "instance", "Ljava/lang/Object;"));
+ methodNode.instructions.insertBefore(
+ findNode,
+ new MethodInsnNode(Opcodes.INVOKESTATIC, "Game/Client", "init", "()V", false));
+ }
+ }
+ }
+ }
+
+ private void patchCamera(ClassNode node) {
+ Logger.Info("Patching camera (" + node.name + ".class)");
+
+ /*Iterator methodNodeList = node.methods.iterator();
+ while(methodNodeList.hasNext())
+ {
+ MethodNode methodNode = methodNodeList.next();
+
+ hookClassVariable(methodNode, "lb", "Mb", "I", "Game/Camera", "distance1", "I", false, true);
+ hookClassVariable(methodNode, "lb", "X", "I", "Game/Camera", "distance2", "I", false, true);
+ hookClassVariable(methodNode, "lb", "P", "I", "Game/Camera", "distance3", "I", false, true);
+ hookClassVariable(methodNode, "lb", "G", "I", "Game/Camera", "distance4", "I", false, true);
+ }*/
+ }
+
+ private void patchRenderer(ClassNode node) {
+ Logger.Info("Patching renderer (" + node.name + ".class)");
+ }
+
+ private void patchUtility(ClassNode node) {
+ Logger.Info("Patching utility (" + node.name + ".class)");
+
+ Iterator methodNodeList = node.methods.iterator();
+ while (methodNodeList.hasNext()) {
+ MethodNode methodNode = methodNodeList.next();
+
+ // Patch data read stream
+ if (methodNode.desc.equals("(Ljava/lang/String;)Ljava/io/InputStream;")) {
+ AbstractInsnNode first = methodNode.instructions.getFirst();
+
+ methodNode.instructions.insertBefore(first, new VarInsnNode(Opcodes.ALOAD, 0));
+ methodNode.instructions.insertBefore(
+ first,
+ new MethodInsnNode(
+ Opcodes.INVOKESTATIC,
+ "Game/Utility",
+ "openStream",
+ "(Ljava/lang/String;)Ljava/io/InputStream;"));
+ methodNode.instructions.insertBefore(first, new VarInsnNode(Opcodes.ASTORE, 1));
+ methodNode.instructions.insertBefore(first, new VarInsnNode(Opcodes.ALOAD, 1));
+ methodNode.instructions.insertBefore(first, new InsnNode(Opcodes.ARETURN));
+ }
+ }
+ }
+
+ private void hookClassVariable(
+ MethodNode methodNode,
+ String owner,
+ String var,
+ String desc,
+ String newClass,
+ String newVar,
+ String newDesc,
+ boolean canRead,
+ boolean canWrite) {
+ Iterator insnNodeList = methodNode.instructions.iterator();
+ while (insnNodeList.hasNext()) {
+ AbstractInsnNode insnNode = insnNodeList.next();
+
+ int opcode = insnNode.getOpcode();
+ if (opcode == Opcodes.GETFIELD || opcode == Opcodes.PUTFIELD) {
+ FieldInsnNode field = (FieldInsnNode) insnNode;
+ if (field.owner.equals(owner) && field.name.equals(var) && field.desc.equals(desc)) {
+ if (opcode == Opcodes.GETFIELD && canWrite) {
+ FieldInsnNode newField =
+ new FieldInsnNode(Opcodes.GETSTATIC, newClass, newVar, newDesc);
+ InsnNode pop = new InsnNode(Opcodes.POP);
+ methodNode.instructions.insert(insnNode, newField);
+ methodNode.instructions.insert(insnNode, pop);
+ } else if (opcode == Opcodes.PUTFIELD && canRead) {
+ InsnNode dup = new InsnNode(Opcodes.DUP_X1);
+ FieldInsnNode newPut = new FieldInsnNode(Opcodes.PUTSTATIC, newClass, newVar, newDesc);
+ methodNode.instructions.insertBefore(insnNode, dup);
+ methodNode.instructions.insert(insnNode, newPut);
+ }
+ }
+ }
+ }
+ }
+
+ private void hookStaticVariable(
+ MethodNode methodNode,
+ String owner,
+ String var,
+ String desc,
+ String newClass,
+ String newVar,
+ String newDesc,
+ boolean canRead,
+ boolean canWrite) {
+ Iterator insnNodeList = methodNode.instructions.iterator();
+ while (insnNodeList.hasNext()) {
+ AbstractInsnNode insnNode = insnNodeList.next();
+
+ int opcode = insnNode.getOpcode();
+ if (opcode == Opcodes.GETSTATIC || opcode == Opcodes.PUTSTATIC) {
+ FieldInsnNode field = (FieldInsnNode) insnNode;
+ if (field.owner.equals(owner) && field.name.equals(var) && field.desc.equals(desc)) {
+ if (opcode == Opcodes.GETSTATIC && canWrite) {
+ field.owner = newClass;
+ field.name = newVar;
+ field.desc = newDesc;
+ } else if (opcode == Opcodes.PUTSTATIC && canRead) {
+ field.owner = newClass;
+ field.name = newVar;
+ field.desc = newDesc;
+ }
+ }
+ }
+ }
+ }
+
+ private void dumpClass(ClassNode node) {
+ BufferedWriter writer = null;
+
+ try {
+ File file = new File(/*Settings.Dir.DUMP +*/ "/" + node.name + ".dump");
+ writer = new BufferedWriter(new FileWriter(file));
+
+ writer.write(decodeAccess(node.access) + node.name + " extends " + node.superName + ";\n");
+ writer.write("\n");
+
+ Iterator fieldNodeList = node.fields.iterator();
+ while (fieldNodeList.hasNext()) {
+ FieldNode fieldNode = fieldNodeList.next();
+ writer.write(
+ decodeAccess(fieldNode.access) + fieldNode.desc + " " + fieldNode.name + ";\n");
+ }
+
+ writer.write("\n");
+
+ Iterator methodNodeList = node.methods.iterator();
+ while (methodNodeList.hasNext()) {
+ MethodNode methodNode = methodNodeList.next();
+ writer.write(
+ decodeAccess(methodNode.access) + methodNode.name + " " + methodNode.desc + ":\n");
+
+ Iterator insnNodeList = methodNode.instructions.iterator();
+ while (insnNodeList.hasNext()) {
+ AbstractInsnNode insnNode = insnNodeList.next();
+ String instruction = decodeInstruction(insnNode);
+ writer.write(instruction);
+ }
+ writer.write("\n");
+ }
+
+ writer.close();
+ } catch (Exception e) {
+ try {
+ writer.close();
+ } catch (Exception e2) {
+ }
+ }
+ }
+
+ private String decodeAccess(int access) {
+ String res = "";
+
+ if ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC) res += "public ";
+ if ((access & Opcodes.ACC_PRIVATE) == Opcodes.ACC_PRIVATE) res += "private ";
+ if ((access & Opcodes.ACC_PROTECTED) == Opcodes.ACC_PROTECTED) res += "protected ";
+
+ if ((access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC) res += "static ";
+ if ((access & Opcodes.ACC_FINAL) == Opcodes.ACC_FINAL) res += "final ";
+ if ((access & Opcodes.ACC_VOLATILE) == Opcodes.ACC_VOLATILE) res += "protected ";
+ if ((access & Opcodes.ACC_SYNCHRONIZED) == Opcodes.ACC_SYNCHRONIZED) res += "synchronized ";
+ if ((access & Opcodes.ACC_ABSTRACT) == Opcodes.ACC_ABSTRACT) res += "abstract ";
+ if ((access & Opcodes.ACC_INTERFACE) == Opcodes.ACC_INTERFACE) res += "interface ";
+
+ return res;
+ }
+
+ private String decodeInstruction(AbstractInsnNode insnNode) {
+ Printer printer = new Textifier();
+ TraceMethodVisitor mp = new TraceMethodVisitor(printer);
+
+ insnNode.accept(mp);
+ StringWriter sw = new StringWriter();
+ printer.print(new PrintWriter(sw));
+ printer.getText().clear();
+ return sw.toString();
+ }
+
+ public static JClassPatcher getInstance() {
+ if (instance == null) {
+ synchronized (JClassPatcher.class) {
+ instance = new JClassPatcher();
+ }
+ }
+ return instance;
+ }
+}
diff --git a/src/Client/JConfig.java b/src/Client/JConfig.java
new file mode 100644
index 0000000..b0a8369
--- /dev/null
+++ b/src/Client/JConfig.java
@@ -0,0 +1,107 @@
+/**
+ * rsctimes
+ *
+ * This file is part of rsctimes.
+ *
+ *
rsctimes 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.
+ *
+ *
rsctimes 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 rsctimes. If not,
+ * see .
+ *
+ *
Authors: see
+ */
+package Client;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Parses, stores, and retrieves values from a jav_config.ws file */
+public class JConfig {
+ // Server information
+ /**
+ * Stores the jav_config.ws 'param' value sets
+ *
+ * @see #m_data
+ */
+ public Map parameters = new HashMap<>();
+
+ /**
+ * Stores the jav_config.ws value sets for everything but 'param' and 'msg'
+ *
+ * @see JConfig#parameters
+ */
+ private Map m_data = new HashMap<>();
+
+ public void create(int world) {
+ if (world > 5) world = 5;
+ else if (world < 1) world = 1;
+
+ m_data.put("code", "mudclient.class");
+ m_data.put("width", "512");
+ m_data.put("height", "355");
+ m_data.put("codebase", "http://penguin.local/");
+
+ parameters.put("nodeid", "" + (5000 + world));
+ parameters.put("modewhere", "0");
+ parameters.put("modewhat", "0");
+ if (world == 1) parameters.put("servertype", "" + 3);
+ else parameters.put("servertype", "" + 1);
+ }
+
+ /**
+ * Prepares the client to log into a given world and saves the choice in the config
+ *
+ * @param world The desired world to log into
+ */
+ public void changeWorld(int world) {
+ if (world == 1) {
+ m_data.put("codebase", "http://game.openrsc.com/");
+ Game.Client.connection_port = 43593;
+ return;
+ }
+ }
+
+ /**
+ * Gets the corresponding String from the {@link #m_data} HashMap, given the key
+ *
+ * @param key A key for the {@link #m_data} HashMap
+ * @return A String stored in the {@link #m_data} HashMap
+ */
+ public String getString(String key) {
+ return m_data.get(key);
+ }
+
+ /**
+ * Attempts to return a URL from {@link #m_data}, given the key
+ *
+ * @param key The key to a corresponding URL
+ * @return A URL
+ */
+ public URL getURL(String key) {
+ try {
+ return new URL(m_data.get(key));
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Gets values in the {@link #m_data} HashMap.
+ *
+ * @param key The HashMap key
+ * @return The value of the {@link #m_data} HashMap for the supplied key
+ */
+ public int getInteger(String key) {
+ String string = m_data.get(key);
+ if (string != null) return Integer.valueOf(string);
+ else return 0;
+ }
+}
diff --git a/src/Client/Launcher.java b/src/Client/Launcher.java
new file mode 100644
index 0000000..c6bb1ca
--- /dev/null
+++ b/src/Client/Launcher.java
@@ -0,0 +1,281 @@
+/**
+ * rsctimes
+ *
+ * This file is part of rsctimes.
+ *
+ *
rsctimes 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.
+ *
+ *
rsctimes 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 rsctimes. If not,
+ * see .
+ *
+ *
Authors: see
+ */
+package Client;
+
+import Game.Client;
+import Game.Game;
+import java.applet.Applet;
+import java.awt.AWTEvent;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.awt.event.AWTEventListener;
+import java.awt.event.KeyEvent;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+import javax.swing.JProgressBar;
+import javax.swing.SwingUtilities;
+
+/** Singleton main class which renders a loading window and the game client window. */
+public class Launcher extends JFrame implements Runnable {
+
+ // Singleton
+ private static Launcher instance;
+
+ public static ImageIcon icon = null;
+ public static ImageIcon icon_warn = null;
+
+ private JProgressBar m_progressBar;
+ private JClassLoader m_classLoader;
+
+ private Launcher() {
+ // Empty private constructor to prevent extra instances from being created.
+ }
+
+ /** Renders the launcher progress bar window, then calls {@link #run()}. */
+ public void init() {
+ Logger.start();
+ Logger.Info("Starting rsctimes");
+
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ getContentPane().setBackground(Color.BLACK);
+
+ // Set window icon
+ URL iconURL = getResource("/assets/icon.png");
+ if (iconURL != null) {
+ icon = new ImageIcon(iconURL);
+ setIconImage(icon.getImage());
+ }
+ iconURL = getResource("/assets/icon_warn.png");
+ if (iconURL != null) {
+ icon_warn = new ImageIcon(iconURL);
+ }
+
+ // Set size
+ getContentPane().setPreferredSize(new Dimension(280, 32));
+ setTitle("rsctimes Launcher");
+ setResizable(false);
+ pack();
+ setLocationRelativeTo(null);
+
+ // Add progress bar
+ m_progressBar = new JProgressBar();
+ m_progressBar.setStringPainted(true);
+ m_progressBar.setBorderPainted(true);
+ m_progressBar.setForeground(Color.GRAY.brighter());
+ m_progressBar.setBackground(Color.BLACK);
+ m_progressBar.setString("Initializing");
+ getContentPane().add(m_progressBar);
+
+ setVisible(true);
+ new Thread(this).start();
+ }
+
+ /** Generates a config file if needed and launches the main client window. */
+ @Override
+ public void run() {
+ JConfig config = Game.getInstance().getJConfig();
+ config.create(1);
+
+ m_classLoader = new JClassLoader();
+ if (!m_classLoader.fetch("/assets/mudclient38-recreated.jar")) {
+ error("Unable to fetch Jar");
+ }
+
+ setStatus("Launching game...");
+ Game game = Game.getInstance();
+ try {
+ Class> client = m_classLoader.loadClass("mudclient");
+ game.setApplet((Applet) client.newInstance());
+ Toolkit toolkit = Toolkit.getDefaultToolkit();
+
+ AWTEventListener listener =
+ new AWTEventListener() {
+ public void eventDispatched(AWTEvent event) {
+ if (event instanceof KeyEvent) {
+ KeyEvent evt = (KeyEvent) event;
+ if (evt.getID() == 401) {
+ Client.handler_keyboard.keyPressed(evt);
+ } else if (evt.getID() == 402) {
+ Client.handler_keyboard.keyReleased(evt);
+ }
+ evt.consume();
+ }
+ }
+ };
+ toolkit.addAWTEventListener(listener, AWTEvent.KEY_EVENT_MASK);
+ } catch (Exception e) {
+ e.printStackTrace();
+ error("Unable to launch game");
+ return;
+ }
+ setVisible(false);
+ dispose();
+ game.start();
+ }
+
+ /**
+ * Changes the launcher progress bar text and pauses the thread for 5 seconds.
+ *
+ * @param text the text to change the progress bar text to
+ */
+ public void error(String text) {
+ setStatus("Error: " + text);
+ try {
+ Thread.sleep(5000);
+ System.exit(0);
+ } catch (Exception e) {
+ }
+ }
+
+ /**
+ * Changes the launcher progress bar text.
+ *
+ * @param text the text to change the progress bar text to
+ */
+ public void setStatus(final String text) {
+ SwingUtilities.invokeLater(
+ new Runnable() {
+ @Override
+ public void run() {
+ m_progressBar.setString(text);
+ }
+ });
+ }
+
+ /**
+ * Sets the progress value of the launcher progress bar.
+ *
+ * @param value the number of tasks that have been completed
+ * @param total the total number of tasks to complete
+ */
+ public void setProgress(final int value, final int total) {
+ SwingUtilities.invokeLater(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (total == 0) {
+ m_progressBar.setValue(0);
+ return;
+ }
+
+ m_progressBar.setValue(value * 100 / total);
+ }
+ });
+ }
+
+ public JClassLoader getClassLoader() {
+ return m_classLoader;
+ }
+
+ public static void main(String[] args) {
+ Logger.start();
+ Settings.initDir();
+ /*Properties props = Settings.initSettings();
+
+ if (Settings.javaVersion >= 9) {
+ Logger.Error(
+ "rsc wasn't designed for Java version "
+ + Settings.javaVersion
+ + ". You may encounter additional bugs, for best results use version 8.");
+ } else if (Settings.javaVersion == -1) {
+ Logger.Error(
+ "rsc wasn't designed for your Java version. "
+ + "You may encounter additional bugs, for best results use version 8.");
+ }
+
+ Settings.loadKeybinds(props);
+ Settings.successfullyInitted = true;*/
+ Launcher.getInstance().init();
+ }
+
+ public static Launcher getInstance() {
+ if (instance == null) {
+ synchronized (Launcher.class) {
+ instance = new Launcher();
+ }
+ }
+ return instance;
+ }
+
+ /**
+ * Creates a URL object that points to a specified file relative to the codebase, which is
+ * typically either the jar or location of the package folders.
+ *
+ * @param fileName the file to parse as a URL
+ * @return a URL that points to the specified file
+ */
+ public static URL getResource(String fileName) {
+ URL url = null;
+ try {
+ url = Game.getInstance().getClass().getResource(fileName);
+ } catch (Exception e) {
+ }
+
+ // Try finding assets
+ if (url == null) {
+ try {
+ url = new URL("file://" + Util.findDirectoryReverse("/assets") + fileName);
+ } catch (Exception e) {
+ }
+ }
+
+ Logger.Info("Loading resource: " + fileName);
+
+ return url;
+ }
+
+ /**
+ * Creates an InputStream object that streams the contents of a specified file relative to the
+ * codebase, which is typically either the jar or location of the package folders.
+ *
+ * @param fileName the file to open as an InputStream
+ * @return an InputStream that streams the contents of the specified file
+ */
+ public static InputStream getResourceAsStream(String fileName) {
+ InputStream stream = null;
+ try {
+ stream = Game.getInstance().getClass().getResourceAsStream(fileName);
+ } catch (Exception e) {
+ }
+
+ // Try finding assets
+ if (stream == null) {
+ try {
+ stream = new FileInputStream(Util.findDirectoryReverse("/assets") + fileName);
+ } catch (Exception e) {
+ }
+ }
+
+ Logger.Info("Loading resource as stream: " + fileName);
+
+ if (fileName.equals("/assets/cache/maps14.jag")) {
+ finishedLoading();
+ }
+
+ return stream;
+ }
+
+ public static void finishedLoading() {
+ Game.getInstance().getJConfig().changeWorld(1);
+ }
+}
diff --git a/src/Client/Logger.java b/src/Client/Logger.java
new file mode 100644
index 0000000..7d10fc1
--- /dev/null
+++ b/src/Client/Logger.java
@@ -0,0 +1,76 @@
+/**
+ * rsctimes
+ *
+ * This file is part of rsctimes.
+ *
+ *
rsctimes 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.
+ *
+ *
rsctimes 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 rsctimes. If not,
+ * see .
+ *
+ *
Authors: see
+ */
+package Client;
+
+import java.io.PrintWriter;
+import org.fusesource.jansi.AnsiConsole;
+
+public class Logger {
+ private static PrintWriter m_logWriter;
+
+ public static void start() {
+ AnsiConsole.systemInstall();
+ // File file = new File(Settings.Dir.JAR + "/log.txt");
+ /*try {
+ m_logWriter = new PrintWriter(new FileOutputStream(file));
+ } catch (Exception e) {
+ }*/
+ }
+
+ public static void stop() {
+ /*try {
+ m_logWriter.close();
+ } catch (Exception e) {
+ }*/
+ AnsiConsole.systemUninstall();
+ }
+
+ public enum Type {
+ DEBUG(0),
+ INFO(1),
+ ERROR(2);
+
+ Type(int id) {
+ this.id = id;
+ }
+
+ public int id;
+ };
+
+ public static void Log(Type type, String message) {
+ // if(!Settings.DEBUG && type == Type.DEBUG)
+ // return;
+
+ System.out.println("[" + m_logTypeName[type.id] + "] " + message);
+ }
+
+ public static void Debug(String message) {
+ Log(Type.DEBUG, message);
+ }
+
+ public static void Info(String message) {
+ Log(Type.INFO, message);
+ }
+
+ public static void Error(String message) {
+ Log(Type.ERROR, message);
+ }
+
+ private static final String m_logTypeName[] = {"DEBUG", " INFO", "ERROR"};
+}
diff --git a/src/Client/Settings.java b/src/Client/Settings.java
new file mode 100644
index 0000000..8a9528e
--- /dev/null
+++ b/src/Client/Settings.java
@@ -0,0 +1,51 @@
+/**
+ * rsctimes
+ *
+ * This file is part of rsctimes.
+ *
+ *
rsctimes 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.
+ *
+ *
rsctimes 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 rsctimes. If not,
+ * see .
+ *
+ *
Authors: see
+ */
+package Client;
+
+import Client.Settings.Dir;
+
+public class Settings {
+
+ public static void initDir() { // TODO: Consider moving to a more relevant place
+ // Find JAR directory
+ // TODO: Consider utilizing Util.makeDirectory()
+ Dir.JAR = ".";
+ try {
+ Dir.JAR =
+ Settings.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
+ int indexFileSep1 = Dir.JAR.lastIndexOf('/');
+ int indexFileSep2 = Dir.JAR.lastIndexOf('\\');
+ int index = (indexFileSep1 > indexFileSep2) ? indexFileSep1 : indexFileSep2;
+ if (index != -1) Dir.JAR = Dir.JAR.substring(0, index);
+ } catch (Exception e) {
+ }
+ }
+
+ /** Contains variables which store folder paths. */
+ public static class Dir {
+
+ public static String JAR;
+ public static String DUMP;
+ public static String SCREENSHOT;
+ public static String REPLAY;
+ public static String WORLDS;
+ public static String SPEEDRUN;
+ public static String BANK;
+ }
+}
diff --git a/src/Client/Util.java b/src/Client/Util.java
new file mode 100644
index 0000000..cc1c5eb
--- /dev/null
+++ b/src/Client/Util.java
@@ -0,0 +1,323 @@
+/**
+ * rsctimes
+ *
+ * This file is part of rsctimes.
+ *
+ *
rsctimes 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.
+ *
+ *
rsctimes 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 rsctimes. If not,
+ * see .
+ *
+ *
Authors: see
+ */
+package Client;
+
+import java.awt.Point;
+import java.io.*;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.zip.CRC32;
+
+/** A miscellaneous utility class */
+public class Util {
+
+ /** Stores the world populations in the array indices corresponding to the world numbers */
+ static int[] worldPopArray;
+
+ /** The last time the world populations were checked */
+ static long lastPopCheck = 0;
+
+ public static final float ANGLE_WEST = 0;
+ public static final float ANGLE_SOUTH = 90;
+ public static final float ANGLE_EAST = 180;
+ public static final float ANGLE_NORTH = 270;
+
+ public static String angleNames[] = {"W", "SW", "S", "SE", "E", "NE", "N", "NW"};
+
+ private Util() {
+ // Empty private constructor to prevent instantiation.
+ }
+
+ public static float lengthdir_x(float dist, float angle) {
+ return dist * (float) Math.cos(Math.toRadians(angle));
+ }
+
+ public static float lengthdir_y(float dist, float angle) {
+ return dist * (float) -Math.sin(Math.toRadians(angle));
+ }
+
+ public static float lerp(float a, float b, float c) {
+ return a + c * (b - a);
+ }
+
+ public static float getAngle(Point source, Point target) {
+ float angle = (float) Math.toDegrees(Math.atan2(target.y - source.y, target.x - source.x));
+
+ if (angle < 0) angle += 360;
+
+ return angle;
+ }
+
+ public static String readString(InputStream inputStream) throws IOException {
+ ByteArrayOutputStream into = new ByteArrayOutputStream();
+ byte[] buf = new byte[4096];
+ for (int n; 0 < (n = inputStream.read(buf)); ) {
+ into.write(buf, 0, n);
+ }
+ into.close();
+ return new String(into.toByteArray(), "UTF-8"); // Or whatever encoding
+ }
+
+ public static String getAngleDirectionName(float angle) {
+ return angleNames[getAngleIndex(angle)];
+ }
+
+ public static int getAngleIndex(float angle) {
+ int index = (int) ((angle / (360.0f / 8.0f)) + 0.5f);
+ return index % 8;
+ }
+
+ public static String findDirectoryReverse(String name) {
+ String ret = Settings.Dir.JAR;
+
+ for (int i = 0; i < 8; i++) {
+ File file = new File(ret + name);
+ if (file.exists() && file.isDirectory()) return ret;
+ ret += "/..";
+ }
+
+ return Settings.Dir.JAR;
+ }
+
+ public static boolean isMacOS() {
+ String os = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
+ return (os.contains("mac") || os.contains("darwin"));
+ }
+
+ public static String formatTimeDuration(int millis, int endMillis) {
+ int seconds = (millis / 1000) % 60;
+ int minutes = (millis / 1000 / 60) % 60;
+ int hours = millis / 1000 / 60 / 60;
+
+ int endHours = endMillis / 1000 / 60 / 60;
+
+ String ret = "";
+ if (endHours != 0)
+ ret += ((hours < 10) ? "0" + Integer.toString(hours) : Integer.toString(hours)) + ":";
+ ret += ((minutes < 10) ? "0" + Integer.toString(minutes) : Integer.toString(minutes)) + ":";
+ ret += ((seconds < 10) ? "0" + Integer.toString(seconds) : Integer.toString(seconds));
+ return ret;
+ }
+
+ public static String formatTimeLongShort(int fiftythsOfSecond) {
+ int total_centiseconds = fiftythsOfSecond * 2; // 50fps * 2; converts to hundreths of a second
+ int leftover_centiseconds = total_centiseconds % 100;
+ int total_seconds = (total_centiseconds - leftover_centiseconds) / 100;
+ int leftover_seconds = total_seconds % 60;
+ int total_minutes = (total_seconds - leftover_seconds) / 60;
+ int leftover_minutes = total_minutes % 60;
+ int total_hours = (total_minutes - leftover_minutes) / 60;
+ int leftover_hours = total_hours % 24;
+ int total_days = (total_hours - leftover_hours) / 24;
+
+ if (total_days > 0) {
+ return (String.format(
+ "%d day%s %d:%02d:%02d",
+ total_days,
+ total_days == 1 ? "" : "s",
+ leftover_hours,
+ leftover_minutes,
+ leftover_seconds));
+ } else if (total_hours > 0) {
+ return (String.format("%d:%02d:%02d", total_hours, leftover_minutes, leftover_seconds));
+ } else if (leftover_minutes > 0) {
+ return (String.format("%d:%02d", leftover_minutes, leftover_seconds));
+ } else {
+ return (String.format("%d.%02d�", leftover_seconds, leftover_centiseconds));
+ }
+ }
+
+ /**
+ * Gets the CRC32 of a given file name.
+ *
+ * @param fname Path to the file
+ * @return CRC32 of the file data
+ */
+ public static long fileGetCRC32(String fname) {
+ try {
+ byte[] data = Files.readAllBytes(new File(fname).toPath());
+ CRC32 crc = new CRC32();
+ crc.update(data);
+ return crc.getValue();
+ } catch (Exception e) {
+ }
+
+ return -1;
+ }
+
+ /**
+ * Creates a directory relative to codebase, which is typically either the jar or location of the
+ * package folders.
+ *
+ * @param name the name of the folder to create
+ */
+ public static void makeDirectory(String name) {
+ File dir = new File(name);
+ if (dir.isFile()) dir.delete();
+ if (!dir.exists()) dir.mkdir();
+ }
+
+ /**
+ * Converts a byte array into a String of 2 digit hexadecimal numbers.
+ *
+ * @param data a byte array to convert
+ * @return a String of hexadecimal numbers
+ * @see #hexStringByte
+ */
+ public static String byteHexString(byte[] data) {
+ String ret = "";
+ for (int i = 0; i < data.length; i++) ret += String.format("%02x", data[i]);
+ return ret;
+ }
+
+ /**
+ * Converts a String of 2 digit hexadecimal numbers into a byte array.
+ *
+ * @param data a String to convert
+ * @return a byte array
+ * @see #byteHexString
+ */
+ public static byte[] hexStringByte(String data) {
+ byte[] bytes = new byte[data.length() / 2];
+ int j;
+ for (int i = 0; i < bytes.length; i++) {
+ j = i * 2;
+ String hex_pair = data.substring(j, j + 2);
+ byte b = (byte) (Integer.parseInt(hex_pair, 16) & 0xFF);
+ bytes[i] = b;
+ }
+ return bytes;
+ }
+
+ /**
+ * Polyfill for Java 8 `String.join`
+ *
+ * Convert an arraylist of strings to a single string, where each element is separated by some
+ * deliminator.
+ *
+ * @param delim The string to use when combining elements
+ * @param list The list to combine
+ * @return The string of the arraylist
+ */
+ public static String joinAsString(String delim, ArrayList list) {
+ StringBuilder sb = new StringBuilder();
+ for (String s : list) {
+ sb.append(s);
+ sb.append(delim);
+ }
+ return sb.toString();
+ }
+
+ // recurse through directory to get all folders
+ public static void listf(String directoryName, List files) {
+ File directory = new File(directoryName);
+
+ File[] fList = directory.listFiles();
+ if (fList != null) {
+ for (File file : fList) {
+ if (file.isDirectory()) {
+ listf(file.getAbsolutePath(), files);
+ files.add(file);
+ }
+ }
+ }
+ }
+
+ /** */
+ public static long username2hash(String s) {
+ String s1 = "";
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c >= 'a' && c <= 'z') s1 = s1 + c;
+ else if (c >= 'A' && c <= 'Z') s1 = s1 + (char) ((c + 97) - 65);
+ else if (c >= '0' && c <= '9') s1 = s1 + c;
+ else s1 = s1 + ' ';
+ }
+
+ s1 = s1.trim();
+ if (s1.length() > 12) s1 = s1.substring(0, 12);
+ long hash = 0L;
+ for (int j = 0; j < s1.length(); j++) {
+ char c1 = s1.charAt(j);
+ hash *= 37L;
+ if (c1 >= 'a' && c1 <= 'z') hash += (1 + c1) - 97;
+ else if (c1 >= '0' && c1 <= '9') hash += (27 + c1) - 48;
+ }
+
+ return hash;
+ }
+
+ /** */
+ public static String hash2username(long hash) {
+ if (hash < 0L) return "invalidName";
+ String s = "";
+ while (hash != 0L) {
+ int i = (int) (hash % 37L);
+ hash /= 37L;
+ if (i == 0) s = " " + s;
+ else if (i < 27) {
+ if (hash % 37L == 0L) s = (char) ((i + 65) - 1) + s;
+ else s = (char) ((i + 97) - 1) + s;
+ } else {
+ s = (char) ((i + 48) - 27) + s;
+ }
+ }
+ return s;
+ }
+
+ /** put an int into buffer at specific offset */
+ public static void int_put(byte[] buffer, int offset, int num) {
+ buffer[offset] = (byte) (num >> 24);
+ buffer[offset + 1] = (byte) (num >> 16);
+ buffer[offset + 2] = (byte) (num >> 8);
+ buffer[offset + 3] = (byte) num;
+ }
+
+ /** format a string to only have letters and numbers, with maxlength */
+ public static String formatString(String s, int maxLen) {
+ String lowerString = s.toLowerCase();
+ String res = "";
+ for (int i = 0; i < lowerString.length() && i < maxLen; ++i) {
+ char ch = lowerString.charAt(i);
+ if (ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9') {
+ res = String.valueOf(res) + ch;
+ }
+ }
+ return res;
+ }
+
+ public static int boundUnsignedShort(String num) throws NumberFormatException {
+ int result;
+ int limit = Short.MAX_VALUE - Short.MIN_VALUE;
+ try {
+ result = Integer.parseInt(num);
+ if (result < 0) {
+ return 0;
+ } else if (result >= 0 && result <= limit) {
+ return result;
+ } else {
+ return limit;
+ }
+ } catch (NumberFormatException nfe) {
+ throw nfe;
+ }
+ }
+}
diff --git a/src/Game/Client.java b/src/Game/Client.java
new file mode 100644
index 0000000..8687b5a
--- /dev/null
+++ b/src/Game/Client.java
@@ -0,0 +1,45 @@
+/**
+ * rsctimes
+ *
+ * This file is part of rsctimes.
+ *
+ *
rsctimes 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.
+ *
+ *
rsctimes 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 rsctimes. If not,
+ * see .
+ *
+ *
Authors: see
+ */
+package Game;
+
+import java.applet.Applet;
+
+public class Client {
+
+ // Game's client instance
+ public static Object instance;
+
+ public static MouseHandler handler_mouse;
+ public static KeyboardHandler handler_keyboard;
+
+ public static int connection_port = 43594;
+
+ public static void init() {
+
+ handler_mouse = new MouseHandler();
+ handler_keyboard = new KeyboardHandler();
+
+ Applet applet = Game.getInstance().getApplet();
+ applet.addMouseListener(handler_mouse);
+ applet.addMouseMotionListener(handler_mouse);
+ applet.addMouseWheelListener(handler_mouse);
+ applet.addKeyListener(handler_keyboard);
+ applet.setFocusTraversalKeysEnabled(false);
+ }
+}
diff --git a/src/Game/Game.java b/src/Game/Game.java
new file mode 100644
index 0000000..bc88616
--- /dev/null
+++ b/src/Game/Game.java
@@ -0,0 +1,261 @@
+/**
+ * rsctimes
+ *
+ * This file is part of rsctimes.
+ *
+ *
rsctimes 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.
+ *
+ *
rsctimes 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 rsctimes. If not,
+ * see .
+ *
+ *
Authors: see
+ */
+package Game;
+
+import Client.JConfig;
+import Client.Launcher;
+import Client.Logger;
+import Client.Util;
+import java.applet.Applet;
+import java.applet.AppletContext;
+import java.applet.AppletStub;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.event.*;
+import java.net.URL;
+import javax.swing.JFrame;
+
+/** Singleton class that handles packaging the client into a JFrame and starting the applet. */
+public class Game extends JFrame
+ implements AppletStub, ComponentListener, WindowListener, FocusListener {
+
+ // Singleton
+ private static Game instance = null;
+
+ private JConfig m_config = new JConfig();
+ private Applet m_applet = null;
+ private String m_title = "";
+
+ private Game() {
+ // Empty private constructor to prevent extra instances from being created.
+ }
+
+ public void setApplet(Applet applet) {
+ m_applet = applet;
+ m_applet.setStub(this);
+ }
+
+ public Applet getApplet() {
+ return m_applet;
+ }
+
+ /** Builds the main game client window and adds the applet to it. */
+ public void start() {
+ if (m_applet == null) return;
+
+ // m_applet.addMouseListener(new MouseHandler());
+ // Set window icon
+ setIconImage(Launcher.icon.getImage());
+
+ // Set window properties
+ setResizable(true);
+ addWindowListener(this);
+ setMinimumSize(new Dimension(1, 1));
+
+ // Add applet to window
+ setContentPane(m_applet);
+ getContentPane().setBackground(Color.BLACK);
+ getContentPane().setPreferredSize(new Dimension(512, 346));
+ addComponentListener(this);
+ pack();
+
+ // Hide cursor if software cursor
+ // Settings.checkSoftwareCursor();
+
+ // Position window and make it visible
+ setLocationRelativeTo(null);
+ setVisible(true);
+
+ updateTitle();
+
+ Reflection.Load();
+ Game.getInstance().resizeFrameWithContents();
+ /*Renderer.init();
+
+ if (!Util.isMacOS() && Settings.CUSTOM_CLIENT_SIZE.get(Settings.currentProfile)) {
+ Game.getInstance().resizeFrameWithContents();
+ }*/
+ }
+
+ public JConfig getJConfig() {
+ return m_config;
+ }
+
+ /** Starts the game applet. */
+ public void launchGame() {
+ m_applet.addFocusListener(this);
+ m_applet.init();
+ m_applet.start();
+ }
+
+ public void updateTitle() {
+ String title = "rsctimes";
+ ;
+
+ if (m_title.equals(title)) {
+ return;
+ }
+ m_title = title;
+ super.setTitle(m_title);
+ }
+
+ /*
+ * FocusListener methods
+ */
+
+ @Override
+ public final void focusGained(FocusEvent e) {}
+
+ @Override
+ public final void focusLost(FocusEvent e) {
+ /*KeyboardHandler.keyUp = false;
+ KeyboardHandler.keyDown = false;
+ KeyboardHandler.keyLeft = false;
+ KeyboardHandler.keyRight = false;
+ KeyboardHandler.keyShift = false;*/
+ }
+
+ /*
+ * AppletStub methods
+ */
+
+ @Override
+ public final URL getCodeBase() {
+ return m_config.getURL("codebase");
+ }
+
+ @Override
+ public final URL getDocumentBase() {
+ return getCodeBase();
+ }
+
+ @Override
+ public final String getParameter(String key) {
+ return m_config.parameters.get(key);
+ }
+
+ @Override
+ public final AppletContext getAppletContext() {
+ return null;
+ }
+
+ @Override
+ public final void appletResize(int width, int height) {}
+
+ /*
+ * WindowListener methods
+ */
+
+ @Override
+ public final void windowClosed(WindowEvent e) {
+ if (m_applet == null) return;
+
+ m_applet.stop();
+ m_applet.destroy();
+
+ Logger.stop();
+ }
+
+ @Override
+ public final void windowClosing(WindowEvent e) {
+ dispose();
+ }
+
+ @Override
+ public final void windowOpened(WindowEvent e) {}
+
+ @Override
+ public final void windowDeactivated(WindowEvent e) {}
+
+ @Override
+ public final void windowActivated(WindowEvent e) {}
+
+ @Override
+ public final void windowDeiconified(WindowEvent e) {}
+
+ @Override
+ public final void windowIconified(WindowEvent e) {}
+
+ /*
+ * ComponentListener methods
+ */
+
+ @Override
+ public final void componentHidden(ComponentEvent e) {}
+
+ @Override
+ public final void componentMoved(ComponentEvent e) {}
+
+ @Override
+ public final void componentResized(ComponentEvent e) {
+ if (m_applet == null) return;
+
+ // Handle minimum size and launch game
+ // TODO: This is probably a bad spot and should be moved
+ if (getMinimumSize().width == 1) {
+ setMinimumSize(getSize());
+ launchGame();
+
+ // This workaround appears to be for a bug in the macOS JVM
+ // Without it, mac users get very angry
+ if (Util.isMacOS()) {
+ setExtendedState(getExtendedState() | JFrame.MAXIMIZED_BOTH);
+ setLocationRelativeTo(null);
+ }
+ }
+
+ // Renderer.resize(getContentPane().getWidth(), getContentPane().getHeight());
+ }
+
+ @Override
+ public final void componentShown(ComponentEvent e) {}
+
+ /**
+ * Gets the game client instance. It makes one if one doesn't exist.
+ *
+ * @return The game client instance
+ */
+ public static Game getInstance() {
+ if (instance == null) {
+ synchronized (Game.class) {
+ instance = new Game();
+ }
+ }
+ return instance;
+ }
+
+ /**
+ * Resizes the Game window to match the X and Y values stored in Settings. The applet's size will
+ * be recalculated on the next rendering tick.
+ */
+ public void resizeFrameWithContents() {
+ /*int windowWidth =
+ Settings.CUSTOM_CLIENT_SIZE_X.get(Settings.currentProfile)
+ + getInsets().left
+ + getInsets().right;
+ int windowHeight =
+ Settings.CUSTOM_CLIENT_SIZE_Y.get(Settings.currentProfile)
+ + getInsets().top
+ + getInsets().bottom;*/
+ int windowWidth = 512;
+ int windowHeight = 396;
+ setSize(windowWidth, windowHeight);
+ setLocationRelativeTo(null);
+ }
+}
diff --git a/src/Game/KeyboardHandler.java b/src/Game/KeyboardHandler.java
new file mode 100644
index 0000000..960b48f
--- /dev/null
+++ b/src/Game/KeyboardHandler.java
@@ -0,0 +1,74 @@
+/**
+ * rsctimes
+ *
+ * This file is part of rsctimes.
+ *
+ *
rsctimes 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.
+ *
+ *
rsctimes 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 rsctimes. If not,
+ * see .
+ *
+ *
Authors: see
+ */
+package Game;
+
+import java.awt.Event;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.lang.reflect.InvocationTargetException;
+
+/** Listens to keyboard events to trigger specified operations */
+public class KeyboardHandler implements KeyListener {
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (!e.isConsumed()) {
+ try {
+ Event evt = new Event(Client.instance, 401, e);
+ int converted = KeyboardMapHelper.convert(e.getKeyCode());
+ int n =
+ converted == e.getExtendedKeyCode()
+ ? e.getKeyChar()
+ : converted; // with getKeyCode() is always placing letter to upper
+ Reflection.keyDown.invoke(Client.instance, evt, n);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ e.consume();
+ }
+ }
+
+ @Override
+ public void keyReleased(KeyEvent e) {
+ if (!e.isConsumed()) {
+ try {
+ Event evt = new Event(Client.instance, 402, e);
+ int converted = KeyboardMapHelper.convert(e.getKeyCode());
+ int n =
+ converted == e.getExtendedKeyCode()
+ ? e.getKeyChar()
+ : converted; // with getKeyCode() is always placing letter to upper
+ Reflection.keyUp.invoke(Client.instance, evt, n);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ e.consume();
+ }
+ }
+
+ @Override
+ public void keyTyped(KeyEvent e) {
+ System.out.println("Key typed...");
+ /*if (!e.isConsumed()) {
+ e.consume();
+ }*/
+ }
+}
diff --git a/src/Game/KeyboardMapHelper.java b/src/Game/KeyboardMapHelper.java
new file mode 100644
index 0000000..598d470
--- /dev/null
+++ b/src/Game/KeyboardMapHelper.java
@@ -0,0 +1,59 @@
+/**
+ * rsctimes
+ *
+ * This file is part of rsctimes.
+ *
+ *
rsctimes 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.
+ *
+ *
rsctimes 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 rsctimes. If not,
+ * see .
+ *
+ *
Authors: see
+ */
+package Game;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class KeyboardMapHelper {
+
+ private static final Map map =
+ new HashMap() {
+ {
+ put(19, 1024); // pause/break
+ put(20, 1022); // caps lock
+ put(33, 1002); // page up
+ put(34, 1003); // page down
+ put(35, 1001); // end
+ put(36, 1000); // home
+ put(37, 1006); // left arrow
+ put(38, 1004); // up arrow
+ put(39, 1007); // right arrow
+ put(40, 1005); // down arrow
+ put(112, 1008); // f1
+ put(113, 1009); // f2
+ put(114, 1010); // f3
+ put(115, 1011); // f4
+ put(116, 1012); // f5
+ put(117, 1013); // f6
+ put(118, 1014); // f7
+ put(119, 1015); // f8
+ put(120, 1016); // f9
+ put(121, 1017); // f10
+ put(122, 1018); // f11
+ put(123, 1019); // f12
+ put(144, 1023); // num lock
+ put(145, 1021); // scroll lock
+ }
+ };
+
+ public static Integer convert(int modernId) {
+ return map.getOrDefault(modernId, modernId);
+ }
+}
diff --git a/src/Game/MouseHandler.java b/src/Game/MouseHandler.java
new file mode 100644
index 0000000..5ece5ab
--- /dev/null
+++ b/src/Game/MouseHandler.java
@@ -0,0 +1,169 @@
+/**
+ * rsctimes
+ *
+ * This file is part of rsctimes.
+ *
+ *
rsctimes 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.
+ *
+ *
rsctimes 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 rsctimes. If not,
+ * see .
+ *
+ *
Authors: see
+ */
+package Game;
+
+import java.awt.Event;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.lang.reflect.InvocationTargetException;
+import javax.swing.SwingUtilities;
+
+/** Listens to mouse events and stores relevant information about them */
+public class MouseHandler implements MouseListener, MouseMotionListener, MouseWheelListener {
+
+ public static int x = 0;
+ public static int y = 0;
+ public static boolean mouseClicked = false;
+ public static boolean rightClick = false;
+
+ private boolean m_rotating = false;
+ private Point m_rotatePosition;
+ private float m_rotateX = 0.0f;
+
+ public static boolean inBounds(Rectangle bounds) {
+ if (bounds == null) return false;
+ return false;
+ }
+
+ public boolean inConsumableButton() {
+ return false;
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (inConsumableButton()) {
+ e.consume();
+ }
+
+ if (!e.isConsumed()) {
+ x = e.getX();
+ y = e.getY();
+ e.consume();
+ }
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ if (!e.isConsumed()) {
+ x = e.getX();
+ y = e.getY();
+ e.consume();
+ }
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (!e.isConsumed()) {
+ x = -100;
+ y = -100;
+ e.consume();
+ }
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (inConsumableButton()) {
+ e.consume();
+ }
+
+ if (!e.isConsumed()) {
+ x = e.getX();
+ y = e.getY();
+ try {
+ Event evt =
+ new Event(
+ Client.instance, e.getWhen(), 501, e.getX(), e.getY(), 0, e.getModifiers(), e);
+ Reflection.mouseDown.invoke(Client.instance, evt, x, y);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ e.consume();
+ }
+
+ mouseClicked = true;
+ rightClick = SwingUtilities.isRightMouseButton(e);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ if (inConsumableButton()) {
+ e.consume();
+ }
+
+ if (!e.isConsumed()) {
+ x = e.getX();
+ y = e.getY();
+ try {
+ Event evt =
+ new Event(
+ Client.instance, e.getWhen(), 502, e.getX(), e.getY(), 0, e.getModifiers(), e);
+ Reflection.mouseUp.invoke(Client.instance, evt, x, y);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ e.consume();
+ }
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ if (!e.isConsumed()) {
+ x = e.getX();
+ y = e.getY();
+ try {
+ Event evt = new Event(Client.instance, 506, e);
+ Reflection.mouseDrag.invoke(Client.instance, evt, x, y);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ e.consume();
+ }
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ if (!e.isConsumed()) {
+ x = e.getX();
+ y = e.getY();
+ try {
+ Event evt = new Event(Client.instance, 503, e);
+ Reflection.mouseMove.invoke(Client.instance, evt, x, y);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ e.consume();
+ }
+ }
+
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ x = e.getX();
+ y = e.getY();
+ e.consume();
+ }
+}
diff --git a/src/Game/Reflection.java b/src/Game/Reflection.java
new file mode 100644
index 0000000..c9d5463
--- /dev/null
+++ b/src/Game/Reflection.java
@@ -0,0 +1,99 @@
+/**
+ * rsctimes
+ *
+ * This file is part of rsctimes.
+ *
+ *
rsctimes 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.
+ *
+ *
rsctimes 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 rsctimes. If not,
+ * see .
+ *
+ *
Authors: see
+ */
+package Game;
+
+import Client.JClassLoader;
+import Client.Launcher;
+import Client.Logger;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+public class Reflection {
+
+ // Method descriptions
+ private static final String MOUSE_MOVE =
+ "public synchronized boolean jagex.client.k.mouseMove(java.awt.Event,int,int)";
+ private static final String MOUSE_DRAG =
+ "public synchronized boolean jagex.client.k.mouseDrag(java.awt.Event,int,int)";
+ private static final String MOUSE_UP =
+ "public synchronized boolean jagex.client.k.mouseUp(java.awt.Event,int,int)";
+ private static final String MOUSE_DOWN =
+ "public synchronized boolean jagex.client.k.mouseDown(java.awt.Event,int,int)";
+
+ private static final String KEY_UP =
+ "public synchronized boolean jagex.client.k.keyUp(java.awt.Event,int)";
+ private static final String KEY_DOWN =
+ "public synchronized boolean jagex.client.k.keyDown(java.awt.Event,int)";
+
+ private static final String GAME_FRAME = "public java.awt.Frame jagex.client.k.ij()";
+
+ public static Method mouseMove = null;
+ public static Method mouseDrag = null;
+ public static Method mouseUp = null;
+ public static Method mouseDown = null;
+ public static Method keyUp = null;
+ public static Method keyDown = null;
+ public static Method gameFrame = null;
+
+ public static void Load() {
+ try {
+ JClassLoader classLoader = Launcher.getInstance().getClassLoader();
+ ArrayList leftMethods =
+ new ArrayList(); // expected virtual methods to find in given class
+
+ Class> c = classLoader.loadClass("jagex.client.k");
+ Method[] methods = c.getDeclaredMethods();
+ for (Method method : methods) {
+ if (method.toGenericString().equals(MOUSE_MOVE)) {
+ mouseMove = method;
+ Logger.Info("Found mouseMove");
+ } else if (method.toGenericString().equals(MOUSE_DRAG)) {
+ mouseDrag = method;
+ Logger.Info("Found mouseDrag");
+ } else if (method.toGenericString().equals(MOUSE_UP)) {
+ mouseUp = method;
+ Logger.Info("Found mouseUp");
+ } else if (method.toGenericString().equals(MOUSE_DOWN)) {
+ mouseDown = method;
+ Logger.Info("Found mouseDown");
+ } else if (method.toGenericString().equals(KEY_UP)) {
+ keyUp = method;
+ Logger.Info("Found keyUp");
+ } else if (method.toGenericString().equals(KEY_DOWN)) {
+ keyDown = method;
+ Logger.Info("Found keyDown");
+ } else if (method.toGenericString().equals(GAME_FRAME)) {
+ gameFrame = method;
+ Logger.Info("Found gameFrame");
+ }
+ }
+
+ // Set all accessible
+ if (mouseMove != null) mouseMove.setAccessible(true);
+ if (mouseDrag != null) mouseDrag.setAccessible(true);
+ if (mouseUp != null) mouseUp.setAccessible(true);
+ if (mouseDown != null) mouseDown.setAccessible(true);
+ if (keyUp != null) keyUp.setAccessible(true);
+ if (keyDown != null) keyDown.setAccessible(true);
+ if (gameFrame != null) gameFrame.setAccessible(true);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/Game/Utility.java b/src/Game/Utility.java
new file mode 100644
index 0000000..8a6c854
--- /dev/null
+++ b/src/Game/Utility.java
@@ -0,0 +1,34 @@
+/**
+ * rsctimes
+ *
+ * This file is part of rsctimes.
+ *
+ *
rsctimes 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.
+ *
+ *
rsctimes 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 rsctimes. If not,
+ * see .
+ *
+ *
Authors: see
+ */
+package Game;
+
+import Client.Launcher;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class Utility {
+
+ public static InputStream openStream(String file) throws IOException {
+ if (file != null) {
+ return Launcher.getResourceAsStream("/assets/cache/" + file);
+ } else {
+ throw new IOException(file + " not found!");
+ }
+ }
+}
diff --git a/tools/google-java-format-1.6-all-deps.jar b/tools/google-java-format-1.6-all-deps.jar
new file mode 100644
index 0000000..ce02f37
Binary files /dev/null and b/tools/google-java-format-1.6-all-deps.jar differ