Skip to content

Instantly share code, notes, and snippets.

@pardeike
Last active August 12, 2018 11:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pardeike/69c0f08f0f186bb2cc55f6e18d3da72c to your computer and use it in GitHub Desktop.
Save pardeike/69c0f08f0f186bb2cc55f6e18d3da72c to your computer and use it in GitHub Desktop.
CodeMatcher draft
using Harmony.ILCopying;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
namespace Harmony
{
public class CodeMatch
{
public List<OpCode> opcodes = new List<OpCode>();
public List<object> operands = new List<object>();
public List<Label> labels = new List<Label>();
public List<ExceptionBlock> blocks = new List<ExceptionBlock>();
public List<int> jumpsFrom = new List<int>();
public List<int> jumpsTo = new List<int>();
public Func<CodeInstruction, bool> predicate = null;
public CodeMatch(OpCode? opcode = null, object operand = null)
{
if (opcode is OpCode opcodeValue) opcodes.Add(opcodeValue);
if (operand != null) operands.Add(operand);
}
public CodeMatch(CodeInstruction instruction) : this(instruction.opcode, instruction.operand) { }
public CodeMatch(Func<CodeInstruction, bool> predicate)
=> this.predicate = predicate;
public bool Matches(List<CodeInstruction> codes, CodeInstruction instruction)
{
if (predicate != null) return predicate(instruction);
if (opcodes.Count > 0 && opcodes.Contains(instruction.opcode) == false) return false;
if (operands.Count > 0 && operands.Contains(instruction.operand) == false) return false;
if (labels.Count > 0 && labels.Intersect(instruction.labels).Any() == false) return false;
if (blocks.Count > 0 && blocks.Intersect(instruction.blocks).Any() == false) return false;
if (jumpsFrom.Count > 0 && jumpsFrom.Select(index => codes[index].operand).OfType<Label>()
.Intersect(instruction.labels).Any() == false) return false;
if (jumpsTo.Count > 0)
{
var operand = instruction.operand;
if (operand == null || operand.GetType() != typeof(Label)) return false;
var label = (Label)operand;
var indices = Enumerable.Range(0, codes.Count).Where(idx => codes[idx].labels.Contains(label));
if (jumpsTo.Intersect(indices).Any() == false) return false;
}
return true;
}
public override string ToString()
{
var result = "[";
if (opcodes.Count > 0)
result += "opcodes=" + opcodes.Join() + " ";
if (operands.Count > 0)
result += "operands=" + operands.Join() + " ";
if (labels.Count > 0)
result += "labels=" + labels.Join() + " ";
if (blocks.Count > 0)
result += "blocks=" + blocks.Join() + " ";
if (jumpsFrom.Count > 0)
result += "jumpsFrom=" + jumpsFrom.Join() + " ";
if (jumpsTo.Count > 0)
result += "jumpsTo=" + jumpsTo.Join() + " ";
if (predicate != null)
result += "predicate=yes ";
return result.TrimEnd() + "]";
}
}
public class CodeMatcher
{
private readonly ILGenerator generator;
private readonly List<CodeInstruction> codes = new List<CodeInstruction>();
public int Pos { get; private set; } = -1;
private void FixStart() { Pos = Math.Max(0, Pos); }
private void SetOutOfBounds(int direction) { Pos = direction > 0 ? Length : -1; }
public CodeInstruction Instruction => codes[Pos];
public int Length => codes.Count;
public bool Valid => Pos >= 0 && Pos < Length;
public int Remaining => Length - Math.Max(0, Pos);
public CodeMatcher Clone => new CodeMatcher(generator, codes) { Pos = Pos };
public CodeMatcher() { }
public ref OpCode Opcode => ref codes[Pos].opcode;
public ref object Operand => ref codes[Pos].operand;
public ref List<Label> Labels => ref codes[Pos].labels;
public ref List<ExceptionBlock> Blocks => ref codes[Pos].blocks;
// make a deep copy of all instructions and settings
//
public CodeMatcher(ILGenerator generator, IEnumerable<CodeInstruction> instructions)
{
this.generator = generator;
codes = instructions.Select(c => new CodeInstruction(c)).ToList();
}
// reading instructions out ---------------------------------------------
public CodeInstruction InstructionAt(int offset) => codes[Pos + offset];
public List<CodeInstruction> Instructions() => codes;
public List<CodeInstruction> Instructions(int count)
=> codes.GetRange(Pos, count).Select(c => new CodeInstruction(c)).ToList();
public List<CodeInstruction> InstructionsInRange(int start, int end)
{
var instructions = codes;
if (start > end) { var tmp = start; start = end; end = tmp; }
instructions = instructions.GetRange(start, end - start + 1);
return instructions.Select(c => new CodeInstruction(c)).ToList();
}
public List<CodeInstruction> InstructionsWithOffsets(int startOffset, int endOffset)
=> InstructionsInRange(Pos + startOffset, Pos + endOffset);
public List<Label> DistinctLabels(IEnumerable<CodeInstruction> instructions)
=> instructions.SelectMany(instruction => instruction.labels).Distinct().ToList();
// edit operation -------------------------------------------------------
public void SetInstruction(CodeInstruction instruction)
=> codes[Pos] = instruction;
public void SetInstructionAndAdvance(CodeInstruction instruction)
{
SetInstruction(instruction);
Pos++;
}
public void Set(OpCode opcode, object operand)
{
Opcode = opcode;
Operand = operand;
}
public void SetAndAdvance(OpCode opcode, object operand)
{
Set(opcode, operand);
Pos++;
}
public void SetOpcodeAndAdvance(OpCode opcode)
{
Opcode = opcode;
Pos++;
}
public void SetOperandAndAdvance(object operand)
{
Operand = operand;
Pos++;
}
public Label CreateLabel()
{
var label = generator.DefineLabel();
Labels.Add(label);
return label;
}
public Label CreateLabelAt(int position)
{
var label = generator.DefineLabel();
codes[position].labels.Add(label);
return label;
}
public void AddLabels(IEnumerable<Label> labels)
=> Labels.AddRange(labels);
public void AddLabelsAt(int position, IEnumerable<Label> labels)
=> codes[position].labels.AddRange(labels);
public Label SetJumpTo(int destination)
{
var label = CreateLabelAt(destination);
Labels.Add(label);
return label;
}
// insert operations ----------------------------------------------------
public void Insert(CodeInstruction instruction)
=> codes.Insert(Pos, instruction);
public void Insert(IEnumerable<CodeInstruction> instructions)
=> codes.InsertRange(Pos, instructions);
public void Insert(params CodeInstruction[] instructions)
=> codes.InsertRange(Pos, instructions);
public CodeInstruction InsertBranch(OpCode opcode, int destination)
{
var label = CreateLabelAt(destination);
var instruction = new CodeInstruction(opcode, label);
codes.Insert(Pos, instruction);
return instruction;
}
public void InsertAndAdvance(CodeInstruction instruction)
{
Insert(instruction);
Pos++;
}
public void InsertAndAdvance(IEnumerable<CodeInstruction> instructions)
=> instructions.Do(instruction => InsertAndAdvance(instruction));
public void InsertAndAdvance(params CodeInstruction[] instructions)
=> instructions.Do(instruction => InsertAndAdvance(instruction));
public CodeInstruction InsertBranchAndAdvance(OpCode opcode, int destination)
{
var instruction = InsertBranch(opcode, destination);
Pos++;
return instruction;
}
// delete operations --------------------------------------------------------
public CodeInstruction RemoveInstruction()
{
var instruction = new CodeInstruction(Instruction);
codes.RemoveAt(Pos);
return instruction;
}
public List<CodeInstruction> RemoveInstructions(int count)
{
var instructions = Instructions(count);
codes.RemoveRange(Pos, Pos + count - 1);
return instructions;
}
public List<CodeInstruction> RemoveInstructionsInRange(int start, int end)
{
var instructions = InstructionsInRange(start, end);
if (start > end) { var tmp = start; start = end; end = tmp; }
codes.RemoveRange(start, end - start + 1);
return instructions;
}
public List<CodeInstruction> RemoveInstructionsWithOffsets(int startOffset, int endOffset)
=> RemoveInstructionsInRange(Pos + startOffset, Pos + endOffset);
// moving around ------------------------------------------------------------
public CodeMatcher Advance(int offset)
{
var matcher = Clone;
matcher.Pos += offset;
if (matcher.Valid == false) matcher.SetOutOfBounds(offset);
return matcher;
}
public CodeMatcher Start()
{
var matcher = Clone;
matcher.Pos = 0;
return matcher;
}
public CodeMatcher End()
{
var matcher = Clone;
matcher.Pos = Length - 1;
return matcher;
}
public CodeMatcher SearchForward(Func<CodeInstruction, bool> predicate) => Search(predicate, 1);
public CodeMatcher SearchBack(Func<CodeInstruction, bool> predicate) => Search(predicate, -1);
private CodeMatcher Search(Func<CodeInstruction, bool> predicate, int direction)
{
var matcher = Clone;
matcher.FixStart();
while (matcher.Valid && predicate(matcher.Instruction) == false)
matcher.Pos += direction;
return matcher;
}
public CodeMatcher MatchForward(bool useEnd, params CodeMatch[] matches) => Match(matches, 1, useEnd);
public CodeMatcher MatchBack(bool useEnd, params CodeMatch[] matches) => Match(matches, -1, useEnd);
private CodeMatcher Match(CodeMatch[] matches, int direction, bool useEnd)
{
var matcher = Clone;
matcher.FixStart();
while (true)
{
var matched = matcher.MatchSequence(matcher.Pos, matches, out var outOfBounds);
if (outOfBounds)
{
matcher.SetOutOfBounds(direction);
return matcher;
}
if (matched)
{
if (useEnd) matcher.Pos += matches.Count() - 1;
return matcher;
}
matcher.Pos += direction;
}
}
private bool MatchSequence(int start, CodeMatch[] matches, out bool outOfBounds)
{
var end = start + matches.Length - 1;
outOfBounds = start < 0 || end >= Length;
if (outOfBounds) return false;
foreach (var match in matches)
if (match.Matches(codes, codes[start++]) == false) return false;
return true;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment