diff --git a/cache/pom.xml b/cache/pom.xml index a7dcc973ff..6d590fc0bc 100644 --- a/cache/pom.xml +++ b/cache/pom.xml @@ -36,6 +36,10 @@ cache Cache + + 4.6 + + net.runelite @@ -78,6 +82,11 @@ netty-all 4.1.0.Final + + org.antlr + antlr4-runtime + ${antlr4.version} + junit @@ -92,7 +101,6 @@ org.apache.maven.plugins maven-surefire-plugin - 2.16 true -Xmx2048m @@ -115,6 +123,19 @@ + + org.antlr + antlr4-maven-plugin + ${antlr4.version} + + + process-resources + + antlr4 + + + + diff --git a/cache/src/main/antlr4/net/runelite/cache/script/assembler/rs2asm.g4 b/cache/src/main/antlr4/net/runelite/cache/script/assembler/rs2asm.g4 new file mode 100644 index 0000000000..fdd89213f6 --- /dev/null +++ b/cache/src/main/antlr4/net/runelite/cache/script/assembler/rs2asm.g4 @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +grammar rs2asm; + +prog: (line NEWLINE)+ ; +line: instruction | label ; +instruction: instruction_name instruction_operand ; +label: 'LABEL' INT ':' ; + +instruction_name: name_string | name_opcode ; +name_string: INSTRUCTION ; +name_opcode: INT ; + +instruction_operand: operand_int | operand_qstring | operand_label | ; +operand_int: INT ; +operand_qstring: QSTRING ; +operand_label: 'LABEL' INT ; + +NEWLINE: '\n'+ ; +INT: '-'? [0-9]+ ; +QSTRING: '"' (~('"' | '\\' | '\r' | '\n') | '\\' ('"' | '\\'))* '"' ; +INSTRUCTION: [a-z0-9_]+ ; + +WS: (' ' | '\t')+ -> channel(HIDDEN) ; \ No newline at end of file diff --git a/cache/src/main/java/net/runelite/cache/script/Instructions.java b/cache/src/main/java/net/runelite/cache/script/Instructions.java index 1bfb845e40..83acf358f6 100644 --- a/cache/src/main/java/net/runelite/cache/script/Instructions.java +++ b/cache/src/main/java/net/runelite/cache/script/Instructions.java @@ -30,6 +30,7 @@ import java.util.Map; public class Instructions { private static final Map instructions = new HashMap<>(); + private static final Map instructionsByName = new HashMap<>(); public static void init() { @@ -443,6 +444,12 @@ public class Instructions assert instructions.containsKey(opcode) == false; instructions.put(opcode, i); + + if (name != null) + { + assert instructionsByName.containsKey(name) == false; + instructionsByName.put(name, i); + } } private static void add(int opcode, int ipops, int ipushes) @@ -464,4 +471,9 @@ public class Instructions { return instructions.get(opcode); } + + public static Instruction find(String name) + { + return instructionsByName.get(name); + } } diff --git a/cache/src/main/java/net/runelite/cache/script/assembler/Assembler.java b/cache/src/main/java/net/runelite/cache/script/assembler/Assembler.java new file mode 100644 index 0000000000..3cd20f43ba --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/script/assembler/Assembler.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.script.assembler; + +import java.io.IOException; +import java.io.InputStream; +import net.runelite.cache.definitions.ScriptDefinition; +import net.runelite.cache.script.Instructions; +import net.runelite.cache.script.assembler.rs2asmParser.ProgContext; +import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTreeWalker; + +public class Assembler +{ + public ScriptDefinition assemble(InputStream in) throws IOException + { + Instructions.init(); + + // Get our lexer + rs2asmLexer lexer = new rs2asmLexer(new ANTLRInputStream(in)); + + LexerErrorListener errorListener = new LexerErrorListener(); + lexer.addErrorListener(errorListener); + + // Get a list of matched tokens + CommonTokenStream tokens = new CommonTokenStream(lexer); + + // Pass the tokens to the parser + rs2asmParser parser = new rs2asmParser(tokens); + + // Specify our entry point + ProgContext progContext = parser.prog(); + + if (errorListener.getErrors() > 0) + { + throw new RuntimeException("syntax error"); + } + + // Walk it and attach our listener + ParseTreeWalker walker = new ParseTreeWalker(); + + // walk through first and resolve labels + LabelVisitor labelVisitor = new LabelVisitor(); + walker.walk(labelVisitor, progContext); + + ScriptWriter listener = new ScriptWriter(labelVisitor); + walker.walk(listener, progContext); + + return listener.buildScript(); + } +} diff --git a/cache/src/main/java/net/runelite/cache/script/assembler/LabelVisitor.java b/cache/src/main/java/net/runelite/cache/script/assembler/LabelVisitor.java new file mode 100644 index 0000000000..b78cfd6523 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/script/assembler/LabelVisitor.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.script.assembler; + +import java.util.HashMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LabelVisitor extends rs2asmBaseListener +{ + private static final Logger logger = LoggerFactory.getLogger(LabelVisitor.class); + + private int pos; + private final Map map = new HashMap<>(); + + @Override + public void exitInstruction(rs2asmParser.InstructionContext ctx) + { + ++pos; + } + + @Override + public void enterLabel(rs2asmParser.LabelContext ctx) + { + String text = ctx.getText(); + text = text.substring(0, text.length() - 1); // remove trailing : + + logger.debug("Label {} is on instruction {}", text, pos); + + map.put(text, pos); + } + + public Integer getInstructionForLabel(String label) + { + return map.get(label); + } +} diff --git a/cache/src/main/java/net/runelite/cache/script/assembler/LexerErrorListener.java b/cache/src/main/java/net/runelite/cache/script/assembler/LexerErrorListener.java new file mode 100644 index 0000000000..bd3463a946 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/script/assembler/LexerErrorListener.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.script.assembler; + +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; + +public class LexerErrorListener extends BaseErrorListener +{ + private int errors; + + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) + { + ++errors; + } + + public int getErrors() + { + return errors; + } + +} diff --git a/cache/src/main/java/net/runelite/cache/script/assembler/ScriptWriter.java b/cache/src/main/java/net/runelite/cache/script/assembler/ScriptWriter.java new file mode 100644 index 0000000000..119afe155a --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/script/assembler/ScriptWriter.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.script.assembler; + +import java.util.ArrayList; +import java.util.List; +import net.runelite.cache.definitions.ScriptDefinition; +import net.runelite.cache.script.Instruction; +import net.runelite.cache.script.Instructions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ScriptWriter extends rs2asmBaseListener +{ + private static final Logger logger = LoggerFactory.getLogger(ScriptWriter.class); + + private final LabelVisitor labelVisitor; + + private int pos; + private List opcodes = new ArrayList<>(); + private List iops = new ArrayList<>(); + private List sops = new ArrayList<>(); + + public ScriptWriter(LabelVisitor labelVisitor) + { + this.labelVisitor = labelVisitor; + } + + @Override + public void exitInstruction(rs2asmParser.InstructionContext ctx) + { + ++pos; + } + + @Override + public void enterName_string(rs2asmParser.Name_stringContext ctx) + { + String text = ctx.getText(); + Instruction i = Instructions.find(text); + if (i == null) + { + logger.warn("Unknown instruction {}", text); + throw new RuntimeException("Unknown instruction " + text); + } + + int opcode = i.getOpcode(); + addOpcode(opcode); + } + + @Override + public void enterName_opcode(rs2asmParser.Name_opcodeContext ctx) + { + String text = ctx.getText(); + int opcode = Integer.parseInt(text); + addOpcode(opcode); + } + + private void addOpcode(int opcode) + { + assert opcodes.size() == pos; + assert iops.size() == pos; + assert sops.size() == pos; + + opcodes.add(opcode); + iops.add(null); + sops.add(null); + } + + @Override + public void enterOperand_int(rs2asmParser.Operand_intContext ctx) + { + String text = ctx.getText(); + int value = Integer.parseInt(text); + iops.set(pos, value); + } + + @Override + public void enterOperand_qstring(rs2asmParser.Operand_qstringContext ctx) + { + String text = ctx.getText(); + text = text.substring(1, text.length() - 1); + sops.set(pos, text); + } + + @Override + public void enterOperand_label(rs2asmParser.Operand_labelContext ctx) + { + String text = ctx.getText(); + Integer instruction = labelVisitor.getInstructionForLabel(text); + if (instruction == null) + { + throw new RuntimeException("reference to unknown label " + text); + } + + int target = instruction - pos - 1; // -1 to go to the instruction prior + iops.set(pos, target); + } + + public ScriptDefinition buildScript() + { + ScriptDefinition script = new ScriptDefinition(); + script.setInstructions(opcodes.stream().mapToInt(Integer::valueOf).toArray()); + script.setIntOperands(iops.stream() + .map(i -> i == null ? 0 : i) + .mapToInt(Integer::valueOf) + .toArray()); + script.setStringOperands(sops.toArray(new String[0])); + return script; + } +} diff --git a/cache/src/main/java/net/runelite/cache/script/disassembler/Disassembler.java b/cache/src/main/java/net/runelite/cache/script/disassembler/Disassembler.java new file mode 100644 index 0000000000..fd8910a247 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/script/disassembler/Disassembler.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.script.disassembler; + +import java.io.IOException; +import net.runelite.cache.definitions.ScriptDefinition; +import net.runelite.cache.script.Instruction; +import net.runelite.cache.script.Instructions; +import net.runelite.cache.script.Opcodes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Disassembler +{ + private static final Logger logger = LoggerFactory.getLogger(Disassembler.class); + + private boolean isJump(int opcode) + { + switch (opcode) + { + case Opcodes.JUMP: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPNE: + return true; + default: + return false; + } + } + + private boolean[] needLabel(ScriptDefinition script) + { + int[] instructions = script.getInstructions(); + int[] iop = script.getIntOperands(); + boolean[] jumped = new boolean[instructions.length]; + + for (int i = 0; i < instructions.length; ++i) + { + int opcode = instructions[i]; + + if (!isJump(opcode)) + { + continue; + } + + // + 1 because the jumps go to the instructions prior to the + // one you really want, because the pc is incremented on the + // next loop + int to = i + iop[i] + 1; + assert to >= 0 && to < instructions.length; + + jumped[to] = true; + } + + return jumped; + } + + public String disassemble(ScriptDefinition script) throws IOException + { + StringBuilder writer = new StringBuilder(); + + int[] instructions = script.getInstructions(); + int[] iops = script.getIntOperands(); + String[] sops = script.getStringOperands(); + + assert iops.length == instructions.length; + assert sops.length == instructions.length; + + boolean[] jumps = needLabel(script); + + for (int i = 0; i < instructions.length; ++i) + { + int opcode = instructions[i]; + int iop = iops[i]; + String sop = sops[i]; + + Instruction ins = Instructions.find(opcode); + if (ins == null) + { + logger.warn("Unknown instruction {} in script {}", opcode, script.getId()); + } + + if (jumps[i]) + { + // something jumps here + writer.append("LABEL").append(i).append(":\n"); + } + + String name; + if (ins != null && ins.getName() != null) + { + name = ins.getName(); + } + else + { + name = String.format("%03d", opcode); + } + + writer.append(String.format(" %-22s", name)); + + if (iop != 0 || opcode == Opcodes.LOAD_INT) + { + if (isJump(opcode)) + { + writer.append(" LABEL").append(i + iop + 1); + } + else + { + writer.append(" ").append(iop); + } + } + + if (sop != null) + { + writer.append(" \"").append(sop).append("\""); + } + writer.append("\n"); + } + + return writer.toString(); + } +} diff --git a/cache/src/test/java/net/runelite/cache/script/assembler/AssemblerTest.java b/cache/src/test/java/net/runelite/cache/script/assembler/AssemblerTest.java new file mode 100644 index 0000000000..8623974bdc --- /dev/null +++ b/cache/src/test/java/net/runelite/cache/script/assembler/AssemblerTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.script.assembler; + +import java.io.InputStream; +import net.runelite.cache.definitions.ScriptDefinition; +import net.runelite.cache.script.disassembler.Disassembler; +import org.apache.commons.compress.utils.IOUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.impl.SimpleLogger; + +public class AssemblerTest +{ + private static final Logger logger = LoggerFactory.getLogger(AssemblerTest.class); + + @Before + public void before() + { + System.setProperty(SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "DEBUG"); + } + + @Test + public void testAssemble() throws Exception + { + InputStream in = AssemblerTest.class.getResourceAsStream("395.rs2asm"); + Assert.assertNotNull(in); + + Assembler assembler = new Assembler(); + ScriptDefinition script = assembler.assemble(in); + + // compare with disassembler + Disassembler disassembler = new Disassembler(); + String out = disassembler.disassemble(script); + + in = AssemblerTest.class.getResourceAsStream("395.rs2asm"); + Assert.assertNotNull(in); + + String original = new String(IOUtils.toByteArray(in)); + + logger.debug(original); + logger.debug("-----------------------"); + logger.debug(out); + + Assert.assertEquals(original, out); + } + +} diff --git a/cache/src/test/java/net/runelite/cache/ScriptDumperTest.java b/cache/src/test/java/net/runelite/cache/script/disassembler/DisassemblerTest.java similarity index 51% rename from cache/src/test/java/net/runelite/cache/ScriptDumperTest.java rename to cache/src/test/java/net/runelite/cache/script/disassembler/DisassemblerTest.java index a829b0c5c9..692025c265 100644 --- a/cache/src/test/java/net/runelite/cache/ScriptDumperTest.java +++ b/cache/src/test/java/net/runelite/cache/script/disassembler/DisassemblerTest.java @@ -22,29 +22,28 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.cache; +package net.runelite.cache.script.disassembler; -import java.io.BufferedWriter; -import java.io.FileWriter; +import com.google.common.io.Files; import java.io.IOException; +import net.runelite.cache.IndexType; +import net.runelite.cache.StoreLocation; import net.runelite.cache.definitions.ScriptDefinition; import net.runelite.cache.definitions.loaders.ScriptLoader; import net.runelite.cache.fs.Archive; import net.runelite.cache.fs.File; import net.runelite.cache.fs.Index; import net.runelite.cache.fs.Store; -import net.runelite.cache.script.Instruction; import net.runelite.cache.script.Instructions; -import net.runelite.cache.script.Opcodes; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ScriptDumperTest +public class DisassemblerTest { - private static final Logger logger = LoggerFactory.getLogger(ScriptDumperTest.class); + private static final Logger logger = LoggerFactory.getLogger(DisassemblerTest.class); @Rule public TemporaryFolder folder = StoreLocation.getTemporaryFolder(); @@ -74,7 +73,11 @@ public class ScriptDumperTest ScriptDefinition script = loader.load(file.getFileId(), contents); java.io.File outFile = new java.io.File(outDir, archive.getArchiveId() + ".rs2asm"); - writeScript(outFile, script); + + Disassembler disassembler = new Disassembler(); + String out = disassembler.disassemble(script); + + Files.write(out.getBytes(), outFile); ++count; } @@ -82,112 +85,4 @@ public class ScriptDumperTest logger.info("Dumped {} scripts to {}", count, outDir); } - - private boolean isJump(int opcode) - { - switch (opcode) - { - case Opcodes.JUMP: - case Opcodes.IF_ICMPEQ: - case Opcodes.IF_ICMPGE: - case Opcodes.IF_ICMPGT: - case Opcodes.IF_ICMPLE: - case Opcodes.IF_ICMPLT: - case Opcodes.IF_ICMPNE: - return true; - default: - return false; - } - } - - private boolean[] needLabel(ScriptDefinition script) - { - int[] instructions = script.getInstructions(); - int[] iop = script.getIntOperands(); - boolean[] jumped = new boolean[instructions.length]; - - for (int i = 0; i < instructions.length; ++i) - { - int opcode = instructions[i]; - - if (!isJump(opcode)) - { - continue; - } - - // + 1 because the jumps go to the instructions prior to the - // one you really want, because the pc is incremented on the - // next loop - int to = i + iop[i] + 1; - assert to >= 0 && to < instructions.length; - - jumped[to] = true; - } - - return jumped; - } - - private void writeScript(java.io.File file, ScriptDefinition script) throws IOException - { - try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) - { - int[] instructions = script.getInstructions(); - int[] iops = script.getIntOperands(); - String[] sops = script.getStringOperands(); - - assert iops.length == instructions.length; - assert sops.length == instructions.length; - - boolean[] jumps = needLabel(script); - - for (int i = 0; i < instructions.length; ++i) - { - int opcode = instructions[i]; - int iop = iops[i]; - String sop = sops[i]; - - Instruction ins = Instructions.find(opcode); - if (ins == null) - { - logger.warn("Unknown instruction {} in script {}", opcode, script.getId()); - } - - String name; - if (ins != null && ins.getName() != null) - { - name = ins.getName(); - } - else - { - name = String.format("%03d", opcode); - } - - if (jumps[i]) - { - // something jumps here - writer.write("LABEL" + i + ":\n"); - } - - writer.write(String.format(" %-22s", name)); - - if (iop != 0 || opcode == Opcodes.LOAD_INT) - { - if (isJump(opcode)) - { - writer.write(" LABEL" + (i + iop + 1)); - } - else - { - writer.write(" " + iop); - } - } - - if (sop != null) - { - writer.write(" \"" + sop + "\""); - } - writer.write("\n"); - } - } - } } diff --git a/cache/src/test/resources/net/runelite/cache/script/assembler/395.rs2asm b/cache/src/test/resources/net/runelite/cache/script/assembler/395.rs2asm new file mode 100644 index 0000000000..c6f8b3b88a --- /dev/null +++ b/cache/src/test/resources/net/runelite/cache/script/assembler/395.rs2asm @@ -0,0 +1,99 @@ + 033 + get_boostedskilllevels + int_to_string + 1112 + 033 + get_realskilllevels + 034 3 + 033 3 + int_to_string + 1112 1 + 033 + get_skillexperiences + 034 4 + load_string "," + 036 1 + 035 + load_string " XP:" + 037 2 + 036 2 + 033 4 + 035 1 + 040 46 + 036 3 + load_int 0 + 034 5 + 025 4181 + load_int 0 + if_icmpeq LABEL29 + jump LABEL60 +LABEL29: + 033 3 + load_int 99 + if_icmplt LABEL33 + jump LABEL59 +LABEL33: + load_int 105 + load_int 105 + load_int 256 + 033 3 + load_int 1 + iadd + 3408 + 034 5 + 035 2 + load_string "|Next level at:|Remaining XP:" + concat_string + 036 2 + 035 3 + load_string "|" + 033 5 + 035 1 + 040 46 + load_string "|" + 033 5 + 033 4 + isub + 035 1 + 040 46 + 037 4 + concat_string + 036 3 +LABEL59: + jump LABEL78 +LABEL60: + 035 2 + load_string "|Next level at:" + concat_string + 036 2 + 035 3 + load_string "|" + load_int 105 + load_int 105 + load_int 256 + 033 3 + load_int 1 + iadd + 3408 + 035 1 + 040 46 + 037 2 + concat_string + 036 3 +LABEL78: + load_int 992 + load_int -2147483645 + load_int -1 + 033 2 + 035 2 + 035 3 + load_int 495 + load_int 25 + load_int 5 + idiv + load_string "IiIssfi" + 033 1 + 2412 + load_int 0 + 043 2 + return