Skip to content
This repository was archived by the owner on May 3, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ShenzhenMod/Installer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ private void ApplyPatches(string unpatchedPath, string patchedPath)

sm_log.Info("Applying patches");
string exeDir = Path.GetDirectoryName(unpatchedPath);
new LazySort(types).Apply();
new IncreaseMaxBoardSize(types).Apply();
new AddBiggerSandbox(types, exeDir).Apply();
new AdjustPlaybackSpeedSlider(types, exeDir).Apply();
Expand Down
153 changes: 153 additions & 0 deletions ShenzhenMod/Patches/LazySort.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using static System.FormattableString;

namespace ShenzhenMod.Patches
{
/// <summary>
/// Patches the code to make solution chip list sorting lazy.
public class LazySort
{
private static readonly log4net.ILog sm_log = log4net.LogManager.GetLogger(typeof(LazySort));

private ShenzhenTypes m_types;

public LazySort(ShenzhenTypes types)
{
m_types = types;
}

public void Apply()
{
sm_log.Info("Applying patch");

PatchSort();
}

/// <summary>
/// Patches the code that sorts the list of Chips to be lazy (only sort if needed).
/// Sorting the list causes a `Collection was modified` exception to be thrown.
/// </summary>
private void PatchSort()
{
// Find the method that sorts the list of chips
var method = m_types.Solution.Type.Methods.Single(m => !m.IsPublic && !m.IsConstructor && !m.IsStatic && m.Parameters.Count == 0 && m.ReturnType == m_types.BuiltIn.Void);
var il = method.Body.GetILProcessor();
method.Body.SimplifyMacros();

/* Replace this:

this.chipList.Sort((chip1, chip2) => { chip1.Index.CompareTo(chip2.Index); });

with this:

var comparison = new Comparison<Chip>((chip1, chip2) => { chip1.Index.CompareTo(chip2.Index); });
for (int i = 1; i < this.chipList.Count; i++)
{
if (comparison.Compare(this.chipList[i - 1], this.chipList[i]) > 0)
{
this.chipList.Sort(comparison);
return;
}
}

*/

var instructions = method.Body.Instructions;
var loadChipList = instructions[1];
if (loadChipList.OpCode != OpCodes.Ldfld)
{
throw new Exception(Invariant($"Unexpected instruction, wanted: ldfld, found: {loadChipList.OpCode}"));
}
var callSort = instructions[instructions.Count - 2];
if (callSort.OpCode != OpCodes.Callvirt)
{
throw new Exception(Invariant($"Unexpected instruction, wanted: callvirt, found: {callSort.OpCode}"));
}

var listChipType = m_types.Module.ImportReference(typeof(List<>)).MakeGenericInstanceType(m_types.Chip.Type);
var comparisonChipType = m_types.Module.ImportReference(typeof(Comparison<>)).MakeGenericInstanceType(m_types.Chip.Type);

var lbl_static_init_end = il.Create(OpCodes.Nop);
method.FindInstruction(OpCodes.Brtrue, callSort).Operand = lbl_static_init_end;

var comparison = new VariableDefinition(comparisonChipType);
method.Body.Variables.Add(comparison);

var sortAndReturn = new List<Instruction>();
// ldarg.0; ldfld List<Chip> Solution::chipList
sortAndReturn.AddRange(il.RemoveRange(
method.FindInstruction(OpCodes.Ldarg, il.Create(OpCodes.Ldarg, 0).Operand),
loadChipList.Next));
sortAndReturn.Add(il.Create(OpCodes.Ldloc, comparison));
// callVirt List<Chip>::Sort(Comparison<Chip>); ret
sortAndReturn.AddRange(il.RemoveRange(callSort, null));

il.Append(lbl_static_init_end);
il.Emit(OpCodes.Stloc, comparison);

// for (int i = 1; i < this.chipList.Count; i++)...
var i = new VariableDefinition(m_types.BuiltIn.Int32);
method.Body.Variables.Add(i);
// int i = 1
il.Emit(OpCodes.Ldc_I4, 1);
il.Emit(OpCodes.Stloc, i);
var lbl_for_start = il.Create(OpCodes.Nop);
var lbl_for_end = il.Create(OpCodes.Nop);
il.Append(lbl_for_start);
// i < this.chipList.Count
il.Emit(OpCodes.Ldloc, i);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, (FieldDefinition)loadChipList.Operand);
il.Emit(OpCodes.Callvirt, m_types.Module.ImportReference(listChipType.Resolve().FindMethod("get_Count").MakeGeneric(m_types.Chip.Type)));
il.Emit(OpCodes.Clt);
il.Emit(OpCodes.Brfalse, lbl_for_end);
{
// if (comparison.Compare(this.chipList[i - 1], this.chipList[i]) > 0)...
il.Emit(OpCodes.Ldloc, comparison);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, (FieldDefinition)loadChipList.Operand);
il.Emit(OpCodes.Ldloc, i);
il.Emit(OpCodes.Ldc_I4, 1);
il.Emit(OpCodes.Sub);
il.Emit(OpCodes.Callvirt, m_types.Module.ImportReference(listChipType.Resolve().FindMethod("get_Item").MakeGeneric(m_types.Chip.Type)));
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, (FieldDefinition)loadChipList.Operand);
il.Emit(OpCodes.Ldloc, i);
il.Emit(OpCodes.Callvirt, m_types.Module.ImportReference(listChipType.Resolve().FindMethod("get_Item").MakeGeneric(m_types.Chip.Type)));
il.Emit(OpCodes.Callvirt, m_types.Module.ImportReference(comparisonChipType.Resolve().FindMethod("Invoke").MakeGeneric(m_types.Chip.Type)));
il.Emit(OpCodes.Ldc_I4, 0);
il.Emit(OpCodes.Cgt);
var lbl_endif = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Brfalse, lbl_endif);
{
// this.chipList.Sort(comparison);
// return;
sortAndReturn.ForEach(il.Append);

il.Append(lbl_endif);
}
// end if (if (comparison.Compare(this.chipList[i - 1], this.chipList[i]) > 0)...)

// i++
il.Emit(OpCodes.Ldloc, i);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Stloc, i);
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Br, lbl_for_start);
il.Append(lbl_for_end);
} // end for (for (int i = 1; i < this.chipList.Count; i++)...)

// return;
il.Emit(OpCodes.Ret);

method.Body.OptimizeMacros();
}
}
}
1 change: 1 addition & 0 deletions ShenzhenMod/ShenzhenMod.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
<Compile Include="Patches\IncreaseMaxSpeed.cs" />
<Compile Include="Patches\IncreaseMaxBoardSize.cs" />
<Compile Include="Patches\AddBiggerSandbox.cs" />
<Compile Include="Patches\LazySort.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ShenzhenLocator.cs" />
Expand Down
12 changes: 12 additions & 0 deletions ShenzhenMod/ShenzhenTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ namespace ShenzhenMod
{
public class ShenzhenTypes
{
public class ChipType
{
public readonly TypeDefinition Type;

public ChipType(ModuleDefinition module)
{
Type = module.FindType("Chip");
}
}

public class CircuitEditorScreenType
{
public readonly TypeDefinition Type;
Expand Down Expand Up @@ -237,6 +247,7 @@ public TracesType(ModuleDefinition module)
public readonly ModuleDefinition Module;
public readonly TypeSystem BuiltIn;

public readonly ChipType Chip;
public readonly GameLogicType GameLogic;
public readonly GlobalsType Globals;
public readonly Index2Type Index2;
Expand All @@ -256,6 +267,7 @@ public ShenzhenTypes(ModuleDefinition module)
Module = module;
BuiltIn = module.TypeSystem;

Chip = new ChipType(module);
GameLogic = new GameLogicType(module);
Globals = new GlobalsType(module);
Index2 = new Index2Type(module);
Expand Down