diff --git a/Cpp2IL.Core/Analysis/MetadataResolver.cs b/Cpp2IL.Core/Analysis/MetadataResolver.cs index 7265f9490..9316d60ff 100644 --- a/Cpp2IL.Core/Analysis/MetadataResolver.cs +++ b/Cpp2IL.Core/Analysis/MetadataResolver.cs @@ -5,7 +5,6 @@ using Cpp2IL.Core.ISIL; using Cpp2IL.Core.Model.Contexts; using Cpp2IL.Core.Utils; -using LibCpp2IL; namespace Cpp2IL.Core.Analysis; diff --git a/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs b/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs index cd036485c..c6dd8a6f2 100644 --- a/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs +++ b/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using Cpp2IL.Core.Il2CppApiFunctions; using Cpp2IL.Core.ISIL; @@ -15,7 +14,7 @@ public abstract class Cpp2IlInstructionSet /// The method to get the body for /// True if this is an attribute generator function, false if it's a managed method /// A byte array representing the method's body - public abstract Memory GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator); + public abstract BinarySlice GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator); /// /// Returns the virtual address from which the given method starts. By default, returns the property, but diff --git a/Cpp2IL.Core/BinarySlice.cs b/Cpp2IL.Core/BinarySlice.cs new file mode 100644 index 000000000..3304cb1ee --- /dev/null +++ b/Cpp2IL.Core/BinarySlice.cs @@ -0,0 +1,48 @@ +using System; +using LibCpp2IL; + +namespace Cpp2IL.Core; + +public readonly struct BinarySlice +{ + public static readonly BinarySlice Empty = new([]); + + public readonly int Length; + + private readonly byte[]? _computed; + + private readonly Il2CppBinary? _binary; + private readonly int _offset; + + public BinarySlice(byte[] computed) + { + _computed = computed; + Length = computed.Length; + } + + public BinarySlice(Il2CppBinary binary, int offset, int length) + { + _binary = binary; + _offset = offset; + Length = length; + } + + public byte[] ToArray() + { + if (_computed is not null) + return _computed; + + return _binary!.GetRawBinaryContent() + .Slice(_offset, Length) + .ToArray(); + } + + public ReadOnlySpan AsSpan() + { + if (_computed is not null) + return _computed; + + return _binary!.GetRawBinaryContent() + .Slice(_offset, Length); + } +} diff --git a/Cpp2IL.Core/Extensions/MiscExtensions.cs b/Cpp2IL.Core/Extensions/MiscExtensions.cs index 7ab466642..d2a475f28 100644 --- a/Cpp2IL.Core/Extensions/MiscExtensions.cs +++ b/Cpp2IL.Core/Extensions/MiscExtensions.cs @@ -160,7 +160,7 @@ public static IEnumerable Peek(this IEnumerable enumerable, Action a }); } - public static unsafe uint ReadUInt(this Span span, int start) + public static unsafe uint ReadUInt(this ReadOnlySpan span, int start) { if (start >= span.Length) throw new ArgumentOutOfRangeException(nameof(start), $"start=[{start}], mem.Length=[{span.Length}]"); diff --git a/Cpp2IL.Core/ISIL/Instruction.cs b/Cpp2IL.Core/ISIL/Instruction.cs index e7e89fe42..b56d063a9 100644 --- a/Cpp2IL.Core/ISIL/Instruction.cs +++ b/Cpp2IL.Core/ISIL/Instruction.cs @@ -1,6 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using AsmResolver.DotNet; using Cpp2IL.Core.Graphs; using Cpp2IL.Core.Model.Contexts; diff --git a/Cpp2IL.Core/Il2CppApiFunctions/Arm64KeyFunctionAddresses.cs b/Cpp2IL.Core/Il2CppApiFunctions/Arm64KeyFunctionAddresses.cs index b18a93dea..fc2daaf33 100644 --- a/Cpp2IL.Core/Il2CppApiFunctions/Arm64KeyFunctionAddresses.cs +++ b/Cpp2IL.Core/Il2CppApiFunctions/Arm64KeyFunctionAddresses.cs @@ -46,7 +46,7 @@ protected override void Init(ApplicationAnalysisContext context) var oldLength = primaryExecutableSection.Length; var toRemove = (int)(attributeGeneratorList[^1] - primaryExecutableSectionVa); - primaryExecutableSection = primaryExecutableSection.Skip(toRemove).ToArray(); + primaryExecutableSection = primaryExecutableSection.Slice(toRemove); primaryExecutableSectionVa = attributeGeneratorList[^1]; @@ -79,7 +79,7 @@ protected override void Init(ApplicationAnalysisContext context) oldLength = primaryExecutableSection.Length; toRemove = (int)(startFrom - primaryExecutableSectionVa); - primaryExecutableSection = primaryExecutableSection.Skip(toRemove).ToArray(); + primaryExecutableSection = primaryExecutableSection.Slice(toRemove); primaryExecutableSectionVa = startFrom; @@ -99,7 +99,7 @@ protected override void Init(ApplicationAnalysisContext context) oldLength = primaryExecutableSection.Length; toRemove = (int)(startFrom - primaryExecutableSectionVa); - primaryExecutableSection = primaryExecutableSection.Skip(toRemove).ToArray(); + primaryExecutableSection = primaryExecutableSection.Slice(toRemove); primaryExecutableSectionVa = startFrom; @@ -115,7 +115,7 @@ protected override void Init(ApplicationAnalysisContext context) var oldLength = primaryExecutableSection.Length; var toKeep = (int)(attributeGeneratorList[^1] - primaryExecutableSectionVa); - primaryExecutableSection = primaryExecutableSection.SubArray(..toKeep); + primaryExecutableSection = primaryExecutableSection[..toKeep]; //This doesn't change, we've trimmed the end, not the beginning // primaryExecutableSectionVa = primaryExecutableSectionVa; @@ -124,7 +124,7 @@ protected override void Init(ApplicationAnalysisContext context) } } - _allInstructions = disassembler.Disassemble(primaryExecutableSection, (long)primaryExecutableSectionVa).ToList(); + _allInstructions = disassembler.Disassemble(primaryExecutableSection.ToArray(), (long)primaryExecutableSectionVa).ToList(); } protected override IEnumerable FindAllThunkFunctions(ulong addr, uint maxBytesBack = 0, params ulong[] addressesToIgnore) diff --git a/Cpp2IL.Core/InstructionSets/Arm64InstructionSet.cs b/Cpp2IL.Core/InstructionSets/Arm64InstructionSet.cs index 424a88232..2c3f9471c 100644 --- a/Cpp2IL.Core/InstructionSets/Arm64InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/Arm64InstructionSet.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -12,23 +11,25 @@ namespace Cpp2IL.Core.InstructionSets; public class Arm64InstructionSet : Cpp2IlInstructionSet { - public override Memory GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator) + public override BinarySlice GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator) { + var binary = context.AppContext.Binary; + //Avoid use of capstone where possible if (true || context is not ConcreteGenericMethodAnalysisContext) { //Managed method or attr gen => grab raw byte range between a and b - var startOfNextFunction = (int)MiscUtils.GetAddressOfNextFunctionStart(context.UnderlyingPointer, context.AppContext.Binary) - 1; + var startOfNextFunction = (int)MiscUtils.GetAddressOfNextFunctionStart(context.UnderlyingPointer, binary) - 1; var ptrAsInt = (int)context.UnderlyingPointer; var count = startOfNextFunction - ptrAsInt; if (startOfNextFunction > 0) - return context.AppContext.Binary.GetRawBinaryContent().AsMemory(ptrAsInt, count); + return new BinarySlice(binary, ptrAsInt, count); } - var instructions = Arm64Utils.GetArm64MethodBodyAtVirtualAddress(context.AppContext.Binary, context.UnderlyingPointer); + var instructions = Arm64Utils.GetArm64MethodBodyAtVirtualAddress(binary, context.UnderlyingPointer); - return instructions.SelectMany(i => i.Bytes).ToArray(); + return new BinarySlice(instructions.SelectMany(i => i.Bytes).ToArray()); } public override List GetIsilFromMethod(MethodAnalysisContext context) diff --git a/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs b/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs index 64d08e79e..c1b79b21f 100644 --- a/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -12,14 +11,15 @@ namespace Cpp2IL.Core.InstructionSets; public class ArmV7InstructionSet : Cpp2IlInstructionSet { - public override Memory GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator) + public override BinarySlice GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator) { - if (ArmV7Utils.TryGetMethodBodyBytesFast(context.AppContext.Binary, context.UnderlyingPointer, context is AttributeGeneratorMethodAnalysisContext) is { } ret) - return ret; + var slice = ArmV7Utils.TryGetMethodBodyBytesFast(context.AppContext.Binary, context.UnderlyingPointer, isAttributeGenerator); + if (slice.Length > 0) + return slice; var instructions = ArmV7Utils.GetArmV7MethodBodyAtVirtualAddress(context.AppContext.Binary, context.UnderlyingPointer); - return instructions.SelectMany(i => i.Bytes).ToArray(); + return new BinarySlice(instructions.SelectMany(i => i.Bytes).ToArray()); } public override List GetIsilFromMethod(MethodAnalysisContext context) diff --git a/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs b/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs index 6f5bc2ef8..aa22c7950 100644 --- a/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs @@ -8,8 +8,6 @@ using Cpp2IL.Core.Model.Contexts; using Cpp2IL.Core.Utils; using Disarm.InternalDisassembly; -using LibCpp2IL; -using Cpp2IL.Core.Logging; namespace Cpp2IL.Core.InstructionSets; @@ -18,32 +16,34 @@ public class NewArmV8InstructionSet : Cpp2IlInstructionSet [ThreadStatic] private static Dictionary adrpOffsets = new(); - public override Memory GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator) + public override BinarySlice GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator) { + var binary = context.AppContext.Binary; + if (context is not ConcreteGenericMethodAnalysisContext) { //Managed method or attr gen => grab raw byte range between a and b - var startOfNextFunction = (int)MiscUtils.GetAddressOfNextFunctionStart(context.UnderlyingPointer, context.AppContext.Binary); + var startOfNextFunction = (int)MiscUtils.GetAddressOfNextFunctionStart(context.UnderlyingPointer, binary); var ptrAsInt = (int)context.UnderlyingPointer; var count = startOfNextFunction - ptrAsInt; if (startOfNextFunction > 0) - return context.AppContext.Binary.GetRawBinaryContent().AsMemory(ptrAsInt, count); + return new BinarySlice(binary, ptrAsInt, count); } - var result = NewArm64Utils.GetArm64MethodBodyAtVirtualAddress(context.AppContext.Binary, context.UnderlyingPointer); + var result = NewArm64Utils.GetArm64MethodBodyAtVirtualAddress(binary, context.UnderlyingPointer); var lastInsn = result.LastValid(); - var start = (int)context.AppContext.Binary.MapVirtualAddressToRaw(context.UnderlyingPointer); + var start = (int)binary.MapVirtualAddressToRaw(context.UnderlyingPointer); // Map the last instruction (always within segment) and add 4 (ARM64 instruction size). // This avoids mapping endVa which may land exactly at a segment boundary gap. - var end = (int)context.AppContext.Binary.MapVirtualAddressToRaw(lastInsn.Address) + 4; + var end = (int)binary.MapVirtualAddressToRaw(lastInsn.Address) + 4; //Sanity check - if (start < 0 || end < 0 || start >= context.AppContext.Binary.RawLength || end >= context.AppContext.Binary.RawLength) - throw new Exception($"Failed to map virtual address 0x{context.UnderlyingPointer:X} to raw address for method {context!.DeclaringType?.FullName}/{context.Name} - start: 0x{start:X}, end: 0x{end:X} are out of bounds for length {context.AppContext.Binary.RawLength}."); + if (start < 0 || end < 0 || start >= binary.RawLength || end >= binary.RawLength) + throw new Exception($"Failed to map virtual address 0x{context.UnderlyingPointer:X} to raw address for method {context!.DeclaringType?.FullName}/{context.Name} - start: 0x{start:X}, end: 0x{end:X} are out of bounds for length {binary.RawLength}."); - return context.AppContext.Binary.GetRawBinaryContent().AsMemory(start, end - start); + return new BinarySlice(binary, start, end - start); } public override List GetParameterOperandsFromMethod(MethodAnalysisContext context) @@ -56,10 +56,10 @@ public override List GetIsilFromMethod(MethodAnalysisContext contex { var insns = NewArm64Utils.GetArm64MethodBodyAtVirtualAddress(context.AppContext.Binary, context.UnderlyingPointer); - if (adrpOffsets == null!) //Null suppress because thread static weirdness + if (adrpOffsets == null!) // initializers for ThreadStatic fields only run on the first thread adrpOffsets = new(); - - adrpOffsets.Clear(); + else + adrpOffsets.Clear(); var instructions = new List(); var addresses = new List(); @@ -267,7 +267,7 @@ void AddCall(MethodAnalysisContext context, object? returnRegister2, ulong addre //Unconditional branch to outside the method, treat as call (tail-call, specifically) followed by return var returnRegister2 = GetReturnRegisterForContext(context); AddCall(context, returnRegister2, address, target); - + if (returnRegister2 == null) Add(address, OpCode.Return); else @@ -517,7 +517,7 @@ private object ConvertOperand(Arm64Instruction instruction, int operand) public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance() => new NewArm64KeyFunctionAddresses(); - public override string PrintAssembly(MethodAnalysisContext context) => context.RawBytes.Span.Length <= 0 ? "" : string.Join("\n", Disassembler.Disassemble(context.RawBytes.Span, context.UnderlyingPointer, new Disassembler.Options(true, true, false)).ToList()); + public override string PrintAssembly(MethodAnalysisContext context) => context.RawBytes.Length <= 0 ? "" : string.Join("\n", Disassembler.Disassemble(context.RawBytes.AsSpan(), context.UnderlyingPointer, new Disassembler.Options(true, true, false)).ToList()); private object? GetReturnRegisterForContext(MethodAnalysisContext context) { diff --git a/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs b/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs index 23f51f1da..500743e23 100644 --- a/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using Cpp2IL.Core.Api; using Cpp2IL.Core.Il2CppApiFunctions; @@ -12,7 +11,7 @@ namespace Cpp2IL.Core.InstructionSets; public class WasmInstructionSet : Cpp2IlInstructionSet { - public override Memory GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator) + public override BinarySlice GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator) { if (context.Definition is { } methodDefinition) { @@ -21,16 +20,16 @@ public override Memory GetRawBytesForMethod(MethodAnalysisContext context, if (wasmDef == null) { Logger.WarnNewline($"Could not find WASM definition for method {methodDefinition.HumanReadableSignature} in {methodDefinition.DeclaringType?.FullName}, probably incorrect signature calculation (signature was {WasmUtils.BuildSignature(context)}, index is {context.UnderlyingPointer})", "WasmInstructionSet"); - return Array.Empty(); + return BinarySlice.Empty; } if (wasmDef.AssociatedFunctionBody == null) throw new($"WASM definition {wasmDef}, resolved from MethodAnalysisContext {context.Definition.HumanReadableSignature} in {context.DeclaringType?.FullName} has no associated function body (signature was {WasmUtils.BuildSignature(context)}, index is {context.UnderlyingPointer})"); - return wasmDef.AssociatedFunctionBody.Instructions; + return new BinarySlice(wasmDef.AssociatedFunctionBody.Instructions); } - return Array.Empty(); + return BinarySlice.Empty; } public override List GetIsilFromMethod(MethodAnalysisContext context) diff --git a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs index 11d20f592..35acb3977 100644 --- a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs @@ -31,7 +31,7 @@ public static string FormatInstruction(Instruction instruction) } } - public override Memory GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator) => X86Utils.GetRawManagedOrCaCacheGenMethodBody(context.UnderlyingPointer, isAttributeGenerator, context.AppContext.Binary); + public override BinarySlice GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator) => X86Utils.GetRawManagedOrCaCacheGenMethodBody(context.UnderlyingPointer, isAttributeGenerator, context.AppContext.Binary); public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance() => new X86KeyFunctionAddresses(); @@ -39,7 +39,7 @@ public override string PrintAssembly(MethodAnalysisContext context) { lock (Formatter) { - var insns = X86Utils.Iterate(X86Utils.GetRawManagedOrCaCacheGenMethodBody(context.UnderlyingPointer, false, context.AppContext.Binary), context.UnderlyingPointer, context.AppContext.Binary.is32Bit); + var insns = X86Utils.Iterate(context); return string.Join("\n", insns.Select(FormatInstructionInternal)); } diff --git a/Cpp2IL.Core/Model/Contexts/AttributeGeneratorMethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/AttributeGeneratorMethodAnalysisContext.cs index d43ea068a..433eaf651 100644 --- a/Cpp2IL.Core/Model/Contexts/AttributeGeneratorMethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/AttributeGeneratorMethodAnalysisContext.cs @@ -2,9 +2,10 @@ namespace Cpp2IL.Core.Model.Contexts; -public class AttributeGeneratorMethodAnalysisContext : MethodAnalysisContext +public class AttributeGeneratorMethodAnalysisContext(ulong pointer, ApplicationAnalysisContext context, HasCustomAttributes associatedMember) + : MethodAnalysisContext(context) { - public override ulong UnderlyingPointer { get; } + public override ulong UnderlyingPointer { get; } = pointer; protected override bool IsInjected => true; public override string DefaultName => ""; @@ -13,12 +14,5 @@ public class AttributeGeneratorMethodAnalysisContext : MethodAnalysisContext public override TypeAnalysisContext DefaultReturnType => AppContext.SystemTypes.SystemVoidType; protected override int CustomAttributeIndex => -1; - public readonly HasCustomAttributes AssociatedMember; - - public AttributeGeneratorMethodAnalysisContext(ulong pointer, ApplicationAnalysisContext context, HasCustomAttributes associatedMember) : base(context) - { - UnderlyingPointer = pointer; - AssociatedMember = associatedMember; - rawMethodBody = AppContext.InstructionSet.GetRawBytesForMethod(this, true); - } + public readonly HasCustomAttributes AssociatedMember = associatedMember; } diff --git a/Cpp2IL.Core/Model/Contexts/ConcreteGenericMethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/ConcreteGenericMethodAnalysisContext.cs index 884582b8f..4522804a7 100644 --- a/Cpp2IL.Core/Model/Contexts/ConcreteGenericMethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/ConcreteGenericMethodAnalysisContext.cs @@ -124,9 +124,6 @@ private ConcreteGenericMethodAnalysisContext(Cpp2IlMethodRef? methodRef, MethodA } DefaultReturnType = GenericInstantiation.Instantiate(BaseMethodContext.ReturnType, typeGenericParameters, methodGenericParameters); - - if (UnderlyingPointer != 0) - rawMethodBody = AppContext.InstructionSet.GetRawBytesForMethod(this, false); } private static AssemblyAnalysisContext ResolveDeclaringAssembly(Cpp2IlMethodRef methodRef, ApplicationAnalysisContext context) diff --git a/Cpp2IL.Core/Model/Contexts/HasCustomAttributes.cs b/Cpp2IL.Core/Model/Contexts/HasCustomAttributes.cs index 5fe10bb10..d31892f79 100644 --- a/Cpp2IL.Core/Model/Contexts/HasCustomAttributes.cs +++ b/Cpp2IL.Core/Model/Contexts/HasCustomAttributes.cs @@ -24,7 +24,7 @@ public abstract class HasCustomAttributes(uint token, ApplicationAnalysisContext /// /// On V29, stores the custom attribute blob. Pre-29, stores the bytes for the custom attribute generator function. /// - public Memory RawIl2CppCustomAttributeData = Memory.Empty; + public byte[] RawIl2CppCustomAttributeData = []; /// /// Stores the analyzed custom attribute data once analysis has actually run. @@ -87,7 +87,7 @@ public bool HasCustomAttributeWithFullName(string fullName) protected void InitCustomAttributeData() { - if(IsInjected) + if (IsInjected) return; _hasInitCustomAttributeData = true; @@ -111,14 +111,13 @@ protected void InitCustomAttributeData() if (AttributeTypeRange == null || AttributeTypeRange.count == 0) { - RawIl2CppCustomAttributeData = Array.Empty(); AttributeTypes = []; return; //No attributes } AttributeTypes = Enumerable.Range(AttributeTypeRange.start, AttributeTypeRange.count) - .Select(attrIdx => AppContext.Metadata!.attributeTypes![attrIdx]) //Not null because we've checked we're not on v29 - .Select(typeIdx => AppContext.Binary!.GetType(Il2CppVariableWidthIndex.MakeTemporaryForFixedWidthUsage(typeIdx))) + .Select(attrIdx => AppContext.Metadata.attributeTypes![attrIdx]) //Not null because we've checked we're not on v29 + .Select(typeIdx => AppContext.Binary.GetType(Il2CppVariableWidthIndex.MakeTemporaryForFixedWidthUsage(typeIdx))) .ToList(); } @@ -138,7 +137,6 @@ protected void InitCustomAttributeData() if (caIndex < 0) { - RawIl2CppCustomAttributeData = Array.Empty(); return null; } @@ -157,7 +155,6 @@ private void InitPre29AttributeGeneratorAnalysis(int rangeIndex) { if (rangeIndex < 0) { - RawIl2CppCustomAttributeData = Array.Empty(); return; } @@ -168,7 +165,6 @@ private void InitPre29AttributeGeneratorAnalysis(int rangeIndex) catch (IndexOutOfRangeException) { Logger.WarnNewline("Custom attribute generator out of range for " + this, "CA Restore"); - RawIl2CppCustomAttributeData = Array.Empty(); return; } } @@ -176,7 +172,6 @@ private void InitPre29AttributeGeneratorAnalysis(int rangeIndex) { if(AttributeTypeRange == null || AttributeTypeRange.count == 0 || CustomAttributeAssembly.Definition is null) { - RawIl2CppCustomAttributeData = Array.Empty(); return; } @@ -189,12 +184,11 @@ private void InitPre29AttributeGeneratorAnalysis(int rangeIndex) if (generatorPtr == 0 || !AppContext.Binary.TryMapVirtualAddressToRaw(generatorPtr, out _)) { Logger.WarnNewline($"Supposedly had custom attributes ({string.Join(", ", AttributeTypes ?? [])}), but generator was null for " + this, "CA Restore"); - RawIl2CppCustomAttributeData = Memory.Empty; return; } CaCacheGeneratorAnalysis = new(generatorPtr, AppContext, this); - RawIl2CppCustomAttributeData = CaCacheGeneratorAnalysis.RawBytes; + RawIl2CppCustomAttributeData = CaCacheGeneratorAnalysis.RawBytes.ToArray(); } /// @@ -273,7 +267,7 @@ private void AnalyzeCustomAttributeDataV29() if (RawIl2CppCustomAttributeData.Length == 0) return; - using var blobStream = new MemoryStream(RawIl2CppCustomAttributeData.ToArray()); + using var blobStream = new MemoryStream(RawIl2CppCustomAttributeData); var attributeCount = blobStream.ReadUnityCompressedUint(); var constructors = V29AttributeUtils.ReadConstructors(blobStream, attributeCount, AppContext); diff --git a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs index a2ff4d6e9..7f050e001 100644 --- a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs @@ -41,7 +41,7 @@ public class MethodAnalysisContext : HasGenericParameters, IMethodInfoProvider /// /// The raw method body as machine code in the active instruction set. /// - public Memory RawBytes => rawMethodBody ??= InitRawBytes(); + public BinarySlice RawBytes = BinarySlice.Empty; /// /// The first-stage-analyzed Instruction-Set-Independent Language Instructions. @@ -154,8 +154,6 @@ public TypeAnalysisContext ReturnType get => OverrideReturnType ?? DefaultReturnType; set => OverrideReturnType = value; } - - protected Memory? rawMethodBody; public MethodAnalysisContext? BaseMethod { @@ -284,39 +282,27 @@ public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisCon Parameters.Add(new(parameterDefinition, i, this)); } } - else - rawMethodBody = Array.Empty(); } - [MemberNotNull(nameof(rawMethodBody))] public void EnsureRawBytes() - { - rawMethodBody ??= InitRawBytes(); - } - - private Memory InitRawBytes() { //Some abstract methods (on interfaces, no less) apparently have a body? Unity doesn't support default interface methods so idk what's going on here. //E.g. UnityEngine.Purchasing.AppleCore.dll: UnityEngine.Purchasing.INativeAppleStore::SetUnityPurchasingCallback on among us (itch.io build) if (Definition != null && Definition.MethodPointer != 0 && !Definition.Attributes.HasFlag(MethodAttributes.Abstract)) { - var ret = AppContext.InstructionSet.GetRawBytesForMethod(this, false); + var ret = AppContext.InstructionSet.GetRawBytesForMethod(this, this is AttributeGeneratorMethodAnalysisContext); if (ret.Length == 0) { Logger.VerboseNewline("\t\t\tUnexpectedly got 0-byte method body for " + this + $". Pointer was 0x{Definition.MethodPointer:X}", "MAC"); } - return ret; + RawBytes = ret; } - else - return Array.Empty(); } protected MethodAnalysisContext(ApplicationAnalysisContext context) : base(0, context) - { - rawMethodBody = Array.Empty(); - } + { } [MemberNotNull(nameof(ConvertedIsil))] public void Analyze() diff --git a/Cpp2IL.Core/Model/Contexts/NativeMethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/NativeMethodAnalysisContext.cs index 3f6be677d..60873f51d 100644 --- a/Cpp2IL.Core/Model/Contexts/NativeMethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/NativeMethodAnalysisContext.cs @@ -36,7 +36,5 @@ public NativeMethodAnalysisContext(TypeAnalysisContext parent, ulong address, bo { DefaultName = $"NativeMethod_0x{UnderlyingPointer:X}"; } - - rawMethodBody = AppContext.InstructionSet.GetRawBytesForMethod(this, false); } } diff --git a/Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs index 3184fed3a..e65675588 100644 --- a/Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Reflection; using System.Text; -using Cpp2IL.Core.Api; using Cpp2IL.Core.Utils; using LibCpp2IL.BinaryStructures; using LibCpp2IL.Metadata; diff --git a/Cpp2IL.Core/Utils/Arm64Utils.cs b/Cpp2IL.Core/Utils/Arm64Utils.cs index f19c7839e..2ac900a16 100644 --- a/Cpp2IL.Core/Utils/Arm64Utils.cs +++ b/Cpp2IL.Core/Utils/Arm64Utils.cs @@ -1,7 +1,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using Cpp2IL.Core.Extensions; using Gee.External.Capstone; using Gee.External.Capstone.Arm64; using LibCpp2IL; @@ -75,8 +74,8 @@ public static string GetRegisterNameNew(Arm64RegisterId registerId) registerId = (registerId - Arm64RegisterId.ARM64_REG_H0) + Arm64RegisterId.ARM64_REG_V0; //S to V - if (registerId is >= Arm64RegisterId.ARM64_REG_B0 and <= Arm64RegisterId.ARM64_REG_B31) - registerId = (registerId - Arm64RegisterId.ARM64_REG_B0) + Arm64RegisterId.ARM64_REG_V0; + if (registerId is >= Arm64RegisterId.ARM64_REG_S0 and <= Arm64RegisterId.ARM64_REG_S31) + registerId = (registerId - Arm64RegisterId.ARM64_REG_S0) + Arm64RegisterId.ARM64_REG_V0; //D to V if (registerId is >= Arm64RegisterId.ARM64_REG_D0 and <= Arm64RegisterId.ARM64_REG_D31) @@ -123,7 +122,7 @@ public static List GetArm64MethodBodyAtVirtualAddress(Il2CppBi if (rawStartOfNextMethod < rawStart) rawStartOfNextMethod = binary.RawLength; - byte[] bytes = binary.GetRawBinaryContent().SubArray((int)rawStart..(int)rawStartOfNextMethod); + byte[] bytes = binary.GetRawBinaryContent()[(int)rawStart..(int)rawStartOfNextMethod].ToArray(); var iter = disassembler.Iterate(bytes, (long)virtAddress); if (count > 0) @@ -141,7 +140,7 @@ public static List GetArm64MethodBodyAtVirtualAddress(Il2CppBi while (!ret.Any(i => i.Mnemonic is "b" or ".byte") && (count == -1 || ret.Count < count)) { //All arm64 instructions are 4 bytes - ret.AddRange(disassembler.Iterate(allBytes.SubArray(pos..(pos + 4)), (long)virtAddress)); + ret.AddRange(disassembler.Iterate(allBytes[pos..(pos + 4)].ToArray(), (long)virtAddress)); virtAddress += 4; pos += 4; } diff --git a/Cpp2IL.Core/Utils/ArmV7Utils.cs b/Cpp2IL.Core/Utils/ArmV7Utils.cs index d93eed0a6..2dfd7b83c 100644 --- a/Cpp2IL.Core/Utils/ArmV7Utils.cs +++ b/Cpp2IL.Core/Utils/ArmV7Utils.cs @@ -1,6 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; -using Cpp2IL.Core.Extensions; using Gee.External.Capstone; using Gee.External.Capstone.Arm; using LibCpp2IL; @@ -11,6 +11,7 @@ public static class ArmV7Utils { private static CapstoneArmDisassembler? _armDisassembler; + [MemberNotNull(nameof(_armDisassembler))] private static void InitArmDecompilation() { var disassembler = CapstoneDisassembler.CreateArmDisassembler(ArmDisassembleMode.Arm); @@ -20,17 +21,17 @@ private static void InitArmDecompilation() _armDisassembler = disassembler; } - public static byte[]? TryGetMethodBodyBytesFast(Il2CppBinary binary, ulong virtAddress, bool isCAGen) + public static BinarySlice TryGetMethodBodyBytesFast(Il2CppBinary binary, ulong virtAddress, bool isCAGen) { var startOfNext = MiscUtils.GetAddressOfNextFunctionStart(virtAddress, binary); var length = (startOfNext - virtAddress); if (isCAGen && length > 50_000) - return null; + return BinarySlice.Empty; if (startOfNext <= 0) //We have to fall through to default behavior for the last method because we cannot accurately pinpoint its end - return null; + return BinarySlice.Empty; var rawStartOfNextMethod = binary.MapVirtualAddressToRaw(startOfNext); @@ -38,7 +39,7 @@ private static void InitArmDecompilation() if (rawStartOfNextMethod < rawStart) rawStartOfNextMethod = binary.RawLength; - return binary.GetRawBinaryContent().SubArray((int)rawStart..(int)rawStartOfNextMethod); + return new BinarySlice(binary, (int)rawStart, (int)(rawStartOfNextMethod - rawStart)); } public static List GetArmV7MethodBodyAtVirtualAddress(Il2CppBinary binary, ulong virtAddress, bool managed = true, int count = -1) @@ -62,9 +63,9 @@ public static List GetArmV7MethodBodyAtVirtualAddress(Il2CppBina if (rawStartOfNextMethod < rawStart) rawStartOfNextMethod = binary.RawLength; - byte[] bytes = binary.GetRawBinaryContent().SubArray((int)rawStart..(int)rawStartOfNextMethod); + byte[] bytes = binary.GetRawBinaryContent()[(int)rawStart..(int)rawStartOfNextMethod].ToArray(); - var iter = _armDisassembler!.Iterate(bytes, (long)virtAddress); + var iter = _armDisassembler.Iterate(bytes, (long)virtAddress); if (count > 0) iter = iter.Take(count); @@ -80,7 +81,7 @@ public static List GetArmV7MethodBodyAtVirtualAddress(Il2CppBina while (!ret.Any(i => i.Mnemonic is "b" or ".byte") && (count == -1 || ret.Count < count)) { //All arm64 instructions are 4 bytes - ret.AddRange(_armDisassembler!.Iterate(allBytes.SubArray(pos..(pos + 4)), (long)virtAddress)); + ret.AddRange(_armDisassembler.Iterate(allBytes[pos..(pos + 4)].ToArray(), (long)virtAddress)); virtAddress += 4; pos += 4; } diff --git a/Cpp2IL.Core/Utils/NewArm64Utils.cs b/Cpp2IL.Core/Utils/NewArm64Utils.cs index 76ae5f899..ea65f73e4 100644 --- a/Cpp2IL.Core/Utils/NewArm64Utils.cs +++ b/Cpp2IL.Core/Utils/NewArm64Utils.cs @@ -23,7 +23,7 @@ public static List GetArm64MethodBodyAtVirtualAddress(Il2CppBi if (rawStartOfNextMethod < rawStart) rawStartOfNextMethod = binary.RawLength; - var bytes = binary.GetRawBinaryContent().AsSpan((int)rawStart, (int)(rawStartOfNextMethod - rawStart)); + var bytes = binary.GetRawBinaryContent().Slice((int)rawStart, (int)(rawStartOfNextMethod - rawStart)); return Disassemble(bytes, virtAddress); } @@ -32,7 +32,7 @@ public static List GetArm64MethodBodyAtVirtualAddress(Il2CppBi //Unmanaged function, look for first b var pos = (int)binary.MapVirtualAddressToRaw(virtAddress); var allBytes = binary.GetRawBinaryContent(); - var span = allBytes.AsSpan(pos, 4); + var span = allBytes.Slice(pos, 4); List ret = []; while ((count == -1 || ret.Count < count) && !ret.Any(i => i.Mnemonic is Arm64Mnemonic.B || i.Mnemonic is Arm64Mnemonic.INVALID)) @@ -40,13 +40,13 @@ public static List GetArm64MethodBodyAtVirtualAddress(Il2CppBi ret = Disassemble(span, virtAddress); //All arm64 instructions are 4 bytes - span = allBytes.AsSpan(pos, span.Length + 4); + span = allBytes.Slice(pos, span.Length + 4); } return ret; } - private static List Disassemble(Span bytes, ulong virtAddress) + private static List Disassemble(ReadOnlySpan bytes, ulong virtAddress) { try { diff --git a/Cpp2IL.Core/Utils/X64CallingConventionResolver.cs b/Cpp2IL.Core/Utils/X64CallingConventionResolver.cs index 2a14e325c..be4899b28 100644 --- a/Cpp2IL.Core/Utils/X64CallingConventionResolver.cs +++ b/Cpp2IL.Core/Utils/X64CallingConventionResolver.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using Cpp2IL.Core.ISIL; using Cpp2IL.Core.Model.Contexts; -using LibCpp2IL.BinaryStructures; using LibCpp2IL.PE; namespace Cpp2IL.Core.Utils; diff --git a/Cpp2IL.Core/Utils/X86Utils.cs b/Cpp2IL.Core/Utils/X86Utils.cs index f8debf279..74eec0475 100644 --- a/Cpp2IL.Core/Utils/X86Utils.cs +++ b/Cpp2IL.Core/Utils/X86Utils.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text.RegularExpressions; using Cpp2IL.Core.Extensions; using Cpp2IL.Core.Logging; @@ -17,66 +18,72 @@ public static class X86Utils private static readonly ConcurrentDictionary CachedUpscaledRegisters = new(); private static readonly ConcurrentDictionary CachedX86RegNamesNew = new(); - //TODO Consider implementing a CodeReader for Memory - public static InstructionList Disassemble(Memory bytes, ulong methodBase, bool is32Bit) - => Disassemble(bytes.ToArray(), methodBase, is32Bit); - - public static InstructionList Disassemble(byte[] bytes, ulong methodBase, bool is32Bit) + public static unsafe InstructionList Disassemble(ReadOnlySpan bytes, ulong methodBase, bool is32Bit) { - var codeReader = new ByteArrayCodeReader(bytes); - var decoder = Decoder.Create(is32Bit ? 32 : 64, codeReader); - decoder.IP = methodBase; - var instructions = new InstructionList(); - var endRip = decoder.IP + (uint)bytes.Length; + fixed (byte* ptr = bytes) + { + var codeReader = new MemoryCodeReader(ptr, bytes.Length); + var decoder = Decoder.Create(is32Bit ? 32 : 64, codeReader); + decoder.IP = methodBase; + var endRip = decoder.IP + (uint)bytes.Length; - while (decoder.IP < endRip) - instructions.Add(decoder.Decode()); + InstructionList instructions = []; + while (decoder.IP < endRip) + instructions.Add(decoder.Decode()); - return instructions; + return instructions; + } } - public static InstructionList Disassemble(MethodAnalysisContext context) + public static InstructionList Disassemble(List bytes, ulong methodBase, bool is32Bit) { - return Disassemble(context.RawBytes, context.UnderlyingPointer, context.AppContext.Binary.is32Bit); +#if NET6_0_OR_GREATER + ReadOnlySpan span = CollectionsMarshal.AsSpan(bytes); +#else + ReadOnlySpan span = bytes.ToArray(); +#endif + return Disassemble(span, methodBase, is32Bit); } - public static IEnumerable Iterate(Memory bytes, ulong methodBase, bool is32Bit) + public static unsafe InstructionList Iterate(ReadOnlySpan bytes, ulong methodBase, bool is32Bit) { - return Iterate(bytes.AsEnumerable(), methodBase, is32Bit); - } + fixed (byte* ptr = bytes) + { + var codeReader = new MemoryCodeReader(ptr, bytes.Length); + var decoder = Decoder.Create(is32Bit ? 32 : 64, codeReader); + decoder.IP = methodBase; - public static IEnumerable Iterate(IEnumerable bytes, ulong methodBase, bool is32Bit) - { - var codeReader = new EnumerableCodeReader(bytes); - var decoder = Decoder.Create(is32Bit ? 32 : 64, codeReader); - decoder.IP = methodBase; + var instructions = new InstructionList(); - decoder.Decode(out var instruction); - while (!instruction.IsInvalid) - { - yield return instruction; - decoder.Decode(out instruction); + decoder.Decode(out var instruction); + while (!instruction.IsInvalid) + { + instructions.Add(instruction); + decoder.Decode(out instruction); + } + + return instructions; } } public static IEnumerable Iterate(MethodAnalysisContext context) { - return Iterate(context.RawBytes, context.UnderlyingPointer, context.AppContext.Binary.is32Bit); + return Iterate(context.RawBytes.AsSpan(), context.UnderlyingPointer, context.AppContext.Binary.is32Bit); } - public static Memory GetRawManagedOrCaCacheGenMethodBody(ulong ptr, bool isCaGen, Il2CppBinary binary) + public static BinarySlice GetRawManagedOrCaCacheGenMethodBody(ulong ptr, bool isCaGen, Il2CppBinary binary) { var rawAddr = binary.MapVirtualAddressToRaw(ptr, false); if (rawAddr <= 0) - return Memory.Empty; + return BinarySlice.Empty; var virtStartNextFunc = MiscUtils.GetAddressOfNextFunctionStart(ptr, binary); if (virtStartNextFunc == 0 || (isCaGen && virtStartNextFunc - ptr > 50000)) { GetMethodBodyAtVirtAddressNew(ptr, false, binary, out var ret); - return ret; + return new BinarySlice(ret); } var ra2 = binary.MapVirtualAddressToRaw(virtStartNextFunc, false); @@ -85,7 +92,7 @@ public static Memory GetRawManagedOrCaCacheGenMethodBody(ulong ptr, bool i { //Don't have a known end point => fall back GetMethodBodyAtVirtAddressNew(ptr, false, binary, out var ret); - return ret; + return new BinarySlice(ret); } var startOfNextFunc = (int)ra2; @@ -94,25 +101,26 @@ public static Memory GetRawManagedOrCaCacheGenMethodBody(ulong ptr, bool i { Logger.WarnNewline($"StartOfNextFunc returned va 0x{virtStartNextFunc:X}, raw address 0x{startOfNextFunc:X}, for raw address 0x{rawAddr:X}. It should be more than raw address. Falling back to manual, slow, decompiler-based approach."); GetMethodBodyAtVirtAddressNew(ptr, false, binary, out var ret); - return ret; + return new BinarySlice(ret); } - var rawArray = binary.GetRawBinaryContent(); + var rawBinary = binary.GetRawBinaryContent(); var lastPos = startOfNextFunc - 1; - if (lastPos >= rawArray.Length) + if (lastPos >= rawBinary.Length) { Logger.WarnNewline($"StartOfNextFunc returned va 0x{virtStartNextFunc:X}, raw address 0x{startOfNextFunc:X}, for raw address 0x{rawAddr:X}. LastPos should be less than the raw array length. Falling back to manual, slow, decompiler-based approach."); GetMethodBodyAtVirtAddressNew(ptr, false, binary, out var ret); - return ret; + return new BinarySlice(ret); } - while (rawArray[lastPos] == 0xCC && lastPos > rawAddr) + while (rawBinary[lastPos] == 0xCC && lastPos > rawAddr) lastPos--; - var memArray = rawArray.AsMemory((int)rawAddr, (int)(lastPos - rawAddr + 1)); - if (TryFindJumpTableStart(memArray, ptr, virtStartNextFunc, out var startIndex, out var jumpTableElements)) + var span = rawBinary.Slice((int)rawAddr, (int)(lastPos - rawAddr + 1)); + + if (TryFindJumpTableStart(span, ptr, virtStartNextFunc, out var startIndex, out var jumpTableElements)) { // TODO: Figure out what to do with jumpTableElements, how do we handle returning it from this function? // we might need to return the address it was found at in TryFindJumpTableStart function too @@ -121,20 +129,20 @@ public static Memory GetRawManagedOrCaCacheGenMethodBody(ulong ptr, bool i foreach (var element in jumpTableElements) //Logger.InfoNewline($"Jump table element: 0x{element:x8}."); */ - memArray = memArray.Slice(0, startIndex); + return new BinarySlice(binary, (int)rawAddr, startIndex); } - return memArray; + return new BinarySlice(binary, (int)rawAddr, span.Length); } - private static bool TryFindJumpTableStart(Memory methodBytes, ulong methodPtr, ulong nextMethodPtr, out int startIndex, out List jumpTableElements) + private static bool TryFindJumpTableStart(ReadOnlySpan methodBytes, ulong methodPtr, ulong nextMethodPtr, out int startIndex, out List jumpTableElements) { bool foundTable = false; startIndex = 0; jumpTableElements = []; for (int i = (int)(methodPtr % 4); i < methodBytes.Length; i += 4) { - var result = (ulong)methodBytes.Span.ReadUInt(i); + var result = (ulong)methodBytes.ReadUInt(i); var possibleJumpAddress = result + 0x180000000; // image base if (possibleJumpAddress > methodPtr && possibleJumpAddress < nextMethodPtr) { @@ -177,7 +185,7 @@ public static InstructionList GetMethodBodyAtVirtAddressNew(ulong addr, bool pee buff.Add(binary.GetByteAtRawAddress((ulong)rawAddr)); - ret = X86Utils.Disassemble(buff.ToArray(), functionStart, binary.is32Bit); + ret = X86Utils.Disassemble(buff, functionStart, binary.is32Bit); if (ret.All(i => i.Mnemonic != Mnemonic.INVALID) && ret.Any(i => i.Code == Code.Int3)) con = false; @@ -200,8 +208,8 @@ public static InstructionList GetMethodBodyAtVirtAddressNew(ulong addr, bool pee public static string UpscaleRegisters(string replaceIn) { - if (CachedUpscaledRegisters.ContainsKey(replaceIn)) - return CachedUpscaledRegisters[replaceIn]; + if (CachedUpscaledRegisters.TryGetValue(replaceIn, out var reg)) + return reg; if (replaceIn.Length < 2) return replaceIn; @@ -261,16 +269,16 @@ public static string GetRegisterName(Register register) return ret; } - private class EnumerableCodeReader(IEnumerable bytes) : CodeReader + private sealed unsafe class MemoryCodeReader(byte* ptr, int length) : CodeReader { - private readonly IEnumerator _enumerator = bytes.GetEnumerator(); + private int _position; public override int ReadByte() { - if (_enumerator.MoveNext()) - return _enumerator.Current; + if (_position >= length) + return -1; - return -1; + return ptr[_position++]; } } } diff --git a/Cpp2IL.Plugin.Pdb/PdbOutputFormat.cs b/Cpp2IL.Plugin.Pdb/PdbOutputFormat.cs index 9928517ed..fed2ba1a2 100644 --- a/Cpp2IL.Plugin.Pdb/PdbOutputFormat.cs +++ b/Cpp2IL.Plugin.Pdb/PdbOutputFormat.cs @@ -2,6 +2,7 @@ using AssetRipper.Bindings.MsPdbCore; using Cpp2IL.Core.Api; using Cpp2IL.Core.Model.Contexts; +using LibCpp2IL.PE; namespace Cpp2IL.Plugin.Pdb; @@ -16,7 +17,7 @@ public override void DoOutput(ApplicationAnalysisContext context, string outputR if (!Directory.Exists(outputRoot)) Directory.CreateDirectory(outputRoot); - using var peReader = new PEReader(new MemoryStream(context.Binary.GetRawBinaryContent())); + using var peReader = new PEReader(((PE)context.Binary).ToMemoryStream()); var pdbFilePath = Path.Combine(outputRoot, "GameAssembly.pdb"); MsPdbCore.PDBOpen2W(pdbFilePath, "w", out var err, out var openError, out var pdb); diff --git a/LibCpp2IL/BinarySearcher.cs b/LibCpp2IL/BinarySearcher.cs index c296fbd13..8635a06ea 100644 --- a/LibCpp2IL/BinarySearcher.cs +++ b/LibCpp2IL/BinarySearcher.cs @@ -13,41 +13,31 @@ namespace LibCpp2IL; public class BinarySearcher(Il2CppBinary binary, Il2CppMetadata metadata, int methodCount, int typeDefinitionsCount) { - private readonly byte[] _binaryBytes = binary.GetRawBinaryContent(); - //Used for codereg location pre-2019 //Used for metadata reg location in 24.5+ - private static int FindSequence(byte[] haystack, byte[] needle, int requiredAlignment = 1, int startOffset = 0) + private static int FindSequence(ReadOnlySpan haystack, ReadOnlySpan needle, int requiredAlignment = 1, int startOffset = 0) { - //Convert needle to a span now, rather than in the loop (implicitly as call to SequenceEqual) - var needleSpan = new ReadOnlySpan(needle); - var haystackSpan = haystack.AsSpan(); - var firstByte = needleSpan[0]; + var currentHaystack = haystack.Slice(startOffset); + while (true) + { + // Use Span's built-in IndexOf to find the needle + var relativeIdx = currentHaystack.IndexOf(needle); - //Find the first occurrence of the first byte of the needle - var nextMatchIdx = Array.IndexOf(haystack, firstByte, startOffset); + // If not found, we're done + if (relativeIdx == -1) return -1; - var needleLength = needleSpan.Length; - var endIdx = haystack.Length - needleLength; - var checkAlignment = requiredAlignment > 1; + var absoluteIdx = haystack.Length - currentHaystack.Length + relativeIdx; - while (0 <= nextMatchIdx && nextMatchIdx <= endIdx) - { - //If we're not aligned, skip this match - if (!checkAlignment || nextMatchIdx % requiredAlignment == 0) + // Check alignment + if (requiredAlignment <= 1 || absoluteIdx % requiredAlignment == 0) { - //Take a slice of the array at this position and the length of the needle, and compare - if (haystackSpan.Slice(nextMatchIdx, needleLength).SequenceEqual(needleSpan)) - return nextMatchIdx; + return absoluteIdx; } - //Find the next occurrence of the first byte of the needle - nextMatchIdx = Array.IndexOf(haystack, firstByte, nextMatchIdx + 1); + // If not aligned, slice the haystack to start searching just after this failed match + currentHaystack = currentHaystack.Slice(relativeIdx + 1); } - - //No match found - return -1; } // Find all occurrences of a sequence of bytes, using word alignment by default @@ -58,7 +48,7 @@ private IEnumerable FindAllBytes(byte[] signature, int alignment = 0) var ptrSize = binary.is32Bit ? 4 : 8; while (offset != -1) { - offset = FindSequence(_binaryBytes, signature, alignment != 0 ? alignment : ptrSize, offset); + offset = FindSequence(binary.GetRawBinaryContent(), signature, alignment != 0 ? alignment : ptrSize, offset); if (offset != -1) { yield return (uint)offset; diff --git a/LibCpp2IL/BinaryStructures/Il2CppArrayType.cs b/LibCpp2IL/BinaryStructures/Il2CppArrayType.cs index 53214546d..dc85ca4ac 100644 --- a/LibCpp2IL/BinaryStructures/Il2CppArrayType.cs +++ b/LibCpp2IL/BinaryStructures/Il2CppArrayType.cs @@ -1,5 +1,3 @@ -using System; - namespace LibCpp2IL.BinaryStructures; public class Il2CppArrayType : ReadableClass diff --git a/LibCpp2IL/BinaryStructures/Il2CppCodeGenModule.cs b/LibCpp2IL/BinaryStructures/Il2CppCodeGenModule.cs index 9b6af0508..6b9534404 100644 --- a/LibCpp2IL/BinaryStructures/Il2CppCodeGenModule.cs +++ b/LibCpp2IL/BinaryStructures/Il2CppCodeGenModule.cs @@ -1,5 +1,3 @@ -using System; - namespace LibCpp2IL.BinaryStructures; public class Il2CppCodeGenModule : ReadableClass diff --git a/LibCpp2IL/Elf/ElfFile.cs b/LibCpp2IL/Elf/ElfFile.cs index 648b7caae..45f7b6b8b 100644 --- a/LibCpp2IL/Elf/ElfFile.cs +++ b/LibCpp2IL/Elf/ElfFile.cs @@ -746,7 +746,7 @@ public override ulong MapRawAddressToVirtual(uint offset, bool throwOnError = tr public override ulong GetRva(ulong pointer) => (ulong)((long)pointer - _globalOffset); - public override byte[] GetRawBinaryContent() => _raw; + public override ReadOnlySpan GetRawBinaryContent() => _raw; public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind) { @@ -778,13 +778,13 @@ public override IEnumerable> GetExportedFunctions() public override ulong GetVirtualAddressOfPrimaryExecutableSection() => _elfSectionHeaderEntries.FirstOrDefault(s => s.Name == ".text")?.VirtualAddress ?? 0; - public override byte[] GetEntirePrimaryExecutableSection() + public override ReadOnlySpan GetEntirePrimaryExecutableSection() { var primarySection = _elfSectionHeaderEntries.FirstOrDefault(s => s.Name == ".text"); if (primarySection == null) - return []; + return ReadOnlySpan.Empty; - return GetRawBinaryContent().SubArray((int)primarySection.RawAddress, (int)primarySection.Size); + return GetRawBinaryContent().Slice((int)primarySection.RawAddress, (int)primarySection.Size); } } diff --git a/LibCpp2IL/Il2CppBinary.cs b/LibCpp2IL/Il2CppBinary.cs index 5ccb2eba8..7c2690501 100644 --- a/LibCpp2IL/Il2CppBinary.cs +++ b/LibCpp2IL/Il2CppBinary.cs @@ -10,7 +10,7 @@ namespace LibCpp2IL; -public abstract class Il2CppBinary(MemoryStream input) : ClassReadingBinaryReader(input) +public abstract class Il2CppBinary(Stream input) : ClassReadingBinaryReader(input) { public delegate void RegistrationStructLocationFailureHandler(Il2CppBinary binary, Il2CppMetadata metadata, ref Il2CppCodeRegistration? codeReg, ref Il2CppMetadataRegistration? metaReg); @@ -83,6 +83,8 @@ public void Init(LibCpp2IlContext context) context.Binary = this; _context = context; + LibLogger.InfoNewline("Searching Binary for Required Data..."); + var start = DateTime.Now; var (codereg, metareg) = FindCodeAndMetadataReg(metadata); @@ -508,7 +510,7 @@ public ulong GetMethodPointer(int methodIndex, Il2CppVariableWidthIndex GetRawBinaryContent(); public abstract ulong GetVirtualAddressOfExportedFunctionByName(string toFind); public virtual bool IsExportedFunction(ulong addr) => false; @@ -520,7 +522,7 @@ public virtual bool TryGetExportedFunctionName(ulong addr, [NotNullWhen(true)] o public virtual IEnumerable> GetExportedFunctions() => []; - public abstract byte[] GetEntirePrimaryExecutableSection(); + public abstract ReadOnlySpan GetEntirePrimaryExecutableSection(); public abstract ulong GetVirtualAddressOfPrimaryExecutableSection(); diff --git a/LibCpp2IL/LibCpp2IlBinaryRegistry.cs b/LibCpp2IL/LibCpp2IlBinaryRegistry.cs index 5c60598a9..191783ffd 100644 --- a/LibCpp2IL/LibCpp2IlBinaryRegistry.cs +++ b/LibCpp2IL/LibCpp2IlBinaryRegistry.cs @@ -61,9 +61,6 @@ internal static Il2CppBinary CreateAndInit(byte[] buffer, LibCpp2IlContext conte LibLogger.InfoNewline($"Using binary type {match.Name} (from {match.Source})"); var memStream = new MemoryStream(buffer, 0, buffer.Length, true, true); - - LibLogger.InfoNewline("Searching Binary for Required Data..."); - var binary = match.FactoryFunc(memStream); binary.Init(context); diff --git a/LibCpp2IL/MachO/MachOFile.cs b/LibCpp2IL/MachO/MachOFile.cs index 1f7380feb..9fb6e97b4 100644 --- a/LibCpp2IL/MachO/MachOFile.cs +++ b/LibCpp2IL/MachO/MachOFile.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Collections.Generic; using System.Linq; @@ -130,7 +131,7 @@ public override ulong GetRva(ulong pointer) return pointer; } - public override byte[] GetRawBinaryContent() => _raw; + public override ReadOnlySpan GetRawBinaryContent() => _raw; public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind) { @@ -162,11 +163,11 @@ private MachOSection GetTextSection64() return textSection; } - public override byte[] GetEntirePrimaryExecutableSection() + public override ReadOnlySpan GetEntirePrimaryExecutableSection() { var textSection = GetTextSection64(); - return _raw.SubArray((int)textSection.Offset, (int)textSection.Size); + return _raw.AsSpan((int)textSection.Offset, (int)textSection.Size); } public override ulong GetVirtualAddressOfPrimaryExecutableSection() => GetTextSection64().Address; diff --git a/LibCpp2IL/NintendoSwitch/NsoFile.cs b/LibCpp2IL/NintendoSwitch/NsoFile.cs index ae66bcc93..b78744146 100644 --- a/LibCpp2IL/NintendoSwitch/NsoFile.cs +++ b/LibCpp2IL/NintendoSwitch/NsoFile.cs @@ -336,16 +336,16 @@ public override ulong GetRva(ulong pointer) return pointer; } - public override byte[] GetRawBinaryContent() => _raw; + public override ReadOnlySpan GetRawBinaryContent() => _raw; public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind) { return 0; } - public override byte[] GetEntirePrimaryExecutableSection() + public override ReadOnlySpan GetEntirePrimaryExecutableSection() { - return _raw.Skip((int)_header.TextSegment.FileOffset).Take((int)_header.TextSegment.DecompressedSize).ToArray(); + return _raw.AsSpan((int)_header.TextSegment.FileOffset, (int)_header.TextSegment.DecompressedSize); } public override ulong GetVirtualAddressOfPrimaryExecutableSection() diff --git a/LibCpp2IL/PE/PE.cs b/LibCpp2IL/PE/PE.cs index 48d6bbd10..c925a905c 100644 --- a/LibCpp2IL/PE/PE.cs +++ b/LibCpp2IL/PE/PE.cs @@ -10,11 +10,11 @@ namespace LibCpp2IL.PE; public sealed class PE : Il2CppBinary { //Initialized in constructor - internal readonly byte[] raw; //Internal for PlusSearch + private readonly byte[] raw; //PE-Specific Stuff - internal readonly SectionHeader[] peSectionHeaders; //Internal for the one use in PlusSearch - internal readonly ulong peImageBase; //Internal for the one use in PlusSearch + private readonly SectionHeader[] peSectionHeaders; + private readonly ulong peImageBase; private readonly OptionalHeader64? peOptionalHeader64; private readonly OptionalHeader? peOptionalHeader32; @@ -246,14 +246,14 @@ public override ulong GetRva(ulong pointer) return pointer - peImageBase; } - public override byte[] GetEntirePrimaryExecutableSection() + public override ReadOnlySpan GetEntirePrimaryExecutableSection() { var primarySection = peSectionHeaders.FirstOrDefault(s => s.Name == ".text"); if (primarySection == null) - return []; + return ReadOnlySpan.Empty; - return GetRawBinaryContent().SubArray((int)primarySection.PointerToRawData, (int)primarySection.SizeOfRawData); + return GetRawBinaryContent().Slice((int)primarySection.PointerToRawData, (int)primarySection.SizeOfRawData); } public override ulong GetVirtualAddressOfPrimaryExecutableSection() => peSectionHeaders.FirstOrDefault(s => s.Name == ".text")?.VirtualAddress + peImageBase ?? 0; @@ -262,5 +262,7 @@ public override byte[] GetEntirePrimaryExecutableSection() public override long RawLength => raw.Length; - public override byte[] GetRawBinaryContent() => raw; + public override ReadOnlySpan GetRawBinaryContent() => raw; + + public MemoryStream ToMemoryStream() => new(raw, false); } diff --git a/LibCpp2IL/Wasm/WasmCodeSection.cs b/LibCpp2IL/Wasm/WasmCodeSection.cs index 99fae8f3a..836f8fb4d 100644 --- a/LibCpp2IL/Wasm/WasmCodeSection.cs +++ b/LibCpp2IL/Wasm/WasmCodeSection.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using LibCpp2IL.Logging; @@ -21,5 +22,5 @@ internal WasmCodeSection(WasmSectionId type, long pointer, ulong size, WasmFile LibLogger.VerboseNewline($"\t\tRead {Functions.Count} function bodies"); } - public byte[] RawSectionContent => _file.GetRawBinaryContent().SubArray((int)Pointer, (int)Size); + public ReadOnlySpan RawSectionContent => _file.GetRawBinaryContent().Slice((int)Pointer, (int)Size); } diff --git a/LibCpp2IL/Wasm/WasmFile.cs b/LibCpp2IL/Wasm/WasmFile.cs index b47fadc96..04ca59dfc 100644 --- a/LibCpp2IL/Wasm/WasmFile.cs +++ b/LibCpp2IL/Wasm/WasmFile.cs @@ -76,7 +76,7 @@ public WasmFile(MemoryStream input) : base(input) public override long RawLength => _memoryBlock.Bytes.Length; public override byte GetByteAtRawAddress(ulong addr) => _memoryBlock.Bytes[addr]; - public override byte[] GetRawBinaryContent() => _memoryBlock.Bytes; + public override ReadOnlySpan GetRawBinaryContent() => _memoryBlock.Bytes; public WasmFunctionDefinition GetFunctionFromIndexAndSignature(ulong index, string signature) { @@ -274,7 +274,7 @@ public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind) return 0; //Never going to be anything useful, so don't bother looking } - public override byte[] GetEntirePrimaryExecutableSection() => ((WasmCodeSection)Sections.First(s => s.Type == WasmSectionId.SEC_CODE)).RawSectionContent; + public override ReadOnlySpan GetEntirePrimaryExecutableSection() => ((WasmCodeSection)Sections.First(s => s.Type == WasmSectionId.SEC_CODE)).RawSectionContent; public override ulong GetVirtualAddressOfPrimaryExecutableSection() => (ulong)Sections.First(s => s.Type == WasmSectionId.SEC_CODE).Pointer; }