53 Commits
Author SHA1 Message Date
ds5678 444e6164f6 Bump version to 1.3.0 in Directory.Build.props
Updated the VersionPrefix from 1.2.2 to 1.3.0 to prepare for a new minor release. No other changes were made.
2026-02-27 21:35:10 -08:00
ds5678 5df204203b Formatting 2026-02-27 21:30:41 -08:00
ds5678 9fa935cb0f Use the underlying types for enums in CilInstructionCollectionExtensions
This affects:
* AddDefaultValue
* InitializeDefaultValue
* AddLoadElement
* AddStoreElement

The code for AddLoadIndirect and AddStoreIndirect was also refactored, but functionality did not change.
2026-02-27 21:27:41 -08:00
ds5678 f10f09581d Fix output for AddLoadElement and AddStoreElement with pointer-sized types 2026-02-27 21:21:40 -08:00
ds5678 672c585e52 Fix stelem and ldelem missing the type 2026-02-27 21:12:08 -08:00
ds5678 c27bbc1164 Refactor generic param array creation to use for loop
Replaced LINQ-based creation of the typeArguments array in MethodStubber with a manual for loop. This improves readability and may enhance performance by eliminating LINQ overhead.
2026-02-27 11:54:11 -08:00
ds5678 eda49e0ff3 Upgrade from AsmResolver beta 5 to beta 6 2026-02-27 11:47:52 -08:00
ds5678 ae137f5afd Update test dependencies and remove coverlet.collector
Removed coverlet.collector package and updated test-related NuGet dependencies: Microsoft.NET.Test.Sdk to 18.3.0, NUnit to 4.5.0, and NUnit3TestAdapter to 6.1.0. No changes to NUnit.Analyzers or project references.
2026-02-27 11:23:10 -08:00
ds5678 b73fe6f510 Bump version to 1.2.2 2025-11-25 01:08:37 -08:00
ds5678 06216e6982 Support .NET 10 2025-11-25 01:07:10 -08:00
ds5678 90ebeb49a1 Update dependencies in test and main projects
Updated the following dependencies to their latest versions:
- `Microsoft.NET.Test.Sdk` to `18.0.1` in `AssetRipper.CIL.Tests.csproj`
- `NUnit.Analyzers` to `4.11.2` in `AssetRipper.CIL.Tests.csproj`
- `NUnit3TestAdapter` to `5.2.0` in `AssetRipper.CIL.Tests.csproj`
- `AsmResolver.DotNet` to `6.0.0-beta.5` in `AssetRipper.CIL.csproj`

These updates include improvements, fixes, and compatibility updates.
2025-11-25 01:05:08 -08:00
ds5678 2d68e67393 Bump version to 1.2.1
Updated the version number in the project properties from 1.2.0 to 1.2.1.
2025-09-15 15:47:05 -07:00
ds5678 d8230781d9 AddThrowNull 2025-09-15 14:43:00 -07:00
ds5678 2990c647c5 Add extensions from AssemblyDumper 2025-09-15 14:41:33 -07:00
ds5678 e871881517 Update project version to 1.2.0
Bump the version number in the project properties from 1.1.6 to 1.2.0.
2025-09-08 14:30:19 -07:00
ds5678 c1eceeda51 AsmResolver 6 beta 4
Resolves #2
2025-09-08 14:25:12 -07:00
ds5678 44996e27ff Update NuGet packages in project files
Updated several NuGet packages in `AssetRipper.CIL.Tests.csproj`:
- Microsoft.NET.Test.Sdk: 17.13.0 -> 17.14.1
- NUnit: 4.3.2 -> 4.4.0
- NUnit.Analyzers: 4.6.0 -> 4.10.0
- NUnit3TestAdapter: 5.0.0 -> 5.1.0

Also updated `System.Runtime.CompilerServices.Unsafe` in `AssetRipper.CIL.csproj` from version 6.1.1 to 6.1.2.
2025-09-08 14:20:20 -07:00
ds5678 0039dc95b3 Use IsAccessibleFromType when searching for a base constructor
* Related: #2
2025-04-08 15:06:57 -07:00
ds5678 0953640bf1 Add ReplaceMethodBodyWithThrowNull method
Introduces a new method `ReplaceMethodBodyWithThrowNull` in the `MethodDefinitionExtensions` class. This method modifies the body of a managed method to throw a `null` exception, with appropriate XML documentation. It includes checks to ensure the method is managed and has a body before making changes.
2025-03-23 20:20:02 -07:00
ds5678 c1e8657d67 Ensure references are imported 2025-03-21 19:23:54 -07:00
ds5678 09db4f64ae Bump version to 1.1.6 2025-03-20 23:03:22 -07:00
ds5678 0af62d2590 Support generating enums 2025-03-20 23:01:02 -07:00
ds5678 cc71118bfb Update package versions in project files
- Bump `coverlet.collector` to version `6.0.4`
- Bump `Microsoft.NET.Test.Sdk` to version `17.13.0`
- Bump `NUnit3TestAdapter` to version `5.0.0`
- Bump `AsmResolver.DotNet` to version `6.0.0-beta.3`
2025-03-20 22:01:39 -07:00
ds5678 21d8f70781 Add more extensions 2025-03-20 22:00:07 -07:00
ds5678 75a48b3ce0 Do not attempt to initialize fixed buffers 2025-03-20 21:11:29 -07:00
ds5678 c164acde5f Bump version 2025-01-23 14:28:47 -08:00
ds5678 8935137a6f Expand type check to include PointerTypeSignature
Modified conditions in CilInstructionCollectionExtensions.cs to check for both ByReferenceTypeSignature and PointerTypeSignature. This ensures null references are added and stored for both types.
2025-01-23 14:27:38 -08:00
ds5678 7e3e7bfa8c Bump version 2025-01-11 01:07:46 -08:00
ds5678 777fb5ea1c Fix AddLoadIndirect and AddStoreIndirect
* Resolves #1
2025-01-11 01:07:24 -08:00
ds5678 be8f11f6b5 Fix incorrect field references for generic types 2025-01-10 23:47:59 -08:00
ds5678 1338ca2f95 Add method stubbing tool 2025-01-10 23:46:05 -08:00
ds5678 e95ff3bae6 Bump tools to .NET 9 2025-01-10 23:45:32 -08:00
ds5678 44cf58eef6 Bump version 2024-12-10 12:11:31 -08:00
ds5678 cb89ec09b2 Bump to .NET 9 2024-12-10 12:10:25 -08:00
ds5678 677f5349b3 CIL Manipulators 2024-09-23 23:26:35 -07:00
ds5678 7fd99d382d Include exception handlers in validation 2024-09-23 21:13:35 -07:00
ds5678 2ad3472b3e Forgot nullable attribute in event extensions 2024-09-07 12:58:58 -07:00
ds5678 c8e1b8809c Bump version 2024-09-07 12:50:59 -07:00
ds5678 a0c48e0216 Backing field improvements
* More strict identification
* Add extensions for fields, events, and properties
2024-09-07 12:50:18 -07:00
ds5678 1df36de7df Field extensions 2024-09-01 08:21:39 -07:00
ds5678 89d8b4a7cd Event and property extensions 2024-09-01 08:15:23 -07:00
ds5678 9106df0c05 Version bump 2024-07-25 07:33:21 -07:00
ds5678 7f4f811679 More extensions 2024-07-25 07:32:35 -07:00
ds5678 2085e5f81e For standardization, don't replace br false with br true 2024-07-21 10:58:12 -07:00
ds5678 5806496459 Handle switch instructions for macro standardization 2024-07-21 10:55:38 -07:00
ds5678 ecdd481b87 Add assembly validator project 2024-07-12 20:32:22 -07:00
ds5678 2e4fa07015 MacroStandardization 2024-07-09 23:35:26 -07:00
ds5678 995d4d5e05 Add more extension methods 2024-07-06 13:07:57 -04:00
ds5678 58e24a2bb2 Bump version 2024-06-15 23:27:34 -07:00
ds5678 88e6e617cf Add more extension methods 2024-06-15 23:26:17 -07:00
ds5678 92dbe51e75 public MethodDefinitionExtensions 2024-06-15 20:45:24 -07:00
ds5678 0f996365c6 Make CilInstructionCollectionExtensions public 2024-06-15 20:14:18 -07:00
ds5678 5f3fbe86fe Update to AsmResolver 6 2024-06-15 20:06:14 -07:00
39 changed files with 2259 additions and 116 deletions

No files matched your search

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AssetRipper.CIL\AssetRipper.CIL.csproj" />
</ItemGroup>
</Project>
+18
View File
@@ -0,0 +1,18 @@
using AsmResolver.DotNet;
namespace AssetRipper.CIL.Standardizer
{
internal class Program
{
static void Main(string[] args)
{
ModuleDefinition module = ModuleDefinition.FromFile(args[0]);
foreach (MethodDefinition method in module.GetAllTypes().SelectMany(t => t.Methods))
{
method.CilMethodBody?.Instructions.StandardizeMacros();
}
module.Write(args[1]);
Console.WriteLine("Done!");
}
}
}
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AssetRipper.CIL\AssetRipper.CIL.csproj" />
</ItemGroup>
</Project>
+18
View File
@@ -0,0 +1,18 @@
using AsmResolver.DotNet;
namespace AssetRipper.CIL.Stubber
{
internal class Program
{
static void Main(string[] args)
{
ModuleDefinition module = ModuleDefinition.FromFile(args[0]);
foreach (MethodDefinition method in module.GetAllTypes().SelectMany(t => t.Methods))
{
method.ReplaceMethodBodyWithMinimalImplementation();
}
module.Write(args[1]);
Console.WriteLine("Done!");
}
}
}
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageReference Include="NUnit" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.11.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AssetRipper.CIL\AssetRipper.CIL.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>
</Project>
+113
View File
@@ -0,0 +1,113 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Signatures;
using AsmResolver.PE.DotNet.Cil;
using AsmResolver.PE.DotNet.Metadata.Tables;
namespace AssetRipper.CIL.Tests;
public class LoadIndirectTests
{
[TestCase(ElementType.I1, CilCode.Ldind_I1)]
[TestCase(ElementType.I2, CilCode.Ldind_I2)]
[TestCase(ElementType.I4, CilCode.Ldind_I4)]
[TestCase(ElementType.I8, CilCode.Ldind_I8)]
[TestCase(ElementType.U1, CilCode.Ldind_U1)]
[TestCase(ElementType.U2, CilCode.Ldind_U2)]
[TestCase(ElementType.U4, CilCode.Ldind_U4)]
[TestCase(ElementType.U8, CilCode.Ldind_I8)]
[TestCase(ElementType.I, CilCode.Ldind_I)]
[TestCase(ElementType.U, CilCode.Ldind_I)]
[TestCase(ElementType.R4, CilCode.Ldind_R4)]
[TestCase(ElementType.R8, CilCode.Ldind_R8)]
[TestCase(ElementType.Boolean, CilCode.Ldind_U1)]
[TestCase(ElementType.Char, CilCode.Ldind_U2)]
[TestCase(ElementType.Object, CilCode.Ldind_Ref)]
[TestCase(ElementType.String, CilCode.Ldind_Ref)]
[TestCase(ElementType.TypedByRef, CilCode.Ldobj)]
public void CorlibLoadIndirect(ElementType type, CilCode code)
{
ModuleDefinition module = new ModuleDefinition("TestModule");
CorLibTypeSignature? typeToLoad = module.CorLibTypeFactory.FromElementType(type);
Assert.That(typeToLoad, Is.Not.Null);
AssertCorrectLoadIndirect(typeToLoad, code);
}
[TestCase(ElementType.I1, CilCode.Ldind_I1)]
[TestCase(ElementType.I2, CilCode.Ldind_I2)]
[TestCase(ElementType.I4, CilCode.Ldind_I4)]
[TestCase(ElementType.I8, CilCode.Ldind_I8)]
[TestCase(ElementType.U1, CilCode.Ldind_U1)]
[TestCase(ElementType.U2, CilCode.Ldind_U2)]
[TestCase(ElementType.U4, CilCode.Ldind_U4)]
[TestCase(ElementType.U8, CilCode.Ldind_I8)]
public void EnumLoadIndirect(ElementType type, CilCode code)
{
ModuleDefinition module = new ModuleDefinition("TestModule");
TypeSignature? typeToLoad = CreateEnum(module, type).ToTypeSignature();
Assert.That(typeToLoad, Is.Not.Null);
AssertCorrectLoadIndirect(typeToLoad, code);
}
[Test]
public void PointerLoadIndirect()
{
ModuleDefinition module = new ModuleDefinition("TestModule");
PointerTypeSignature typeToLoad = module.CorLibTypeFactory.Void.MakePointerType();
AssertCorrectLoadIndirect(typeToLoad, CilCode.Ldind_I);
}
[Test]
public void GenericParameterLoadIndirect()
{
ModuleDefinition module = new ModuleDefinition("TestModule");
GenericParameterSignature typeToLoad = new GenericParameterSignature(module, GenericParameterType.Type, 0);
AssertCorrectLoadIndirect(typeToLoad, CilCode.Ldobj);
}
[Test]
public void SzArrayLoadIndirect()
{
ModuleDefinition module = new ModuleDefinition("TestModule");
TypeSignature typeToLoad = module.CorLibTypeFactory.Int32.MakeSzArrayType();
AssertCorrectLoadIndirect(typeToLoad, CilCode.Ldind_Ref);
}
[Test]
public void ClassLoadIndirect()
{
ModuleDefinition module = new ModuleDefinition("TestModule");
TypeDefinition type = new TypeDefinition("TestNamespace", "TestName", TypeAttributes.Public, module.CorLibTypeFactory.Object.ToTypeDefOrRef());
module.TopLevelTypes.Add(type);
AssertCorrectLoadIndirect(type.ToTypeSignature(), CilCode.Ldind_Ref);
}
[Test]
public void StructLoadIndirect()
{
ModuleDefinition module = new ModuleDefinition("TestModule");
TypeDefinition type = new TypeDefinition("TestNamespace", "TestName", TypeAttributes.Public | TypeAttributes.SequentialLayout | TypeAttributes.Sealed, module.DefaultImporter.ImportType(typeof(ValueType)));
module.TopLevelTypes.Add(type);
AssertCorrectLoadIndirect(type.ToTypeSignature(), CilCode.Ldobj);
}
private static void AssertCorrectLoadIndirect(TypeSignature typeToLoad, CilCode expectedOpCode)
{
MethodDefinition method = new MethodDefinition("Test", MethodAttributes.Public | MethodAttributes.Static, null);
method.CilMethodBody = new();
CilInstructionCollection instructions = method.CilMethodBody.Instructions;
instructions.AddLoadIndirect(typeToLoad);
Assert.That(instructions, Has.Count.EqualTo(1));
Assert.That(instructions[0].OpCode.Code, Is.EqualTo(expectedOpCode));
}
private static TypeDefinition CreateEnum(ModuleDefinition module, ElementType underlyingType)
{
TypeDefinition enumType = new TypeDefinition("TestNamespace", "TestEnum", TypeAttributes.Public, module.DefaultImporter.ImportType(typeof(Enum)));
module.TopLevelTypes.Add(enumType);
CorLibTypeSignature underlyingTypeSignature = module.CorLibTypeFactory.FromElementType(underlyingType) ?? throw new NullReferenceException();
FieldDefinition valueField = new FieldDefinition("value__", FieldAttributes.Public | FieldAttributes.SpecialName | FieldAttributes.RuntimeSpecialName, underlyingTypeSignature);
enumType.Fields.Add(valueField);
return enumType;
}
}
+113
View File
@@ -0,0 +1,113 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Signatures;
using AsmResolver.PE.DotNet.Cil;
using AsmResolver.PE.DotNet.Metadata.Tables;
namespace AssetRipper.CIL.Tests;
public class StoreIndirectTests
{
[TestCase(ElementType.I1, CilCode.Stind_I1)]
[TestCase(ElementType.I2, CilCode.Stind_I2)]
[TestCase(ElementType.I4, CilCode.Stind_I4)]
[TestCase(ElementType.I8, CilCode.Stind_I8)]
[TestCase(ElementType.U1, CilCode.Stind_I1)]
[TestCase(ElementType.U2, CilCode.Stind_I2)]
[TestCase(ElementType.U4, CilCode.Stind_I4)]
[TestCase(ElementType.U8, CilCode.Stind_I8)]
[TestCase(ElementType.I, CilCode.Stind_I)]
[TestCase(ElementType.U, CilCode.Stind_I)]
[TestCase(ElementType.R4, CilCode.Stind_R4)]
[TestCase(ElementType.R8, CilCode.Stind_R8)]
[TestCase(ElementType.Boolean, CilCode.Stind_I1)]
[TestCase(ElementType.Char, CilCode.Stind_I2)]
[TestCase(ElementType.Object, CilCode.Stind_Ref)]
[TestCase(ElementType.String, CilCode.Stind_Ref)]
[TestCase(ElementType.TypedByRef, CilCode.Stobj)]
public void CorlibStoreIndirect(ElementType type, CilCode code)
{
ModuleDefinition module = new ModuleDefinition("TestModule");
CorLibTypeSignature? typeToStore = module.CorLibTypeFactory.FromElementType(type);
Assert.That(typeToStore, Is.Not.Null);
AssertCorrectStoreIndirect(typeToStore, code);
}
[TestCase(ElementType.I1, CilCode.Stind_I1)]
[TestCase(ElementType.I2, CilCode.Stind_I2)]
[TestCase(ElementType.I4, CilCode.Stind_I4)]
[TestCase(ElementType.I8, CilCode.Stind_I8)]
[TestCase(ElementType.U1, CilCode.Stind_I1)]
[TestCase(ElementType.U2, CilCode.Stind_I2)]
[TestCase(ElementType.U4, CilCode.Stind_I4)]
[TestCase(ElementType.U8, CilCode.Stind_I8)]
public void EnumStoreIndirect(ElementType type, CilCode code)
{
ModuleDefinition module = new ModuleDefinition("TestModule");
TypeSignature? typeToStore = CreateEnum(module, type).ToTypeSignature();
Assert.That(typeToStore, Is.Not.Null);
AssertCorrectStoreIndirect(typeToStore, code);
}
[Test]
public void PointerStoreIndirect()
{
ModuleDefinition module = new ModuleDefinition("TestModule");
PointerTypeSignature typeToStore = module.CorLibTypeFactory.Void.MakePointerType();
AssertCorrectStoreIndirect(typeToStore, CilCode.Stind_I);
}
[Test]
public void GenericParameterStoreIndirect()
{
ModuleDefinition module = new ModuleDefinition("TestModule");
GenericParameterSignature typeToStore = new GenericParameterSignature(module, GenericParameterType.Type, 0);
AssertCorrectStoreIndirect(typeToStore, CilCode.Stobj);
}
[Test]
public void SzArrayStoreIndirect()
{
ModuleDefinition module = new ModuleDefinition("TestModule");
TypeSignature typeToStore = module.CorLibTypeFactory.Int32.MakeSzArrayType();
AssertCorrectStoreIndirect(typeToStore, CilCode.Stind_Ref);
}
[Test]
public void ClassStoreIndirect()
{
ModuleDefinition module = new ModuleDefinition("TestModule");
TypeDefinition type = new TypeDefinition("TestNamespace", "TestName", TypeAttributes.Public, module.CorLibTypeFactory.Object.ToTypeDefOrRef());
module.TopLevelTypes.Add(type);
AssertCorrectStoreIndirect(type.ToTypeSignature(), CilCode.Stind_Ref);
}
[Test]
public void StructStoreIndirect()
{
ModuleDefinition module = new ModuleDefinition("TestModule");
TypeDefinition type = new TypeDefinition("TestNamespace", "TestName", TypeAttributes.Public | TypeAttributes.SequentialLayout | TypeAttributes.Sealed, module.DefaultImporter.ImportType(typeof(ValueType)));
module.TopLevelTypes.Add(type);
AssertCorrectStoreIndirect(type.ToTypeSignature(), CilCode.Stobj);
}
private static void AssertCorrectStoreIndirect(TypeSignature typeToStore, CilCode expectedOpCode)
{
MethodDefinition method = new MethodDefinition("Test", MethodAttributes.Public | MethodAttributes.Static, null);
method.CilMethodBody = new();
CilInstructionCollection instructions = method.CilMethodBody.Instructions;
instructions.AddStoreIndirect(typeToStore);
Assert.That(instructions, Has.Count.EqualTo(1));
Assert.That(instructions[0].OpCode.Code, Is.EqualTo(expectedOpCode));
}
private static TypeDefinition CreateEnum(ModuleDefinition module, ElementType underlyingType)
{
TypeDefinition enumType = new TypeDefinition("TestNamespace", "TestEnum", TypeAttributes.Public, module.DefaultImporter.ImportType(typeof(Enum)));
module.TopLevelTypes.Add(enumType);
CorLibTypeSignature underlyingTypeSignature = module.CorLibTypeFactory.FromElementType(underlyingType) ?? throw new NullReferenceException();
FieldDefinition valueField = new FieldDefinition("value__", FieldAttributes.Public | FieldAttributes.SpecialName | FieldAttributes.RuntimeSpecialName, underlyingTypeSignature);
enumType.Fields.Add(valueField);
return enumType;
}
}
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AssetRipper.CIL\AssetRipper.CIL.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,177 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Collections;
using AsmResolver.DotNet.Signatures;
using AsmResolver.PE.DotNet.Cil;
namespace AssetRipper.CIL.Validator;
public static class CilInstructionCollectionEquality
{
public static bool Equals(CilMethodBody left, CilMethodBody right)
{
// Local Variables
if (left.LocalVariables.Count != right.LocalVariables.Count)
{
return false;
}
for (int i = 0; i < left.LocalVariables.Count; i++)
{
if (!SignatureComparer.Default.Equals(left.LocalVariables[i].VariableType, right.LocalVariables[i].VariableType))
{
return false;
}
}
// Exception Handlers
if (left.ExceptionHandlers.Count != right.ExceptionHandlers.Count)
{
return false;
}
for (int i = 0; i < left.ExceptionHandlers.Count; i++)
{
if (!Equals(left.ExceptionHandlers[i], right.ExceptionHandlers[i]))
{
return false;
}
}
// Instructions
return Equals(left.Instructions, right.Instructions);
}
public static bool Equals(CilExceptionHandler left, CilExceptionHandler right)
{
return left.HandlerType == right.HandlerType
&& left.TryStart?.Offset == right.TryStart?.Offset
&& left.TryEnd?.Offset == right.TryEnd?.Offset
&& left.HandlerStart?.Offset == right.HandlerStart?.Offset
&& left.HandlerEnd?.Offset == right.HandlerEnd?.Offset
&& left.FilterStart?.Offset == right.FilterStart?.Offset
&& SignatureComparer.Default.Equals(left.ExceptionType, right.ExceptionType);
}
public static bool Equals(CilInstructionCollection left, CilInstructionCollection right)
{
if (left.Count != right.Count)
{
return false;
}
for (int i = 0; i < left.Count; i++)
{
CilInstruction leftInstruction = left[i];
CilInstruction rightInstruction = right[i];
if (leftInstruction.OpCode != rightInstruction.OpCode || leftInstruction.Operand?.GetType() != rightInstruction.Operand?.GetType())
{
return false;
}
if (rightInstruction.Operand is null)
{
// Both operands are null because of the type check.
continue;
}
switch (leftInstruction.Operand)
{
case CilInstructionLabel leftLabel:
{
CilInstructionLabel rightLabel = (CilInstructionLabel)rightInstruction.Operand;
int leftIndex = leftLabel.Instruction is null ? -1 : left.IndexOf(leftLabel.Instruction);
int rightIndex = rightLabel.Instruction is null ? -1 : right.IndexOf(rightLabel.Instruction);
if (leftIndex != rightIndex)
{
return false;
}
}
break;
case CilInstructionLabel[] leftLabels:
{
CilInstructionLabel[] rightLabels = (CilInstructionLabel[])rightInstruction.Operand;
if (leftLabels.Length != rightLabels.Length)
{
return false;
}
for (int j = 0; j < leftLabels.Length; j++)
{
CilInstructionLabel leftLabel = leftLabels[j];
CilInstructionLabel rightLabel = rightLabels[j];
int leftIndex = leftLabel.Instruction is null ? -1 : left.IndexOf(leftLabel.Instruction);
int rightIndex = rightLabel.Instruction is null ? -1 : right.IndexOf(rightLabel.Instruction);
if (leftIndex != rightIndex)
{
return false;
}
}
}
break;
case Parameter leftParameter:
{
Parameter rightParameter = (Parameter)rightInstruction.Operand;
if (leftParameter.Index != rightParameter.Index)
{
return false;
}
}
break;
case CilLocalVariable leftVariable:
{
CilLocalVariable rightVariable = (CilLocalVariable)rightInstruction.Operand;
if (left.Owner.LocalVariables.IndexOf(leftVariable) != right.Owner.LocalVariables.IndexOf(rightVariable))
{
return false;
}
}
break;
case MemberReference leftMember:
{
MemberReference rightMember = (MemberReference)rightInstruction.Operand;
if (!SignatureComparer.Default.Equals(leftMember, rightMember))
{
return false;
}
}
break;
case IFieldDescriptor leftField:
{
IFieldDescriptor rightField = (IFieldDescriptor)rightInstruction.Operand;
if (!SignatureComparer.Default.Equals(leftField, rightField))
{
return false;
}
}
break;
case ITypeDefOrRef leftType:
{
ITypeDefOrRef rightType = (ITypeDefOrRef)rightInstruction.Operand;
if (!SignatureComparer.Default.Equals(leftType, rightType))
{
return false;
}
}
break;
case IMethodDescriptor leftMethod:
{
IMethodDescriptor rightMethod = (IMethodDescriptor)rightInstruction.Operand;
if (!SignatureComparer.Default.Equals(leftMethod, rightMethod))
{
return false;
}
}
break;
default:
if (!leftInstruction.Operand!.Equals(rightInstruction.Operand))
{
return false;
}
break;
}
}
return true;
}
}
+236
View File
@@ -0,0 +1,236 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Signatures;
namespace AssetRipper.CIL.Validator;
internal class Program
{
static void Main(string[] args)
{
ModuleDefinition module1 = ModuleDefinition.FromFile(args[0]);
ModuleDefinition module2 = ModuleDefinition.FromFile(args[1]);
List<TypeDefinition> typesMissingFrom1 = new();
List<TypeDefinition> typesMissingFrom2 = new();
Dictionary<TypeDefinition, TypeDefinition> type1ToType2 = new();
MatchTypes(module1, module2, typesMissingFrom1, typesMissingFrom2, type1ToType2);
List<MethodDefinition> methodsMissingFrom1 = new();
List<MethodDefinition> methodsMissingFrom2 = new();
Dictionary<MethodDefinition, MethodDefinition> method1ToMethod2 = new();
foreach ((TypeDefinition type1, TypeDefinition type2) in type1ToType2)
{
MatchNameAndSignature(type1.Methods, type2.Methods, methodsMissingFrom1, methodsMissingFrom2, method1ToMethod2, m => m.Signature!, SignatureComparer.Default);
}
List<(MethodDefinition, MethodDefinition)> differentMethods = new();
foreach ((MethodDefinition method1, MethodDefinition method2) in method1ToMethod2)
{
if (method1.CilMethodBody is null)
{
if (method2.CilMethodBody is not null)
{
differentMethods.Add((method1, method2));
}
continue;
}
else if (method2.CilMethodBody is null)
{
differentMethods.Add((method1, method2));
continue;
}
method1.CilMethodBody.Instructions.ExpandMacros();
method2.CilMethodBody.Instructions.ExpandMacros();
if (!CilInstructionCollectionEquality.Equals(method1.CilMethodBody, method2.CilMethodBody))
{
CilInstructionCollectionEquality.Equals(method1.CilMethodBody, method2.CilMethodBody);
differentMethods.Add((method1, method2));
}
}
List<FieldDefinition> fieldsMissingFrom1 = new();
List<FieldDefinition> fieldsMissingFrom2 = new();
Dictionary<FieldDefinition, FieldDefinition> field1ToField2 = new();
foreach ((TypeDefinition type1, TypeDefinition type2) in type1ToType2)
{
MatchName(type1.Fields, type2.Fields, fieldsMissingFrom1, fieldsMissingFrom2, field1ToField2);
}
List<(FieldDefinition, FieldDefinition)> differentFields = new();
foreach ((FieldDefinition field1, FieldDefinition field2) in field1ToField2)
{
if (!SignatureComparer.Default.Equals(field1.Signature, field2.Signature))
{
differentFields.Add((field1, field2));
}
}
List<PropertyDefinition> propertiesMissingFrom1 = new();
List<PropertyDefinition> propertiesMissingFrom2 = new();
Dictionary<PropertyDefinition, PropertyDefinition> property1ToProperty2 = new();
foreach ((TypeDefinition type1, TypeDefinition type2) in type1ToType2)
{
MatchNameAndSignature(type1.Properties, type2.Properties, propertiesMissingFrom1, propertiesMissingFrom2, property1ToProperty2, p => p.Signature!, SignatureComparer.Default);
}
List<EventDefinition> eventsMissingFrom1 = new();
List<EventDefinition> eventsMissingFrom2 = new();
Dictionary<EventDefinition, EventDefinition> event1ToEvent2 = new();
foreach ((TypeDefinition type1, TypeDefinition type2) in type1ToType2)
{
MatchName(type1.Events, type2.Events, eventsMissingFrom1, eventsMissingFrom2, event1ToEvent2);
}
List<(EventDefinition, EventDefinition)> differentEvents = new();
foreach ((EventDefinition event1, EventDefinition event2) in event1ToEvent2)
{
if (!SignatureComparer.Default.Equals(event1.EventType, event2.EventType))
{
differentEvents.Add((event1, event2));
}
}
Console.WriteLine($"Types missing from module 1: {typesMissingFrom1.Count}");
Console.WriteLine($"Types missing from module 2: {typesMissingFrom2.Count}");
Console.WriteLine($"Types matched: {type1ToType2.Count}");
Console.WriteLine();
Console.WriteLine($"Methods missing from module 1: {methodsMissingFrom1.Count}");
Console.WriteLine($"Methods missing from module 2: {methodsMissingFrom2.Count}");
Console.WriteLine($"Methods matched: {method1ToMethod2.Count}");
Console.WriteLine($"Different methods: {differentMethods.Count}");
Console.WriteLine();
Console.WriteLine($"Fields missing from module 1: {fieldsMissingFrom1.Count}");
Console.WriteLine($"Fields missing from module 2: {fieldsMissingFrom2.Count}");
Console.WriteLine($"Fields matched: {field1ToField2.Count}");
Console.WriteLine($"Different fields: {differentFields.Count}");
Console.WriteLine();
Console.WriteLine($"Properties missing from module 1: {propertiesMissingFrom1.Count}");
Console.WriteLine($"Properties missing from module 2: {propertiesMissingFrom2.Count}");
Console.WriteLine($"Properties matched: {property1ToProperty2.Count}");
Console.WriteLine();
Console.WriteLine($"Events missing from module 1: {eventsMissingFrom1.Count}");
Console.WriteLine($"Events missing from module 2: {eventsMissingFrom2.Count}");
Console.WriteLine($"Events matched: {event1ToEvent2.Count}");
Console.WriteLine($"Different events: {differentEvents.Count}");
}
private static void MatchName<T>(IList<T> list1, IList<T> list2, List<T> missingFrom1, List<T> missingFrom2, Dictionary<T, T> value1ToValue2)
where T : class, INameProvider
{
for (int i = 0; i < list2.Count; i++)
{
T value2 = list2[i];
if (i < list1.Count && list1[i].Name == value2.Name)
{
value1ToValue2.Add(list1[i], value2);
continue;
}
T? value1 = list1.FirstOrDefault(v => v.Name == value2.Name);
if (value1 is null)
{
missingFrom1.Add(value2);
}
else
{
value1ToValue2.Add(value1, value2);
}
}
foreach (T value1 in list1)
{
if (!value1ToValue2.ContainsKey(value1))
{
missingFrom2.Add(value1);
}
}
}
private static void MatchNameAndSignature<T, TSignature>(IList<T> list1, IList<T> list2, List<T> missingFrom1, List<T> missingFrom2, Dictionary<T, T> value1ToValue2, Func<T, TSignature> getSignature, IEqualityComparer<TSignature> signatureComparer)
where T : class, INameProvider
{
for (int i = 0; i < list2.Count; i++)
{
T value2 = list2[i];
if (i < list1.Count && list1[i].Name == value2.Name && SignatureEquals(list1[i], value2, getSignature, signatureComparer))
{
value1ToValue2.Add(list1[i], value2);
continue;
}
T? value1 = list1.FirstOrDefault(v => v.Name == value2.Name && SignatureEquals(v, value2, getSignature, signatureComparer));
if (value1 is null)
{
missingFrom1.Add(value2);
}
else
{
value1ToValue2.Add(value1, value2);
}
}
foreach (T value1 in list1)
{
if (!value1ToValue2.ContainsKey(value1))
{
missingFrom2.Add(value1);
}
}
static bool SignatureEquals(T value1, T value2, Func<T, TSignature> getSignature, IEqualityComparer<TSignature> signatureComparer)
{
return signatureComparer.Equals(getSignature(value1), getSignature(value2));
}
}
private static void MatchTypes(ModuleDefinition module1, ModuleDefinition module2, List<TypeDefinition> typesMissingFrom1, List<TypeDefinition> typesMissingFrom2, Dictionary<TypeDefinition, TypeDefinition> type1ToType2)
{
foreach (TypeDefinition type2 in module2.TopLevelTypes)
{
TypeDefinition? type1 = module1.TopLevelTypes.FirstOrDefault(t => SignatureComparer.Default.Equals(t, type2));
if (type1 is null)
{
typesMissingFrom1.Add(type2);
}
else
{
type1ToType2.Add(type1, type2);
MatchNestedTypes(type1, type2, typesMissingFrom1, typesMissingFrom2, type1ToType2);
}
}
foreach (TypeDefinition type1 in module1.TopLevelTypes)
{
if (!type1ToType2.ContainsKey(type1))
{
typesMissingFrom2.Add(type1);
}
}
static void MatchNestedTypes(TypeDefinition type1, TypeDefinition type2, List<TypeDefinition> typesMissingFrom1, List<TypeDefinition> typesMissingFrom2, Dictionary<TypeDefinition, TypeDefinition> type1ToType2)
{
foreach (TypeDefinition nestedType2 in type2.NestedTypes)
{
TypeDefinition? nestedType1 = type1.NestedTypes.FirstOrDefault(t => t.Name == nestedType2.Name);
if (nestedType1 is null)
{
typesMissingFrom1.Add(nestedType2);
}
else
{
type1ToType2.Add(nestedType1, nestedType2);
MatchNestedTypes(nestedType1, nestedType2, typesMissingFrom1, typesMissingFrom2, type1ToType2);
}
}
foreach (TypeDefinition nestedType1 in type1.NestedTypes)
{
if (!type1ToType2.ContainsKey(nestedType1))
{
typesMissingFrom2.Add(nestedType1);
}
}
}
}
}
+58 -34
View File
@@ -1,34 +1,58 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34321.82
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetRipper.CIL", "AssetRipper.CIL\AssetRipper.CIL.csproj", "{3F385513-F04F-4282-9B91-2693CA93ED1C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{88415A5B-37C1-4CDF-93E0-2682460B6390}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
Directory.Build.props = Directory.Build.props
LICENSE = LICENSE
README.md = README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3F385513-F04F-4282-9B91-2693CA93ED1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F385513-F04F-4282-9B91-2693CA93ED1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F385513-F04F-4282-9B91-2693CA93ED1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F385513-F04F-4282-9B91-2693CA93ED1C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FEAD0DEB-0A49-4BC9-A468-3D1E9D52EF2C}
EndGlobalSection
EndGlobal
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34321.82
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetRipper.CIL", "AssetRipper.CIL\AssetRipper.CIL.csproj", "{3F385513-F04F-4282-9B91-2693CA93ED1C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{88415A5B-37C1-4CDF-93E0-2682460B6390}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
Directory.Build.props = Directory.Build.props
LICENSE = LICENSE
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetRipper.CIL.Validator", "AssetRipper.CIL.Validator\AssetRipper.CIL.Validator.csproj", "{0E177F49-031D-486A-ADD2-6823263D1923}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetRipper.CIL.Standardizer", "AssetRipper.CIL.Standardizer\AssetRipper.CIL.Standardizer.csproj", "{9E6D8580-5C3C-4AA8-89C9-34F475B79B24}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetRipper.CIL.Stubber", "AssetRipper.CIL.Stubber\AssetRipper.CIL.Stubber.csproj", "{A35A3452-2211-4DF5-A5A5-406FDF1E1A76}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetRipper.CIL.Tests", "AssetRipper.CIL.Tests\AssetRipper.CIL.Tests.csproj", "{FC03589F-7457-45E7-9628-F9E003F30BAB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3F385513-F04F-4282-9B91-2693CA93ED1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F385513-F04F-4282-9B91-2693CA93ED1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F385513-F04F-4282-9B91-2693CA93ED1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F385513-F04F-4282-9B91-2693CA93ED1C}.Release|Any CPU.Build.0 = Release|Any CPU
{0E177F49-031D-486A-ADD2-6823263D1923}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E177F49-031D-486A-ADD2-6823263D1923}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E177F49-031D-486A-ADD2-6823263D1923}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E177F49-031D-486A-ADD2-6823263D1923}.Release|Any CPU.Build.0 = Release|Any CPU
{9E6D8580-5C3C-4AA8-89C9-34F475B79B24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E6D8580-5C3C-4AA8-89C9-34F475B79B24}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E6D8580-5C3C-4AA8-89C9-34F475B79B24}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E6D8580-5C3C-4AA8-89C9-34F475B79B24}.Release|Any CPU.Build.0 = Release|Any CPU
{A35A3452-2211-4DF5-A5A5-406FDF1E1A76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A35A3452-2211-4DF5-A5A5-406FDF1E1A76}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A35A3452-2211-4DF5-A5A5-406FDF1E1A76}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A35A3452-2211-4DF5-A5A5-406FDF1E1A76}.Release|Any CPU.Build.0 = Release|Any CPU
{FC03589F-7457-45E7-9628-F9E003F30BAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC03589F-7457-45E7-9628-F9E003F30BAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC03589F-7457-45E7-9628-F9E003F30BAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC03589F-7457-45E7-9628-F9E003F30BAB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FEAD0DEB-0A49-4BC9-A468-3D1E9D52EF2C}
EndGlobalSection
EndGlobal
+4 -3
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<PackageId>AssetRipper.CIL</PackageId>
@@ -15,8 +15,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AsmResolver.DotNet" Version="5.5.1" />
<PackageReference Include="PolySharp" Version="1.14.1" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<PackageReference Include="AsmResolver.DotNet" Version="6.0.0-beta.6" />
<PackageReference Include="PolySharp" Version="1.15.0" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.2" />
</ItemGroup>
<ItemGroup>
@@ -1,13 +1,14 @@
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Signatures.Types;
using AsmResolver.DotNet.Signatures;
using AsmResolver.PE.DotNet.Cil;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
using AsmResolver.PE.DotNet.Metadata.Tables;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace AssetRipper.CIL;
internal static class CilInstructionCollectionExtensions
public static class CilInstructionCollectionExtensions
{
public static CilLocalVariable AddLocalVariable(this CilInstructionCollection instructions, TypeSignature variableType)
{
@@ -16,16 +17,20 @@ internal static class CilInstructionCollectionExtensions
return variable;
}
public static void AddDefaultValueForType(this CilInstructionCollection instructions, TypeSignature type)
public static void AddDefaultValue(this CilInstructionCollection instructions, TypeSignature type)
{
if (type is CorLibTypeSignature { IsValueType: true } corLibTypeSignature)
{
instructions.AddDefaultPrimitiveValue(corLibTypeSignature);
}
else if (type is ByReferenceTypeSignature)
else if (type is ByReferenceTypeSignature or PointerTypeSignature)
{
instructions.AddNullRef();
}
else if (type is TypeDefOrRefSignature t && TryGetEnumUnderlyingType(instructions, t, out CorLibTypeSignature? underlyingType))
{
instructions.AddDefaultPrimitiveValue(underlyingType);
}
else if (type.IsValueTypeOrGenericParameter())
{
instructions.AddDefaultValueForUnknownType(type);
@@ -36,6 +41,195 @@ internal static class CilInstructionCollectionExtensions
}
}
public static void InitializeDefaultValue(this CilInstructionCollection instructions, CilLocalVariable localVariable)
{
TypeSignature type = localVariable.VariableType;
if (type is CorLibTypeSignature { IsValueType: true } corLibTypeSignature)
{
instructions.InitializeDefaultPrimitiveValue(corLibTypeSignature, localVariable);
}
else if (type is ByReferenceTypeSignature or PointerTypeSignature)
{
instructions.AddNullRef();
instructions.Add(CilOpCodes.Stloc, localVariable);
}
else if (type is TypeDefOrRefSignature t && TryGetEnumUnderlyingType(instructions, t, out CorLibTypeSignature? underlyingType))
{
instructions.InitializeDefaultPrimitiveValue(underlyingType, localVariable);
}
else if (type.IsValueTypeOrGenericParameter())
{
instructions.InitializeDefaultValueForUnknownType(localVariable);
}
else
{
instructions.Add(CilOpCodes.Ldnull);
instructions.Add(CilOpCodes.Stloc, localVariable);
}
}
/// <summary>
/// Remove the last instruction in the collection
/// </summary>
/// <param name="instructions">The collection to remove the instruction from</param>
public static void Pop(this CilInstructionCollection instructions) => instructions.RemoveAt(instructions.Count - 1);
public static void AddBooleanNot(this CilInstructionCollection instructions)
{
instructions.Add(CilOpCodes.Ldc_I4_0);
instructions.Add(CilOpCodes.Ceq);
}
public static void AddThrowNull(this CilInstructionCollection instructions)
{
instructions.Add(CilOpCodes.Ldnull);
instructions.Add(CilOpCodes.Throw);
}
public static CilInstruction AddLoadElement(this CilInstructionCollection instructions, TypeSignature elementType)
{
return elementType switch
{
CorLibTypeSignature corLibTypeSignature => corLibTypeSignature.ElementType switch
{
ElementType.Boolean => instructions.Add(CilOpCodes.Ldelem_U1),
ElementType.I1 => instructions.Add(CilOpCodes.Ldelem_I1),
ElementType.U1 => instructions.Add(CilOpCodes.Ldelem_U1),
ElementType.I2 => instructions.Add(CilOpCodes.Ldelem_I2),
ElementType.U2 => instructions.Add(CilOpCodes.Ldelem_U2),
ElementType.I4 => instructions.Add(CilOpCodes.Ldelem_I4),
ElementType.U4 => instructions.Add(CilOpCodes.Ldelem_U4),
ElementType.I8 => instructions.Add(CilOpCodes.Ldelem_I8),
ElementType.U8 => instructions.Add(CilOpCodes.Ldelem_I8),
ElementType.R4 => instructions.Add(CilOpCodes.Ldelem_R4),
ElementType.R8 => instructions.Add(CilOpCodes.Ldelem_R8),
ElementType.I or ElementType.U => instructions.Add(CilOpCodes.Ldelem_I),
ElementType.Object or ElementType.String => instructions.Add(CilOpCodes.Ldelem_Ref),
_ => instructions.Add(CilOpCodes.Ldelem, elementType.ToTypeDefOrRef()),
},
GenericParameterSignature => instructions.Add(CilOpCodes.Ldelem, elementType.ToTypeDefOrRef()),
PointerTypeSignature => instructions.Add(CilOpCodes.Ldelem_I),
TypeDefOrRefSignature t => TryGetEnumUnderlyingType(instructions, t, out CorLibTypeSignature? underlyingType)
? instructions.AddLoadElement(underlyingType)
: AddUnknown(instructions, t),
_ => AddUnknown(instructions, elementType)
};
static CilInstruction AddUnknown(CilInstructionCollection instructions, TypeSignature elementType)
{
return elementType.IsValueType
? instructions.Add(CilOpCodes.Ldelem, elementType.ToTypeDefOrRef())
: instructions.Add(CilOpCodes.Ldelem_Ref);
}
}
public static CilInstruction AddStoreElement(this CilInstructionCollection instructions, TypeSignature elementType)
{
return elementType switch
{
CorLibTypeSignature corLibTypeSignature => corLibTypeSignature.ElementType switch
{
ElementType.Boolean => instructions.Add(CilOpCodes.Stelem_I1),
ElementType.I1 => instructions.Add(CilOpCodes.Stelem_I1),
ElementType.U1 => instructions.Add(CilOpCodes.Stelem_I1),
ElementType.I2 => instructions.Add(CilOpCodes.Stelem_I2),
ElementType.U2 => instructions.Add(CilOpCodes.Stelem_I2),
ElementType.I4 => instructions.Add(CilOpCodes.Stelem_I4),
ElementType.U4 => instructions.Add(CilOpCodes.Stelem_I4),
ElementType.I8 => instructions.Add(CilOpCodes.Stelem_I8),
ElementType.U8 => instructions.Add(CilOpCodes.Stelem_I8),
ElementType.R4 => instructions.Add(CilOpCodes.Stelem_R4),
ElementType.R8 => instructions.Add(CilOpCodes.Stelem_R8),
ElementType.I or ElementType.U => instructions.Add(CilOpCodes.Stelem_I),
ElementType.Object or ElementType.String => instructions.Add(CilOpCodes.Stelem_Ref),
_ => instructions.Add(CilOpCodes.Stelem, elementType.ToTypeDefOrRef()),
},
GenericParameterSignature => instructions.Add(CilOpCodes.Stelem, elementType.ToTypeDefOrRef()),
PointerTypeSignature => instructions.Add(CilOpCodes.Stelem_I),
TypeDefOrRefSignature t => TryGetEnumUnderlyingType(instructions, t, out CorLibTypeSignature? underlyingType)
? instructions.AddStoreElement(underlyingType)
: AddUnknown(instructions, t),
_ => AddUnknown(instructions, elementType)
};
static CilInstruction AddUnknown(CilInstructionCollection instructions, TypeSignature elementType)
{
return elementType.IsValueType
? instructions.Add(CilOpCodes.Stelem, elementType.ToTypeDefOrRef())
: instructions.Add(CilOpCodes.Stelem_Ref);
}
}
public static CilInstruction AddLoadIndirect(this CilInstructionCollection instructions, TypeSignature type)
{
return type switch
{
CorLibTypeSignature corLibTypeSignature => corLibTypeSignature.ElementType switch
{
ElementType.I1 => instructions.Add(CilOpCodes.Ldind_I1),
ElementType.I2 => instructions.Add(CilOpCodes.Ldind_I2),
ElementType.I4 => instructions.Add(CilOpCodes.Ldind_I4),
ElementType.I8 or ElementType.U8 => instructions.Add(CilOpCodes.Ldind_I8),
ElementType.U1 or ElementType.Boolean => instructions.Add(CilOpCodes.Ldind_U1),
ElementType.U2 or ElementType.Char => instructions.Add(CilOpCodes.Ldind_U2),
ElementType.U4 => instructions.Add(CilOpCodes.Ldind_U4),
ElementType.R4 => instructions.Add(CilOpCodes.Ldind_R4),
ElementType.R8 => instructions.Add(CilOpCodes.Ldind_R8),
ElementType.I or ElementType.U => instructions.Add(CilOpCodes.Ldind_I),
ElementType.Object or ElementType.String => instructions.Add(CilOpCodes.Ldind_Ref),
_ => instructions.Add(CilOpCodes.Ldobj, type.ToTypeDefOrRef()),
},
PointerTypeSignature => instructions.Add(CilOpCodes.Ldind_I),
GenericParameterSignature => instructions.Add(CilOpCodes.Ldobj, type.ToTypeDefOrRef()),
TypeDefOrRefSignature t => TryGetEnumUnderlyingType(instructions, t, out CorLibTypeSignature? underlyingType)
? instructions.AddLoadIndirect(underlyingType)
: AddUnknown(instructions, type),
_ => AddUnknown(instructions, type),
};
static CilInstruction AddUnknown(CilInstructionCollection instructions, TypeSignature type)
{
return type.IsValueType
? instructions.Add(CilOpCodes.Ldobj, type.ToTypeDefOrRef())
: instructions.Add(CilOpCodes.Ldind_Ref);
}
}
public static CilInstruction AddStoreIndirect(this CilInstructionCollection instructions, TypeSignature type)
{
return type switch
{
CorLibTypeSignature corLibTypeSignature => corLibTypeSignature.ElementType switch
{
ElementType.I1 => instructions.Add(CilOpCodes.Stind_I1),
ElementType.I2 => instructions.Add(CilOpCodes.Stind_I2),
ElementType.I4 => instructions.Add(CilOpCodes.Stind_I4),
ElementType.I8 or ElementType.U8 => instructions.Add(CilOpCodes.Stind_I8),
ElementType.U1 or ElementType.Boolean => instructions.Add(CilOpCodes.Stind_I1),
ElementType.U2 or ElementType.Char => instructions.Add(CilOpCodes.Stind_I2),
ElementType.U4 => instructions.Add(CilOpCodes.Stind_I4),
ElementType.R4 => instructions.Add(CilOpCodes.Stind_R4),
ElementType.R8 => instructions.Add(CilOpCodes.Stind_R8),
ElementType.I or ElementType.U => instructions.Add(CilOpCodes.Stind_I),
ElementType.Object or ElementType.String => instructions.Add(CilOpCodes.Stind_Ref),
_ => instructions.Add(CilOpCodes.Stobj, type.ToTypeDefOrRef()),
},
PointerTypeSignature => instructions.Add(CilOpCodes.Stind_I),
GenericParameterSignature => instructions.Add(CilOpCodes.Stobj, type.ToTypeDefOrRef()),
TypeDefOrRefSignature t => TryGetEnumUnderlyingType(instructions, t, out CorLibTypeSignature? underlyingType)
? instructions.AddStoreIndirect(underlyingType)
: AddUnknown(instructions, t),
_ => AddUnknown(instructions, type),
};
static CilInstruction AddUnknown(CilInstructionCollection instructions, TypeSignature type)
{
return type.IsValueType
? instructions.Add(CilOpCodes.Stobj, type.ToTypeDefOrRef())
: instructions.Add(CilOpCodes.Stind_Ref);
}
}
/// <summary>
/// Load a null reference onto the stack.
/// </summary>
@@ -46,7 +240,7 @@ internal static class CilInstructionCollectionExtensions
/// However, it does produce a compiler warning.
/// </remarks>
/// <param name="instructions"></param>
private static void AddNullRef(this CilInstructionCollection instructions)
public static void AddNullRef(this CilInstructionCollection instructions)
{
instructions.Add(CilOpCodes.Ldc_I4_0);
instructions.Add(CilOpCodes.Conv_U);
@@ -62,13 +256,19 @@ internal static class CilInstructionCollectionExtensions
/// <param name="type"></param>
private static void AddDefaultValueForUnknownType(this CilInstructionCollection instructions, TypeSignature type)
{
Debug.Assert(type is not CorLibTypeSignature { ElementType: ElementType.Void } and not ByReferenceTypeSignature);
CilLocalVariable variable = instructions.AddLocalVariable(type);
instructions.Add(CilOpCodes.Ldloca, variable);
instructions.Add(CilOpCodes.Initobj, type.ToTypeDefOrRef());
InitializeDefaultValueForUnknownType(instructions, variable);
instructions.Add(CilOpCodes.Ldloc, variable);
}
private static void InitializeDefaultValueForUnknownType(this CilInstructionCollection instructions, CilLocalVariable localVariable)
{
TypeSignature type = localVariable.VariableType;
Debug.Assert(type is not CorLibTypeSignature { ElementType: ElementType.Void } and not ByReferenceTypeSignature);
instructions.Add(CilOpCodes.Ldloca, localVariable);
instructions.Add(CilOpCodes.Initobj, type.ToTypeDefOrRef());
}
private static void AddDefaultPrimitiveValue(this CilInstructionCollection instructions, CorLibTypeSignature type)
{
switch (type.ElementType)
@@ -104,10 +304,64 @@ internal static class CilInstructionCollectionExtensions
instructions.Add(CilOpCodes.Ldnull);
break;
case ElementType.TypedByRef:
default:
instructions.AddDefaultValueForUnknownType(type);
break;
default:
throw new ArgumentOutOfRangeException(null);
}
}
private static void InitializeDefaultPrimitiveValue(this CilInstructionCollection instructions, CorLibTypeSignature type, CilLocalVariable localVariable)
{
switch (type.ElementType)
{
case ElementType.Void:
break;
case ElementType.U1 or ElementType.U2 or ElementType.U4 or ElementType.I1 or ElementType.I2 or ElementType.I4 or ElementType.Boolean or ElementType.Char:
instructions.Add(CilOpCodes.Ldc_I4_0);
instructions.Add(CilOpCodes.Stloc, localVariable);
break;
case ElementType.I8:
instructions.Add(CilOpCodes.Ldc_I4_0);
instructions.Add(CilOpCodes.Conv_I8);
instructions.Add(CilOpCodes.Stloc, localVariable);
break;
case ElementType.U8:
instructions.Add(CilOpCodes.Ldc_I4_0);
instructions.Add(CilOpCodes.Conv_U8);
instructions.Add(CilOpCodes.Stloc, localVariable);
break;
case ElementType.I:
instructions.Add(CilOpCodes.Ldc_I4_0);
instructions.Add(CilOpCodes.Conv_I);
instructions.Add(CilOpCodes.Stloc, localVariable);
break;
case ElementType.U:
instructions.Add(CilOpCodes.Ldc_I4_0);
instructions.Add(CilOpCodes.Conv_U);
instructions.Add(CilOpCodes.Stloc, localVariable);
break;
case ElementType.R4:
instructions.Add(CilOpCodes.Ldc_R4, 0f);
instructions.Add(CilOpCodes.Stloc, localVariable);
break;
case ElementType.R8:
instructions.Add(CilOpCodes.Ldc_R8, 0d);
instructions.Add(CilOpCodes.Stloc, localVariable);
break;
case ElementType.Object or ElementType.String:
instructions.Add(CilOpCodes.Ldnull);
instructions.Add(CilOpCodes.Stloc, localVariable);
break;
case ElementType.TypedByRef:
default:
instructions.InitializeDefaultValueForUnknownType(localVariable);
break;
}
}
private static bool TryGetEnumUnderlyingType(CilInstructionCollection instructions, TypeDefOrRefSignature type, [NotNullWhen(true)] out CorLibTypeSignature? underlyingType)
{
underlyingType = type.GetUnderlyingType(instructions.Owner.Owner?.DeclaringModule?.RuntimeContext) as CorLibTypeSignature;
return underlyingType is not null;
}
}
@@ -0,0 +1,53 @@
using AsmResolver.PE.DotNet.Cil;
namespace AssetRipper.CIL;
public static class CilInstructionExtensions
{
public static bool IsLdcI4OrI8(this CilInstruction instruction, out long value)
{
if (instruction.IsLdcI4())
{
value = instruction.GetLdcI4Constant();
return true;
}
else if (instruction.OpCode == CilOpCodes.Ldc_I8 && instruction.Operand is long i64)
{
value = i64;
return true;
}
else
{
value = default;
return false;
}
}
public static bool IsLdcI4(this CilInstruction instruction, out int value)
{
if (instruction.IsLdcI4())
{
value = instruction.GetLdcI4Constant();
return true;
}
else
{
value = default;
return false;
}
}
public static bool IsLdcI8(this CilInstruction instruction, out long value)
{
if (instruction.OpCode == CilOpCodes.Ldc_I8 && instruction.Operand is long i64)
{
value = i64;
return true;
}
else
{
value = default;
return false;
}
}
}
@@ -0,0 +1,83 @@
using AsmResolver.DotNet.Signatures;
namespace AssetRipper.CIL;
internal static class CorLibTypeFactoryExtensions
{
public static CorLibTypeSignature? FromType(this CorLibTypeFactory factory, Type type)
{
if (type == typeof(bool))
{
return factory.Boolean;
}
if (type == typeof(char))
{
return factory.Char;
}
if (type == typeof(byte))
{
return factory.Byte;
}
if (type == typeof(sbyte))
{
return factory.SByte;
}
if (type == typeof(short))
{
return factory.Int16;
}
if (type == typeof(ushort))
{
return factory.UInt16;
}
if (type == typeof(int))
{
return factory.Int32;
}
if (type == typeof(uint))
{
return factory.UInt32;
}
if (type == typeof(long))
{
return factory.Int64;
}
if (type == typeof(ulong))
{
return factory.UInt64;
}
if (type == typeof(float))
{
return factory.Single;
}
if (type == typeof(double))
{
return factory.Double;
}
if (type == typeof(string))
{
return factory.String;
}
if (type == typeof(IntPtr))
{
return factory.IntPtr;
}
if (type == typeof(UIntPtr))
{
return factory.UIntPtr;
}
if (type == typeof(object))
{
return factory.Object;
}
if (type == typeof(void))
{
return factory.Void;
}
if (type == typeof(TypedReference))
{
return factory.TypedReference;
}
return null;
}
}
@@ -0,0 +1,30 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Signatures;
namespace AssetRipper.CIL;
public static class CustomAttributeExtensions
{
public static CustomAttributeArgument AddFixedArgument(this CustomAttribute attribute, TypeSignature paramType, object paramValue)
{
attribute.Signature ??= new();
CustomAttributeArgument argument = new(paramType, paramValue);
attribute.Signature.FixedArguments.Add(argument);
return argument;
}
public static CustomAttributeArgument AddFixedArgument<T>(this CustomAttribute attribute, TypeSignature paramType, T[] paramValue)
{
CustomAttributeArgument argument = new CustomAttributeArgument(paramType, paramValue.Select(e => (object?)e));
attribute.Signature!.FixedArguments.Add(argument);
return argument;
}
public static CustomAttributeNamedArgument AddNamedArgument(this CustomAttribute attribute, TypeSignature memberType, string memberName, TypeSignature paramType, object paramValue, CustomAttributeArgumentMemberType memberKind)
{
CustomAttributeArgument argument = new CustomAttributeArgument(paramType, paramValue);
CustomAttributeNamedArgument namedArgument = new CustomAttributeNamedArgument(memberKind, memberName, memberType, argument);
attribute.Signature!.NamedArguments.Add(namedArgument);
return namedArgument;
}
}
@@ -0,0 +1,61 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Signatures;
using System.Diagnostics.CodeAnalysis;
namespace AssetRipper.CIL;
public static class EventDefinitionExtensions
{
public static bool IsInstance(this EventDefinition eventDefinition)
{
return (eventDefinition.AddMethod?.IsInstance() ?? false)
|| (eventDefinition.RemoveMethod?.IsInstance() ?? false);
}
public static bool IsStatic(this EventDefinition eventDefinition)
{
return (eventDefinition.AddMethod?.IsStatic ?? false)
|| (eventDefinition.RemoveMethod?.IsStatic ?? false);
}
public static bool HasAddMethod(this EventDefinition eventDefinition)
{
return eventDefinition.AddMethod is not null;
}
public static bool HasRemoveMethod(this EventDefinition eventDefinition)
{
return eventDefinition.RemoveMethod is not null;
}
public static bool HasFireMethod(this EventDefinition eventDefinition)
{
return eventDefinition.FireMethod is not null;
}
public static bool HasBackingField(this EventDefinition eventDefinition, [NotNullWhen(true)] out FieldDefinition? backingField)
{
RuntimeContext? runtimeContext = eventDefinition.DeclaringModule?.RuntimeContext;
if (runtimeContext is null)
{
backingField = null;
return false;
}
TypeSignature? eventType = eventDefinition.EventType?.ToTypeSignature(runtimeContext);
if (eventType is not null)
{
foreach (FieldDefinition field in eventDefinition.DeclaringType?.Fields ?? [])
{
if (field.IsPrivate && field.Name == eventDefinition.Name && runtimeContext.SignatureComparer.Equals(field.Signature?.FieldType, eventType))
{
backingField = field;
return true;
}
}
}
backingField = null;
return false;
}
}
@@ -0,0 +1,66 @@
using AsmResolver.DotNet;
using System.Diagnostics.CodeAnalysis;
namespace AssetRipper.CIL;
public static class FieldDefinitionExtensions
{
public static bool IsInstance(this FieldDefinition field)
{
return !field.IsStatic;
}
private static bool IsPropertyBackingField(this FieldDefinition field, [NotNullWhen(true)] out string? propertyName)
{
string? name = field.Name;
if (name is ['<', ..] && name.EndsWith(">k__BackingField", StringComparison.Ordinal))
{
propertyName = name[1..^16];
return true;
}
else
{
propertyName = null;
return false;
}
}
public static bool IsPropertyBackingField(this FieldDefinition field, [NotNullWhen(true)] out PropertyDefinition? property)
{
if (field.IsPropertyBackingField(out string? propertyName))
{
property = field.DeclaringType?.Properties.FirstOrDefault(p => p.Name == propertyName);
return property is not null
&& field.IsStatic == property.IsStatic()
&& field.DeclaringModule?.RuntimeContext is { } runtimeContext
&& runtimeContext.SignatureComparer.Equals(field.Signature?.FieldType, property.Signature?.ReturnType);
}
else
{
property = null;
return false;
}
}
public static bool IsEventBackingField(this FieldDefinition field, [NotNullWhen(true)] out EventDefinition? @event)
{
if (field.IsPrivate)
{
@event = field.DeclaringType?.Events.FirstOrDefault(e => e.Name == field.Name);
return @event is not null
&& field.IsStatic == @event.IsStatic()
&& @event.DeclaringModule?.RuntimeContext is { } runtimeContext
&& runtimeContext.SignatureComparer.Equals(@event.EventType?.ToTypeSignature(runtimeContext), field.Signature?.FieldType);
}
else
{
@event = null;
return false;
}
}
public static bool IsFixedBuffer(this FieldDefinition field)
{
return field.HasCustomAttribute("System.Runtime.CompilerServices", "FixedBufferAttribute");
}
}
@@ -0,0 +1,104 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Signatures;
using System.Runtime.CompilerServices;
namespace AssetRipper.CIL;
public static class IHasCustomAttributeExtensions
{
public static CustomAttribute AddCustomAttribute(this IHasCustomAttribute _this, IMethodDefOrRef constructor)
{
if (constructor is ICustomAttributeType usableConstructor)
{
CustomAttribute attribute = new(usableConstructor);
_this.CustomAttributes.Add(attribute);
return attribute;
}
else
{
throw new ArgumentException("Constructor is not ICustomAttributeType", nameof(constructor));
}
}
public static CustomAttribute AddCustomAttribute(this IHasCustomAttribute _this, IMethodDefOrRef constructor, TypeSignature paramType, object paramValue)
{
CustomAttribute attribute = _this.AddCustomAttribute(constructor);
attribute.AddFixedArgument(paramType, paramValue);
return attribute;
}
public static CustomAttribute AddCustomAttribute<T>(this IHasCustomAttribute _this, IMethodDefOrRef constructor, SzArrayTypeSignature paramType, T[] paramValue)
{
CustomAttribute attribute = _this.AddCustomAttribute(constructor);
attribute.AddFixedArgument(paramType, paramValue);
return attribute;
}
public static CustomAttribute AddCustomAttribute(this IHasCustomAttribute _this, IMethodDefOrRef constructor, params (TypeSignature, object)[] parameters)
{
CustomAttribute attribute = _this.AddCustomAttribute(constructor);
for (int i = 0; i < parameters.Length; i++)
{
attribute.AddFixedArgument(parameters[i].Item1, parameters[i].Item2);
}
return attribute;
}
internal static ModuleDefinition GetModule(this IHasCustomAttribute _this)
{
return (_this as IModuleProvider)?.ContextModule ?? throw new ArgumentException("Entity does not have a module", nameof(_this));
}
public static CustomAttribute AddFlagsAttribute(this IHasCustomAttribute _this)
{
ModuleDefinition module = _this.GetModule();
if (module.TryGetTopLevelType("System", nameof(FlagsAttribute), out TypeDefinition? flagsType))
{
return _this.AddCustomAttribute(flagsType.GetDefaultConstructor());
}
else
{
TypeReference flagsTypeRef = new(module.CorLibTypeFactory.CorLibScope, "System", nameof(FlagsAttribute));
MemberReference constructor = new(flagsTypeRef, ".ctor", MethodSignature.CreateInstance(module.CorLibTypeFactory.Void));
return _this.AddCustomAttribute(module.DefaultImporter.ImportMethod(constructor));
}
}
/// <summary>
/// Applies the <see cref="CompilerGeneratedAttribute"/>
/// </summary>
/// <param name="_this">The entity on which to apply the attribute</param>
/// <returns>The resulting custom attribute</returns>
public static CustomAttribute AddCompilerGeneratedAttribute(this IHasCustomAttribute _this)
{
ModuleDefinition module = _this.GetModule();
if (module.TryGetTopLevelType("System.Runtime.CompilerServices", nameof(CompilerGeneratedAttribute), out TypeDefinition? compilerGeneratedType))
{
return _this.AddCustomAttribute(compilerGeneratedType.GetDefaultConstructor());
}
else
{
AssemblyReference compilerServicesAssembly = GetOrAddReferenceToSystemRuntimeCompilerServices(module);
TypeReference compilerGeneratedTypeRef = new(compilerServicesAssembly, "System.Runtime.CompilerServices", nameof(CompilerGeneratedAttribute));
MemberReference constructor = new(compilerGeneratedTypeRef, ".ctor", MethodSignature.CreateInstance(module.CorLibTypeFactory.Void));
return _this.AddCustomAttribute(module.DefaultImporter.ImportMethod(constructor));
}
static AssemblyReference GetOrAddReferenceToSystemRuntimeCompilerServices(ModuleDefinition module)
{
AssemblyReference? compilerServicesAssembly = module.AssemblyReferences.FirstOrDefault(a => a.Name == "System.Runtime.CompilerServices");
if (compilerServicesAssembly is null)
{
Version version = module.CorLibTypeFactory.CorLibScope.GetAssembly()?.Version ?? new Version(0, 0, 0, 0);
compilerServicesAssembly = new("System.Runtime.CompilerServices", version);
module.AssemblyReferences.Add(compilerServicesAssembly);
}
return compilerServicesAssembly;
}
}
}
+20
View File
@@ -0,0 +1,20 @@
using AsmResolver.DotNet.Code.Cil;
using AssetRipper.CIL.Manipulation;
namespace AssetRipper.CIL;
public static class MacroStandardization
{
/// <summary>
/// This method expands all macros in the collection and applies some standardizations, such as removing nop and switch instructions.
/// </summary>
/// <param name="instructions">The collection to modify.</param>
public static void StandardizeMacros(this CilInstructionCollection instructions)
{
new CilManipulationContext(instructions)
.Execute<MacroExpander>()
.Execute<NopRemover>()
.Execute<SwitchRemover>()
.Execute<RedundantConversionRemover>();
}
}
@@ -0,0 +1,105 @@
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.PE.DotNet.Cil;
namespace AssetRipper.CIL.Manipulation;
public sealed class CilManipulationContext
{
private readonly Dictionary<CilInstruction, HashSet<CilInstructionLabel>> instructionReferences = new();
public CilInstructionCollection Instructions { get; }
public CilManipulationContext(CilInstructionCollection instructions)
{
Instructions = instructions;
foreach (CilExceptionHandler handler in instructions.Owner.ExceptionHandlers)
{
AddReference(handler.TryStart as CilInstructionLabel);
AddReference(handler.TryEnd as CilInstructionLabel);
AddReference(handler.HandlerStart as CilInstructionLabel);
AddReference(handler.HandlerEnd as CilInstructionLabel);
AddReference(handler.FilterStart as CilInstructionLabel);
}
foreach (CilInstruction instruction in instructions)
{
if (instruction.Operand is CilInstructionLabel label)
{
AddReference(label);
}
else if (instruction.Operand is IReadOnlyList<ICilLabel> labels) // switch
{
foreach (ICilLabel l in labels)
{
AddReference(l as CilInstructionLabel);
}
}
}
}
public bool CanRemove(CilInstruction instruction) => !instructionReferences.ContainsKey(instruction);
public void Remove(CilInstruction instruction)
{
if (!CanRemove(instruction))
{
throw new InvalidOperationException("Cannot remove instruction with references");
}
Instructions.Remove(instruction);
}
public bool TryRemove(CilInstruction instruction)
{
return CanRemove(instruction) && Instructions.Remove(instruction);
}
public void Replace(CilInstruction oldInstruction, CilInstruction newInstruction)
{
if (instructionReferences.TryGetValue(oldInstruction, out HashSet<CilInstructionLabel>? references))
{
instructionReferences.Remove(oldInstruction);
instructionReferences.Add(newInstruction, references);
foreach (CilInstructionLabel label in references)
{
label.Instruction = newInstruction;
}
}
Instructions[Instructions.IndexOf(oldInstruction)] = newInstruction;
}
public void ReassignReferences(CilInstruction oldInstruction, CilInstruction newInstruction)
{
if (instructionReferences.TryGetValue(oldInstruction, out HashSet<CilInstructionLabel>? oldReferences))
{
instructionReferences.Remove(oldInstruction);
foreach (CilInstructionLabel label in oldReferences)
{
label.Instruction = newInstruction;
}
if (instructionReferences.TryGetValue(newInstruction, out HashSet<CilInstructionLabel>? newReferences))
{
newReferences.UnionWith(oldReferences);
}
else
{
instructionReferences.Add(newInstruction, oldReferences);
}
}
}
public CilManipulationContext Execute<T>() where T : ICilManipulator, new()
{
SingletonCache<T>.Instance.Execute(this);
return this;
}
private void AddReference(CilInstructionLabel? label)
{
CilInstruction? instruction = label?.Instruction;
if (instruction is not null)
{
instructionReferences.GetOrCreate(instruction).Add(label!);
}
}
}
@@ -0,0 +1,16 @@
namespace AssetRipper.CIL.Manipulation;
internal static class DictionaryExtensions
{
public static TValue GetOrCreate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
where TKey : notnull
where TValue : new()
{
if (!dictionary.TryGetValue(key, out TValue? value))
{
value = new TValue();
dictionary[key] = value;
}
return value;
}
}
@@ -0,0 +1,12 @@
namespace AssetRipper.CIL.Manipulation;
/// <summary>
/// An interface for manipulating CIL instructions.
/// </summary>
/// <remarks>
/// Derived classes are expected to be stateless and have a parameterless constructor.
/// </remarks>
public interface ICilManipulator
{
void Execute(CilManipulationContext context);
}
@@ -0,0 +1,6 @@
namespace AssetRipper.CIL.Manipulation;
public sealed class MacroExpander : ICilManipulator
{
public void Execute(CilManipulationContext context) => context.Instructions.ExpandMacros();
}
@@ -0,0 +1,6 @@
namespace AssetRipper.CIL.Manipulation;
public sealed class MacroOptimizer : ICilManipulator
{
public void Execute(CilManipulationContext context) => context.Instructions.OptimizeMacros();
}
@@ -0,0 +1,31 @@
using AsmResolver.PE.DotNet.Cil;
namespace AssetRipper.CIL.Manipulation;
/// <summary>
/// This removes all nop instructions.
/// </summary>
/// <remarks>
/// This is a stronger version of <see cref="UnreferencedNopRemover"/>.
/// </remarks>
public sealed class NopRemover : ICilManipulator
{
public void Execute(CilManipulationContext context)
{
int i = 1;
while (i < context.Instructions.Count)
{
CilInstruction previousInstruction = context.Instructions[i - 1];
if (previousInstruction.OpCode == CilOpCodes.Nop)
{
CilInstruction currentInstruction = context.Instructions[i];
context.ReassignReferences(previousInstruction, currentInstruction);
context.Remove(previousInstruction);
}
else
{
i++;
}
}
}
}
@@ -0,0 +1,29 @@
using AsmResolver.PE.DotNet.Cil;
namespace AssetRipper.CIL.Manipulation;
public sealed class RedundantConversionRemover : ICilManipulator
{
public void Execute(CilManipulationContext context)
{
int i = 1;
while (i < context.Instructions.Count)
{
CilInstruction previousInstruction = context.Instructions[i - 1];
CilInstruction instruction = context.Instructions[i];
if (previousInstruction.OpCode == instruction.OpCode
&& instruction.OpCode.Code
is CilCode.Conv_I1 or CilCode.Conv_I2 or CilCode.Conv_I4 or CilCode.Conv_I8
or CilCode.Conv_U1 or CilCode.Conv_U2 or CilCode.Conv_U4 or CilCode.Conv_U8
or CilCode.Conv_R4 or CilCode.Conv_R8
or CilCode.Conv_I or CilCode.Conv_U
&& (context.TryRemove(instruction) || context.TryRemove(previousInstruction)))
{
}
else
{
i++;
}
}
}
}
@@ -0,0 +1,6 @@
namespace AssetRipper.CIL.Manipulation;
internal static class SingletonCache<T> where T : new()
{
public static T Instance { get; } = new T();
}
@@ -0,0 +1,44 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.PE.DotNet.Cil;
namespace AssetRipper.CIL.Manipulation;
/// <summary>
/// This manipulator removes switch instructions by converting them to a series of conditional branches.
/// </summary>
public sealed class SwitchRemover : ICilManipulator
{
public void Execute(CilManipulationContext context)
{
ModuleDefinition? module = context.Instructions.Owner.Owner?.DeclaringModule;
if (module is null)
{
return;
}
for (int i = 0; i < context.Instructions.Count; i++)
{
CilInstruction instruction = context.Instructions[i];
if (instruction.OpCode == CilOpCodes.Switch && instruction.Operand is IReadOnlyList<ICilLabel> labels)
{
CilLocalVariable switchValue = context.Instructions.AddLocalVariable(module.CorLibTypeFactory.UInt32);
context.Replace(instruction, new CilInstruction(CilOpCodes.Conv_U4));
context.Instructions.Insert(i + 1, new CilInstruction(CilOpCodes.Stloc, switchValue));
i++; // No need to process the newly added instruction
for (int j = 0; j < labels.Count; j++)
{
ICilLabel label = labels[j];
context.Instructions.Insert(i + 1, new CilInstruction(CilOpCodes.Ldloc, switchValue));
context.Instructions.Insert(i + 2, new CilInstruction(CilOpCodes.Ldc_I4, j));
context.Instructions.Insert(i + 3, new CilInstruction(CilOpCodes.Ceq));
context.Instructions.Insert(i + 4, new CilInstruction(CilOpCodes.Brtrue, label));
i += 4; // No need to process the newly added instructions
}
}
}
}
}
@@ -0,0 +1,25 @@
using AsmResolver.PE.DotNet.Cil;
namespace AssetRipper.CIL.Manipulation;
/// <summary>
/// This removes unreferenced nop instructions.
/// </summary>
public sealed class UnreferencedNopRemover : ICilManipulator
{
public void Execute(CilManipulationContext context)
{
int i = 0;
while (i < context.Instructions.Count)
{
CilInstruction instruction = context.Instructions[i];
if (instruction.OpCode == CilOpCodes.Nop && context.TryRemove(instruction))
{
}
else
{
i++;
}
}
}
}
+92 -11
View File
@@ -1,19 +1,69 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Collections;
using AsmResolver.DotNet.Signatures;
using AsmResolver.DotNet.Signatures.Types;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
using AsmResolver.PE.DotNet.Metadata.Tables;
using System.Diagnostics.CodeAnalysis;
namespace AssetRipper.CIL;
internal static class MethodDefinitionExtensions
public static class MethodDefinitionExtensions
{
public static bool IsInstanceConstructor(this MethodDefinition methodDefinition)
/// <summary>
/// Replaces the method body with a sensible minimal implementation.
/// If the method is for an auto-property, the method will be replaced with a simple getter or setter.
/// Otherwise, the method content will be filled with a valid default implementation that does not throw
/// and handles all required logic, such as setting out parameters.
/// </summary>
/// <remarks>
/// If the method is not managed or should not have a body, this method does nothing.
/// </remarks>
/// <param name="methodDefinition">The method to modify.</param>
public static void ReplaceMethodBodyWithMinimalImplementation(this MethodDefinition methodDefinition)
{
return methodDefinition.IsConstructor && !methodDefinition.IsStatic;
MethodStubber.FillMethodBodyWithStub(methodDefinition);
}
public static bool ShouldCallBaseConstructor(this MethodDefinition methodDefinition)
/// <summary>
/// Replaces the method body with: <code>throw null;</code>
/// </summary>
/// <remarks>
/// If the method is not managed or should not have a body, this method does nothing.
/// </remarks>
/// <param name="methodDefinition">The method to modify.</param>
public static void ReplaceMethodBodyWithThrowNull(this MethodDefinition methodDefinition)
{
if (!methodDefinition.IsManagedMethodWithBody())
{
return;
}
methodDefinition.CilMethodBody = new();
CilInstructionCollection methodInstructions = methodDefinition.CilMethodBody.Instructions;
methodInstructions.AddThrowNull();
}
public static bool IsInstance(this MethodDefinition methodDefinition)
{
return !methodDefinition.IsStatic;
}
public static bool IsInstanceConstructor(this MethodDefinition methodDefinition)
{
return methodDefinition.IsConstructor && methodDefinition.IsInstance();
}
public static bool IsStaticConstructor(this MethodDefinition methodDefinition)
{
return methodDefinition.IsConstructor && methodDefinition.IsStatic;
}
public static bool HasReturnType(this MethodDefinition methodDefinition)
{
return methodDefinition.Signature is not null
&& methodDefinition.Signature.ReturnType is not CorLibTypeSignature { ElementType: ElementType.Void };
}
internal static bool ShouldCallBaseConstructor(this MethodDefinition methodDefinition)
{
return methodDefinition.IsInstanceConstructor() && !methodDefinition.IsDeclaredByValueType();
}
@@ -26,7 +76,7 @@ internal static class MethodDefinitionExtensions
return methodDefinition.DeclaringType?.IsValueType is true;
}
public static bool ShouldInitializeFields(this MethodDefinition methodDefinition)
internal static bool ShouldInitializeFields(this MethodDefinition methodDefinition)
{
return methodDefinition.IsInstanceConstructor() && methodDefinition.IsDeclaredByValueType();
}
@@ -41,6 +91,11 @@ internal static class MethodDefinitionExtensions
&& !managedMethod.IsRuntime;
}
public static CilInstructionCollection GetInstructions(this MethodDefinition method)
{
return method.CilMethodBody?.Instructions ?? throw new ArgumentException("CilMethodBody was null", nameof(method));
}
public static bool IsAutoPropertyGetMethod(this MethodDefinition method, [NotNullWhen(true)] out FieldDefinition? backingField)
{
if (!method.IsGetMethod
@@ -56,8 +111,10 @@ internal static class MethodDefinitionExtensions
string backingFieldName = $"<{method.Name.Value[4..]}>k__BackingField";
FieldDefinition? field = method.DeclaringType.Fields.FirstOrDefault(f => f.Name == backingFieldName);
if (field is null
|| !SignatureComparer.Default.Equals(field.Signature?.FieldType, method.Signature?.ReturnType)
|| field.IsStatic != method.IsStatic)
|| !field.IsPrivate
|| field.IsStatic != method.IsStatic
|| method.DeclaringModule?.RuntimeContext is not { } runtimeContext
|| !runtimeContext.SignatureComparer.Equals(field.Signature?.FieldType, method.Signature?.ReturnType))
{
backingField = null;
return false;
@@ -85,8 +142,10 @@ internal static class MethodDefinitionExtensions
string backingFieldName = $"<{method.Name.Value[4..]}>k__BackingField";
FieldDefinition? field = method.DeclaringType.Fields.FirstOrDefault(f => f.Name == backingFieldName);
if (field is null
|| !SignatureComparer.Default.Equals(field.Signature?.FieldType, method.Parameters[0].ParameterType)
|| field.IsStatic != method.IsStatic)
|| !field.IsPrivate
|| field.IsStatic != method.IsStatic
|| method.DeclaringModule?.RuntimeContext is not { } runtimeContext
|| !runtimeContext.SignatureComparer.Equals(field.Signature?.FieldType, method.Parameters[0].ParameterType))
{
backingField = null;
return false;
@@ -97,4 +156,26 @@ internal static class MethodDefinitionExtensions
return true;
}
}
public static Parameter AddParameter(this MethodDefinition method, TypeSignature parameterSignature, string? parameterName)
{
return method.AddParameter(parameterSignature, parameterName, out _);
}
public static Parameter AddParameter(this MethodDefinition method, TypeSignature parameterSignature, string? parameterName, out ParameterDefinition parameterDefinition)
{
parameterDefinition = new ParameterDefinition((ushort)(method.Signature!.ParameterTypes.Count + 1), parameterName, default);
method.Signature.ParameterTypes.Add(parameterSignature);
method.ParameterDefinitions.Add(parameterDefinition);
method.Parameters.PullUpdatesFromMethodSignature();
return method.Parameters.Single(parameter => parameter.Name == parameterName && parameter.ParameterType == parameterSignature);
}
public static Parameter AddParameter(this MethodDefinition method, TypeSignature parameterSignature)
{
method.Signature!.ParameterTypes.Add(parameterSignature);
method.Parameters.PullUpdatesFromMethodSignature();
return method.Parameters[^1];
}
}
+43 -29
View File
@@ -2,34 +2,33 @@
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Collections;
using AsmResolver.DotNet.Signatures;
using AsmResolver.DotNet.Signatures.Types;
using AsmResolver.PE.DotNet.Cil;
using System.Diagnostics.CodeAnalysis;
namespace AssetRipper.CIL;
public static class MethodStubber
internal static class MethodStubber
{
public static void FillMethodBodyWithStub(this MethodDefinition methodDefinition)
public static void FillMethodBodyWithStub(MethodDefinition methodDefinition)
{
if (!methodDefinition.IsManagedMethodWithBody())
{
return;
}
methodDefinition.CilMethodBody = new(methodDefinition);
methodDefinition.CilMethodBody = new();
CilInstructionCollection methodInstructions = methodDefinition.CilMethodBody.Instructions;
if (methodDefinition.IsAutoPropertyGetMethod(out FieldDefinition? backingField))
{
if (methodDefinition.IsStatic)
{
methodInstructions.Add(CilOpCodes.Ldsfld, backingField);
methodInstructions.Add(CilOpCodes.Ldsfld, backingField.MakeGenericIfNecessary());
}
else
{
methodInstructions.Add(CilOpCodes.Ldarg_0);
methodInstructions.Add(CilOpCodes.Ldfld, backingField);
methodInstructions.Add(CilOpCodes.Ldfld, backingField.MakeGenericIfNecessary());
}
methodInstructions.Add(CilOpCodes.Ret);
return;
@@ -39,13 +38,13 @@ public static class MethodStubber
if (methodDefinition.IsStatic)
{
methodInstructions.Add(CilOpCodes.Ldarg_0);
methodInstructions.Add(CilOpCodes.Stsfld, backingField);
methodInstructions.Add(CilOpCodes.Stsfld, backingField.MakeGenericIfNecessary());
}
else
{
methodInstructions.Add(CilOpCodes.Ldarg_0);
methodInstructions.Add(CilOpCodes.Ldarg_1);
methodInstructions.Add(CilOpCodes.Stfld, backingField);
methodInstructions.Add(CilOpCodes.Stfld, backingField.MakeGenericIfNecessary());
}
methodInstructions.Add(CilOpCodes.Ret);
return;
@@ -56,14 +55,14 @@ public static class MethodStubber
TypeDefinition declaringType = methodDefinition.DeclaringType!;
foreach (FieldDefinition field in declaringType.Fields)
{
if (field.IsStatic || field.Signature is null)
if (field.IsStatic || field.Signature is null || field.IsFixedBuffer())
{
continue;
}
methodInstructions.Add(CilOpCodes.Ldarg_0);
methodInstructions.AddDefaultValueForType(field.Signature.FieldType);
methodInstructions.Add(CilOpCodes.Stfld, field);
methodInstructions.AddDefaultValue(field.Signature.FieldType);
methodInstructions.Add(CilOpCodes.Stfld, field.MakeGenericIfNecessary());
}
}
@@ -75,7 +74,7 @@ public static class MethodStubber
methodInstructions.Add(CilOpCodes.Ldarg_0);
foreach (TypeSignature baseParameterType in methodSignature.ParameterTypes)
{
methodInstructions.AddDefaultValueForType(baseParameterType);
methodInstructions.AddDefaultValue(baseParameterType);
}
methodInstructions.Add(CilOpCodes.Call, baseConstructor);
}
@@ -103,7 +102,7 @@ public static class MethodStubber
}
}
}
methodInstructions.AddDefaultValueForType(methodDefinition.Signature!.ReturnType);
methodInstructions.AddDefaultValue(methodDefinition.Signature!.ReturnType);
methodInstructions.Add(CilOpCodes.Ret);
methodInstructions.OptimizeMacros();
}
@@ -119,6 +118,11 @@ public static class MethodStubber
{
return null;
}
RuntimeContext? runtimeContext = methodDefinition.DeclaringModule?.RuntimeContext;
if (runtimeContext is null)
{
return null;
}
if (baseType is TypeDefinition baseTypeDef)
{
@@ -126,19 +130,19 @@ public static class MethodStubber
}
else if (baseType is TypeReference baseTypeRef)
{
TypeDefinition? baseTypeResolved = baseTypeRef.Resolve();
if (baseTypeResolved is null)
ResolutionStatus resolutionStatus = baseTypeRef.Resolve(runtimeContext, out TypeDefinition? baseTypeResolved);
if (baseTypeResolved is null || resolutionStatus is not ResolutionStatus.Success)
{
return null;
}
MethodDefinition? baseConstructor = GetFirstCompatibleConstructor(methodDefinition.DeclaringType, baseTypeResolved);
if (baseConstructor is null || methodDefinition.DeclaringType.Module is null)
if (baseConstructor is null || methodDefinition.DeclaringType.DeclaringModule is null)
{
return null;
}
else
{
return methodDefinition.DeclaringType.Module.DefaultImporter.ImportMethod(baseConstructor);
return methodDefinition.DeclaringType.DeclaringModule.DefaultImporter.ImportMethod(baseConstructor);
}
}
else if (baseType is TypeSpecification baseTypeSpec)
@@ -148,17 +152,17 @@ public static class MethodStubber
{
return null;
}
TypeDefinition? baseTypeResolved = genericInstanceTypeSignature.GenericType.Resolve();
if (baseTypeResolved is null)
ResolutionStatus resolutionStatus = genericInstanceTypeSignature.GenericType.Resolve(runtimeContext, out TypeDefinition? baseTypeResolved);
if (baseTypeResolved is null || resolutionStatus is not ResolutionStatus.Success)
{
return null;
}
MethodDefinition? baseConstructor = GetFirstCompatibleConstructor(methodDefinition.DeclaringType, baseTypeResolved);
if (baseConstructor is null || methodDefinition.DeclaringType.Module is null)
if (baseConstructor is null || methodDefinition.DeclaringType.DeclaringModule is null)
{
return null;
}
IMethodDefOrRef baseConstructorImported = methodDefinition.DeclaringType.Module.DefaultImporter.ImportMethod(baseConstructor);
IMethodDefOrRef baseConstructorImported = methodDefinition.DeclaringType.DeclaringModule.DefaultImporter.ImportMethod(baseConstructor);
return new MemberReference(baseType, baseConstructorImported.Name, baseConstructorImported.Signature);
}
@@ -166,14 +170,7 @@ public static class MethodStubber
static MethodDefinition? GetFirstCompatibleConstructor(TypeDefinition declaringType, TypeDefinition baseType)
{
if (declaringType.Module == baseType.Module)
{
return baseType.Methods.FirstOrDefault(m => m.IsInstanceConstructor() && !m.IsPrivate);
}
else
{
return baseType.Methods.FirstOrDefault(m => m.IsInstanceConstructor() && (m.IsFamily || m.IsPublic));
}
return baseType.Methods.FirstOrDefault(m => m.IsInstanceConstructor() && m.IsAccessibleFromType(declaringType, m.DeclaringModule?.RuntimeContext));
}
}
@@ -182,4 +179,21 @@ public static class MethodStubber
baseConstructor = TryGetBaseConstructor(methodDefinition);
return baseConstructor is not null;
}
private static IFieldDescriptor MakeGenericIfNecessary(this FieldDefinition field)
{
if (field.DeclaringType is null or { GenericParameters.Count: 0 })
{
return field;
}
int count = field.DeclaringType.GenericParameters.Count;
TypeSignature[] typeArguments = new TypeSignature[count];
for (int i = 0; i < count; i++)
{
typeArguments[i] = new GenericParameterSignature(field.DeclaringModule, GenericParameterType.Type, i);
}
return new MemberReference(field.DeclaringType.MakeGenericInstanceType(field.DeclaringModule?.RuntimeContext, typeArguments).ToTypeDefOrRef(), field.Name, field.Signature);
}
}
@@ -0,0 +1,94 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Signatures;
using AsmResolver.PE.DotNet.Metadata.Tables;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace AssetRipper.CIL;
public static class ModuleDefinitionExtensions
{
public static bool TryGetTopLevelType(this ModuleDefinition module, string? @namespace, string? name, [NotNullWhen(true)] out TypeDefinition? type)
{
type = module.TopLevelTypes.FirstOrDefault(t => t.Namespace == @namespace && t.Name == name);
return type != null;
}
public static bool HasTopLevelType(this ModuleDefinition module, string? @namespace, string? name)
{
return module.TryGetTopLevelType(@namespace, name, out _);
}
public static TypeDefinition AddEmptyEnum(this ModuleDefinition module, string? @namespace, string? name, Visibility visibility, ElementType underlyingType)
{
CorLibTypeSignature underlyingTypeSignature = module.CorLibTypeFactory.FromElementType(underlyingType)
?? throw new ArgumentOutOfRangeException(nameof(underlyingType), underlyingType, null);
TypeReference baseType = new(module.CorLibTypeFactory.CorLibScope, "System", "Enum");
TypeDefinition type = new(@namespace, name, visibility.ToTypeAttributes() | TypeAttributes.Sealed, module.DefaultImporter.ImportType(baseType));
module.TopLevelTypes.Add(type);
AddEnumValue(type, underlyingTypeSignature);
return type;
static void AddEnumValue(TypeDefinition type, TypeSignature underlyingType)
{
FieldSignature fieldSignature = new FieldSignature(underlyingType);
FieldDefinition fieldDef = new FieldDefinition("value__", FieldAttributes.Public | FieldAttributes.SpecialName | FieldAttributes.RuntimeSpecialName, fieldSignature);
type.Fields.Add(fieldDef);
}
}
public static TypeDefinition AddExistingEnum<T>(this ModuleDefinition module) where T : struct, Enum
{
Type underlyingType = Enum.GetUnderlyingType(typeof(T));
bool isSigned = underlyingType == typeof(sbyte) || underlyingType == typeof(short) || underlyingType == typeof(int) || underlyingType == typeof(long);
CorLibTypeSignature underlyingTypeSignature = module.CorLibTypeFactory.FromType(underlyingType)
?? throw new InvalidOperationException($"Failed to get underlying type for {typeof(T)}");
TypeDefinition type = module.AddEmptyEnum(typeof(T).Namespace, typeof(T).Name, VisibilityExtensions.GetVisibility(typeof(T)), underlyingTypeSignature.ElementType);
foreach (T value in GetValues())
{
type.AddEnumField(value.ToString(), ToInt64(value));
}
if (typeof(T).CustomAttributes.Any(a => a.AttributeType is { Namespace: "System", Name: nameof(FlagsAttribute) }))
{
type.AddFlagsAttribute();
}
return type;
static IEnumerable<T> GetValues()
{
#if NETSTANDARD
return Enum.GetValues(typeof(T)).Cast<T>();
#else
return Enum.GetValues<T>();
#endif
}
long ToInt64(T value)
{
if (Unsafe.SizeOf<T>() == sizeof(long))
{
return Unsafe.As<T, long>(ref value);
}
else if (Unsafe.SizeOf<T>() == sizeof(int))
{
return isSigned ? Unsafe.As<T, int>(ref value) : Unsafe.As<T, uint>(ref value);
}
else if (Unsafe.SizeOf<T>() == sizeof(short))
{
return isSigned ? Unsafe.As<T, short>(ref value) : Unsafe.As<T, ushort>(ref value);
}
else if (Unsafe.SizeOf<T>() == sizeof(byte))
{
return isSigned ? Unsafe.As<T, sbyte>(ref value) : Unsafe.As<T, byte>(ref value);
}
else
{
throw new NotSupportedException($"Unsupported enum underlying type {typeof(T)}");
}
}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
using AsmResolver.DotNet.Collections;
using AsmResolver.DotNet.Signatures.Types;
using AsmResolver.DotNet.Signatures;
using System.Diagnostics.CodeAnalysis;
namespace AssetRipper.CIL;
@@ -0,0 +1,45 @@
using AsmResolver.DotNet;
using System.Diagnostics.CodeAnalysis;
namespace AssetRipper.CIL;
public static class PropertyDefinitionExtensions
{
public static bool HasBackingField(this PropertyDefinition property, [NotNullWhen(true)] out FieldDefinition? backingField)
{
string backingFieldName = $"<{property.Name}>k__BackingField";
backingField = property.DeclaringType?.Fields.FirstOrDefault(f => f.Name == backingFieldName);
if (backingField is null
|| !backingField.IsPrivate
|| backingField.IsStatic != property.IsStatic()
|| backingField.DeclaringModule?.RuntimeContext is not { } runtimeContext
|| !runtimeContext.SignatureComparer.Equals(backingField.Signature?.FieldType, property.Signature?.ReturnType))
{
backingField = null;
return false;
}
else
{
return true;
}
}
public static bool HasGetMethod(this PropertyDefinition property)
{
return property.GetMethod is not null;
}
public static bool HasSetMethod(this PropertyDefinition property)
{
return property.SetMethod is not null;
}
public static bool HasBothAccessors(this PropertyDefinition property)
{
return property.HasGetMethod() && property.HasSetMethod();
}
public static bool IsInstance(this PropertyDefinition property) => property.Signature?.HasThis ?? false;
public static bool IsStatic(this PropertyDefinition property) => !property.Signature?.HasThis ?? false;
}
+105
View File
@@ -0,0 +1,105 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Signatures;
using AsmResolver.PE.DotNet.Metadata.Tables;
namespace AssetRipper.CIL;
public static class TypeDefinitionExtensions
{
public static FieldDefinition AddField(this TypeDefinition type, string? fieldName, TypeSignature fieldType, bool isStatic = false, Visibility visibility = Visibility.Public)
{
FieldAttributes fieldAttributes = visibility.ToFieldAttributes() | (isStatic ? FieldAttributes.Static : default);
FieldDefinition field = new FieldDefinition(fieldName, fieldAttributes, fieldType);
type.Fields.Add(field);
return field;
}
public static FieldDefinition AddConstantField(
this TypeDefinition type,
TypeSignature fieldType,
string fieldName,
Constant constant,
Visibility visibility = Visibility.Public)
{
FieldDefinition field = new FieldDefinition(
fieldName,
FieldAttributes.Static |
FieldAttributes.Literal |
FieldAttributes.HasDefault |
visibility.ToFieldAttributes(),
new FieldSignature(fieldType));
field.Constant = constant;
type.Fields.Add(field);
return field;
}
public static MethodDefinition AddMethod(this TypeDefinition type, string? methodName, MethodAttributes methodAttributes, TypeSignature returnType)
{
MethodDefinition method = CreateMethod(methodName, methodAttributes, returnType);
type.Methods.Add(method);
return method;
}
public static MethodDefinition AddConstructor(this TypeDefinition type, Visibility visibility = Visibility.Public)
{
MethodAttributes methodAttributes = visibility.ToMethodAttributes() | MethodAttributes.SpecialName | MethodAttributes.RuntimeSpecialName;
return type.AddMethod(".ctor", methodAttributes, type.DeclaringModule!.CorLibTypeFactory.Void);
}
public static FieldDefinition AddEnumField(this TypeDefinition type, string name, long value)
{
TypeSignature underlyingType = type.GetEnumUnderlyingType() ?? throw new ArgumentException("Type is not an enum", nameof(type));
return type.AddConstantField(type.ToTypeSignature(), name, FromValue(value, underlyingType.ElementType));
static Constant FromValue(long value, ElementType elementType) => elementType switch
{
ElementType.I1 => Constant.FromValue((sbyte)value),
ElementType.U1 => Constant.FromValue((byte)value),
ElementType.I2 => Constant.FromValue((short)value),
ElementType.U2 => Constant.FromValue((ushort)value),
ElementType.I4 => Constant.FromValue((int)value),
ElementType.U4 => Constant.FromValue((uint)value),
ElementType.I8 => Constant.FromValue(value),
ElementType.U8 => Constant.FromValue(unchecked((ulong)value)),
_ => throw new ArgumentOutOfRangeException(nameof(elementType)),
};
}
private static MethodDefinition CreateMethod(string? methodName, MethodAttributes methodAttributes, TypeSignature returnType)
{
bool isStatic = (methodAttributes & MethodAttributes.Static) != 0;
MethodSignature methodSignature = isStatic
? MethodSignature.CreateStatic(returnType)
: MethodSignature.CreateInstance(returnType);
MethodDefinition result = new MethodDefinition(methodName, methodAttributes, methodSignature);
result.CilMethodBody = new CilMethodBody();
return result;
}
/// <summary>
/// Gets the default constructor for a <see cref="TypeDefinition"/>. Throws an exception if one doesn't exist.
/// </summary>
public static MethodDefinition GetDefaultConstructor(this TypeDefinition type)
{
return type.Methods.First(m => m.IsInstanceConstructor() && m.Parameters.Count == 0);
}
/// <summary>
/// Get all the instance constructors for a type
/// </summary>
/// <param name="type">The definition for the type</param>
/// <returns>All the instance constructors</returns>
public static IEnumerable<MethodDefinition> GetInstanceConstructors(this TypeDefinition type)
{
return type.Methods.Where(m => !m.IsStatic && m.IsConstructor);
}
public static bool IsStatic(this TypeDefinition type)
{
return type.IsSealed && type.IsAbstract;
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
using AsmResolver.DotNet.Signatures.Types;
using AsmResolver.DotNet.Signatures;
namespace AssetRipper.CIL;
+84
View File
@@ -0,0 +1,84 @@
using AsmResolver.PE.DotNet.Metadata.Tables;
namespace AssetRipper.CIL;
public enum Visibility
{
Public,
Protected,
Internal,
ProtectedInternal,
Private,
PrivateProtected
}
public static class VisibilityExtensions
{
public static MethodAttributes ToMethodAttributes(this Visibility visibility) => visibility switch
{
Visibility.Public => MethodAttributes.Public,
Visibility.Protected => MethodAttributes.Family,
Visibility.Internal => MethodAttributes.Assembly,
Visibility.ProtectedInternal => MethodAttributes.FamilyOrAssembly,
Visibility.Private => MethodAttributes.Private,
Visibility.PrivateProtected => MethodAttributes.FamilyAndAssembly,
_ => throw new ArgumentOutOfRangeException(nameof(visibility), visibility, null),
};
public static FieldAttributes ToFieldAttributes(this Visibility visibility) => visibility switch
{
Visibility.Public => FieldAttributes.Public,
Visibility.Protected => FieldAttributes.Family,
Visibility.Internal => FieldAttributes.Assembly,
Visibility.ProtectedInternal => FieldAttributes.FamilyOrAssembly,
Visibility.Private => FieldAttributes.Private,
Visibility.PrivateProtected => FieldAttributes.FamilyAndAssembly,
_ => throw new ArgumentOutOfRangeException(nameof(visibility), visibility, null),
};
public static TypeAttributes ToTypeAttributes(this Visibility visibility, bool nested = false) => nested
? visibility switch
{
Visibility.Public => TypeAttributes.NestedPublic,
Visibility.Protected => TypeAttributes.NestedFamily,
Visibility.Internal => TypeAttributes.NestedAssembly,
Visibility.ProtectedInternal => TypeAttributes.NestedFamilyOrAssembly,
Visibility.Private => TypeAttributes.NestedPrivate,
Visibility.PrivateProtected => TypeAttributes.NestedFamilyAndAssembly,
_ => throw new ArgumentOutOfRangeException(nameof(visibility), visibility, null),
}
: visibility switch
{
Visibility.Public => TypeAttributes.Public,
Visibility.Internal => TypeAttributes.NotPublic,
_ => throw new ArgumentOutOfRangeException(nameof(visibility), visibility, null),
};
internal static Visibility GetVisibility(Type type)
{
if (type.IsPublic || type.IsNestedPublic)
{
return Visibility.Public;
}
if (type.IsNestedFamily)
{
return Visibility.Protected;
}
if (type.IsNotPublic || type.IsNestedAssembly)
{
return Visibility.Internal;
}
if (type.IsNestedFamORAssem)
{
return Visibility.ProtectedInternal;
}
if (type.IsNestedPrivate)
{
return Visibility.Private;
}
if (type.IsNestedFamANDAssem)
{
return Visibility.PrivateProtected;
}
throw new ArgumentException($"Unknown visibility for type {type}", nameof(type));
}
}
+24 -26
View File
@@ -1,27 +1,25 @@
<Project>
<PropertyGroup>
<LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable>
<IsAotCompatible>true</IsAotCompatible>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<PolySharpIncludeRuntimeSupportedAttributes>true</PolySharpIncludeRuntimeSupportedAttributes>
<Version>1.0.2</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<Copyright>Copyright (c) 2024 ds5678</Copyright>
<Authors>ds5678</Authors>
<Company>AssetRipper</Company>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/AssetRipper/AssetRipper.CIL</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<Project>
<PropertyGroup>
<LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<PolySharpIncludeRuntimeSupportedAttributes>true</PolySharpIncludeRuntimeSupportedAttributes>
<VersionPrefix>1.3.0</VersionPrefix>
<Copyright>Copyright (c) 2025 ds5678</Copyright>
<Authors>ds5678</Authors>
<Company>AssetRipper</Company>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/AssetRipper/AssetRipper.CIL</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
</Project>