From 50b7ff206462e303cd657b6d6ba8891663dacb49 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 7 May 2017 17:24:00 -0400 Subject: [PATCH] cache: add switch instruction --- .../runelite/cache/script/assembler/rs2asm.g4 | 13 +- .../cache/definitions/ScriptDefinition.java | 10 +- .../definitions/loaders/ScriptLoader.java | 18 +-- .../runelite/cache/script/Instructions.java | 1 + .../net/runelite/cache/script/Opcodes.java | 1 + .../{Attribute.java => LookupCase.java} | 35 ++---- .../cache/script/assembler/LookupSwitch.java | 38 ++++++ .../cache/script/assembler/ScriptWriter.java | 111 ++++++++++------- .../script/disassembler/Disassembler.java | 66 ++++++---- .../cache/script/assembler/AssemblerTest.java | 21 +++- .../cache/script/assembler/395.rs2asm | 2 - .../runelite/cache/script/assembler/91.rs2asm | 117 ++++++++++++++++++ 12 files changed, 318 insertions(+), 115 deletions(-) rename cache/src/main/java/net/runelite/cache/script/assembler/{Attribute.java => LookupCase.java} (86%) create mode 100644 cache/src/main/java/net/runelite/cache/script/assembler/LookupSwitch.java create mode 100644 cache/src/test/resources/net/runelite/cache/script/assembler/91.rs2asm 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 index 0633ef370f..07694a7f20 100644 --- a/cache/src/main/antlr4/net/runelite/cache/script/assembler/rs2asm.g4 +++ b/cache/src/main/antlr4/net/runelite/cache/script/assembler/rs2asm.g4 @@ -24,14 +24,9 @@ */ grammar rs2asm; -prog: (attr NEWLINE)* (line NEWLINE)+ ; +prog: (line NEWLINE)+ ; -attr: '.attr ' attr_idx ' ' attr_key ' ' attr_value ; -attr_idx: INT ; -attr_key: INT ; -attr_value: INT ; - -line: instruction | label ; +line: instruction | label | switch_lookup ; instruction: instruction_name instruction_operand ; label: 'LABEL' INT ':' ; @@ -44,6 +39,10 @@ operand_int: INT ; operand_qstring: QSTRING ; operand_label: 'LABEL' INT ; +switch_lookup: switch_key ':' switch_value ; +switch_key: INT ; +switch_value: 'LABEL' INT ; + NEWLINE: '\n'+ ; INT: '-'? [0-9]+ ; QSTRING: '"' (~('"' | '\\' | '\r' | '\n') | '\\' ('"' | '\\'))* '"' ; diff --git a/cache/src/main/java/net/runelite/cache/definitions/ScriptDefinition.java b/cache/src/main/java/net/runelite/cache/definitions/ScriptDefinition.java index 3b23ade1de..37d330a91c 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/ScriptDefinition.java +++ b/cache/src/main/java/net/runelite/cache/definitions/ScriptDefinition.java @@ -36,7 +36,7 @@ public class ScriptDefinition private int localStringCount; private int stringStackCount; private int localIntCount; - private Map[] attributes; + private Map[] switches; public int getId() { @@ -118,13 +118,13 @@ public class ScriptDefinition this.localIntCount = localIntCount; } - public Map[] getAttributes() + public Map[] getSwitches() { - return attributes; + return switches; } - public void setAttributes(Map[] attributes) + public void setSwitches(Map[] switches) { - this.attributes = attributes; + this.switches = switches; } } diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/ScriptLoader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/ScriptLoader.java index fe77a4da3e..ba3481970e 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/loaders/ScriptLoader.java +++ b/cache/src/main/java/net/runelite/cache/definitions/loaders/ScriptLoader.java @@ -48,23 +48,23 @@ public class ScriptLoader int intStackCount = in.readUnsignedShort(); int stringStackCount = in.readUnsignedShort(); - int attributeCount = in.readUnsignedByte(); - if (attributeCount > 0) + int numSwitches = in.readUnsignedByte(); + if (numSwitches > 0) { - Map[] attributes = new Map[attributeCount]; - def.setAttributes(attributes); + Map[] switches = new Map[numSwitches]; + def.setSwitches(switches); - for (int i = 0; i < attributeCount; ++i) + for (int i = 0; i < numSwitches; ++i) { - attributes[i] = new HashMap<>(); + switches[i] = new HashMap<>(); int count = in.readUnsignedShort(); while (count-- > 0) { - int key = in.readInt(); - int value = in.readInt(); + int key = in.readInt(); // int from stack is compared to this + int pcOffset = in.readInt(); // pc jumps by this - attributes[i].put(key, value); + switches[i].put(key, pcOffset); } } } 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 c4f72ba59b..c7ac18ba6f 100644 --- a/cache/src/main/java/net/runelite/cache/script/Instructions.java +++ b/cache/src/main/java/net/runelite/cache/script/Instructions.java @@ -66,6 +66,7 @@ public class Instructions add(46, 2, 0); add(47, 0, 0, 0, 1); add(48, 0, 0, 1, 0); + add(Opcodes.SWITCH, "switch", 1, 0); add(100, 3, 0); add(101, 0, 0); add(102, 1, 0); diff --git a/cache/src/main/java/net/runelite/cache/script/Opcodes.java b/cache/src/main/java/net/runelite/cache/script/Opcodes.java index 3e8005f076..a6c4b6fd73 100644 --- a/cache/src/main/java/net/runelite/cache/script/Opcodes.java +++ b/cache/src/main/java/net/runelite/cache/script/Opcodes.java @@ -41,6 +41,7 @@ public class Opcodes public static final int POP_INT = 38; public static final int POP_STRING = 39; //public static final int INVOKE = 40; + public static final int SWITCH = 60; public static final int WIDGET_PUT_HIDDEN = 1003; public static final int WIDGET_PUT_SCROLL = 1100; public static final int WIDGET_PUT_TEXTCOLOR = 1101; diff --git a/cache/src/main/java/net/runelite/cache/script/assembler/Attribute.java b/cache/src/main/java/net/runelite/cache/script/assembler/LookupCase.java similarity index 86% rename from cache/src/main/java/net/runelite/cache/script/assembler/Attribute.java rename to cache/src/main/java/net/runelite/cache/script/assembler/LookupCase.java index 688fdb2a75..8c97feef3d 100644 --- a/cache/src/main/java/net/runelite/cache/script/assembler/Attribute.java +++ b/cache/src/main/java/net/runelite/cache/script/assembler/LookupCase.java @@ -24,31 +24,10 @@ */ package net.runelite.cache.script.assembler; -public class Attribute +public class LookupCase { - private int idx; - private int key; private int value; - - public int getIdx() - { - return idx; - } - - public void setIdx(int idx) - { - this.idx = idx; - } - - public int getKey() - { - return key; - } - - public void setKey(int key) - { - this.key = key; - } + private int offset; public int getValue() { @@ -59,4 +38,14 @@ public class Attribute { this.value = value; } + + public int getOffset() + { + return offset; + } + + public void setOffset(int offset) + { + this.offset = offset; + } } diff --git a/cache/src/main/java/net/runelite/cache/script/assembler/LookupSwitch.java b/cache/src/main/java/net/runelite/cache/script/assembler/LookupSwitch.java new file mode 100644 index 0000000000..e1d305d40e --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/script/assembler/LookupSwitch.java @@ -0,0 +1,38 @@ +/* + * 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; + +public class LookupSwitch +{ + private final List cases = new ArrayList<>(); + + public List getCases() + { + return cases; + } +} 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 index aeb5ca9129..39111e5901 100644 --- a/cache/src/main/java/net/runelite/cache/script/assembler/ScriptWriter.java +++ b/cache/src/main/java/net/runelite/cache/script/assembler/ScriptWriter.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import net.runelite.cache.definitions.ScriptDefinition; import net.runelite.cache.script.Instruction; import net.runelite.cache.script.Instructions; @@ -44,45 +45,13 @@ public class ScriptWriter extends rs2asmBaseListener private List opcodes = new ArrayList<>(); private List iops = new ArrayList<>(); private List sops = new ArrayList<>(); - private List attrs = new ArrayList<>(); + private List switches = new ArrayList<>(); public ScriptWriter(LabelVisitor labelVisitor) { this.labelVisitor = labelVisitor; } - @Override - public void exitAttr_idx(rs2asmParser.Attr_idxContext ctx) - { - String text = ctx.getText(); - int idx = Integer.parseInt(text); - - Attribute attr = new Attribute(); - attr.setIdx(idx); - - attrs.add(attr); - } - - @Override - public void exitAttr_key(rs2asmParser.Attr_keyContext ctx) - { - String text = ctx.getText(); - int key = Integer.parseInt(text); - - Attribute attr = attrs.get(attrs.size() - 1); - attr.setKey(key); - } - - @Override - public void exitAttr_value(rs2asmParser.Attr_valueContext ctx) - { - String text = ctx.getText(); - int value = Integer.parseInt(text); - - Attribute attr = attrs.get(attrs.size() - 1); - attr.setValue(value); - } - @Override public void exitInstruction(rs2asmParser.InstructionContext ctx) { @@ -117,10 +86,12 @@ public class ScriptWriter extends rs2asmBaseListener assert opcodes.size() == pos; assert iops.size() == pos; assert sops.size() == pos; + assert switches.size() == pos; opcodes.add(opcode); iops.add(null); sops.add(null); + switches.add(null); } @Override @@ -153,6 +124,54 @@ public class ScriptWriter extends rs2asmBaseListener iops.set(pos, target); } + @Override + public void enterSwitch_lookup(rs2asmParser.Switch_lookupContext ctx) + { + if (switches.get(pos - 1) != null) + { + return; + } + + LookupSwitch ls = new LookupSwitch(); + switches.set(pos - 1, ls); + } + + @Override + public void exitSwitch_key(rs2asmParser.Switch_keyContext ctx) + { + String text = ctx.getText(); + int key = Integer.parseInt(text); + + LookupSwitch ls = switches.get(pos - 1); + assert ls != null; + + LookupCase scase = new LookupCase(); + scase.setValue(key); + + ls.getCases().add(scase); + } + + @Override + public void exitSwitch_value(rs2asmParser.Switch_valueContext ctx) + { + String text = ctx.getText(); + Integer instruction = labelVisitor.getInstructionForLabel(text); + if (instruction == null) + { + throw new RuntimeException("reference to unknown label " + text); + } + + int target = instruction // target instruction index + - (pos - 1) // pos is already at the instruction after the switch, so - 1 + - 1; // to go to the instruction prior to target + + LookupSwitch ls = switches.get(pos - 1); + assert ls != null; + + LookupCase scase = ls.getCases().get(ls.getCases().size() - 1); + scase.setOffset(target); + } + public ScriptDefinition buildScript() { ScriptDefinition script = new ScriptDefinition(); @@ -162,28 +181,34 @@ public class ScriptWriter extends rs2asmBaseListener .mapToInt(Integer::valueOf) .toArray()); script.setStringOperands(sops.toArray(new String[0])); - script.setAttributes(buildAttributes()); + script.setSwitches(buildSwitches()); return script; } - private Map[] buildAttributes() + private Map[] buildSwitches() { - if (attrs == null || attrs.isEmpty()) + int count = (int) switches.stream().filter(Objects::nonNull).count(); + + if (count == 0) { return null; } - Map[] maps = new Map[attrs.stream().map(attr -> attr.getIdx()).max(Integer::compare).get() + 1]; - for (Attribute attr : attrs) + int index = 0; + Map[] maps = new Map[count]; + for (LookupSwitch lswitch : switches) { - Map map = maps[attr.getIdx()]; - if (map == null) + if (lswitch == null) { - map = new HashMap<>(); - maps[attr.getIdx()] = map; + continue; } - map.put(attr.getKey(), attr.getValue()); + Map map = maps[index++] = new HashMap<>(); + + for (LookupCase scase : lswitch.getCases()) + { + map.put(scase.getValue(), scase.getOffset()); + } } return maps; } 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 index a70f47bc4c..b3f4432988 100644 --- a/cache/src/main/java/net/runelite/cache/script/disassembler/Disassembler.java +++ b/cache/src/main/java/net/runelite/cache/script/disassembler/Disassembler.java @@ -58,12 +58,29 @@ public class Disassembler private boolean[] needLabel(ScriptDefinition script) { int[] instructions = script.getInstructions(); - int[] iop = script.getIntOperands(); + int[] iops = script.getIntOperands(); + Map[] switches = script.getSwitches(); + boolean[] jumped = new boolean[instructions.length]; for (int i = 0; i < instructions.length; ++i) { int opcode = instructions[i]; + int iop = iops[i]; + + if (opcode == Opcodes.SWITCH) + { + Map switchMap = switches[iop]; + + for (Entry entry : switchMap.entrySet()) + { + int offset = entry.getValue(); + + int to = i + offset + 1; + assert to >= 0 && to < instructions.length; + jumped[to] = true; + } + } if (!isJump(opcode)) { @@ -73,7 +90,7 @@ public class Disassembler // + 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; + int to = i + iop + 1; assert to >= 0 && to < instructions.length; jumped[to] = true; @@ -86,11 +103,10 @@ public class Disassembler { StringBuilder writer = new StringBuilder(); - writeAttributes(script, writer); - int[] instructions = script.getInstructions(); int[] iops = script.getIntOperands(); String[] sops = script.getStringOperands(); + Map[] switches = script.getSwitches(); assert iops.length == instructions.length; assert sops.length == instructions.length; @@ -127,7 +143,7 @@ public class Disassembler writer.append(String.format(" %-22s", name)); - if (iop != 0 || opcode == Opcodes.LOAD_INT) + if (shouldWriteIntOperand(opcode, iop)) { if (isJump(opcode)) { @@ -143,34 +159,36 @@ public class Disassembler { writer.append(" \"").append(sop).append("\""); } + + if (opcode == Opcodes.SWITCH) + { + Map switchMap = switches[iop]; + + for (Entry entry : switchMap.entrySet()) + { + int value = entry.getKey(); + int jump = entry.getValue(); + + writer.append("\n"); + writer.append(" ").append(value).append(": LABEL").append(i + jump + 1); + } + } + writer.append("\n"); } return writer.toString(); } - private void writeAttributes(ScriptDefinition script, StringBuilder writer) + private boolean shouldWriteIntOperand(int opcode, int operand) { - Map[] attributes = script.getAttributes(); - if (attributes == null) + if (opcode == Opcodes.SWITCH) { - return; + // table follows instruction + return false; } - int index = -1; - for (Map map : attributes) - { - ++index; - - if (map == null) - { - continue; - } - - for (Entry entry : map.entrySet()) - { - writer.append(".attr ").append(index).append(" ").append(entry.getKey()).append(" ").append(entry.getValue()).append("\n"); - } - } + // Write if operand != 0 or if the instruction is specifically to load int + return operand != 0 || opcode == Opcodes.LOAD_INT; } } 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 index cbad86bec9..7bcb9fa9fa 100644 --- a/cache/src/test/java/net/runelite/cache/script/assembler/AssemblerTest.java +++ b/cache/src/test/java/net/runelite/cache/script/assembler/AssemblerTest.java @@ -30,17 +30,34 @@ 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.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@RunWith(Parameterized.class) public class AssemblerTest { private static final Logger logger = LoggerFactory.getLogger(AssemblerTest.class); + @Parameter + public String script; + + @Parameters + public static String[] scripts() + { + return new String[] + { + "395.rs2asm", "91.rs2asm" + }; + } + @Test public void testAssemble() throws Exception { - InputStream in = AssemblerTest.class.getResourceAsStream("395.rs2asm"); + InputStream in = AssemblerTest.class.getResourceAsStream(script); Assert.assertNotNull(in); Assembler assembler = new Assembler(); @@ -50,7 +67,7 @@ public class AssemblerTest Disassembler disassembler = new Disassembler(); String out = disassembler.disassemble(script); - in = AssemblerTest.class.getResourceAsStream("395.rs2asm"); + in = AssemblerTest.class.getResourceAsStream(this.script); Assert.assertNotNull(in); String original = new String(IOUtils.toByteArray(in)); 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 index d1411a5e6a..c6f8b3b88a 100644 --- a/cache/src/test/resources/net/runelite/cache/script/assembler/395.rs2asm +++ b/cache/src/test/resources/net/runelite/cache/script/assembler/395.rs2asm @@ -1,5 +1,3 @@ -.attr 4 8 15 -.attr 16 23 42 033 get_boostedskilllevels int_to_string diff --git a/cache/src/test/resources/net/runelite/cache/script/assembler/91.rs2asm b/cache/src/test/resources/net/runelite/cache/script/assembler/91.rs2asm new file mode 100644 index 0000000000..c4d12dc77f --- /dev/null +++ b/cache/src/test/resources/net/runelite/cache/script/assembler/91.rs2asm @@ -0,0 +1,117 @@ + 033 + switch + 3: LABEL20 + 5: LABEL54 + 6: LABEL54 + 7: LABEL3 + jump LABEL84 +LABEL3: + 033 1 + 042 175 + if_icmplt LABEL7 + jump LABEL9 +LABEL7: + load_int 0 + return +LABEL9: + 035 + string_remove_html + 3623 + load_int 1 + if_icmpeq LABEL15 + jump LABEL17 +LABEL15: + load_int 0 + return +LABEL17: + load_int 1 + return + jump LABEL84 +LABEL20: + 033 1 + 042 175 + if_icmplt LABEL24 + jump LABEL26 +LABEL24: + load_int 0 + return +LABEL26: + 035 + string_remove_html + 3623 + load_int 1 + if_icmpeq LABEL32 + jump LABEL34 +LABEL32: + load_int 0 + return +LABEL34: + 5005 + load_int 0 + if_icmpeq LABEL38 + jump LABEL40 +LABEL38: + load_int 1 + return +LABEL40: + 5005 + load_int 1 + if_icmpeq LABEL44 + jump LABEL51 +LABEL44: + 035 + 3609 + load_int 1 + if_icmpeq LABEL49 + jump LABEL51 +LABEL49: + load_int 1 + return +LABEL51: + load_int 0 + return + jump LABEL84 +LABEL54: + 033 1 + 042 175 + if_icmplt LABEL58 + jump LABEL60 +LABEL58: + load_int 0 + return +LABEL60: + 033 + load_int 5 + if_icmpeq LABEL64 + jump LABEL76 +LABEL64: + 025 1627 + load_int 0 + if_icmpeq LABEL68 + jump LABEL76 +LABEL68: + get_gamecycle + 033 1 + isub + load_int 500 + if_icmpge LABEL74 + jump LABEL76 +LABEL74: + load_int 0 + return +LABEL76: + 5005 + load_int 2 + if_icmpne LABEL80 + jump LABEL82 +LABEL80: + load_int 1 + return +LABEL82: + load_int 0 + return +LABEL84: + load_int 0 + return + load_int -1 + return