Change rasterizer raw injector from depending on hardcoded values

This commit is contained in:
Lucwousin
2019-11-01 07:43:48 +01:00
parent 163193a026
commit a980aa660c
6 changed files with 274 additions and 381 deletions

View File

@@ -52,8 +52,7 @@ public abstract class ArrayStore extends Instruction implements ArrayStoreInstru
if (r.getInstruction() instanceof GetFieldInstruction)
{
GetFieldInstruction gf = (GetFieldInstruction) r.getInstruction();
Field f = gf.getMyField();
return f;
return gf.getMyField();
}
return null;
@@ -89,7 +88,7 @@ public abstract class ArrayStore extends Instruction implements ArrayStoreInstru
Field f1 = gf1.getMyField(),
f2 = gf2.getMyField();
assert MappingExecutorUtil.isMaybeEqual(f1, f2);
if (f1 != null && f2 != null)

View File

@@ -86,7 +86,7 @@ public class InvokeStatic extends Instruction implements InvokeInstruction
@SuppressWarnings("unchecked")
public List<net.runelite.asm.Method> getMethods()
{
return myMethod != null ? Arrays.asList(myMethod) : Collections.EMPTY_LIST;
return myMethod != null ? Collections.singletonList(myMethod) : Collections.EMPTY_LIST;
}
@Override

View File

@@ -70,10 +70,9 @@ public class MethodContext
return contexts.get(i);
}
@SuppressWarnings("unchecked")
public Collection<InstructionContext> getInstructionContexts()
{
return (Collection) contexts.values();
return contexts.values();
}
public void reset()

View File

@@ -49,7 +49,7 @@ import static net.runelite.injector.InjectUtil.getFieldType;
import net.runelite.injector.raw.ClearColorBuffer;
import net.runelite.injector.raw.DrawAfterWidgets;
import net.runelite.injector.raw.Occluder;
import net.runelite.injector.raw.RasterizerHook;
import net.runelite.injector.raw.RasterizerAlpha;
import net.runelite.injector.raw.RenderDraw;
import net.runelite.injector.raw.ScriptVM;
import net.runelite.mapping.Import;
@@ -58,8 +58,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.runelite.injector.raw.HidePlayerAttacks;
// import net.runelite.injector.raw.DrawMenu;
public class Inject
{
public static final java.lang.Class<?> CLIENT_CLASS = RSClient.class;
@@ -207,7 +205,7 @@ public class Inject
// Has to be done before mixins
// well, can be done after really
// but why do that when you can do it before
new RasterizerHook(this).inject();
new RasterizerAlpha(this).inject();
// requires interfaces to be injected
mixinInjector.inject();

View File

@@ -0,0 +1,268 @@
/*
* Copyright (c) 2018, Lucas <https://github.com/Lucwousin>
* 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.injector.raw;
import com.google.common.collect.Lists;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.InstructionType;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.FieldInstruction;
import net.runelite.asm.attributes.code.instruction.types.GetFieldInstruction;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.IALoad;
import net.runelite.asm.attributes.code.instructions.IAStore;
import net.runelite.asm.attributes.code.instructions.IAdd;
import net.runelite.asm.attributes.code.instructions.ILoad;
import net.runelite.asm.attributes.code.instructions.IOr;
import net.runelite.asm.attributes.code.instructions.IShR;
import net.runelite.asm.attributes.code.instructions.ISub;
import net.runelite.asm.attributes.code.instructions.IUShR;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.SiPush;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.MethodContext;
import net.runelite.asm.execution.StackContext;
import net.runelite.asm.execution.VariableContext;
import net.runelite.asm.pool.Class;
import net.runelite.asm.signature.Signature;
import net.runelite.injector.Inject;
import net.runelite.injector.InjectUtil;
import net.runelite.injector.InjectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RasterizerAlpha
{
private static final Logger logger = LoggerFactory.getLogger(RasterizerAlpha.class);
private static final net.runelite.asm.pool.Method DRAWALPHA = new net.runelite.asm.pool.Method(
new Class("client"),
"drawAlpha",
new Signature("([IIII)V")
);
private static final int ALPHA = 0xff000000;
private final Inject inject;
public RasterizerAlpha(Inject inject)
{
this.inject = inject;
}
/*
* This class exists cause of removing colour banding.
*
* Push array on stack
* Push array index on stack
* Push colour on stack -> we're interested in where the colour comes from
* Put colour in array, popping array, index and colour
*
*
*
*/
public void inject() throws InjectionException
{
final Field r2dPx = InjectUtil.findDeobField(inject, "Rasterizer2D_pixels", "Rasterizer2D");
final Method draw = InjectUtil.findMethod(inject, "draw", "Client");
final ClassFile rasterizer2D = r2dPx.getClassFile();
final Execution ex = new Execution(rasterizer2D.getGroup());
ex.staticStep = false;
ex.step = false;
ex.addMethod(draw);
int[] counts = new int[2];
ex.addMethodContextVisitor((MethodContext mc) ->
{
Instructions instrs = getInstrs(mc);
if (instrs == null)
return;
int count = 0;
int orCount = 0;
for (InstructionContext ic : mc.getInstructionContexts())
{
Instruction instruction = ic.getInstruction();
if (!(instruction instanceof IAStore))
continue;
// Field field = astore.getMyField(ic);
// doesn't track into methods so doing it here
StackContext array = ic.getPops().get(2);
if (!isSameField(r2dPx, array))
continue;
// This is the colour that's being set
StackContext colour = ic.getPops().get(0);
// resolve gets the original value pusher
InstructionContext colPusher = colour.getPushed().resolve(colour);
Instruction colPushI = colPusher.getInstruction();
// If it's not a >> or a | we're not interested
if (colPushI instanceof LVTInstruction // when called from a method we didn't execute
|| colPushI instanceof PushConstantInstruction &&
!((PushConstantInstruction) colPushI).getConstant().equals(0)
|| colPushI instanceof IALoad)
{
// OR with 0xFF000000, unless 0
int storeIdx = instrs.getInstructions().indexOf(instruction);
instrs.addInstruction(storeIdx++, new LDC(instrs, ALPHA));
instrs.addInstruction(storeIdx, new IOr(instrs, InstructionType.IOR));
++orCount;
continue;
}
else if (!(
colPushI instanceof IShR ||
colPushI instanceof IUShR ||
colPushI instanceof IAdd))
{
continue;
}
// So we know we're dealing with alpha here, now we need the alpha value
// earlier on in the method there's been a 256 - XXX, where xxx is alpha
for (InstructionContext ins : mc.getInstructionContexts())
{
if (!(ins.getInstruction() instanceof SiPush))
continue;
SiPush pci = (SiPush) ins.getInstruction();
if ((short) pci.getConstant() != (short) 256)
continue;
InstructionContext isub = ins.getPushes().get(0).getPopped().get(0);
if (!(isub.getInstruction() instanceof ISub))
continue;
StackContext alphaPop = isub.getPops().get(0);
InstructionContext alphaPusher = alphaPop.getPushed().resolve(alphaPop);
InstructionContext isubResult = isub.getPushes().get(0).getPopped().get(0);
if (pushesToSameField(isubResult, alphaPusher))
{
alphaPusher = resolveFieldThroughInvokes(alphaPop);
if (alphaPusher == null)
throw new RuntimeException("Alpha var is overwritten and we don't know what pushed it"); // cheeky unchecked
}
int storeIdx = instrs.getInstructions().indexOf(instruction);
Instruction alphaPushI = alphaPusher.getInstruction();
if (alphaPushI instanceof GetStatic)
{
instrs.addInstruction(storeIdx++, new LDC(instrs, 255));
instrs.addInstruction(storeIdx++, new GetStatic(instrs, ((GetStatic) alphaPushI).getField()));
instrs.addInstruction(storeIdx++, new ISub(instrs, InstructionType.ISUB));
}
else if (alphaPushI instanceof LVTInstruction)
{
instrs.addInstruction(storeIdx++, new ILoad(instrs, ((LVTInstruction) alphaPushI).getVariableIndex()));
}
instrs.getInstructions().set(storeIdx, new InvokeStatic(instrs, DRAWALPHA));
++count;
break;
}
}
if (orCount != 0)
{
counts[0] += orCount;
logger.info("Added {} OR's into {}", orCount, mc.getMethod());
}
if (count != 0)
{
counts[1] += count;
logger.info("Injected {} DrawAlpha invokes into {}", count, mc.getMethod());
}
});
ex.run();
logger.info("Injected {} DrawAlpha invokes and {} ors", counts[1], counts[0]);
}
private static boolean pushesToSameField(InstructionContext cA, InstructionContext cB)
{
if (cA.getInstruction() instanceof FieldInstruction && cB instanceof FieldInstruction)
{
Field a = ((FieldInstruction) cA.getInstruction()).getMyField();
Field b = ((FieldInstruction) cB.getInstruction()).getMyField();
return a == b;
}
return false;
}
private static Instructions getInstrs(MethodContext mc)
{
Code c = mc.getMethod().getCode();
if (c == null)
return null;
return c.getInstructions();
}
private static InstructionContext resolveFieldThroughInvokes(StackContext stackContext)
{
InstructionContext pusher = stackContext.getPushed().resolve(stackContext);
if (pusher.getInstruction() instanceof GetFieldInstruction)
return pusher;
// No field I wanna trace, rn at least
if (!(pusher.getInstruction() instanceof LVTInstruction))
return null;
int vidx = ((LVTInstruction) pusher.getInstruction()).getVariableIndex();
VariableContext vc = pusher.getVariables().get(vidx);
stackContext = Lists.reverse(vc.getInstructionWhichStored().getPops()).get(vidx);
return resolveFieldThroughInvokes(stackContext);
}
private static boolean isSameField(Field f, StackContext array)
{
InstructionContext ic = resolveFieldThroughInvokes(array);
if (ic == null)
return false;
return ((GetFieldInstruction) ic.getInstruction()).getMyField() == f;
}
}

View File

@@ -1,371 +0,0 @@
package net.runelite.injector.raw;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.InstructionType;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.attributes.code.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.ArrayStore;
import net.runelite.asm.attributes.code.instructions.GetField;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.IALoad;
import net.runelite.asm.attributes.code.instructions.IAStore;
import net.runelite.asm.attributes.code.instructions.ILoad;
import net.runelite.asm.attributes.code.instructions.IOr;
import net.runelite.asm.attributes.code.instructions.ISub;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.pool.Class;
import net.runelite.asm.signature.Signature;
import net.runelite.injector.Inject;
import static net.runelite.injector.InjectUtil.findDeobField;
import static net.runelite.injector.InjectUtil.findStaticMethod;
import net.runelite.injector.InjectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RasterizerHook
{
// TODO: Should probably make this better
private static final Logger logger = LoggerFactory.getLogger(ClearColorBuffer.class);
private static final int val = -16777216;
private static final String font_alpha = "AbstractFont_placeGlyphAlpha";
private static final String circle_alpha = "Rasterizer2D_drawCircleAlpha";
private static final String line_alpha = "Rasterizer2D_drawHorizontalLineAlpha";
private static final String line_alpha2 = "Rasterizer2D_drawVerticalLineAlpha";
private static final String fill_rect_alpha = "Rasterizer2D_fillRectangleAlpha";
private static final String r3d_vert = "Rasterizer3D_vertAlpha";
private static final String r3d_horiz = "Rasterizer3D_horizAlpha";
private static final String r3d_field = "Rasterizer3D_alpha";
private static final String sprite_alpha1 = "Sprite_drawTransparent";
private static final String sprite_alpha2 = "Sprite_drawTransScaled";
private static final String font = "AbstractFont_placeGlyph";
private static final String rast3D = "Rasterizer3D_iDontKnow";
private static final String rast3D2 = "Rasterizer3D_textureAlpha";
private static final String sprite = "Sprite_draw";
private static final String sprite2 = "Sprite_drawScaled";
private static final String sprite3 = "Sprite_drawTransOverlay";
private static final String sprite4 = "Sprite_drawTransBg";
private static final String indexedSprite = "IndexedSprite_something";
private static final String indexedSprite2 = "IndexedSprite_two";
private static final net.runelite.asm.pool.Method drawAlpha = new net.runelite.asm.pool.Method(
new Class("client"),
"drawAlpha",
new Signature("([IIII)V")
);
private final Inject inject;
private int count;
public RasterizerHook(Inject inject)
{
this.inject = inject;
}
public void inject() throws InjectionException
{
runDrawAlpha();
logger.info("Injected {} drawAlpha's", count);
assert count == 35 : "Either too many or not enough drawAlpha's were injected";
count = 0;
runVars();
run();
}
private void runDrawAlpha() throws InjectionException
{
runR3DAlpha(r3d_horiz, 15, r3d_field);
runR3DAlpha(r3d_vert, 12, r3d_field);
runFontAlpha(font_alpha, 1, 9); // speshul cause 255 - var9
runAlpha(circle_alpha, 2, 4);
runAlpha(line_alpha, 1, 4);
runAlpha(line_alpha2, 1, 4);
runAlpha(fill_rect_alpha, 1, 5);
runAlpha(sprite_alpha1, 1, 9, 0);
runAlpha(sprite_alpha2, 1, 12, 0);
}
private void runR3DAlpha(String methodName, int req, String fieldName) throws InjectionException
{
Method meth = findStaticMethod(inject, methodName);
Field field = findDeobField(inject, fieldName);
Instructions ins = meth.getCode().getInstructions();
int varIdx = 0; // This is obviously dumb but I cba making this better
int added = 0;
List<Integer> indices = new ArrayList<>();
for (Instruction i : meth.findLVTInstructionsForVariable(varIdx))
{
indices.add(ins.getInstructions().indexOf(i));
}
if (indices.isEmpty())
{
throw new InjectionException("Couldn't find hook location in " + methodName);
}
for (int i : indices)
{
for (int codeIndex = i + added; codeIndex < ins.getInstructions().size(); codeIndex++)
{
if (ins.getInstructions().get(codeIndex) instanceof IAStore)
{
ins.getInstructions().set(codeIndex, new InvokeStatic(ins, drawAlpha));
ins.getInstructions().add(codeIndex, new ISub(ins, InstructionType.ISUB));
ins.getInstructions().add(codeIndex, new GetStatic(ins, field.getPoolField()));
ins.getInstructions().add(codeIndex, new LDC(ins, 255));
added++;
count++;
break;
}
}
}
}
private void runAlpha(String methodName, int req, int extraArg) throws InjectionException
{
runAlpha(methodName, req, extraArg, -1);
}
private void runAlpha(String methodName, int req, int extraArg, int varIndex) throws InjectionException
{
final net.runelite.asm.pool.Field pixels = findDeobField(inject, "Rasterizer2D_pixels").getPoolField();
Method meth = findStaticMethod(inject, methodName);
if (meth == null)
{
throw new InjectionException(methodName + " couldnt be found");
}
Code code = meth.getCode();
Instructions ins = code.getInstructions();
int added = 0;
List<Integer> indices = new ArrayList<>();
for (Instruction i : ins.getInstructions())
{
if (!(i instanceof IALoad) && !(i instanceof GetField) && !(i instanceof ALoad))
{
continue;
}
if (i instanceof GetField)
{
if (((GetField) i).getField().equals(pixels))
{
indices.add(ins.getInstructions().indexOf(i));
}
}
else if ((i instanceof ALoad) && varIndex >= 0 && ((LVTInstruction) i).getVariableIndex() == varIndex)
{
indices.add(ins.getInstructions().indexOf(i));
}
else if (varIndex == -1)
{
indices.add(ins.getInstructions().indexOf(i));
}
}
if (indices.isEmpty())
{
throw new InjectionException("Couldn't find hook location in " + methodName);
}
int oldCount = count;
for (int i : indices)
{
for (int codeIndex = i + added; codeIndex < ins.getInstructions().size(); codeIndex++)
{
if (ins.getInstructions().get(codeIndex) instanceof IAStore)
{
ins.getInstructions().set(codeIndex, new InvokeStatic(ins, drawAlpha));
if (extraArg != -1)
{
ins.getInstructions().add(codeIndex, new ILoad(ins, extraArg));
added++;
}
count++;
break;
}
}
}
if (count - oldCount > req)
{
throw new InjectionException("Too many drawAlpha's were injected into " + methodName);
}
}
private void runFontAlpha(String methodName, int req, int extraArg) throws InjectionException
{
Method meth = findStaticMethod(inject, methodName);
Instructions ins = meth.getCode().getInstructions();
int varIdx = 0; // This is obviously dumb but I cba making this better
int added = 0;
List<Integer> indices = new ArrayList<>();
for (Instruction i : meth.findLVTInstructionsForVariable(varIdx))
{
indices.add(ins.getInstructions().indexOf(i));
}
if (indices.isEmpty())
{
throw new InjectionException("Couldn't find hook location in " + methodName);
}
int oldCount = count;
for (int i : indices)
{
for (int codeIndex = i + added; codeIndex < ins.getInstructions().size(); codeIndex++)
{
if (ins.getInstructions().get(codeIndex) instanceof IAStore)
{
ins.getInstructions().set(codeIndex, new InvokeStatic(ins, drawAlpha));
ins.getInstructions().add(codeIndex, new ISub(ins, InstructionType.ISUB));
ins.getInstructions().add(codeIndex, new ILoad(ins, extraArg));
ins.getInstructions().add(codeIndex, new LDC(ins, 255));
added++;
count++;
break;
}
}
}
if (count - req != oldCount)
{
throw new InjectionException(req != oldCount ? req > count - oldCount ? "Not enough" : "Too many" : "No" + " drawAlpha's were injected into " + methodName);
}
}
private void runVars() throws InjectionException
{
runOnMethodWithVar(rast3D, 0);
runOnMethodWithVar(rast3D2, 0);
// 36 expected
runOnMethodWithVar(font, 0);
// 5 expected
runOnMethodWithVar(sprite, 1);
runOnMethodWithVar(sprite2, 0);
runOnMethodWithVar(sprite3, 0);
runOnMethodWithVar(sprite4, 0);
// 12 expected
runOnMethodWithVar(indexedSprite, 0);
runOnMethodWithVar(indexedSprite2, 0);
// 6 expected
}
private void run() throws InjectionException
{
final int startCount = count; // Cause you can't just count shit ty
final net.runelite.asm.pool.Field pixels = findDeobField(inject, "Rasterizer2D_pixels").getPoolField();
Execution ex = new Execution(inject.getVanilla());
ex.populateInitialMethods();
Set<Instruction> done = new HashSet<>();
ex.addExecutionVisitor((InstructionContext ic) ->
{
Instruction i = ic.getInstruction();
Instructions ins = i.getInstructions();
Code code = ins.getCode();
Method method = code.getMethod();
//logger.debug(i.toString());
if (!(i instanceof IAStore))
{
return;
}
if (!done.add(i))
{
return;
}
ArrayStore as = (ArrayStore) i;
Field fieldBeingSet = as.getMyField(ic);
if (fieldBeingSet == null)
{
return;
}
if (!fieldBeingSet.getPoolField().equals(pixels))
{
return;
}
int index = ins.getInstructions().indexOf(i);
if (!(ins.getInstructions().get(index - 1) instanceof ILoad) && !ic.getPops().get(0).getValue().isUnknownOrNull())
{
if ((int) ic.getPops().get(0).getValue().getValue() == 0)
{
logger.debug("Didn't add hook in method {}.{}. {} added, {} total, value 0", method.getClassFile().getClassName(), method.getName(), count - startCount, count);
return;
}
}
ins.getInstructions().add(index, new IOr(ins, InstructionType.IOR)); // Add instructions backwards
ins.getInstructions().add(index, new LDC(ins, val));
count++;
logger.debug("Added hook in method {}.{}. {} added, {} total", method.getClassFile().getClassName(), method.getName(), count - startCount, count);
});
ex.run();
}
private void runOnMethodWithVar(String meth, int varIndex) throws InjectionException
{
Method method = findStaticMethod(inject, meth);
Instructions ins = method.getCode().getInstructions();
List<Integer> indices = new ArrayList<>();
for (Instruction i : method.findLVTInstructionsForVariable(varIndex))
{
int index = ins.getInstructions().indexOf(i);
assert index != -1;
assert ins.getInstructions().get(index + 1) instanceof ILoad; // Index in the array
indices.add(index);
}
int added = 0;
for (int i : indices)
{
for (int codeIndex = i + added; codeIndex < ins.getInstructions().size(); codeIndex++)
{
if (ins.getInstructions().get(codeIndex) instanceof IAStore)
{
added += 2;
count++;
ins.addInstruction(codeIndex, new IOr(ins, InstructionType.IOR)); // Add instructions backwards
ins.addInstruction(codeIndex, new LDC(ins, val));
break;
}
}
}
logger.info("Added {} instructions in {}. {} total", added >>> 1, meth, count);
}
}