diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..0006a8e2 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "nuget" # See documentation for possible values + target-branch: "development" + directory: "/" # Location of package manifests + schedule: + interval: "daily" + + - package-ecosystem: "github-actions" + target-branch: "development" + # Workflow files stored in the + # default location of `.github/workflows` + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/dotnet-core-pull.yml b/.github/workflows/dotnet-core-pull.yml deleted file mode 100644 index 08d618b8..00000000 --- a/.github/workflows/dotnet-core-pull.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: .NET Core - Release - -on: - pull_request: - branches: [ master ] - -jobs: - release: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 5.0.100 - - name: Install dependencies - run: dotnet restore - - name: Build - Windows x64 - run: dotnet publish --configuration Release --no-restore -r win-x64 /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true - - name: Upload Win x64 Artifact - uses: actions/upload-artifact@v2 - with: - name: Cpp2IL-Win - path: ./Cpp2IL/bin/Release/net5.0/win-x64/publish/Cpp2IL.exe - - name: Build - Ubuntu x64 - run: dotnet publish --configuration Release --no-restore -r ubuntu-x64 /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true - - name: Upload Ubuntu x64 Artifact - uses: actions/upload-artifact@v2 - with: - name: Cpp2IL-Linux - path: ./Cpp2IL/bin/Release/net5.0/ubuntu-x64/publish/Cpp2IL - - name: Build - OSX x64 - run: dotnet publish --configuration Release --no-restore -r osx-x64 /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true - - name: Upload OSX x64 Artifact - uses: actions/upload-artifact@v2 - with: - name: Cpp2IL-Mac - path: ./Cpp2IL/bin/Release/net5.0/osx-x64/publish/Cpp2IL diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index bf42f5d6..5d3d8a8a 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -2,7 +2,11 @@ name: .NET Core - Release on: push: - branches: [ master, new-analysis ] + branches: [ new-analysis ] + pull_request: + branches: [ new-analysis ] + workflow_dispatch: + jobs: release: @@ -18,14 +22,14 @@ jobs: - name: Install dependencies run: dotnet restore - name: Build - Common Lib - run: dotnet publish /p:Configuration=Release --no-restore + run: dotnet build /p:Configuration=Release --no-restore working-directory: ./LibCpp2IL/ - name: Build - Windows x64 working-directory: ./Cpp2IL/ run: dotnet publish /p:Configuration=Release /p:TargetFramework=net6.0 --no-restore -r win-x64 - name: Build - Net Framework 4.8, Windows working-directory: ./Cpp2IL/ - run: dotnet publish /p:Configuration=Release /p:TargetFramework=net472 --no-restore + run: dotnet publish /p:Configuration=Release /p:TargetFramework=net472 --no-restore -r win-x64 - name: Build - Ubuntu x64 working-directory: ./Cpp2IL/ run: dotnet publish /p:Configuration=Release /p:TargetFramework=net6.0 --no-restore -r ubuntu-x64 @@ -37,67 +41,42 @@ jobs: dotnet build dotnet test -v=n --no-build - name: Upload Common Lib Files - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: LibCpp2IL path: ./LibCpp2IL/bin/Release/netstandard2.0/publish/LibCpp2IL* - name: Upload Win x64 Artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Cpp2IL-Windows path: ./Cpp2IL/bin/Release/net6.0/win-x64/publish/Cpp2IL.exe - name: Upload Netframework Artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Cpp2IL-Netframework472-Windows - path: ./Cpp2IL/bin/Release/net472/publish/ + path: ./Cpp2IL/bin/Release/net472/win-x64/publish/ - name: Upload Ubuntu x64 Artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Cpp2IL-Linux path: ./Cpp2IL/bin/Release/net6.0/ubuntu-x64/publish/Cpp2IL - name: Upload OSX x64 Artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Cpp2IL-Mac path: ./Cpp2IL/bin/Release/net6.0/osx-x64/publish/Cpp2IL -# - name: Create Release -# id: create_release -# uses: actions/create-release@v1 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# with: -# tag_name: commit_${{ github.sha }} -# release_name: Commit ${{ github.sha }} -# draft: false -# prerelease: true -# - name: Release Windows Build -# id: release-win -# uses: actions/upload-release-asset@v1 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# with: -# upload_url: ${{ steps.create_release.outputs.upload_url }} -# asset_path: ./Cpp2IL/bin/Release/net5.0/win-x64/publish/Cpp2IL.exe -# asset_name: Cpp2IL-Win.exe -# asset_content_type: application/octet-stream -# - name: Release Linux Build -# id: release-linux -# uses: actions/upload-release-asset@v1 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# with: -# upload_url: ${{ steps.create_release.outputs.upload_url }} -# asset_path: ./Cpp2IL/bin/Release/net5.0/ubuntu-x64/publish/Cpp2IL -# asset_name: Cpp2IL-Linux -# asset_content_type: application/octet-stream -# - name: Release OSX Build -# id: release-osx -# uses: actions/upload-release-asset@v1 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# with: -# upload_url: ${{ steps.create_release.outputs.upload_url }} -# asset_path: ./Cpp2IL/bin/Release/net5.0/osx-x64/publish/Cpp2IL -# asset_name: Cpp2IL-OSX -# asset_content_type: application/octet-stream + - name: Upload LibCpp2IL nuget package + uses: actions/upload-artifact@v4 + with: + name: LibCpp2IL.nupkg + path: ./LibCpp2IL/bin/Release/Samboy063.LibCpp2IL*.nupkg + - name: Upload Cpp2IL.Core nuget package + uses: actions/upload-artifact@v4 + with: + name: Cpp2IL.Core.nupkg + path: ./Cpp2IL.Core/bin/Release/Samboy063.Cpp2IL.Core*.nupkg + - name: Upload WasmDisassembler nuget package + uses: actions/upload-artifact@v4 + with: + name: WasmDisassembler.nupkg + path: ./WasmDisassembler/bin/Release/Samboy063.WasmDisassembler*.nupkg diff --git a/.gitignore b/.gitignore index f25463cd..a411d957 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,15 @@ Il2CppBinaryAnalyzer/bin/ WasmDisassembler/obj/ WasmDisassembler/bin/ +Cpp2IL.*/obj/ +Cpp2IL.*/bin/ + +# Because these are a pain when switching branches +StableNameDotNet/obj/ +StableNameDotNet/bin/ +artifacts/ +NuGet.Config + .vs *.user diff --git a/Cpp2IL.Core/Analysis/Actions/ARM64/Arm64AllocateArrayAction.cs b/Cpp2IL.Core/Analysis/Actions/ARM64/Arm64AllocateArrayAction.cs new file mode 100644 index 00000000..b98f9949 --- /dev/null +++ b/Cpp2IL.Core/Analysis/Actions/ARM64/Arm64AllocateArrayAction.cs @@ -0,0 +1,60 @@ +using System; +using Cpp2IL.Core.Analysis.Actions.Base; +using Cpp2IL.Core.Analysis.ResultModels; +using Gee.External.Capstone.Arm64; +using Mono.Cecil; + +namespace Cpp2IL.Core.Analysis.Actions.ARM64 +{ + public class Arm64AllocateArrayAction : AbstractArrayAllocationAction + { + public Arm64AllocateArrayAction(MethodAnalysis context, Arm64Instruction associatedInstruction) : base(context, associatedInstruction) + { + //X0 => type of array + //X1 => size + //Return array in x0 + var typeConstant = context.GetConstantInReg("x0"); + + if (typeConstant == null) + return; + + if (typeConstant.Value is TypeReference reference) + { + TypeOfArray = reference; + } + + var sizeOperand = context.GetOperandInRegister("x1"); + + if (sizeOperand == null) + return; + + if (sizeOperand is LocalDefinition {KnownInitialValue: ulong or uint} local) + { + RegisterUsedLocal(local, context); + SizeAllocated = Convert.ToInt32(local.KnownInitialValue); + } + else if (sizeOperand is ConstantDefinition {Value: ulong sizeC}) + { + SizeAllocated = (int) sizeC; + } else if (sizeOperand is ConstantDefinition {Value: uint sizeCSmall}) + { + SizeAllocated = (int) sizeCSmall; + } else if (sizeOperand is ConstantDefinition {Value: int sizeCSmallUnsigned}) + { + SizeAllocated = sizeCSmallUnsigned; + } + else if (sizeOperand is LocalDefinition localDefinition) + { + LocalArraySize = true; + LocalUsedForArraySize = localDefinition; + RegisterUsedLocal(localDefinition, context); + } + + if (TypeOfArray is not ArrayType arrayType) + return; + + LocalWritten = context.MakeLocal(arrayType, reg: "x0", knownInitialValue: new AllocatedArray(SizeAllocated, arrayType)); + RegisterUsedLocal(LocalWritten, context); //Used implicitly until I can find out what's causing these issues + } + } +} \ No newline at end of file diff --git a/Cpp2IL.Core/Analysis/Actions/ARM64/Arm64ManagedFunctionCallAction.cs b/Cpp2IL.Core/Analysis/Actions/ARM64/Arm64ManagedFunctionCallAction.cs index 8ee5f47e..4a17cec2 100644 --- a/Cpp2IL.Core/Analysis/Actions/ARM64/Arm64ManagedFunctionCallAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/ARM64/Arm64ManagedFunctionCallAction.cs @@ -52,8 +52,6 @@ public Arm64ManagedFunctionCallAction(MethodAnalysis context, ManagedMethodBeingCalled = possibleTarget.AsManaged(); else AddComment($"Failed to resolve any matching method (there are {listOfCallableMethods.Count} at this address)"); - - CacheMethodInfoArg(context); HandleReturnType(context); } @@ -74,5 +72,16 @@ private void HandleReturnType(MethodAnalysis context) } } } + + public override string? ToPsuedoCode() + { + if (wasArrayInstantiation) + { + var arrayType = ((ArrayType) ((LocalDefinition) Arguments![0]!).Type!).ElementType; + return $"{Arguments![0]!.GetPseudocodeRepresentation()} = new {arrayType}[] {{{string.Join(", ", instantiatedArrayValues!)}}}"; + } + + return base.ToPsuedoCode(); + } } } \ No newline at end of file diff --git a/Cpp2IL.Core/Analysis/Actions/Base/AbstractArrayAllocationAction.cs b/Cpp2IL.Core/Analysis/Actions/Base/AbstractArrayAllocationAction.cs index 9496602f..423b6eac 100644 --- a/Cpp2IL.Core/Analysis/Actions/Base/AbstractArrayAllocationAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/Base/AbstractArrayAllocationAction.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using Cpp2IL.Core.Analysis.ResultModels; using Mono.Cecil; using Mono.Cecil.Cil; diff --git a/Cpp2IL.Core/Analysis/Actions/Base/AbstractCallAction.cs b/Cpp2IL.Core/Analysis/Actions/Base/AbstractCallAction.cs index f0416c91..ab4b4fcf 100644 --- a/Cpp2IL.Core/Analysis/Actions/Base/AbstractCallAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/Base/AbstractCallAction.cs @@ -20,23 +20,16 @@ public abstract class AbstractCallAction : BaseAction protected bool IsCallToSuperclassMethod; protected bool ShouldUseCallvirt; protected TypeReference? StaticMethodGenericTypeOverride; - protected IAnalysedOperand? MethodInfoArg; protected AbstractCallAction(MethodAnalysis context, T instruction) : base(context, instruction) { } - protected void CacheMethodInfoArg(MethodAnalysis context) - { - if(ManagedMethodBeingCalled != null) - MethodInfoArg = MethodUtils.GetMethodInfoArg(ManagedMethodBeingCalled, context); - } - public override string? ToPsuedoCode() { - if (ManagedMethodBeingCalled == null) + if (ManagedMethodBeingCalled == null) return "[instruction error - managed method being called is null]"; - + var ret = new StringBuilder(); if (ReturnedLocal != null) @@ -48,18 +41,17 @@ protected void CacheMethodInfoArg(MethodAnalysis context) ret.Append(IsCallToSuperclassMethod ? "base" : "this"); else ret.Append(InstanceBeingCalledOn?.Name); - + if (ManagedMethodBeingCalled is MethodDefinition mDef) { - if (mDef.Name.StartsWith("get_")) + if (mDef.Name.StartsWith("get_") && mDef.Parameters.Count <= 0) { var unmanaged = mDef.AsUnmanaged(); var prop = unmanaged.DeclaringType!.Properties!.FirstOrDefault(p => p.Getter == unmanaged); if (prop != null) return ret.Append('.').Append(prop.Name).ToString(); - } - else if (mDef.Name.StartsWith("set_") && Arguments?.Count > 0) + } else if (mDef.Name.StartsWith("set_") && Arguments?.Count > 0) { var unmanaged = mDef.AsUnmanaged(); var prop = unmanaged.DeclaringType!.Properties!.FirstOrDefault(p => p.Setter == unmanaged); @@ -73,7 +65,7 @@ protected void CacheMethodInfoArg(MethodAnalysis context) if (ManagedMethodBeingCalled is GenericInstanceMethod gim) ret.Append('<').Append(string.Join(", ", gim.GenericArguments)).Append('>'); - + ret.Append('('); if (Arguments != null && Arguments.Count > 0) @@ -103,6 +95,9 @@ public override Mono.Cecil.Cil.Instruction[] ToILInstructions(MethodAnalysis if (InstanceBeingCalledOn == null && !ManagedMethodBeingCalled.Resolve().IsStatic) throw new TaintedInstructionException("Method is non-static but don't have an instance"); + + if (InstanceBeingCalledOn != null && !ManagedMethodBeingCalled.Resolve().IsStatic && !ManagedMethodBeingCalled.DeclaringType.Resolve().IsAssignableFrom(InstanceBeingCalledOn.Type?.Resolve())) + throw new TaintedInstructionException($"Mismatched instance parameter. Expecting an instance of {ManagedMethodBeingCalled.DeclaringType.FullName}, actually {InstanceBeingCalledOn}"); if (ManagedMethodBeingCalled.Name == ".ctor") { @@ -110,32 +105,28 @@ public override Mono.Cecil.Cil.Instruction[] ToILInstructions(MethodAnalysis if (!(IsCallToSuperclassMethod && InstanceBeingCalledOn?.Name == "this")) return Array.Empty(); //Ignore ctors that aren't super calls, because we're allocating a new instance. } - - if (InstanceBeingCalledOn != null && !ManagedMethodBeingCalled.Resolve().IsStatic && !ManagedMethodBeingCalled.DeclaringType.Resolve().IsAssignableFrom(InstanceBeingCalledOn.Type?.Resolve())) - throw new TaintedInstructionException($"Mismatched instance parameter. Expecting an instance of {ManagedMethodBeingCalled.DeclaringType.FullName}, actually {InstanceBeingCalledOn}"); - + var result = GetILToLoadParams(context, processor); var toCall = ManagedMethodBeingCalled; + + if(context.GetMethodDefinition() is {} contextMethod) + GenericMethodUtils.PrepareGenericMethodForEmissionToBody(toCall, toCall.DeclaringType, contextMethod.Module); + + if (ManagedMethodBeingCalled.HasGenericParameters && !ManagedMethodBeingCalled.IsGenericInstance) + toCall = ManagedMethodBeingCalled.Resolve(); + if (ManagedMethodBeingCalled.DeclaringType is GenericInstanceType git && git.HasAnyGenericParams()) + toCall = ManagedMethodBeingCalled.Resolve(); + if (ManagedMethodBeingCalled is GenericInstanceMethod gim && gim.GenericArguments.Any(g => g is GenericParameter || g is GenericInstanceType git2 && git2.HasAnyGenericParams())) + toCall = ManagedMethodBeingCalled.Resolve(); + + if(toCall is GenericInstanceMethod gim2) + toCall = processor.ImportRecursive(gim2); - if (context.GetMethodDefinition() is { } contextMethod) - //TODO Finish this method then re-enable - toCall = contextMethod.Module.ImportMethodButCleanly(toCall); - - // if (ManagedMethodBeingCalled.HasGenericParameters && !ManagedMethodBeingCalled.IsGenericInstance) - // toCall = ManagedMethodBeingCalled.Resolve(); - // if (ManagedMethodBeingCalled.DeclaringType is GenericInstanceType git && git.HasAnyGenericParams()) - // toCall = ManagedMethodBeingCalled.Resolve(); - // if (ManagedMethodBeingCalled is GenericInstanceMethod gim && gim.GenericArguments.Any(g => g is GenericParameter || g is GenericInstanceType git2 && git2.HasAnyGenericParams())) - // toCall = ManagedMethodBeingCalled.Resolve(); - // - // if (toCall is GenericInstanceMethod gim2) - // toCall = processor.ImportRecursive(gim2); - // - // if (toCall.DeclaringType is GenericInstanceType git2) - // toCall.DeclaringType = processor.ImportRecursive(git2); - // - // toCall = processor.ImportParameterTypes(toCall); + if (toCall.DeclaringType is GenericInstanceType git2) + toCall.DeclaringType = processor.ImportRecursive(git2); + + toCall = processor.ImportParameterTypes(toCall); result.Add(processor.Create(ShouldUseCallvirt ? OpCodes.Callvirt : OpCodes.Call, processor.ImportReference(toCall))); @@ -203,8 +194,8 @@ private TypeReference ResolveGenericReturnTypeIfNeeded(TypeReference returnType, { returnType = GenericInstanceUtils.ResolveGenericParameterType(gp, gmr.Type, gmr.Method) ?? returnType; StaticMethodGenericTypeOverride = gmr.Type; - - if (ManagedMethodBeingCalled.Resolve() == gmr.Method.Resolve()) + + if(ManagedMethodBeingCalled.Resolve() == gmr.Method.Resolve()) ManagedMethodBeingCalled = gmr.Method; } else @@ -249,7 +240,7 @@ protected void RegisterLocals(MethodAnalysis context) { if (operand == null) throw new TaintedInstructionException($"Found null operand in Arguments: {Arguments.ToStringEnumerable()}"); - + if (operand is LocalDefinition l) result.Add(context.GetIlToLoad(l, processor)); else if (operand is ConstantDefinition c) diff --git a/Cpp2IL.Core/Analysis/Actions/Base/AbstractFieldReadAction.cs b/Cpp2IL.Core/Analysis/Actions/Base/AbstractFieldReadAction.cs index 593b860e..abea4792 100644 --- a/Cpp2IL.Core/Analysis/Actions/Base/AbstractFieldReadAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/Base/AbstractFieldReadAction.cs @@ -1,10 +1,7 @@ using System.Collections.Generic; -using System.Linq; using Cpp2IL.Core.Analysis.ResultModels; using Cpp2IL.Core.Utils; -using Mono.Cecil; using Mono.Cecil.Cil; -using Mono.Cecil.Rocks; namespace Cpp2IL.Core.Analysis.Actions.Base { @@ -13,36 +10,10 @@ public abstract class AbstractFieldReadAction : BaseAction public FieldUtils.FieldBeingAccessedData? FieldRead; public LocalDefinition? LocalWritten; protected LocalDefinition? ReadFrom; - protected TypeReference? ReadFromType; - + protected AbstractFieldReadAction(MethodAnalysis context, T instruction) : base(context, instruction) { } - - protected void FixUpFieldRefForAnyPotentialGenericType(MethodAnalysis context) - { - if(context.GetMethodDefinition() is not {} contextMethod) - return; - - if(FieldRead == null) - return; - - if (ReadFromType is null or TypeDefinition {HasGenericParameters: false}) - return; - - if (ReadFromType is TypeDefinition) - ReadFromType = ReadFromType.MakeGenericInstanceType(ReadFromType.GenericParameters.Cast().ToArray()); - - if (FieldRead.ImpliedFieldLoad is { } impliedLoad) - { - var fieldRef = new FieldReference(impliedLoad.Name, impliedLoad.FieldType, ReadFromType); - FieldRead.ImpliedFieldLoad = contextMethod.Module.ImportFieldButCleanly(fieldRef); - } else if (FieldRead.FinalLoadInChain is { } finalLoad) - { - var fieldRef = new FieldReference(finalLoad.Name, finalLoad.FieldType, ReadFromType); - FieldRead.FinalLoadInChain = contextMethod.Module.ImportFieldButCleanly(fieldRef); - } - } public override string ToPsuedoCode() { diff --git a/Cpp2IL.Core/Analysis/Actions/Base/AbstractFieldWriteAction.cs b/Cpp2IL.Core/Analysis/Actions/Base/AbstractFieldWriteAction.cs index 7e03a804..92a2222d 100644 --- a/Cpp2IL.Core/Analysis/Actions/Base/AbstractFieldWriteAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/Base/AbstractFieldWriteAction.cs @@ -1,10 +1,7 @@ using System.Collections.Generic; -using System.Linq; using Cpp2IL.Core.Analysis.ResultModels; using Cpp2IL.Core.Utils; -using Mono.Cecil; using Mono.Cecil.Cil; -using Mono.Cecil.Rocks; namespace Cpp2IL.Core.Analysis.Actions.Base { @@ -20,34 +17,6 @@ protected AbstractFieldWriteAction(MethodAnalysis context, T instruction) : b protected abstract string? GetValuePseudocode(); protected abstract Instruction[] GetIlToLoadValue(MethodAnalysis context, ILProcessor processor); - protected void FixUpFieldRefForAnyPotentialGenericType(MethodAnalysis context) - { - if(context.GetMethodDefinition() is not {} contextMethod) - return; - - if(FieldWritten == null) - return; - - if(InstanceBeingSetOn?.Type is not {} writtenOnType) - return; - - if (writtenOnType is null or TypeDefinition {HasGenericParameters: false}) - return; - - if (writtenOnType is TypeDefinition) - writtenOnType = writtenOnType.MakeGenericInstanceType(writtenOnType.GenericParameters.Cast().ToArray()); - - if (FieldWritten.ImpliedFieldLoad is { } impliedLoad) - { - var fieldRef = new FieldReference(impliedLoad.Name, impliedLoad.FieldType, writtenOnType); - FieldWritten.ImpliedFieldLoad = contextMethod.Module.ImportFieldButCleanly(fieldRef); - } else if (FieldWritten.FinalLoadInChain is { } finalLoad) - { - var fieldRef = new FieldReference(finalLoad.Name, finalLoad.FieldType, writtenOnType); - FieldWritten.FinalLoadInChain = contextMethod.Module.ImportFieldButCleanly(fieldRef); - } - } - public override Instruction[] ToILInstructions(MethodAnalysis context, ILProcessor processor) { if (InstanceBeingSetOn == null || FieldWritten == null) diff --git a/Cpp2IL.Core/Analysis/Actions/Base/AbstractNewObjAction.cs b/Cpp2IL.Core/Analysis/Actions/Base/AbstractNewObjAction.cs index 598ad38a..ef5823e6 100644 --- a/Cpp2IL.Core/Analysis/Actions/Base/AbstractNewObjAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/Base/AbstractNewObjAction.cs @@ -37,28 +37,26 @@ public override Instruction[] ToILInstructions(MethodAnalysis context, ILProc var ctorToCall = managedConstructorCall.ManagedMethodBeingCalled!; - // if (ctorToCall.DeclaringType.ToString() != TypeCreated.ToString()) - // ctorToCall = TypeCreated?.Resolve()?.Methods.FirstOrDefault(m => m.Name == ".ctor" && m.Parameters.Count == ctorToCall.Parameters.Count) ?? throw new TaintedInstructionException($"Could not resolve a constructor with {ctorToCall.Parameters.Count} parameters."); - // - // if (ctorToCall.HasGenericParameters && TypeCreated is GenericInstanceType git) - // ctorToCall = ctorToCall.MakeMethodOnGenericType(git.GenericArguments.ToArray()); - // - // if (ctorToCall is GenericInstanceMethod gim2 && gim2.GenericArguments.Any(g => g is GenericParameter { Position: -1 })) - // ctorToCall = ctorToCall.Resolve(); - // if (ctorToCall is { DeclaringType: GenericInstanceType git2 } && git2.GenericArguments.Any(g => g is GenericParameter { Position: -1 })) - // ctorToCall = ctorToCall.Resolve(); - // - // if (ctorToCall is GenericInstanceMethod gim) - // ctorToCall = processor.ImportRecursive(gim); - // else - // ctorToCall = processor.ImportReference(ctorToCall); - // - // if (ctorToCall.DeclaringType is GenericInstanceType git3) - // ctorToCall.DeclaringType = processor.ImportRecursive(git3); - // - // ctorToCall = processor.ImportParameterTypes(ctorToCall); - if(context.GetMethodDefinition() is {} contextMethod) - ctorToCall = contextMethod.Module.ImportMethodButCleanly(ctorToCall); + if (ctorToCall.DeclaringType.ToString() != TypeCreated.ToString()) + ctorToCall = TypeCreated?.Resolve()?.Methods.FirstOrDefault(m => m.Name == ".ctor" && m.Parameters.Count == ctorToCall.Parameters.Count) ?? throw new TaintedInstructionException($"Could not resolve a constructor with {ctorToCall.Parameters.Count} parameters."); + + if (ctorToCall.HasGenericParameters && TypeCreated is GenericInstanceType git) + ctorToCall = ctorToCall.MakeMethodOnGenericType(git.GenericArguments.ToArray()); + + if (ctorToCall is GenericInstanceMethod gim2 && gim2.GenericArguments.Any(g => g is GenericParameter { Position: -1 })) + ctorToCall = ctorToCall.Resolve(); + if (ctorToCall is { DeclaringType: GenericInstanceType git2 } && git2.GenericArguments.Any(g => g is GenericParameter { Position: -1 })) + ctorToCall = ctorToCall.Resolve(); + + if (ctorToCall is GenericInstanceMethod gim) + ctorToCall = processor.ImportRecursive(gim); + else + ctorToCall = processor.ImportReference(ctorToCall); + + if (ctorToCall.DeclaringType is GenericInstanceType git3) + ctorToCall.DeclaringType = processor.ImportRecursive(git3); + + ctorToCall = processor.ImportParameterTypes(ctorToCall); result.Add(processor.Create(OpCodes.Newobj, ctorToCall)); diff --git a/Cpp2IL.Core/Analysis/Actions/Base/AbstractReturnAction.cs b/Cpp2IL.Core/Analysis/Actions/Base/AbstractReturnAction.cs index 12be18ab..c9de7008 100644 --- a/Cpp2IL.Core/Analysis/Actions/Base/AbstractReturnAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/Base/AbstractReturnAction.cs @@ -61,24 +61,27 @@ public override bool IsImportant() protected void TryCorrectConstant(MethodAnalysis context) { - if (_isVoid || returnValue is not ConstantDefinition constantDefinition || !typeof(IConvertible).IsAssignableFrom(constantDefinition.Type) || constantDefinition.Type == typeof(string)) - return; - - if (context.ReturnType.Resolve() is {IsEnum: true} returnTypeDefinition) - { - var underLyingType = typeof(int).Module.GetType(returnTypeDefinition.GetEnumUnderlyingType().FullName); - constantDefinition.Type = underLyingType; - constantDefinition.Value = MiscUtils.ReinterpretBytes((IConvertible) constantDefinition.Value, underLyingType); - } - else if (!string.IsNullOrEmpty(context.ReturnType?.FullName)) + if (!_isVoid && returnValue is ConstantDefinition constantDefinition && typeof(IConvertible).IsAssignableFrom(constantDefinition.Type) && constantDefinition.Type != typeof(string)) { - var returnValueType = typeof(int).Module.GetType(context.ReturnType!.FullName); - if (string.IsNullOrEmpty(returnValueType?.FullName) || returnValueType!.IsArray) - return; - if (!TypeDefinitions.IConvertible.IsAssignableFrom(context.ReturnType) || !context.ReturnType.IsPrimitive || context.ReturnType.Name == "String") - return; - constantDefinition.Value = MiscUtils.ReinterpretBytes((IConvertible) constantDefinition.Value, context.ReturnType); - constantDefinition.Type = returnValueType; + var returnTypeDefinition = context.ReturnType.Resolve(); + if (returnTypeDefinition.IsEnum) + { + var underLyingType = typeof(int).Module.GetType(returnTypeDefinition.GetEnumUnderlyingType().FullName); + constantDefinition.Type = underLyingType; + constantDefinition.Value = MiscUtils.ReinterpretBytes((IConvertible) constantDefinition.Value, underLyingType); + } + else if (!string.IsNullOrEmpty(context.ReturnType?.FullName)) + { + var returnValueType = typeof(int).Module.GetType(context.ReturnType!.FullName); + if (!string.IsNullOrEmpty(returnValueType?.FullName) && !returnValueType!.IsArray) + { + if (TypeDefinitions.IConvertible.IsAssignableFrom(context.ReturnType) && context.ReturnType.IsPrimitive && context.ReturnType.Name != "String") + { + constantDefinition.Value = MiscUtils.ReinterpretBytes((IConvertible) constantDefinition.Value, context.ReturnType); + constantDefinition.Type = returnValueType; + } + } + } } } } diff --git a/Cpp2IL.Core/Analysis/Actions/x86/GlobalMethodRefToConstantAction.cs b/Cpp2IL.Core/Analysis/Actions/x86/GlobalMethodRefToConstantAction.cs index f761e84a..d6d0229c 100644 --- a/Cpp2IL.Core/Analysis/Actions/x86/GlobalMethodRefToConstantAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/x86/GlobalMethodRefToConstantAction.cs @@ -54,7 +54,9 @@ public GlobalMethodRefToConstantAction(MethodAnalysis context, Inst if (_genericMethodParams.Count > 0) { - _method = _method.MakeGenericInstanceMethod(_genericMethodParams.ToArray()); + var gMethod = new GenericInstanceMethod(_method); + _genericMethodParams.ForEach(gMethod.GenericArguments.Add); + _method = gMethod; } if (instruction.Mnemonic != Mnemonic.Push) diff --git a/Cpp2IL.Core/Analysis/Actions/x86/Important/CallManagedFunctionAction.cs b/Cpp2IL.Core/Analysis/Actions/x86/Important/CallManagedFunctionAction.cs index aa7ba57b..06163b57 100644 --- a/Cpp2IL.Core/Analysis/Actions/x86/Important/CallManagedFunctionAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/x86/Important/CallManagedFunctionAction.cs @@ -48,20 +48,11 @@ public CallManagedFunctionAction(MethodAnalysis context, Instructio { MethodReference locatedMethod; if (matchingConstant.Value is MethodReference value) - { locatedMethod = value; - AddComment("Method resolved from concrete implementations at this address, with the help of a nongeneric constant value to identify which concrete implementation."); - } - else - { - var locatedGmr = (GenericMethodReference) matchingConstant.Value; - locatedMethod = locatedGmr.Method; + else + locatedMethod = ((GenericMethodReference) matchingConstant.Value).Method; - if (locatedGmr.Type is GenericInstanceType locatedGit) - locatedMethod = locatedMethod.MakeMethodOnGenericType(locatedGit.GenericArguments.ToArray()); - - AddComment("Method resolved from concrete implementations at this address, with the help of a generic constant value to identify which concrete implementation."); - } + AddComment("Method resolved from concrete implementations at this address, with the help of a constant value to identify which concrete implementation."); if (locatedMethod.HasThis && LibCpp2IlMain.Binary!.is32Bit && context.Stack.TryPeek(out var op) && op is LocalDefinition local) { @@ -227,7 +218,10 @@ public CallManagedFunctionAction(MethodAnalysis context, Instructio if (possibleTarget != null) ManagedMethodBeingCalled = SharedState.UnmanagedToManagedMethods[possibleTarget]; else - AddComment($"Failed to resolve any matching method (there are {listOfCallableMethods.Count} at this address)"); + { + var methodSigs = listOfCallableMethods.Select(m => m.DeclaringType?.FullName + "::" + m.Name).Take(10).ToList(); + AddComment($"Failed to resolve any matching method (there are {listOfCallableMethods.Count} at this address - {string.Join(", ", methodSigs)})."); + } if (ManagedMethodBeingCalled != null && MethodUtils.GetMethodInfoArg(ManagedMethodBeingCalled, context) is ConstantDefinition {Value: GenericMethodReference gmr} arg) { @@ -237,7 +231,6 @@ public CallManagedFunctionAction(MethodAnalysis context, Instructio ManagedMethodBeingCalled = gmr.Method.MakeMethodOnGenericType(git.GenericArguments.ToArray()); } - CacheMethodInfoArg(context); HandleReturnType(context); } diff --git a/Cpp2IL.Core/Analysis/Actions/x86/Important/CallManagedFunctionInRegAction.cs b/Cpp2IL.Core/Analysis/Actions/x86/Important/CallManagedFunctionInRegAction.cs index b32fb338..caf38442 100644 --- a/Cpp2IL.Core/Analysis/Actions/x86/Important/CallManagedFunctionInRegAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/x86/Important/CallManagedFunctionInRegAction.cs @@ -45,7 +45,6 @@ public CallManagedFunctionInRegAction(MethodAnalysis context, Instr } CreateLocalForReturnType(context); - CacheMethodInfoArg(context); RegisterLocals(context); } diff --git a/Cpp2IL.Core/Analysis/Actions/x86/Important/CallMethodSpecAction.cs b/Cpp2IL.Core/Analysis/Actions/x86/Important/CallMethodSpecAction.cs index 77f0cb67..f58a5ad6 100644 --- a/Cpp2IL.Core/Analysis/Actions/x86/Important/CallMethodSpecAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/x86/Important/CallMethodSpecAction.cs @@ -30,10 +30,9 @@ public CallMethodSpecAction(MethodAnalysis context, Instruction ins ShouldUseCallvirt = true; if (methodSpec.classIndexIndex != -1) - ManagedMethodBeingCalled = ManagedMethodBeingCalled.MakeMethodOnGenericType(methodSpec.GenericClassParams.Select(p => MiscUtils.TryResolveTypeReflectionData(p, ManagedMethodBeingCalled, context.GetMethodDefinition())).ToArray()!); + ManagedMethodBeingCalled = ManagedMethodBeingCalled.MakeMethodOnGenericType(methodSpec.GenericClassParams.Select(p => MiscUtils.TryResolveTypeReflectionData(p, ManagedMethodBeingCalled)).ToArray()!); CreateLocalForReturnType(context); - CacheMethodInfoArg(context); RegisterLocals(context); } } diff --git a/Cpp2IL.Core/Analysis/Actions/x86/Important/CallVirtualMethodAction.cs b/Cpp2IL.Core/Analysis/Actions/x86/Important/CallVirtualMethodAction.cs index bd231cb3..c0c54448 100644 --- a/Cpp2IL.Core/Analysis/Actions/x86/Important/CallVirtualMethodAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/x86/Important/CallVirtualMethodAction.cs @@ -1,6 +1,5 @@ using Cpp2IL.Core.Analysis.ResultModels; using Cpp2IL.Core.Utils; -using Mono.Cecil; using Instruction = Iced.Intel.Instruction; namespace Cpp2IL.Core.Analysis.Actions.x86.Important @@ -23,17 +22,11 @@ public CallVirtualMethodAction(MethodAnalysis context, Instruction if (ManagedMethodBeingCalled == null) return; InstanceBeingCalledOn = ManagedMethodBeingCalled.HasThis ? context.GetLocalInReg("rcx") : null; - - if (ManagedMethodBeingCalled is MethodDefinition && InstanceBeingCalledOn?.Type is GenericInstanceType git) - { - ManagedMethodBeingCalled = ManagedMethodBeingCalled.MakeMethodOnGenericType(git.GenericArguments.ToArray()); - } if(!MethodUtils.CheckParameters(instruction, ManagedMethodBeingCalled, context, ManagedMethodBeingCalled.HasThis, out Arguments, InstanceBeingCalledOn?.Type, false)) AddComment("Arguments are incorrect?"); CreateLocalForReturnType(context); - CacheMethodInfoArg(context); RegisterLocals(context); } } diff --git a/Cpp2IL.Core/Analysis/Actions/x86/Important/FieldToLocalAction.cs b/Cpp2IL.Core/Analysis/Actions/x86/Important/FieldToLocalAction.cs index b38d5c9b..d93c1c2e 100644 --- a/Cpp2IL.Core/Analysis/Actions/x86/Important/FieldToLocalAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/x86/Important/FieldToLocalAction.cs @@ -14,20 +14,21 @@ public FieldToLocalAction(MethodAnalysis context, Instruction instr { var sourceRegName = X86Utils.GetRegisterNameNew(instruction.MemoryBase); _destRegName = X86Utils.GetRegisterNameNew(instruction.Op0Register); - var sourceFieldOffset = instruction.MemoryDisplacement32; + var sourceFieldOffset = instruction.MemoryDisplacement; var readFrom = context.GetOperandInRegister(sourceRegName); + TypeReference readFromType; if (readFrom is ConstantDefinition {Value: NewSafeCastResult result}) { - ReadFromType = result.castTo; + readFromType = result.castTo; ReadFrom = result.original; RegisterUsedLocal(ReadFrom, context); } else if(readFrom is LocalDefinition {IsMethodInfoParam: false} l && l.Type?.Resolve() != null) { ReadFrom = l; - ReadFromType = ReadFrom!.Type!; + readFromType = ReadFrom!.Type!; RegisterUsedLocal(ReadFrom, context); } else { @@ -35,12 +36,11 @@ public FieldToLocalAction(MethodAnalysis context, Instruction instr return; } - FieldRead = FieldUtils.GetFieldBeingAccessed(ReadFromType, sourceFieldOffset, false); + FieldRead = FieldUtils.GetFieldBeingAccessed(readFromType, sourceFieldOffset, false); if(FieldRead == null) return; LocalWritten = context.MakeLocal(FieldRead.GetFinalType(), reg: _destRegName); - FixUpFieldRefForAnyPotentialGenericType(context); RegisterDefinedLocalWithoutSideEffects(LocalWritten); } } diff --git a/Cpp2IL.Core/Analysis/Actions/x86/Important/RegToFieldAction.cs b/Cpp2IL.Core/Analysis/Actions/x86/Important/RegToFieldAction.cs index 3b729579..281a59df 100644 --- a/Cpp2IL.Core/Analysis/Actions/x86/Important/RegToFieldAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/x86/Important/RegToFieldAction.cs @@ -43,8 +43,8 @@ public RegToFieldAction(MethodAnalysis context, Instruction instruc } RegisterUsedLocal(InstanceBeingSetOn, context); + FieldWritten = FieldUtils.GetFieldBeingAccessed(InstanceBeingSetOn.Type, destFieldOffset, false); - FixUpFieldRefForAnyPotentialGenericType(context); } internal RegToFieldAction(MethodAnalysis context, Instruction instruction, FieldUtils.FieldBeingAccessedData fieldWritten, LocalDefinition instanceWrittenOn, LocalDefinition readFrom) : base(context, instruction) diff --git a/Cpp2IL.Core/Analysis/AsmAnalyzerArmV8A.InstructionChecks.cs b/Cpp2IL.Core/Analysis/AsmAnalyzerArmV8A.InstructionChecks.cs index 33065b79..480af6d2 100644 --- a/Cpp2IL.Core/Analysis/AsmAnalyzerArmV8A.InstructionChecks.cs +++ b/Cpp2IL.Core/Analysis/AsmAnalyzerArmV8A.InstructionChecks.cs @@ -68,6 +68,10 @@ private void CheckForSingleOpInstruction(Arm64Instruction instruction) { Analysis.Actions.Add(new Arm64ManagedFunctionCallAction(Analysis, instruction)); } + else if (jumpTarget == _keyFunctionAddresses.il2cpp_array_new_specific || jumpTarget == _keyFunctionAddresses.il2cpp_vm_array_new_specific || jumpTarget == _keyFunctionAddresses.SzArrayNew) + { + Analysis.Actions.Add(new Arm64AllocateArrayAction(Analysis, instruction)); + } else if (jumpTarget == _keyFunctionAddresses.il2cpp_object_new || jumpTarget == _keyFunctionAddresses.il2cpp_vm_object_new || jumpTarget == _keyFunctionAddresses.il2cpp_codegen_object_new) { Analysis.Actions.Add(new Arm64NewObjectAction(Analysis, instruction)); diff --git a/Cpp2IL.Core/Analysis/AsmAnalyzerBase.cs b/Cpp2IL.Core/Analysis/AsmAnalyzerBase.cs index b7de1f7f..9d927797 100644 --- a/Cpp2IL.Core/Analysis/AsmAnalyzerBase.cs +++ b/Cpp2IL.Core/Analysis/AsmAnalyzerBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using Cpp2IL.Core.Analysis.ResultModels; @@ -123,14 +124,22 @@ public StringBuilder BuildILToString() try { - // if (varType is GenericInstanceType git2 && git2.HasAnyGenericParams()) - // varType = git2.Resolve(); - // if (varType is GenericInstanceType git) - // varType = processor.ImportRecursive(git, MethodDefinition); - if (varType is ArrayType arr && MiscUtils.GetUltimateElementType(arr).IsGenericParameter) - throw new InvalidOperationException(); - - localDefinition.Variable = new VariableDefinition(processor.Body.Method.Module.ImportTypeButCleanly(varType)); + if (varType is GenericInstanceType git2 && git2.HasAnyGenericParams()) + varType = git2.Resolve(); + if (varType is GenericInstanceType git) + varType = processor.ImportRecursive(git, MethodDefinition); + if (varType is ArrayType arr) + { + if(MiscUtils.GetUltimateElementType(arr).IsGenericParameter) + throw new InvalidOperationException(); + if(arr.ElementType is GenericInstanceType arrGit && arrGit.HasAnyGenericParams()) + varType = arrGit.Resolve().MakeArrayType(); + } + + if(varType is ArrayType { ElementType: GenericInstanceType git3}) + varType = processor.ImportRecursive(git3, MethodDefinition).MakeArrayType(); + + localDefinition.Variable = new VariableDefinition(processor.ImportReference(varType, MethodDefinition)); body.Variables.Add(localDefinition.Variable); } catch (InvalidOperationException) diff --git a/Cpp2IL.Core/Analysis/ResultModels/AnalysedMethodReference.cs b/Cpp2IL.Core/Analysis/ResultModels/AnalysedMethodReference.cs deleted file mode 100644 index ff530cc3..00000000 --- a/Cpp2IL.Core/Analysis/ResultModels/AnalysedMethodReference.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using Mono.Cecil; - -namespace Cpp2IL.Core.Analysis.ResultModels -{ - /// - /// Class to replace Cecil's MethodReference for actual analyzed actions, because it has so many shortcomings - /// - public class AnalysedMethodReference - { - public TypeReference ReturnType; - public TypeReference SpecificDeclaringType; - public MethodDefinition ActualMethodBeingCalled; - public List MethodGenericArguments = new(); - public List Parameters; - - public class AnalyzedMethodParameter - { - public TypeReference Type; - public string Name; - - public AnalyzedMethodParameter(TypeReference type, string name) - { - Type = type; - Name = name; - } - } - } -} \ No newline at end of file diff --git a/Cpp2IL.Core/Analysis/ResultModels/ConstantDefinition.cs b/Cpp2IL.Core/Analysis/ResultModels/ConstantDefinition.cs index 3c8f4e29..e94e9feb 100644 --- a/Cpp2IL.Core/Analysis/ResultModels/ConstantDefinition.cs +++ b/Cpp2IL.Core/Analysis/ResultModels/ConstantDefinition.cs @@ -106,20 +106,20 @@ public Instruction[] GetILToLoad(MethodAnalysis context, I }; if(Type == typeof(MethodReference) && Value is GenericInstanceMethod gim) - return new[] {ilProcessor.Create(OpCodes.Ldftn, ilProcessor.ImportMethodButCleanly(gim))}; + return new[] {ilProcessor.Create(OpCodes.Ldftn, ilProcessor.ImportRecursive(gim))}; if (Type == typeof(MethodReference) && Value is MethodReference reference) - return new[] {ilProcessor.Create(OpCodes.Ldftn, ilProcessor.ImportMethodButCleanly(reference))}; + return new[] {ilProcessor.Create(OpCodes.Ldftn, ilProcessor.ImportReference(reference))}; if(Type == typeof(TypeReference) && Value is GenericInstanceType git) - return new[] {ilProcessor.Create(OpCodes.Ldtoken, ilProcessor.ImportTypeButCleanly(git))}; + return new[] {ilProcessor.Create(OpCodes.Ldtoken, ilProcessor.ImportRecursive(git))}; if (Type == typeof(TypeReference) && Value is TypeReference typeReference) { if (typeReference is TypeSpecification {ElementType: GenericInstanceType} or GenericInstanceType or GenericParameter) throw new TaintedInstructionException("ConstantDefinition: TypeReference loading not supported for generic types because it's a mess."); - return new[] {ilProcessor.Create(OpCodes.Ldtoken, ilProcessor.ImportTypeButCleanly(typeReference))}; + return new[] {ilProcessor.Create(OpCodes.Ldtoken, ilProcessor.ImportReference(typeReference))}; } if (Type == typeof(FieldDefinition) && Value is FieldDefinition fieldDefinition) diff --git a/Cpp2IL.Core/Analysis/ResultModels/MethodAnalysis.cs b/Cpp2IL.Core/Analysis/ResultModels/MethodAnalysis.cs index 8d58f480..4eb281dc 100644 --- a/Cpp2IL.Core/Analysis/ResultModels/MethodAnalysis.cs +++ b/Cpp2IL.Core/Analysis/ResultModels/MethodAnalysis.cs @@ -11,7 +11,6 @@ using LibCpp2IL; using Mono.Cecil; using Mono.Cecil.Cil; -using Mono.Cecil.Rocks; using Instruction = Mono.Cecil.Cil.Instruction; namespace Cpp2IL.Core.Analysis.ResultModels @@ -101,10 +100,6 @@ internal MethodAnalysis(MethodDefinition method, ulong methodStart, ulong initia _allInstructions = new(allInstructions); EmptyRegConstant = MakeConstant(typeof(int), 0, "0"); - var thisParamType = (TypeReference) method.DeclaringType; - if (thisParamType.HasGenericParameters) - thisParamType = thisParamType.MakeGenericInstanceType(thisParamType.GenericParameters.Cast().ToArray()); - var args = method.Parameters.ToList(); var haveHandledMethodInfoArg = false; //Set up parameters in registers & as locals. @@ -126,7 +121,11 @@ internal MethodAnalysis(MethodDefinition method, ulong methodStart, ulong initia if (!method.IsStatic) { method.Body.ThisParameter.Name = "this"; - FunctionArgumentLocals.Add(MakeLocal(thisParamType, "this", _parameterDestRegList.RemoveAndReturn(0)).WithParameter(method.Body.ThisParameter)); + FunctionArgumentLocals.Add(MakeLocal(method.DeclaringType, "this", _parameterDestRegList.RemoveAndReturn(0)).WithParameter(method.Body.ThisParameter)); + } + else if (LibCpp2IlMain.MetadataVersion <= 24f) + { + FunctionArgumentLocals.Add(MakeLocal(TypeDefinitions.Object, "dummythis", _parameterDestRegList.RemoveAndReturn(0))); } while (args.Count > 0) @@ -145,7 +144,7 @@ internal MethodAnalysis(MethodDefinition method, ulong methodStart, ulong initia { //x86_32 and wasm are stack based method.Body.ThisParameter.Name = "this"; - FunctionArgumentLocals.Add(MakeLocal(thisParamType, "this").WithParameter(method.Body.ThisParameter)); + FunctionArgumentLocals.Add(MakeLocal(method.DeclaringType, "this").WithParameter(method.Body.ThisParameter)); Stack.Push(FunctionArgumentLocals.First()); } @@ -732,4 +731,4 @@ public void RegisterInstructionTargetToSwapOut(Instruction jumpInstruction, ulon JumpTargetsToFixByAction[target].Add(jumpInstruction); } } -} \ No newline at end of file +} diff --git a/Cpp2IL.Core/Arm64KeyFunctionAddresses.cs b/Cpp2IL.Core/Arm64KeyFunctionAddresses.cs index 0eed1106..73683e54 100644 --- a/Cpp2IL.Core/Arm64KeyFunctionAddresses.cs +++ b/Cpp2IL.Core/Arm64KeyFunctionAddresses.cs @@ -34,7 +34,16 @@ public Arm64KeyFunctionAddresses() { if (LibCpp2IlMain.Binary is not NsoFile) { - Logger.VerboseNewline($"\tLast attribute generator function is at address 0x{attributeGeneratorList[^1]:X}. Skipping everything before that."); + //The below code is flawed on at least some binaries - e.g. I have an APK for which this trims out the exports. + + var startFrom = attributeGeneratorList[^1]; + + var minExport = LibCpp2IlMain.Binary.GetAllExportedIl2CppFunctionPointers().Min(); + + if(minExport != 0) + startFrom = Math.Min(startFrom, minExport); + + Logger.VerboseNewline($"\tBinary likely contains nothing useful before 0x{startFrom:X} (first attribute generator at 0x{attributeGeneratorList[0]:X}). Skipping everything before that."); //Optimisation: We can skip all bytes up to and including the last attribute restoration function //However we don't know how long the last restoration function is, so just skip up to it, we'd only be saving a further 100 instructions or so @@ -42,10 +51,10 @@ public Arm64KeyFunctionAddresses() //This may not be correct on v29 which uses the Bee compiler, which may do things differently var oldLength = primaryExecutableSection.Length; - var toRemove = (int) (attributeGeneratorList[^1] - primaryExecutableSectionVa); + var toRemove = (int) (startFrom - primaryExecutableSectionVa); primaryExecutableSection = primaryExecutableSection.Skip(toRemove).ToArray(); - primaryExecutableSectionVa = attributeGeneratorList[^1]; + primaryExecutableSectionVa = startFrom; Logger.VerboseNewline($"\tBy trimming out attribute generator functions, reduced decompilation work by {toRemove} of {oldLength} bytes (a {toRemove * 100 / (float) oldLength:f1}% saving)"); @@ -71,7 +80,7 @@ public Arm64KeyFunctionAddresses() $"the first export function is at 0x{firstExport:X} and the last at 0x{lastExport:X}"); //I am assuming, arbitrarily, that the exports are always towards the end of the managed methods, in this case. - var startFrom = Math.Min(firstExport, methodAddresses[^1]); + startFrom = Math.Min(firstExport, methodAddresses[^1]); //Just in case we didn't get the first export, let's subtract a little if (startFrom > 0x100_0000) @@ -91,7 +100,7 @@ public Arm64KeyFunctionAddresses() { Logger.VerboseNewline($"\tDetected that the il2cpp-ified managed methods are in the .text section, but api functions are not available. Attempting to (conservatively) trim out managed methods for KFA scanning - the first managed method is at 0x{methodAddresses[0]:X} and the last at 0x{methodAddresses[^1]:X}"); - var startFrom = methodAddresses[^1]; + startFrom = methodAddresses[^1]; //Just in case the exports are mixed in with the end of the managed methods, let's subtract a little if (startFrom > 0x100_0000) @@ -129,9 +138,9 @@ public Arm64KeyFunctionAddresses() _allInstructions = disassembler.Disassemble(primaryExecutableSection, (long) primaryExecutableSectionVa).ToList(); } - protected override IEnumerable FindAllThunkFunctions(ulong addr, uint maxBytesBack = 0, params ulong[] addressesToIgnore) + protected override IEnumerable FindAllThunkFunctions(ulong addr, bool mustBeJumpNotCall, uint maxBytesBack = 0, params ulong[] addressesToIgnore) { - var allBranchesToAddr = _allInstructions.Where(i => i.Mnemonic is "b" or "bl") + var allBranchesToAddr = _allInstructions.Where(i => mustBeJumpNotCall ? i.Mnemonic is "b" : i.Mnemonic is "b" or "bl") .Where(i => i.Details.Operands[0].IsImmediate() && i.Details.Operands[0].Immediate == (long) addr) .ToList(); @@ -287,7 +296,7 @@ protected override void AttemptInstructionAnalysisToFillGaps() //Looks like we've been inlined and this is just vm::Object::New. addrVmObjectNew = probableResult; - var allThunks = FindAllThunkFunctions((ulong) addrVmObjectNew, 16, (ulong) probableResult).ToList(); + var allThunks = FindAllThunkFunctions((ulong) addrVmObjectNew, false, 16, (ulong) probableResult).ToList(); allThunks.SortByExtractedKey(GetCallerCount); //Sort in ascending order by caller count allThunks.Reverse(); //Reverse to be in descending order diff --git a/Cpp2IL.Core/AssemblyPopulator.cs b/Cpp2IL.Core/AssemblyPopulator.cs index 952f54db..e39ae616 100644 --- a/Cpp2IL.Core/AssemblyPopulator.cs +++ b/Cpp2IL.Core/AssemblyPopulator.cs @@ -33,7 +33,7 @@ public static void ConfigureHierarchy() foreach (var typeDefinition in SharedState.AllTypeDefinitions) { var il2cppTypeDef = SharedState.ManagedToUnmanagedTypes[typeDefinition]; - + //Type generic params. PopulateGenericParamsForType(il2cppTypeDef, typeDefinition); @@ -104,7 +104,7 @@ private static void InjectOurTypes(AssemblyDefinition imageDef, bool suppressAtt MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, imageDef.MainModule.ImportReference(TypeDefinitions.Void) ); - + defaultConstructor.Parameters.Add(new("message", ParameterAttributes.None, stringTypeReference)); var exceptionTypeDef = exceptionTypeReference.Resolve(); @@ -120,7 +120,7 @@ private static void InjectOurTypes(AssemblyDefinition imageDef, bool suppressAtt } analysisFailedExceptionType.Methods.Add(defaultConstructor); - + imageDef.MainModule.Types.Add(analysisFailedExceptionType); } @@ -128,8 +128,8 @@ public static void PopulateStubTypesInAssembly(Il2CppImageDefinition imageDef, b { var firstTypeDefinition = SharedState.TypeDefsByIndex[imageDef.firstTypeIndex]; var currentAssembly = firstTypeDefinition.Module.Assembly; - - InjectOurTypes(currentAssembly, suppressAttributes); + + InjectOurTypes(currentAssembly, suppressAttributes); foreach (var il2CppTypeDefinition in imageDef.Types!) { @@ -181,8 +181,17 @@ private static void FixupExplicitOverridesInType(TypeDefinition ilTypeDefinition var targetParameters = currentlyFixingUp.Parameters.Select(p => p.ParameterType.FullName).ToArray(); MethodReference? baseRef; if (genericParamNames.Length == 0) - baseRef = baseType.Methods.SingleOrDefault(m => - m.Name == baseMethodName && m.Parameters.Count == currentlyFixingUp.Parameters.Count && m.ReturnType.FullName == currentlyFixingUp.ReturnType.FullName && m.Parameters.Select(p => p.ParameterType.FullName).SequenceEqual(targetParameters)); + try + { + baseRef = baseType.Methods.SingleOrDefault(m => + m.Name == baseMethodName && m.Parameters.Count == currentlyFixingUp.Parameters.Count && m.ReturnType.FullName == currentlyFixingUp.ReturnType.FullName && m.Parameters.Select(p => p.ParameterType.FullName).SequenceEqual(targetParameters)); + } + catch (InvalidOperationException) + { + //More than one match - log a warning and skip + Logger.WarnNewline($"\tMore than one potential base method for base type \"{baseType.FullName}\", method name \"{baseMethodName}\", parameter types {targetParameters.ToStringEnumerable()}, while considering explicit override {currentlyFixingUp.FullName}"); + continue; + } else { MethodDefinition nonGenericRef; @@ -200,7 +209,7 @@ private static void FixupExplicitOverridesInType(TypeDefinition ilTypeDefinition TypeReference? ResolveGenericParameter(string name) { var (type, gParams) = MiscUtils.TryLookupTypeDefByName(name); - if (type == null) + if (type == null) return GenericInstanceUtils.ResolveGenericParameterType(new GenericParameter(name, baseType), ilTypeDefinition); if (gParams.Length > 0) @@ -209,10 +218,10 @@ private static void FixupExplicitOverridesInType(TypeDefinition ilTypeDefinition if (parameterRefs.Any(gp => gp == null)) return null; - + return ilTypeDefinition.Module.ImportRecursive(type.MakeGenericInstanceType(parameterRefs)); } - + return type; } @@ -276,10 +285,9 @@ private static void CopyIl2CppDataToManagedType(Il2CppTypeDefinition cppTypeDefi private static void PopulateGenericParamsForType(Il2CppTypeDefinition cppTypeDefinition, TypeDefinition ilTypeDefinition) { - if (cppTypeDefinition.GenericContainer == null) + if (cppTypeDefinition.GenericContainer == null) return; - - var position = 0; + foreach (var param in cppTypeDefinition.GenericContainer.GenericParameters) { if (!SharedState.GenericParamsByIndex.TryGetValue(param.Index, out var p)) @@ -428,7 +436,6 @@ private static void ProcessMethodsInType(Il2CppTypeDefinition cppTypeDefinition, } //Handle generic parameters. - var position = 0; methodDef.GenericContainer?.GenericParameters.ToList() .ForEach(p => { @@ -436,7 +443,7 @@ private static void ProcessMethodsInType(Il2CppTypeDefinition cppTypeDefinition, { if (!methodDefinition.GenericParameters.Contains(gp)) methodDefinition.GenericParameters.Add(gp); - + return; } @@ -450,7 +457,7 @@ private static void ProcessMethodsInType(Il2CppTypeDefinition cppTypeDefinition, .ToList() .ForEach(gp.Constraints.Add); }); - + if (methodDef.slot < ushort.MaxValue) SharedState.VirtualMethodsBySlot[methodDef.slot] = methodDefinition; } diff --git a/Cpp2IL.Core/AttributeRestorer.cs b/Cpp2IL.Core/AttributeRestorer.cs index 3b410f10..e0eee3ab 100644 --- a/Cpp2IL.Core/AttributeRestorer.cs +++ b/Cpp2IL.Core/AttributeRestorer.cs @@ -32,6 +32,8 @@ public static class AttributeRestorer internal static readonly TypeDefinition DummyTypeDefForAttributeList = new TypeDefinition("dummy", "AttributeList", TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit); private static readonly ConcurrentDictionary FieldToParameterMappings = new ConcurrentDictionary(); + private static List _attributeTypeRanges; + private static Dictionary _attributeAttributeCtorsByModule = new(); static AttributeRestorer() => Initialize(); @@ -68,6 +70,8 @@ private static void Initialize() }; SharedState.FieldsByType[DummyTypeDefForAttributeList] = new List(); + + _attributeTypeRanges = LibCpp2IlMain.TheMetadata!.attributeTypeRanges?.ToList() ?? new(); } /// @@ -86,6 +90,10 @@ internal static void Reset() } _simpleFieldToPropertyCache.Clear(); + + _attributeTypeRanges.Clear(); + + _attributeAttributeCtorsByModule.Clear(); Initialize(); } @@ -120,8 +128,18 @@ private static void RestoreAttributesInType(Il2CppImageDefinition imageDef, T { var methodDefinition = methodDef.AsManaged(); - GetCustomAttributesByAttributeIndex(imageDef, methodDef.customAttributeIndex, methodDef.token, typeDefinition.Module, keyFunctionAddresses, methodDefinition.FullName) + GetCustomAttributesByAttributeIndex(imageDef, methodDef.customAttributeIndex, methodDef.token, typeDefinition.Module, keyFunctionAddresses, methodDef.DeclaringType!.FullName + "::" + methodDefinition.FullName) .ForEach(attribute => methodDefinition.CustomAttributes.Add(attribute)); + + var ipd = methodDef.InternalParameterData!; + for (var i = 0; i < methodDef.parameterCount; i++) + { + var paramDef = ipd[i]; + var paramDefinition = methodDefinition.Parameters[i]; + + GetCustomAttributesByAttributeIndex(imageDef, paramDef.customAttributeIndex, paramDef.token, typeDefinition.Module, keyFunctionAddresses, $"Parameter {paramDefinition.Name} of {methodDefinition.FullName}") + .ForEach(attribute => paramDefinition.CustomAttributes.Add(attribute)); + } } //Apply custom attributes to properties @@ -147,7 +165,7 @@ public static List GetCustomAttributesByAttributeIndex(Il2Cp //Get attributes and look for the serialize field attribute. var attributeTypeRange = LibCpp2IlMain.TheMetadata!.GetCustomAttributeData(imageDef, attributeIndex, token); - if (attributeTypeRange == null) + if (attributeTypeRange == null || attributeTypeRange.count == 0) return attributes; //This exists only as a guideline for if we should run analysis or not, and a fallback in case we cannot. @@ -164,7 +182,7 @@ public static List GetCustomAttributesByAttributeIndex(Il2Cp #else var mustRunAnalysis = attributeConstructors.Any(c => c.HasParameters); - + //Grab generator for this context - be it field, type, method, etc. var attributeGeneratorAddress = GetAddressOfAttributeGeneratorFunction(imageDef, attributeTypeRange); @@ -311,7 +329,7 @@ public static List GetCustomAttributesByAttributeIndex(Il2Cp var propertyMappings = GetSimplePropertyFieldMappings(attr, keyFunctionAddresses); foreach (var fieldWriteAction in fieldSets) { - var field = fieldWriteAction.FieldWritten!.GetLast().FinalLoadInChain!.Resolve()!; + var field = fieldWriteAction.FieldWritten!.GetLast().FinalLoadInChain!; if(!propertyMappings.TryGetValue(field, out var prop)) //TODO: This may be an actual field set, not a property set - add support continue; @@ -368,12 +386,15 @@ private static List GenerateAttributesWithoutAnalysis(List t.Namespace == AssemblyPopulator.InjectedNamespaceName && t.Name == "AttributeAttribute"); + if (!_attributeAttributeCtorsByModule.TryGetValue(module, out var attributeCtor)) + { + var attributeType = module.Types.SingleOrDefault(t => t.Namespace == AssemblyPopulator.InjectedNamespaceName && t.Name == "AttributeAttribute"); - if (attributeType == null) - return null; + if (attributeType == null) + return null; - var attributeCtor = attributeType.GetConstructors().First(); + attributeCtor = _attributeAttributeCtorsByModule[module] = attributeType.GetConstructors().First(); + } var ca = new CustomAttribute(attributeCtor); var name = new CustomAttributeNamedArgument("Name", new(module.ImportReference(TypeDefinitions.String), constructor.DeclaringType.Name)); @@ -429,7 +450,7 @@ private static List GetAttributesFromRange(Il2CppCustomAttribute private static ulong GetAddressOfAttributeGeneratorFunction(Il2CppImageDefinition imageDef, Il2CppCustomAttributeTypeRange attributeTypeRange) { - var rangeIndex = Array.IndexOf(LibCpp2IlMain.TheMetadata!.attributeTypeRanges, attributeTypeRange); + var rangeIndex = _attributeTypeRanges.BinarySearch(imageDef.customAttributeStart, (int) imageDef.customAttributeCount, attributeTypeRange, new TokenComparer()); if (rangeIndex < 0) { @@ -581,6 +602,10 @@ private static object AllocateArray(AllocatedArray array) try { var toSet = value == null ? null : AnalysisUtils.CoerceValue(value, typeForArrayToCreateNow); + + //Objects must be wrapped in a CustomAttributeArgument of the pre-casting type. + if(typeForArrayToCreateNow.FullName == "System.Object") + toSet = new CustomAttributeArgument(MiscUtils.TryLookupTypeDefKnownNotGeneric(value!.GetType().FullName), value); arr.SetValue(toSet, index); } @@ -646,7 +671,7 @@ private static object AllocateArray(AllocatedArray array) return null; } - ret = fieldWrites!.Select(f => new FieldToParameterMapping(f.FieldWritten!.FinalLoadInChain!.Resolve(), ((LocalDefinition) f.SourceOperand!).ParameterDefinition!)).ToArray(); + ret = fieldWrites!.Select(f => new FieldToParameterMapping(f.FieldWritten!.FinalLoadInChain!, ((LocalDefinition) f.SourceOperand!).ParameterDefinition!)).ToArray(); FieldToParameterMappings.TryAdd(constructor, ret); return ret; @@ -808,7 +833,7 @@ private static Dictionary GetSimpleProperty var fieldBeingWritten = fieldWriteAction.FieldWritten.GetLast(); - ret[fieldBeingWritten.FinalLoadInChain!.Resolve()] = propertyDefinition; + ret[fieldBeingWritten.FinalLoadInChain!] = propertyDefinition; } _simpleFieldToPropertyCache[type] = ret; diff --git a/Cpp2IL.Core/AttributeRestorerPost29.cs b/Cpp2IL.Core/AttributeRestorerPost29.cs index 9e787662..4f346acf 100644 --- a/Cpp2IL.Core/AttributeRestorerPost29.cs +++ b/Cpp2IL.Core/AttributeRestorerPost29.cs @@ -46,6 +46,16 @@ private static void RestoreAttributesInType(Il2CppImageDefinition imageDef, Type GetCustomAttributesByAttributeIndex(imageDef, typeDefinition.Module, methodDef.token) .ForEach(attribute => methodDefinition.CustomAttributes.Add(attribute)); + + var ipd = methodDef.InternalParameterData!; + for (var i = 0; i < methodDef.parameterCount; i++) + { + var paramDef = ipd[i]; + var paramDefinition = methodDefinition.Parameters[i]; + + GetCustomAttributesByAttributeIndex(imageDef, typeDefinition.Module, paramDef.token) + .ForEach(attribute => paramDefinition.CustomAttributes.Add(attribute)); + } } //Apply custom attributes to properties @@ -117,7 +127,10 @@ private static CustomAttribute ReadAndCreateCustomAttribute(ModuleDefinition mod { bytesRead = 0; var managedConstructor = constructor.AsManaged(); + +#if !DEBUG try +#endif { var ret = new CustomAttribute(module.ImportReference(managedConstructor)); @@ -137,7 +150,7 @@ private static CustomAttribute ReadAndCreateCustomAttribute(ModuleDefinition mod for (var i = 0; i < numCtorArgs; i++) { var ctorParam = managedConstructor.Parameters[i]; - + var val = ReadBlob(pos, out var ctorArgBytesRead, out var typeReference); bytesRead += ctorArgBytesRead; pos += ctorArgBytesRead; @@ -233,11 +246,13 @@ private static CustomAttribute ReadAndCreateCustomAttribute(ModuleDefinition mod return ret; } +#if !DEBUG catch (Exception e) { Logger.WarnNewline($"Failed to parse custom attribute {constructor.DeclaringType!.FullName} due to an exception: {e.GetType()}: {e.Message}"); return MakeFallbackAttribute(module, managedConstructor) ?? throw new("Failed to resolve AttributeAttribute type"); } +#endif } private static object? WrapArrayValuesInCustomAttributeArguments(TypeReference typeReference, Array arr) @@ -257,12 +272,20 @@ private static CustomAttribute ReadAndCreateCustomAttribute(ModuleDefinition mod var objectTypeName = o?.GetType()?.FullName ?? throw new NullReferenceException($"Object {o} in array {arr} of variable type and length {arr.Length} is null, so cannot determine its type"); - var typeDef = MiscUtils.TryLookupTypeDefKnownNotGeneric(objectTypeName); + TypeDefinition typeDef; + if(o is TypeDefinition) + //Special case TypeDefinition => Type + typeDef = TypeDefinitions.Type; + else + typeDef = MiscUtils.TryLookupTypeDefKnownNotGeneric(objectTypeName); if (typeDef == null) throw new Exception($"Couldn't resolve type {objectTypeName}"); + + var typedParam = new CustomAttributeArgument(typeDef, o); - list.Add(new CustomAttributeArgument(typeDef, o)); + //Needs to be wrapped in an additional argument with type System.Object, because cecil + list.Add(new CustomAttributeArgument(TypeDefinitions.Object, typedParam)); } return list.ToArray(); @@ -406,9 +429,17 @@ private static CustomAttribute ReadAndCreateCustomAttribute(ModuleDefinition mod throw new($"Array elements are different but array element type is {arrayElementType}, not IL2CPP_TYPE_OBJECT"); //Create a representation our side of the array + + if(arrTypeDef.Resolve().IsEnum) + arrTypeDef = arrTypeDef.Resolve().GetEnumUnderlyingType(); + var ourRuntimesArrayElementType = typeof(int).Module.GetType(arrTypeDef.FullName!); if (ourRuntimesArrayElementType == typeof(Type)) ourRuntimesArrayElementType = typeof(TypeReference); + + if(ourRuntimesArrayElementType == null) + throw new($"Failed to find type {arrTypeDef.FullName} in corlib module"); + var resultArray = Array.CreateInstance(ourRuntimesArrayElementType, arrLength); //Read the array @@ -519,16 +550,8 @@ private static Il2CppTypeEnum ReadBlobType(long pos, out int bytesRead, out Type Il2CppTypeEnum.IL2CPP_TYPE_STRING => TypeDefinitions.String.AsUnmanaged(), Il2CppTypeEnum.IL2CPP_TYPE_BYREF => TypeDefinitions.TypedReference.AsUnmanaged(), Il2CppTypeEnum.IL2CPP_TYPE_IL2CPP_TYPE_INDEX => TypeDefinitions.Type.AsUnmanaged(), + Il2CppTypeEnum.IL2CPP_TYPE_OBJECT => TypeDefinitions.Object.AsUnmanaged(), _ => throw new ArgumentOutOfRangeException(nameof(typeEnum), typeEnum, null) }; - - public class TokenComparer : IComparer - { - public int Compare(Il2CppCustomAttributeDataRange x, Il2CppCustomAttributeDataRange y) - { - if (ReferenceEquals(x, y)) return 0; - return x.token.CompareTo(y.token); - } - } } } \ No newline at end of file diff --git a/Cpp2IL.Core/BaseKeyFunctionAddresses.cs b/Cpp2IL.Core/BaseKeyFunctionAddresses.cs index bcbc0d41..619d3f42 100644 --- a/Cpp2IL.Core/BaseKeyFunctionAddresses.cs +++ b/Cpp2IL.Core/BaseKeyFunctionAddresses.cs @@ -118,6 +118,12 @@ protected void TryGetInitMetadataFromException() var disasm = X86Utils.GetMethodBodyAtVirtAddressNew(targetMethod.AsUnmanaged().MethodPointer, false); var calls = disasm.Where(i => i.Mnemonic == Mnemonic.Call).ToList(); + + if (calls.Count == 0) + { + Logger.WarnNewline("Couldn't find any call instructions in the method body. This is not expected. Will not have metadata initialization function."); + return; + } if (LibCpp2IlMain.MetadataVersion < 27) { @@ -150,42 +156,42 @@ private void FindThunks() { Logger.Verbose("\t\tLooking for il2cpp_codegen_object_new as a thunk of vm::Object::New..."); - var potentialThunks = FindAllThunkFunctions(il2cpp_vm_object_new, 16); - + var potentialThunks = FindAllThunkFunctions(il2cpp_vm_object_new, false, 16); + //Sort by caller count in ascending order var list = potentialThunks.Select(ptr => (ptr, count: GetCallerCount(ptr))).ToList(); list.SortByExtractedKey(pair => pair.count); //Sort in descending order - most called first list.Reverse(); - + //Take first as the target il2cpp_codegen_object_new = list.FirstOrDefault().ptr; - + Logger.VerboseNewline($"Found at 0x{il2cpp_codegen_object_new:X}"); } - + if (il2cpp_type_get_object != 0) { Logger.Verbose("\t\tMapping il2cpp_resolve_icall to Reflection::GetTypeObject..."); il2cpp_vm_reflection_get_type_object = FindFunctionThisIsAThunkOf(il2cpp_type_get_object); Logger.VerboseNewline($"Found at 0x{il2cpp_vm_reflection_get_type_object:X}"); } - + if (il2cpp_resolve_icall != 0) { Logger.Verbose("\t\tMapping il2cpp_resolve_icall to InternalCalls::Resolve..."); InternalCalls_Resolve = FindFunctionThisIsAThunkOf(il2cpp_resolve_icall); Logger.VerboseNewline($"Found at 0x{InternalCalls_Resolve:X}"); } - + if (il2cpp_string_new != 0) { Logger.Verbose("\t\tMapping il2cpp_string_new to String::New..."); il2cpp_vm_string_new = FindFunctionThisIsAThunkOf(il2cpp_string_new); Logger.VerboseNewline($"Found at 0x{il2cpp_vm_string_new:X}"); } - + if (il2cpp_string_new_wrapper != 0) { Logger.Verbose("\t\tMapping il2cpp_string_new_wrapper to String::NewWrapper..."); @@ -196,7 +202,9 @@ private void FindThunks() if (il2cpp_vm_string_newWrapper != 0) { Logger.Verbose("\t\tMapping String::NewWrapper to il2cpp_codegen_string_new_wrapper..."); - il2cpp_codegen_string_new_wrapper = FindAllThunkFunctions(il2cpp_vm_string_newWrapper, 0, il2cpp_string_new_wrapper).FirstOrDefault(); + il2cpp_codegen_string_new_wrapper = FindAllThunkFunctions(il2cpp_vm_string_newWrapper, false, 0, il2cpp_string_new_wrapper).FirstOrDefault(); + + // var potentialThunks = FindAllThunkFunctions(il2cpp_vm_object_new, false, 0, il2cpp_string_new_wrapper).FirstOrDefault(); Logger.VerboseNewline($"Found at 0x{il2cpp_codegen_string_new_wrapper:X}"); } @@ -224,7 +232,7 @@ private void FindThunks() if (il2cpp_vm_exception_raise != 0) { Logger.Verbose("\t\tMapping il2cpp::vm::Exception::Raise to il2cpp_codegen_raise_exception..."); - il2cpp_codegen_raise_exception = FindAllThunkFunctions(il2cpp_vm_exception_raise, 4, il2cpp_raise_exception).FirstOrDefault(); + il2cpp_codegen_raise_exception = FindAllThunkFunctions(il2cpp_vm_exception_raise, false, 4, il2cpp_raise_exception).FirstOrDefault(); Logger.VerboseNewline($"Found at 0x{il2cpp_codegen_raise_exception:X}"); } @@ -245,7 +253,7 @@ private void FindThunks() if (il2cpp_vm_array_new_specific != 0) { Logger.Verbose("\t\tLooking for SzArrayNew as a thunk function proxying Array::NewSpecific..."); - SzArrayNew = FindAllThunkFunctions(il2cpp_vm_array_new_specific, 4, il2cpp_array_new_specific).FirstOrDefault(); + SzArrayNew = FindAllThunkFunctions(il2cpp_vm_array_new_specific, true, 8, il2cpp_array_new_specific).FirstOrDefault(); Logger.VerboseNewline($"Found at 0x{SzArrayNew:X}"); } } @@ -256,10 +264,11 @@ private void FindThunks() /// Given a function at addr, find a function which serves no purpose other than to call addr. /// /// The address of the function to call. + /// If true, only return thunks using a no-return jump, not a call (e.g b not bl in arm64, jmp not call in x86) /// The maximum number of bytes to go back from any branching instructions to find the actual start of the thunk function. /// A list of function addresses which this function must not return /// The address of the first function in the file which thunks addr, starts within maxBytesBack bytes of the branch, and is not contained within addressesToIgnore, else 0 if none can be found. - protected abstract IEnumerable FindAllThunkFunctions(ulong addr, uint maxBytesBack = 0, params ulong[] addressesToIgnore); + protected abstract IEnumerable FindAllThunkFunctions(ulong addr, bool mustBeJumpNotCall, uint maxBytesBack = 0, params ulong[] addressesToIgnore); /// /// Given a function at thunkPtr, return the address of the function that said function exists only to call. @@ -272,4 +281,4 @@ private void FindThunks() protected abstract int GetCallerCount(ulong toWhere); } -} \ No newline at end of file +} diff --git a/Cpp2IL.Core/CecilCodedIndex.cs b/Cpp2IL.Core/CecilCodedIndex.cs deleted file mode 100644 index f721f162..00000000 --- a/Cpp2IL.Core/CecilCodedIndex.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Cpp2IL.Core -{ - public enum CecilCodedIndex - { - TypeDefOrRef, - HasConstant, - HasCustomAttribute, - HasFieldMarshal, - HasDeclSecurity, - MemberRefParent, - HasSemantics, - MethodDefOrRef, - MemberForwarded, - Implementation, - CustomAttributeType, - ResolutionScope, - TypeOrMethodDef, - HasCustomDebugInformation, - } -} \ No newline at end of file diff --git a/Cpp2IL.Core/CecilEType.cs b/Cpp2IL.Core/CecilEType.cs deleted file mode 100644 index 1ea39c54..00000000 --- a/Cpp2IL.Core/CecilEType.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Cpp2IL.Core -{ - //Copied directly from Mono.Cecil.Metadata.ElementType - public enum CecilEType : byte { - None = 0x00, - Void = 0x01, - Boolean = 0x02, - Char = 0x03, - I1 = 0x04, - U1 = 0x05, - I2 = 0x06, - U2 = 0x07, - I4 = 0x08, - U4 = 0x09, - I8 = 0x0a, - U8 = 0x0b, - R4 = 0x0c, - R8 = 0x0d, - String = 0x0e, - Ptr = 0x0f, // Followed by token - ByRef = 0x10, // Followed by token - ValueType = 0x11, // Followed by token - Class = 0x12, // Followed by token - Var = 0x13, // Followed by generic parameter number - Array = 0x14, // - GenericInst = 0x15, // ... */ - TypedByRef = 0x16, - I = 0x18, // System.IntPtr - U = 0x19, // System.UIntPtr - FnPtr = 0x1b, // Followed by full method signature - Object = 0x1c, // System.Object - SzArray = 0x1d, // Single-dim array with 0 lower bound - MVar = 0x1e, // Followed by generic parameter number - CModReqD = 0x1f, // Required modifier : followed by a TypeDef or TypeRef token - CModOpt = 0x20, // Optional modifier : followed by a TypeDef or TypeRef token - Internal = 0x21, // Implemented within the CLI - Modifier = 0x40, // Or'd with following element types - Sentinel = 0x41, // Sentinel for varargs method signature - Pinned = 0x45, // Denotes a local variable that points at a pinned object - - // special undocumented constants - Type = 0x50, - Boxed = 0x51, - Enum = 0x55 - } -} \ No newline at end of file diff --git a/Cpp2IL.Core/Cpp2IL.Core.csproj b/Cpp2IL.Core/Cpp2IL.Core.csproj index edd03c4d..d774ec96 100644 --- a/Cpp2IL.Core/Cpp2IL.Core.csproj +++ b/Cpp2IL.Core/Cpp2IL.Core.csproj @@ -6,10 +6,10 @@ enable Samboy063.Cpp2IL.Core Samboy063 - 2021.6.1 - 2021.6.1 - 2021.6.1 - Copyright © Samboy063 2019-2021 + 2022.0.7 + 2022.0.7 + 2022.0.7 + Copyright © Samboy063 2019-2022 true MIT git @@ -23,7 +23,7 @@ - + diff --git a/Cpp2IL.Core/Cpp2IlApi.cs b/Cpp2IL.Core/Cpp2IlApi.cs index 78b0c339..82e0eb58 100644 --- a/Cpp2IL.Core/Cpp2IlApi.cs +++ b/Cpp2IL.Core/Cpp2IlApi.cs @@ -60,6 +60,8 @@ public static class Cpp2IlApi { var unityVer = FileVersionInfo.GetVersionInfo(unityPlayerPath); + Logger.VerboseNewline($"Running on windows and have unity player, so using file version: {unityVer.FileMajorPart}.{unityVer.FileMinorPart}.{unityVer.FileBuildPart}"); + return new[] {unityVer.FileMajorPart, unityVer.FileMinorPart, unityVer.FileBuildPart}; } @@ -70,7 +72,9 @@ public static class Cpp2IlApi if (File.Exists(globalgamemanagersPath)) { var ggmBytes = File.ReadAllBytes(globalgamemanagersPath); - return GetVersionFromGlobalGameManagers(ggmBytes); + var ret = GetVersionFromGlobalGameManagers(ggmBytes); + Logger.VerboseNewline($"Got version {ret} from globalgamemanagers"); + return ret; } //Data.unity3d @@ -78,10 +82,13 @@ public static class Cpp2IlApi if (File.Exists(dataPath)) { using var dataStream = File.OpenRead(dataPath); - return GetVersionFromDataUnity3D(dataStream); + var ret = GetVersionFromDataUnity3D(dataStream); + Logger.VerboseNewline($"Got version {ret} from data.unity3d"); + return ret; } } + Logger.VerboseNewline($"Could not determine unity version, gameDataPath is {gameDataPath}, unityPlayerPath is {unityPlayerPath}"); return null; } @@ -164,10 +171,9 @@ public static void InitializeLibCpp2Il(string assemblyPath, string metadataPath, public static void InitializeLibCpp2Il(string assemblyPath, string metadataPath, int[] unityVersion, bool allowUserToInputAddresses = false) { if (IsLibInitialized()) - ResetInternalState(); + DisposeAndCleanupAll(); ConfigureLib(allowUserToInputAddresses); - FixCapstoneLib(); #if !DEBUG try @@ -193,10 +199,9 @@ public static void InitializeLibCpp2Il(byte[] assemblyData, byte[] metadataData, public static void InitializeLibCpp2Il(byte[] assemblyData, byte[] metadataData, int[] unityVersion, bool allowUserToInputAddresses = false) { if (IsLibInitialized()) - ResetInternalState(); + DisposeAndCleanupAll(); ConfigureLib(allowUserToInputAddresses); - FixCapstoneLib(); try { @@ -211,7 +216,10 @@ public static void InitializeLibCpp2Il(byte[] assemblyData, byte[] metadataData, } } - private static void ResetInternalState() + /// + /// Clears all internal caches, lists, references, etc, disposes of the MemoryStream for the binary and metadata, and resets the state of the library. + /// + public static void DisposeAndCleanupAll() { SharedState.Clear(); @@ -365,8 +373,11 @@ public static void GenerateMetadataForAllAssemblies(string rootFolder) { CheckLibInitialized(); + Logger.Info("Generating type dumps for all assemblies..."); foreach (var assemblyDefinition in SharedState.AssemblyList) GenerateMetadataForAssembly(rootFolder, assemblyDefinition); + + Logger.InfoNewline("OK."); } public static void GenerateMetadataForAssembly(string rootFolder, AssemblyDefinition assemblyDefinition) @@ -697,40 +708,5 @@ private static void CheckLibInitialized() if (!IsLibInitialized()) throw new LibraryNotInitializedException(); } - - private static void FixCapstoneLib() - { - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - return; - - //Capstone is super stupid and randomly fails to load on non-windows platforms. Fix it. - var runningFrom = AppContext.BaseDirectory; - var capstonePath = Path.Combine(runningFrom, "Gee.External.Capstone.dll"); - - if (!File.Exists(capstonePath)) - { - Logger.InfoNewline("Detected that Capstone's Managed assembly is missing. Attempting to copy the windows one..."); - var fallbackPath = Path.Combine(runningFrom, "runtimes", "win-x64", "lib", "netstandard2.0", "Gee.External.Capstone.dll"); - - if (!File.Exists(fallbackPath)) - { - Logger.WarnNewline($"Couldn't find it at {fallbackPath}. Your application will probably now throw an exception due to it being missing."); - return; - } - - File.Copy(fallbackPath, capstonePath); - } - - var loaded = Assembly.LoadFile(capstonePath); - Logger.InfoNewline("Loaded capstone: " + loaded.FullName); - - AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => - { - if (args.Name == loaded.FullName) - return loaded; - - return null; - }; - } } } \ No newline at end of file diff --git a/Cpp2IL.Core/Cpp2IlHarmonyPatches.cs b/Cpp2IL.Core/Cpp2IlHarmonyPatches.cs index 870f0d39..abab8d94 100644 --- a/Cpp2IL.Core/Cpp2IlHarmonyPatches.cs +++ b/Cpp2IL.Core/Cpp2IlHarmonyPatches.cs @@ -15,6 +15,9 @@ internal static class Cpp2IlHarmonyPatches { internal static void Install() { +#if DEBUG + return; +#endif Logger.InfoNewline("Patching Cecil for better error messages...", "Harmony"); Logger.VerboseNewline("\tInitializing harmony instance 'dev.samboy.cpp2il'...", "Harmony"); @@ -25,23 +28,18 @@ internal static void Install() Logger.VerboseNewline("\tAdding finalizer to Mono.Cecil.Cil.CodeWriter:WriteOperand...", "Harmony"); harmony.Patch(AccessTools.Method("Mono.Cecil.Cil.CodeWriter:WriteOperand"), finalizer: new(typeof(Cpp2IlHarmonyPatches), nameof(FinalizeWriteOperand))); - + Logger.VerboseNewline("\tAdding finalizer to Mono.Cecil.MetadataBuilder:AddType...", "Harmony"); harmony.Patch(AccessTools.Method("Mono.Cecil.MetadataBuilder:AddType"), finalizer: new(typeof(Cpp2IlHarmonyPatches), nameof(FinalizeAddType))); - + Logger.VerboseNewline("\tAdding finalizer to Mono.Cecil.MetadataBuilder:AddProperty...", "Harmony"); harmony.Patch(AccessTools.Method("Mono.Cecil.MetadataBuilder:AddProperty"), finalizer: new(typeof(Cpp2IlHarmonyPatches), nameof(FinalizeAddProperty))); - + Logger.VerboseNewline("\tAdding finalizer to Mono.Cecil.MetadataBuilder:GetCustomAttributeSignature...", "Harmony"); harmony.Patch(AccessTools.Method("Mono.Cecil.MetadataBuilder:GetCustomAttributeSignature"), finalizer: new(typeof(Cpp2IlHarmonyPatches), nameof(FinalizeGetCustomAttributeSignature))); - - Logger.VerboseNewline("\tAdding finalizer to Mono.Cecil.SignatureWriter:WriteTypeSignature...", "Harmony"); - harmony.Patch(AccessTools.Method("Mono.Cecil.SignatureWriter:WriteTypeSignature"), finalizer: new(typeof(Cpp2IlHarmonyPatches), nameof(FinalizeWriteTypeSignature))); - - Logger.VerboseNewline("\tAdding finalizer to Mono.Cecil.Mixin:CompressMetadataToken...", "Harmony"); - harmony.Patch(AccessTools.Method("Mono.Cecil.Mixin:CompressMetadataToken"), new(typeof(Cpp2IlHarmonyPatches), nameof(PreCompressMetadataToken)), finalizer: new(typeof(Cpp2IlHarmonyPatches), nameof(FinalizeCompressMetadataToken))); - - harmony.Patch(AccessTools.Method("Mono.Cecil.MetadataBuilder+GenericParameterComparer:Compare"), finalizer: new(typeof(Cpp2IlHarmonyPatches), nameof(FinalizeCompareGenericParams))); + + Logger.VerboseNewline("\tAdding finalizer to Mono.Cecil.SignatureWriter:WriteGenericInstanceSignature...", "Harmony"); + harmony.Patch(AccessTools.Method("Mono.Cecil.SignatureWriter:WriteGenericInstanceSignature"), finalizer: new(typeof(Cpp2IlHarmonyPatches), nameof(FinalizeWriteGenericInstanceSignature))); Logger.VerboseNewline("\tDone", "Harmony"); } @@ -69,7 +67,7 @@ internal static void Install() return null; } - + public static Exception? FinalizeAddProperty(PropertyDefinition property, Exception? __exception) { if (__exception != null) @@ -77,7 +75,7 @@ internal static void Install() return null; } - + public static Exception? FinalizeGetCustomAttributeSignature(CustomAttribute attribute, Exception? __exception) { if (__exception != null) @@ -85,31 +83,11 @@ internal static void Install() return null; } - - public static Exception? FinalizeWriteTypeSignature(TypeReference type, Exception? __exception) - { - if (__exception != null) - return new TypeSignatureWriteFailedException(type, __exception); - - return null; - } - - public static void PreCompressMetadataToken(int self, MetadataToken token) - { - } - - public static Exception? FinalizeCompressMetadataToken(int self, MetadataToken token, Exception? __exception) - { - if (__exception != null) - return new MetadataTokenCompressionException((CecilCodedIndex) self, token, __exception); - - return null; - } - public static Exception? FinalizeCompareGenericParams(GenericParameter a, GenericParameter b, Exception? __exception) + public static Exception? FinalizeWriteGenericInstanceSignature(IGenericInstance instance, Exception? __exception) { if (__exception != null) - return new Exception($"Failed to compare generic params {a} (owned by {a.Owner}) and {b} (owned by {b.Owner}) due to an exception", __exception); + return new GenericInstanceWriteFailedException(instance, __exception); return null; } diff --git a/Cpp2IL.Core/Exceptions/GenericInstanceWriteFailedException.cs b/Cpp2IL.Core/Exceptions/GenericInstanceWriteFailedException.cs new file mode 100644 index 00000000..9e70bf9b --- /dev/null +++ b/Cpp2IL.Core/Exceptions/GenericInstanceWriteFailedException.cs @@ -0,0 +1,13 @@ +using System; +using System.Linq; +using Mono.Cecil; + +namespace Cpp2IL.Core.Exceptions +{ + public class GenericInstanceWriteFailedException : Exception + { + public GenericInstanceWriteFailedException(IGenericInstance instance, Exception cause) + : base($"Failed to write generic instance {instance} due to an exception", cause) + { } + } +} \ No newline at end of file diff --git a/Cpp2IL.Core/Exceptions/MetadataTokenCompressionException.cs b/Cpp2IL.Core/Exceptions/MetadataTokenCompressionException.cs deleted file mode 100644 index 716dbdf0..00000000 --- a/Cpp2IL.Core/Exceptions/MetadataTokenCompressionException.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using Mono.Cecil; - -namespace Cpp2IL.Core.Exceptions -{ - public class MetadataTokenCompressionException : Exception - { - public MetadataTokenCompressionException(CecilCodedIndex codedIndex, MetadataToken token, Exception cause) : base($"Failed to compress metadata token {token} (of type {token.TokenType}, RID {token.RID}) into coded index of type {codedIndex}, due to an exception", cause) - { } - } -} \ No newline at end of file diff --git a/Cpp2IL.Core/Exceptions/TypeSignatureWriteFailedException.cs b/Cpp2IL.Core/Exceptions/TypeSignatureWriteFailedException.cs deleted file mode 100644 index b8a6e9c0..00000000 --- a/Cpp2IL.Core/Exceptions/TypeSignatureWriteFailedException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using Cpp2IL.Core.Utils; -using Mono.Cecil; - -namespace Cpp2IL.Core.Exceptions -{ - public class TypeSignatureWriteFailedException : Exception - { - public TypeSignatureWriteFailedException(TypeReference type, Exception cause) : base($"Failed to write type {type} of etype {type.GetEType()} due to an exception", cause) - { } - } -} \ No newline at end of file diff --git a/Cpp2IL.Core/StubAssemblyBuilder.cs b/Cpp2IL.Core/StubAssemblyBuilder.cs index 6c8859a1..d0fe9272 100644 --- a/Cpp2IL.Core/StubAssemblyBuilder.cs +++ b/Cpp2IL.Core/StubAssemblyBuilder.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; -using Cpp2IL.Core.Utils; using LibCpp2IL.Metadata; using Mono.Cecil; using TypeAttributes = Mono.Cecil.TypeAttributes; @@ -11,6 +11,8 @@ namespace Cpp2IL.Core { internal static class StubAssemblyBuilder { + private static readonly FieldInfo _etypeField = typeof(TypeReference).GetField("etype", BindingFlags.NonPublic | BindingFlags.Instance)!; + /// /// Creates all the Assemblies defined in the provided metadata, along with (stub) definitions of all the types contained therein. /// @@ -46,9 +48,11 @@ private static AssemblyDefinition BuildStubAssembly(ModuleParameters moduleParam asmName.HashAlgorithm = (AssemblyHashAlgorithm) assemblyDefinition.AssemblyName.hash_alg; asmName.Attributes = (AssemblyAttributes) assemblyDefinition.AssemblyName.flags; asmName.Culture = assemblyDefinition.AssemblyName.Culture; - asmName.PublicKeyToken = BitConverter.GetBytes(assemblyDefinition.AssemblyName.publicKeyToken); - if (assemblyDefinition.AssemblyName.publicKeyToken == 0) - asmName.PublicKeyToken = Array.Empty(); + + //This just causes more pain than it's worth, so we comment it out + // asmName.PublicKeyToken = BitConverter.GetBytes(assemblyDefinition.AssemblyName.publicKeyToken); + // if (assemblyDefinition.AssemblyName.publicKeyToken == 0) + asmName.PublicKeyToken = Array.Empty(); // asmName.PublicKey = Encoding.UTF8.GetBytes(assemblyDefinition.AssemblyName.PublicKey); //This seems to be garbage data, e.g. "\x0\x0\x0\x0\x0\x0\x0\x0\x4\x0\x0\x0\x0\x0\x0\x0", so we skip asmName.Hash = assemblyDefinition.AssemblyName.hash_len == 0 ? Array.Empty() : Encoding.UTF8.GetBytes(assemblyDefinition.AssemblyName.HashValue); @@ -96,28 +100,28 @@ private static void HandleTypeInAssembly(Il2CppTypeDefinition type, ModuleDefini var etype = name switch { //See ElementType in Mono.Cecil.Metadata - "Void" => CecilEType.Void, - nameof(Boolean) => CecilEType.Boolean, - nameof(Char) => CecilEType.Char, - nameof(SByte) => CecilEType.I1, //I1 - nameof(Byte) => CecilEType.U1, //U1 - nameof(Int16) => CecilEType.I2, //I2 - nameof(UInt16) => CecilEType.U2, //U2 - nameof(Int32) => CecilEType.I4, //I4 - nameof(UInt32) => CecilEType.U4, //U4 - nameof(Int64) => CecilEType.I8, //I8 - nameof(UInt64) => CecilEType.U8, //U8 - nameof(Single) => CecilEType.R4, //R4 - nameof(Double) => CecilEType.R8, //R8 - nameof(String) => CecilEType.String, - nameof(Object) => CecilEType.Object, //Object + "Void" => 1, + nameof(Boolean) => 2, + nameof(Char) => 3, + nameof(SByte) => 4, //I1 + nameof(Byte) => 5, //U1 + nameof(Int16) => 6, //I2 + nameof(UInt16) => 7, //U2 + nameof(Int32) => 8, //I4 + nameof(UInt32) => 9, //U4 + nameof(Int64) => 0xA, //I8 + nameof(UInt64) => 0xB, //U8 + nameof(Single) => 0xC, //R4 + nameof(Double) => 0xD, //R8 + nameof(String) => 0xE, + nameof(Object) => 0x1C, //Object // nameof(IntPtr) => 0xF, - _ => CecilEType.None, + _ => 0x0, }; if(etype != 0) //Fixup internaL cecil etypes for (among other things) attribute blobs. - definition.SetEType(etype); + _etypeField.SetValue(definition, (byte) etype); } if(!isNestedType) diff --git a/Cpp2IL.Core/TokenComparer.cs b/Cpp2IL.Core/TokenComparer.cs new file mode 100644 index 00000000..b89a8d35 --- /dev/null +++ b/Cpp2IL.Core/TokenComparer.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using LibCpp2IL; +using LibCpp2IL.Metadata; + +namespace Cpp2IL.Core +{ + public class TokenComparer : IComparer + { + public int Compare(IIl2CppTokenProvider x, IIl2CppTokenProvider y) + { + if (ReferenceEquals(x, y)) return 0; + return x.Token.CompareTo(y.Token); + } + } +} \ No newline at end of file diff --git a/Cpp2IL.Core/Utils/AnalysisUtils.cs b/Cpp2IL.Core/Utils/AnalysisUtils.cs index 8d373edb..97841f6b 100644 --- a/Cpp2IL.Core/Utils/AnalysisUtils.cs +++ b/Cpp2IL.Core/Utils/AnalysisUtils.cs @@ -80,6 +80,9 @@ public static long[] ReadArrayInitializerForFieldDefinition(FieldDefinition fiel if (value is Il2CppString) throw new Exception("Cannot coerce an Il2CppString. Something has gone wrong here"); + + if (value is UnknownGlobalAddr) + throw new Exception("Cannot coerce an UnknownGlobal. Something has gone wrong here"); if (coerceToType.Resolve() is { IsEnum: true } enumType) coerceToType = enumType.GetEnumUnderlyingType(); diff --git a/Cpp2IL.Core/Utils/CecilUtils.cs b/Cpp2IL.Core/Utils/CecilUtils.cs deleted file mode 100644 index 5100d276..00000000 --- a/Cpp2IL.Core/Utils/CecilUtils.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using LibCpp2IL; -using Mono.Cecil; -using Mono.Cecil.Cil; -using Mono.Cecil.Rocks; - -namespace Cpp2IL.Core.Utils -{ - public static class CecilUtils - { - private static readonly FieldInfo EtypeField = typeof(TypeReference).GetField("etype", BindingFlags.NonPublic | BindingFlags.Instance)!; - private static readonly FieldInfo PositionField = typeof(GenericParameter).GetField("position", BindingFlags.NonPublic | BindingFlags.Instance)!; - - public static bool HasAnyGenericCrapAnywhere(TypeReference reference) - { - if (reference is GenericParameter) - return true; - - if (reference is GenericInstanceType git) - { - //check for e.g. List> - return git.GenericArguments.Any(HasAnyGenericCrapAnywhere); - } - - if (reference is TypeSpecification typeSpec) - //Pointers, byrefs, etc - return HasAnyGenericCrapAnywhere(typeSpec.ElementType); - - return reference.HasGenericParameters; - } - - public static bool HasAnyGenericCrapAnywhere(MethodReference reference, bool checkDeclaringTypeParamsAndReturn = true) - { - if (checkDeclaringTypeParamsAndReturn && HasAnyGenericCrapAnywhere(reference.DeclaringType)) - return true; - - if (checkDeclaringTypeParamsAndReturn && HasAnyGenericCrapAnywhere(reference.ReturnType)) - return true; - - if (checkDeclaringTypeParamsAndReturn && reference.Parameters.Any(p => HasAnyGenericCrapAnywhere(p.ParameterType))) - return true; - - if (reference.HasGenericParameters) - return true; - - if (reference is GenericInstanceMethod gim) - return gim.GenericArguments.Any(HasAnyGenericCrapAnywhere); - - return false; - } - - public static TypeReference ImportTypeButCleanly(this ModuleDefinition module, TypeReference reference) - { - if (reference is GenericParameter) - //These two lines are what cecil is missing. It's so simple :/ - return reference; - - if (reference is GenericInstanceType git) - return module.ImportTypeButCleanly(git.ElementType).MakeGenericInstanceType(git.GenericArguments.Select(module.ImportTypeButCleanly).ToArray()); - - if (reference is ArrayType at) - return module.ImportTypeButCleanly(at.ElementType).MakeArrayType(); - - if (reference is ByReferenceType brt) - return module.ImportTypeButCleanly(brt.ElementType).MakeByReferenceType(); - - if (reference is PointerType pt) - return module.ImportTypeButCleanly(pt.ElementType).MakePointerType(); - - if (reference is TypeDefinition td) - return module.ImportReference(td); - - if (reference.GetType() == typeof(TypeReference)) - return module.ImportReference(reference); - - throw new NotSupportedException($"Support for importing {reference} of type {reference.GetType()} is not implemented"); - } - - public static ParameterDefinition ImportParameterButCleanly(this ModuleDefinition module, ParameterDefinition param) - { - param.ParameterType = HasAnyGenericCrapAnywhere(param.ParameterType) ? module.ImportTypeButCleanly(param.ParameterType) : module.ImportReference(param.ParameterType); - return param; - } - - public static MethodReference ImportMethodButCleanly(this ModuleDefinition module, MethodReference method) - { - var returnType = module.ImportTypeButCleanly(method.ReturnType); - var declaringType = module.ImportTypeButCleanly(method.DeclaringType); - var gParams = method.GenericParameters.ToList(); - var gArgs = (method as GenericInstanceMethod)?.GenericArguments.Select(module.ImportTypeButCleanly).ToList(); - var methodParams = method.Parameters.Select(module.ImportParameterButCleanly).ToList(); - - var ret = new MethodReference(method.Name, returnType, declaringType); - - gParams.ForEach(ret.GenericParameters.Add); - - if (gArgs != null) - { - var gMtd = new GenericInstanceMethod(ret); - gArgs.ForEach(gMtd.GenericArguments.Add); - ret = gMtd; - } - - methodParams.ForEach(ret.Parameters.Add); - - return ret; - } - - public static FieldReference ImportFieldButCleanly(this ModuleDefinition module, FieldReference field) - { - var declaringType = module.ImportTypeButCleanly(field.DeclaringType); - var fieldType = module.ImportTypeButCleanly(field.FieldType); - - return new(field.Name, fieldType, declaringType); - } - - public static TypeReference ImportTypeButCleanly(this ILProcessor processor, TypeReference reference) => processor.Body.Method.Module.ImportTypeButCleanly(reference); - - public static MethodReference ImportMethodButCleanly(this ILProcessor processor, MethodReference reference) => processor.Body.Method.Module.ImportMethodButCleanly(reference); - - public static void SetEType(this TypeReference tr, CecilEType eType) => EtypeField.SetValue(tr, (byte) eType); - - public static CecilEType GetEType(this TypeReference tr) => (CecilEType) (byte) EtypeField.GetValue(tr); - - public static GenericInstanceMethod MakeGenericInstanceMethod(this MethodReference methodReference, params TypeReference[] genericArguments) - { - if (methodReference == null) - throw new ArgumentNullException(nameof(methodReference), "Method to make generic cannot be null"); - - // if (genericArguments.Any(g => g is null) || genericArguments.Length != methodReference.GenericParameters.Count) - // throw new Exception($"Generic arguments {genericArguments.ToStringEnumerable()}, count {genericArguments.Length} are not suitable for use for generic parameters {methodReference.GenericParameters.ToStringEnumerable()}, length {methodReference.GenericParameters.Count}"); - - var gim = new GenericInstanceMethod(methodReference); - - //Cecil sucks major ass and doesn't do this in the constructor above for SOME FUCKING REASON - methodReference.GenericParameters.ToList().ForEach(gim.GenericParameters.Add); - - genericArguments.ToList().ForEach(gim.GenericArguments.Add); - - return gim; - } - - public static void SetPosition(this GenericParameter gp, int position) => PositionField.SetValue(gp, position); - - public static int GetPosition(this GenericParameter gp) => (int) PositionField.GetValue(gp); - } -} \ No newline at end of file diff --git a/Cpp2IL.Core/Utils/FieldUtils.cs b/Cpp2IL.Core/Utils/FieldUtils.cs index e4b89d5f..b7369b63 100644 --- a/Cpp2IL.Core/Utils/FieldUtils.cs +++ b/Cpp2IL.Core/Utils/FieldUtils.cs @@ -132,8 +132,8 @@ private static List RecalculateFieldOffsetsForGenericType(TypeRefer /// public class FieldBeingAccessedData { - public FieldReference? ImpliedFieldLoad; - public FieldReference? FinalLoadInChain; + public readonly FieldDefinition? ImpliedFieldLoad; + public readonly FieldDefinition? FinalLoadInChain; public readonly FieldBeingAccessedData? NextChainLink; private FieldBeingAccessedData(FieldDefinition? impliedFieldLoad, FieldDefinition? finalLoadInChain, FieldBeingAccessedData? nextChainLink) diff --git a/Cpp2IL.Core/Utils/GenericMethodUtils.cs b/Cpp2IL.Core/Utils/GenericMethodUtils.cs new file mode 100644 index 00000000..07c50143 --- /dev/null +++ b/Cpp2IL.Core/Utils/GenericMethodUtils.cs @@ -0,0 +1,31 @@ +using System.Diagnostics; +using Mono.Cecil; + +namespace Cpp2IL.Core.Utils +{ + public static class GenericMethodUtils + { + public static void PrepareGenericMethodForEmissionToBody(MethodReference theMethod, TypeReference declaringType, ModuleDefinition importInto) + { + //Naively resolving everything and its mother doesn't work. + //There are four key parts here. From left-to-right in the signature + // The return type, including generic arguments + // The declaring type, including generic arguments + // The method specification itself, including any generic arguments + // The parameters, with any generic parameters in the source method definition resolved to match those of method reference. + + //The key part is that generic *arguments* on the parameters and return type of the resulting method reference have to be the generic *parameters* of the method definition or type. + //E.g. Enumerable.Where(IEnumerable source, Func predicate), the 'T's in the parameters need to resolve to the T in the method Where, even if the method call itself + //has a generic argument. + //So this should look like Enumerable.Where(IEnumerable source, Func predicate) still, and NOT have the T in the parameters replaced with int. + + //And every single individual part has to be imported. + //And we have to return a unique new MethodReference for this call specifically. + + if(theMethod is MethodDefinition && declaringType is TypeDefinition) + return; + + //Debugger.Break(); + } + } +} \ No newline at end of file diff --git a/Cpp2IL.Core/Utils/MethodUtils.cs b/Cpp2IL.Core/Utils/MethodUtils.cs index c98fc4d8..a30647bd 100644 --- a/Cpp2IL.Core/Utils/MethodUtils.cs +++ b/Cpp2IL.Core/Utils/MethodUtils.cs @@ -149,7 +149,9 @@ private static bool CheckParameters64(MethodReference method, MethodAnalysis< var actualArgs = new List(); if (!isInstance) { - actualArgs.Add(GetValueFromAppropriateX64Reg(method.Parameters.GetValueSafely(0)?.ParameterType?.ShouldBeInFloatingPointRegister(), "xmm0", "rcx", context)); + if(LibCpp2IlMain.MetadataVersion > 24f) + //We can only add rcx/xmm0 if we're > v24, because il2cpp used to always emit an unused parameter for a non-existent this pointer. + actualArgs.Add(GetValueFromAppropriateX64Reg(method.Parameters.GetValueSafely(0)?.ParameterType?.ShouldBeInFloatingPointRegister(), "xmm0", "rcx", context)); actualArgs.Add(GetValueFromAppropriateX64Reg(method.Parameters.GetValueSafely(1)?.ParameterType?.ShouldBeInFloatingPointRegister(), "xmm1", "rdx", context)); actualArgs.Add(GetValueFromAppropriateX64Reg(method.Parameters.GetValueSafely(2)?.ParameterType?.ShouldBeInFloatingPointRegister(), "xmm2", "r8", context)); actualArgs.Add(GetValueFromAppropriateX64Reg(method.Parameters.GetValueSafely(3)?.ParameterType?.ShouldBeInFloatingPointRegister(), "xmm3", "r9", context)); @@ -356,7 +358,8 @@ private static bool CheckParametersArmV8(MethodReference method, MethodAnalys //See MethodAnalysis#HandleArm64Parameters for a detailed explanation of how this works. arguments = null; - var xCount = isInstance ? 1 : 0; + //instance methods have a `this` param, and prior to v24.1, so do static methods, even though they don't use it. + var xCount = isInstance || LibCpp2IlMain.MetadataVersion <= 24f ? 1 : 0; var vCount = 0; var ret = new List(); diff --git a/Cpp2IL.Core/Utils/MiscUtils.cs b/Cpp2IL.Core/Utils/MiscUtils.cs index 7cdcc620..5e064686 100644 --- a/Cpp2IL.Core/Utils/MiscUtils.cs +++ b/Cpp2IL.Core/Utils/MiscUtils.cs @@ -334,7 +334,7 @@ public static TypeReference ImportTypeInto(MemberReference importInto, Il2CppTyp //Generics are dumb. var genericParams = Array.Empty(); - if (definedType == null && name.Contains("<")) + if (definedType == null && name.Contains("<") && name != "<") { //Replace < > with the number of generic params after a ` genericParams = GetGenericParams(name[(name.IndexOf("<", StringComparison.Ordinal) + 1)..^1]); @@ -584,16 +584,10 @@ public static object GetNumericConstant(ulong addr, TypeReference type) if (owner.GenericParameters.FirstOrDefault(a => a.Name == typeData.variableGenericParamName) is { } gp) return gp; - if (owner is MethodReference mRef && mRef.DeclaringType.GenericParameters.FirstOrDefault(g => g.Name == typeData.variableGenericParamName) is { } gp2) - return gp2; - foreach (var extraProvider in extra) { - if (extraProvider?.GenericParameters.FirstOrDefault(a => a.Name == typeData.variableGenericParamName) is { } gp3) - return gp3; - - if (extraProvider is MethodReference mRef2 && mRef2.DeclaringType.GenericParameters.FirstOrDefault(g => g.Name == typeData.variableGenericParamName) is { } gp4) - return gp4; + if (extraProvider?.GenericParameters.FirstOrDefault(a => a.Name == typeData.variableGenericParamName) is { } gp2) + return gp2; } //Generic parameter diff --git a/Cpp2IL.Core/Utils/WasmUtils.cs b/Cpp2IL.Core/Utils/WasmUtils.cs index 40abfa3d..ceb3e14e 100644 --- a/Cpp2IL.Core/Utils/WasmUtils.cs +++ b/Cpp2IL.Core/Utils/WasmUtils.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using LibCpp2IL; using LibCpp2IL.Wasm; using Mono.Cecil; @@ -10,6 +11,7 @@ namespace Cpp2IL.Core.Utils public static class WasmUtils { internal static readonly Dictionary> MethodDefinitionIndices = new(); + private static Regex DynCallRemappingRegex = new(@"Module\[\s*[""'](dynCall_[^""']+)[""']\s*\]\s*=\s*Module\[\s*[""']asm[""']\s*\]\[\s*[""']([^""']+)[""']\s*\]\s*\)\.apply", RegexOptions.Compiled); public static string BuildSignature(MethodDefinition definition) { @@ -81,5 +83,30 @@ private static void CalculateAllMethodDefinitionIndices() return null; } + + public static Dictionary ExtractAndParseDynCallRemaps(string frameworkJsFile) + { + //At least one WASM binary found in the wild had the exported function names obfuscated. + //However, the framework.js file has mappings to the correct names. + /*e.g. + var dynCall_viffiiii = Module["dynCall_viffiiii"] = function() { + return (dynCall_viffiiii = Module["dynCall_viffiiii"] = Module["asm"]["Wo"]).apply(null, arguments) + } + */ + + var ret = new Dictionary(); + var matches = DynCallRemappingRegex.Matches(frameworkJsFile); + foreach (Match match in matches) + { + //Group 1 is the original method name, e.g. dynCall_viffiiii + //Group 2 is the remapped name, e.g Wo + var origName = match.Groups[1]; + var remappedName = match.Groups[2]; + + ret[remappedName.Value] = origName.Value; + } + + return ret; + } } } \ No newline at end of file diff --git a/Cpp2IL.Core/WasmKeyFunctionAddresses.cs b/Cpp2IL.Core/WasmKeyFunctionAddresses.cs index 5ce70811..a9971fb9 100644 --- a/Cpp2IL.Core/WasmKeyFunctionAddresses.cs +++ b/Cpp2IL.Core/WasmKeyFunctionAddresses.cs @@ -9,7 +9,7 @@ protected override ulong GetObjectIsInstFromSystemType() return 0; } - protected override IEnumerable FindAllThunkFunctions(ulong addr, uint maxBytesBack = 0, params ulong[] addressesToIgnore) + protected override IEnumerable FindAllThunkFunctions(ulong addr, bool mustBeJumpNotCall, uint maxBytesBack = 0, params ulong[] addressesToIgnore) { yield break; } diff --git a/Cpp2IL.Core/X86KeyFunctionAddresses.cs b/Cpp2IL.Core/X86KeyFunctionAddresses.cs index c60f2867..be3b7a63 100644 --- a/Cpp2IL.Core/X86KeyFunctionAddresses.cs +++ b/Cpp2IL.Core/X86KeyFunctionAddresses.cs @@ -23,7 +23,7 @@ private InstructionList DisassembleTextSection() return _cachedDisassembledBytes; } - protected override IEnumerable FindAllThunkFunctions(ulong addr, uint maxBytesBack = 0, params ulong[] addressesToIgnore) + protected override IEnumerable FindAllThunkFunctions(ulong addr, bool mustBeJumpNotCall, uint maxBytesBack = 0, params ulong[] addressesToIgnore) { //Disassemble .text var allInstructions = DisassembleTextSection(); @@ -47,7 +47,8 @@ protected override IEnumerable FindAllThunkFunctions(ulong addr, uint max //Double-cc = thunk if (previousByte == 0xCC && nextByte == 0xCC) { - yield return matchingJmp.IP; + if(!mustBeJumpNotCall || matchingJmp.Mnemonic is Mnemonic.Jmp) + yield return matchingJmp.IP; continue; } @@ -61,7 +62,8 @@ protected override IEnumerable FindAllThunkFunctions(ulong addr, uint max if (LibCpp2IlMain.Binary!.GetByteAtRawAddress(offsetInPe - backtrack) == 0xCC) { - yield return matchingJmp.IP - (backtrack - 1); + if(!mustBeJumpNotCall || matchingJmp.Mnemonic is Mnemonic.Jmp) + yield return matchingJmp.IP - (backtrack - 1); break; } } diff --git a/Cpp2IL/CommandLineArgs.cs b/Cpp2IL/CommandLineArgs.cs index acfa0d53..201fe428 100644 --- a/Cpp2IL/CommandLineArgs.cs +++ b/Cpp2IL/CommandLineArgs.cs @@ -67,6 +67,9 @@ public class CommandLineArgs [Option("simple-attribute-restoration", HelpText = "Don't use analysis to restore attributes, meaning any attributes with constructor parameters won't be recovered. Has no effect on metadata v29+")] public bool SimpleAttributeRestoration { get; set; } + + [Option("wasm-framework-file", HelpText = "Path to the wasm *.framework.js file. Only needed if your binary is a WASM file. If provided, it can be used to remap obfuscated dynCall function names in order to correct method pointers.")] + public string? WasmFrameworkFilePath { get; set; } internal bool AreForceOptionsValid { diff --git a/Cpp2IL/ConsoleLogger.cs b/Cpp2IL/ConsoleLogger.cs index a0646ab0..a6944082 100644 --- a/Cpp2IL/ConsoleLogger.cs +++ b/Cpp2IL/ConsoleLogger.cs @@ -31,16 +31,6 @@ public static void Initialize() Write("Verb", source, message, VERB); }; - HarmonyLib.Tools.Logger.MessageReceived += (sender, args) => - { - if (args.LogChannel is HarmonyLib.Tools.Logger.LogChannel.Warn) - Logger.WarnNewline(args.Message, "HarmonyInternal"); - if (args.LogChannel is HarmonyLib.Tools.Logger.LogChannel.Error) - Logger.ErrorNewline(args.Message, "HarmonyInternal"); - }; - - HarmonyLib.Tools.Logger.ChannelFilter = HarmonyLib.Tools.Logger.LogChannel.Warn | HarmonyLib.Tools.Logger.LogChannel.Error; - CheckColorSupport(); } diff --git a/Cpp2IL/Cpp2IL.csproj b/Cpp2IL/Cpp2IL.csproj index d942f44e..d007941b 100644 --- a/Cpp2IL/Cpp2IL.csproj +++ b/Cpp2IL/Cpp2IL.csproj @@ -10,6 +10,7 @@ true true net472;net6.0 + true diff --git a/Cpp2IL/Cpp2IlRuntimeArgs.cs b/Cpp2IL/Cpp2IlRuntimeArgs.cs index 1f44727b..496c58b0 100644 --- a/Cpp2IL/Cpp2IlRuntimeArgs.cs +++ b/Cpp2IL/Cpp2IlRuntimeArgs.cs @@ -14,6 +14,7 @@ public struct Cpp2IlRuntimeArgs public string AssemblyToRunAnalysisFor; public bool AnalyzeAllAssemblies; + public string? WasmFrameworkJsFile; //Feature flags public bool EnableAnalysis; diff --git a/Cpp2IL/Program.cs b/Cpp2IL/Program.cs index 9ca230a5..078d1149 100644 --- a/Cpp2IL/Program.cs +++ b/Cpp2IL/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -7,12 +7,15 @@ using System.Reflection; using System.Runtime; using System.Runtime.InteropServices; +using System.Xml.Linq; using CommandLine; using Cpp2IL.Core; +using Cpp2IL.Core.Utils; #if !DEBUG using Cpp2IL.Core.Exceptions; #endif using LibCpp2IL; +using LibCpp2IL.Wasm; using Mono.Cecil; namespace Cpp2IL @@ -35,129 +38,264 @@ internal class Program private static void ResolvePathsFromCommandLine(string gamePath, string? inputExeName, ref Cpp2IlRuntimeArgs args) { if (Directory.Exists(gamePath)) - { - //Windows game. - args.PathToAssembly = Path.Combine(gamePath, "GameAssembly.dll"); - var exeName = Path.GetFileNameWithoutExtension(Directory.GetFiles(gamePath) - .FirstOrDefault(f => f.EndsWith(".exe") && !BlacklistedExecutableFilenames.Any(bl => f.EndsWith(bl)))); + if (Path.GetExtension(gamePath).ToLowerInvariant() == ".app") + HandleMachOGamePath(gamePath, ref args); + else + HandleWindowsGamePath(gamePath, inputExeName, ref args); + else if (File.Exists(gamePath) && Path.GetExtension(gamePath).ToLowerInvariant() == ".apk") + HandleSingleApk(gamePath, ref args); + else if (File.Exists(gamePath) && Path.GetExtension(gamePath).ToLowerInvariant() == ".xapk") + HandleXapk(gamePath, ref args); + else + throw new SoftException($"Could not find a valid unity game at {gamePath}"); + } - exeName = inputExeName ?? exeName; + private static void HandleWindowsGamePath(string gamePath, string? inputExeName, ref Cpp2IlRuntimeArgs args) + { + //Windows game. + args.PathToAssembly = Path.Combine(gamePath, "GameAssembly.dll"); + var exeName = Path.GetFileNameWithoutExtension(Directory.GetFiles(gamePath) + .FirstOrDefault(f => f.EndsWith(".exe") && !BlacklistedExecutableFilenames.Any(f.EndsWith))); - if (exeName == null) - throw new SoftException("Failed to locate any executable in the provided game directory. Make sure the path is correct, and if you *really* know what you're doing (and know it's not supported), use the force options, documented if you provide --help."); + exeName = inputExeName ?? exeName; - var unityPlayerPath = Path.Combine(gamePath, $"{exeName}.exe"); - args.PathToMetadata = Path.Combine(gamePath, $"{exeName}_Data", "il2cpp_data", "Metadata", "global-metadata.dat"); + if (exeName == null) + throw new SoftException("Failed to locate any executable in the provided game directory. Make sure the path is correct, and if you *really* know what you're doing (and know it's not supported), use the force options, documented if you provide --help."); - if (!File.Exists(args.PathToAssembly) || !File.Exists(unityPlayerPath) || !File.Exists(args.PathToMetadata)) - throw new SoftException("Invalid game-path or exe-name specified. Failed to find one of the following:\n" + - $"\t{args.PathToAssembly}\n" + - $"\t{unityPlayerPath}\n" + - $"\t{args.PathToMetadata}\n"); + var playerExePath = Path.Combine(gamePath, $"{exeName}.exe"); + args.PathToMetadata = Path.Combine(gamePath, $"{exeName}_Data", "il2cpp_data", "Metadata", "global-metadata.dat"); - var gameDataPath = Path.Combine(gamePath, $"{exeName}_Data"); - var uv = Cpp2IlApi.DetermineUnityVersion(unityPlayerPath, gameDataPath); + if (!File.Exists(args.PathToAssembly) || !File.Exists(playerExePath) || !File.Exists(args.PathToMetadata)) + throw new SoftException("Invalid game-path or exe-name specified. Failed to find one of the following:\n" + + $"\t{args.PathToAssembly}\n" + + $"\t{playerExePath}\n" + + $"\t{args.PathToMetadata}\n"); + + var unityPlayerPath = Path.Combine(gamePath, "UnityPlayer.dll"); + + if (!File.Exists(unityPlayerPath)) + unityPlayerPath = playerExePath; + + Logger.VerboseNewline($"Found probable windows game at path: {gamePath}. Attempting to get unity version..."); + var gameDataPath = Path.Combine(gamePath, $"{exeName}_Data"); + var uv = Cpp2IlApi.DetermineUnityVersion(unityPlayerPath, gameDataPath); + Logger.VerboseNewline($"First-attempt unity version detection gave: {uv?.ToString() ?? "null"}"); + + if (uv == null) + { + Logger.Warn("Could not determine unity version, probably due to not running on windows and not having any assets files to determine it from. Enter unity version, if known, in the format of (xxxx.x.x), else nothing to fail: "); + var userInputUv = Console.ReadLine(); + uv = userInputUv?.Split('.').Select(int.Parse).ToArray(); if (uv == null) + throw new SoftException("Failed to determine unity version. If you're not running on windows, I need a globalgamemanagers file or a data.unity3d file, or you need to use the force options."); + } + + args.UnityVersion = uv; + + if (args.UnityVersion[0] < 4) + { + Logger.WarnNewline($"Fail once: Unity version of provided executable is {args.UnityVersion.ToStringEnumerable()}. This is probably not the correct version. Retrying with alternative method..."); + + var readUnityVersionFrom = Path.Combine(gameDataPath, "globalgamemanagers"); + if (File.Exists(readUnityVersionFrom)) + args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(File.ReadAllBytes(readUnityVersionFrom)); + else { - Logger.Warn("Could not determine unity version, probably due to not running on windows and not having any assets files to determine it from. Enter unity version, if known, in the format of (xxxx.x.x), else nothing to fail: "); - var userInputUv = Console.ReadLine(); - uv = userInputUv?.Split('.').Select(int.Parse).ToArray(); - - if(uv == null) - throw new SoftException("Failed to determine unity version. If you're not running on windows, I need a globalgamemanagers file or a data.unity3d file, or you need to use the force options."); + readUnityVersionFrom = Path.Combine(gameDataPath, "data.unity3d"); + using var stream = File.OpenRead(readUnityVersionFrom); + + args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(stream); } + } - args.UnityVersion = uv; + Logger.InfoNewline($"Determined game's unity version to be {string.Join(".", args.UnityVersion)}"); - if (args.UnityVersion[0] < 4) - { - Logger.WarnNewline($"Fail once: Unity version of provided executable is {args.UnityVersion.ToStringEnumerable()}. This is probably not the correct version. Retrying with alternative method..."); + if (args.UnityVersion[0] <= 4) + throw new SoftException($"Unable to determine a valid unity version (got {args.UnityVersion.ToStringEnumerable()})"); - var readUnityVersionFrom = Path.Combine(gameDataPath, "globalgamemanagers"); - if (File.Exists(readUnityVersionFrom)) - args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(File.ReadAllBytes(readUnityVersionFrom)); - else - { - readUnityVersionFrom = Path.Combine(gameDataPath, "data.unity3d"); - using var stream = File.OpenRead(readUnityVersionFrom); + args.Valid = true; + } - args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(stream); - } - } + private static void HandleSingleApk(string gamePath, ref Cpp2IlRuntimeArgs args) + { + //APK + //Metadata: assets/bin/Data/Managed/Metadata + //Binary: lib/(armeabi-v7a)|(arm64-v8a)/libil2cpp.so - Logger.InfoNewline($"Determined game's unity version to be {string.Join(".", args.UnityVersion)}"); + Logger.InfoNewline($"Attempting to extract required files from APK {gamePath}", "APK"); - if (args.UnityVersion[0] <= 4) - throw new SoftException($"Unable to determine a valid unity version (got {args.UnityVersion.ToStringEnumerable()})"); + using var stream = File.OpenRead(gamePath); + using var zipArchive = new ZipArchive(stream); - args.Valid = true; - } - else if (File.Exists(gamePath) && Path.GetExtension(gamePath).ToLowerInvariant() == ".apk") - { - //APK - //Metadata: assets/bin/Data/Managed/Metadata - //Binary: lib/(armeabi-v7a)|(arm64-v8a)/libil2cpp.so + var globalMetadata = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("assets/bin/Data/Managed/Metadata/global-metadata.dat")); + var binary = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("lib/x86/libil2cpp.so")); + binary ??= zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("lib/arm64-v8a/libil2cpp.so")); + binary ??= zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("lib/armeabi-v7a/libil2cpp.so")); - Logger.InfoNewline($"Attempting to extract required files from APK {gamePath}", "APK"); + var globalgamemanagers = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("assets/bin/Data/globalgamemanagers")); + var dataUnity3d = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("assets/bin/Data/data.unity3d")); - using var stream = File.OpenRead(gamePath); - using var zipArchive = new ZipArchive(stream); + if (binary == null) + throw new SoftException("Could not find libil2cpp.so inside the apk."); + if (globalMetadata == null) + throw new SoftException("Could not find global-metadata.dat inside the apk"); + if (globalgamemanagers == null && dataUnity3d == null) + throw new SoftException("Could not find globalgamemanagers or data.unity3d inside the apk"); - var globalMetadata = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("assets/bin/Data/Managed/Metadata/global-metadata.dat")); - var binary = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("lib/x86/libil2cpp.so")); - binary ??= zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("lib/arm64-v8a/libil2cpp.so")); - binary ??= zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("lib/armeabi-v7a/libil2cpp.so")); + var tempFileBinary = Path.GetTempFileName(); + var tempFileMeta = Path.GetTempFileName(); - var globalgamemanagers = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("assets/bin/Data/globalgamemanagers")); - var dataUnity3d = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("assets/bin/Data/data.unity3d")); + _pathsToDeleteOnExit.Add(tempFileBinary); + _pathsToDeleteOnExit.Add(tempFileMeta); - if (binary == null) - throw new SoftException("Could not find libil2cpp.so inside the apk."); - if (globalMetadata == null) - throw new SoftException("Could not find global-metadata.dat inside the apk"); - if (globalgamemanagers == null && dataUnity3d == null) - throw new SoftException("Could not find globalgamemanagers or data.unity3d inside the apk"); + Logger.InfoNewline($"Extracting APK/{binary.FullName} to {tempFileBinary}", "APK"); + binary.ExtractToFile(tempFileBinary, true); + Logger.InfoNewline($"Extracting APK/{globalMetadata.FullName} to {tempFileMeta}", "APK"); + globalMetadata.ExtractToFile(tempFileMeta, true); - var tempFileBinary = Path.GetTempFileName(); - var tempFileMeta = Path.GetTempFileName(); + args.PathToAssembly = tempFileBinary; + args.PathToMetadata = tempFileMeta; - _pathsToDeleteOnExit.Add(tempFileBinary); - _pathsToDeleteOnExit.Add(tempFileMeta); + if (globalgamemanagers != null) + { + Logger.InfoNewline("Reading globalgamemanagers to determine unity version...", "APK"); + var ggmBytes = new byte[0x40]; + using var ggmStream = globalgamemanagers.Open(); + ggmStream.Read(ggmBytes, 0, 0x40); - Logger.InfoNewline($"Extracting APK/{binary.FullName} to {tempFileBinary}", "APK"); - binary.ExtractToFile(tempFileBinary, true); - Logger.InfoNewline($"Extracting APK/{globalMetadata.FullName} to {tempFileMeta}", "APK"); - globalMetadata.ExtractToFile(tempFileMeta, true); + args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(ggmBytes); + } + else + { + Logger.InfoNewline("Reading data.unity3d to determine unity version...", "APK"); + using var du3dStream = dataUnity3d!.Open(); - args.PathToAssembly = tempFileBinary; - args.PathToMetadata = tempFileMeta; + args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(du3dStream); + } - if (globalgamemanagers != null) - { - Logger.InfoNewline("Reading globalgamemanagers to determine unity version...", "APK"); - var ggmBytes = new byte[0x40]; - using var ggmStream = globalgamemanagers.Open(); - ggmStream.Read(ggmBytes, 0, 0x40); + Logger.InfoNewline($"Determined game's unity version to be {string.Join(".", args.UnityVersion)}", "APK"); - args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(ggmBytes); - } - else - { - Logger.InfoNewline("Reading data.unity3d to determine unity version...", "APK"); - using var du3dStream = dataUnity3d!.Open(); + args.Valid = true; + } - args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(du3dStream); - } + private static void HandleXapk(string gamePath, ref Cpp2IlRuntimeArgs args) + { + //XAPK file + //Contains two APKs - one starting with `config.` and one with the package name + //The config one is architecture-specific and so contains the binary + //The other contains the metadata + + Logger.InfoNewline($"Attempting to extract required files from XAPK {gamePath}", "XAPK"); + + using var xapkStream = File.OpenRead(gamePath); + using var xapkZip = new ZipArchive(xapkStream); + + var configApk = xapkZip.Entries.FirstOrDefault(e => e.FullName.Contains("config.") && e.FullName.EndsWith(".apk")); + var mainApk = xapkZip.Entries.FirstOrDefault(e => e != configApk && e.FullName.EndsWith(".apk")); + + Logger.InfoNewline($"Identified APKs inside XAPK - config: {configApk?.FullName}, mainPackage: {mainApk?.FullName}", "XAPK"); + + if (configApk == null) + throw new SoftException("Could not find a config apk inside the XAPK"); + if (mainApk == null) + throw new SoftException("Could not find a main apk inside the XAPK"); + + using var configZip = new ZipArchive(configApk.Open()); + using var mainZip = new ZipArchive(mainApk.Open()); + var binary = configZip.Entries.FirstOrDefault(e => e.FullName.EndsWith("libil2cpp.so")); + var globalMetadata = mainZip.Entries.FirstOrDefault(e => e.FullName.EndsWith("global-metadata.dat")); + + var globalgamemanagers = mainZip.Entries.FirstOrDefault(e => e.FullName.EndsWith("globalgamemanagers")); + var dataUnity3d = mainZip.Entries.FirstOrDefault(e => e.FullName.EndsWith("data.unity3d")); + + if (binary == null) + throw new SoftException("Could not find libil2cpp.so inside the config APK"); + if(globalMetadata == null) + throw new SoftException("Could not find global-metadata.dat inside the main APK"); + if(globalgamemanagers == null && dataUnity3d == null) + throw new SoftException("Could not find globalgamemanagers or data.unity3d inside the main APK"); + + var tempFileBinary = Path.GetTempFileName(); + var tempFileMeta = Path.GetTempFileName(); + + _pathsToDeleteOnExit.Add(tempFileBinary); + _pathsToDeleteOnExit.Add(tempFileMeta); + + Logger.InfoNewline($"Extracting XAPK/{configApk.Name}/{binary.FullName} to {tempFileBinary}", "XAPK"); + binary.ExtractToFile(tempFileBinary, true); + Logger.InfoNewline($"Extracting XAPK{mainApk.Name}/{globalMetadata.FullName} to {tempFileMeta}", "XAPK"); + globalMetadata.ExtractToFile(tempFileMeta, true); - Logger.InfoNewline($"Determined game's unity version to be {string.Join(".", args.UnityVersion)}", "APK"); + args.PathToAssembly = tempFileBinary; + args.PathToMetadata = tempFileMeta; - args.Valid = true; + if (globalgamemanagers != null) + { + Logger.InfoNewline("Reading globalgamemanagers to determine unity version...", "XAPK"); + var ggmBytes = new byte[0x40]; + using var ggmStream = globalgamemanagers.Open(); + ggmStream.Read(ggmBytes, 0, 0x40); + + args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(ggmBytes); } else { - throw new SoftException($"Could not find a valid unity game at {gamePath}"); + Logger.InfoNewline("Reading data.unity3d to determine unity version...", "XAPK"); + using var du3dStream = dataUnity3d!.Open(); + + args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(du3dStream); + } + + Logger.InfoNewline($"Determined game's unity version to be {string.Join(".", args.UnityVersion)}", "XAPK"); + + args.Valid = true; + } + + private static void HandleMachOGamePath(string gamePath, ref Cpp2IlRuntimeArgs args) + { + //APP + //Metadata: Contents/Resources/Data/il2cpp_data/Metadata/global-metadata.dat + //Binary: Contents/Frameworks/GameAssembly.dylib + + Logger.InfoNewline($"Attempting to extract required files from APP {gamePath}", "APP"); + + var binary = Path.Combine(gamePath, "Contents", "Frameworks", "GameAssembly.dylib"); + var globalMetadata = Path.Combine(gamePath, "Contents", "Resources", "Data", "il2cpp_data", "Metadata", "global-metadata.dat"); + var globalgamemanagers = Path.Combine(gamePath, "Contents", "Resources", "Data", "globalgamemanagers"); + + if (binary == null) + throw new SoftException("Could not find GameAssembly.dylib inside the app"); + if (globalMetadata == null) + throw new SoftException("Could not find global-metadata.dat inside the app"); + if (globalgamemanagers == null) + throw new SoftException("Could not find globalgamemanagers inside the app"); + + args.PathToAssembly = binary; + args.PathToMetadata = globalMetadata; + + Logger.VerboseNewline("Attempting to get unity version..."); + + Logger.InfoNewline("Reading globalgamemanagers to determine unity version...", "APP"); + var uv = (File.Exists(globalgamemanagers) ? + Cpp2IlApi.GetVersionFromGlobalGameManagers(File.ReadAllBytes(globalgamemanagers)) : null); + Logger.VerboseNewline($"First-attempt unity version detection gave: {(uv == null ? "null" : string.Join(".", uv))}"); + + if (uv == null) + { + Logger.Warn("Could not determine unity version, probably due to not running on windows and not having any assets files to determine it from. Enter unity version, if known, in the format of (xxxx.x.x), else nothing to fail: "); + var userInputUv = Console.ReadLine(); + uv = userInputUv?.Split('.').Select(int.Parse).ToArray(); + + if (uv == null) + throw new SoftException("Failed to determine unity version. If you're not running on windows, I need a globalgamemanagers file, or you need to use the force options."); } + + args.UnityVersion = uv; + + Logger.InfoNewline($"Determined game's unity version to be {string.Join(".", args.UnityVersion)}", "APP"); + + args.Valid = true; } private static Cpp2IlRuntimeArgs GetRuntimeOptionsFromCommandLine(string[] commandLine) @@ -201,6 +339,7 @@ private static Cpp2IlRuntimeArgs GetRuntimeOptionsFromCommandLine(string[] comma result.IlToAsmContinueThroughErrors = options.ThrowSafetyOutTheWindow; result.DisableMethodDumps = options.DisableMethodDumps; result.SimpleAttributeRestoration = options.SimpleAttributeRestoration; + result.WasmFrameworkJsFile = options.WasmFrameworkFilePath; if (options.UserIsImpatient) { @@ -282,25 +421,26 @@ public static int MainWithArgs(Cpp2IlRuntimeArgs runtimeArgs) ConsoleLogger.ShowVerbose = runtimeArgs.EnableVerboseLogging; + if (runtimeArgs.WasmFrameworkJsFile != null) + try + { + var frameworkJs = File.ReadAllText(runtimeArgs.WasmFrameworkJsFile); + var remaps = WasmUtils.ExtractAndParseDynCallRemaps(frameworkJs); + Logger.InfoNewline($"Parsed {remaps.Count} dynCall remaps from {runtimeArgs.WasmFrameworkJsFile}"); + WasmFile.RemappedDynCallFunctions = remaps; + } + catch (Exception e) + { + WasmFile.RemappedDynCallFunctions = null; + Logger.WarnNewline($"Failed to parse dynCall remaps from Wasm Framework Javascript File: {e}. They will not be used, so you probably won't get method bodies!"); + } + else + WasmFile.RemappedDynCallFunctions = null; + Cpp2IlApi.InitializeLibCpp2Il(runtimeArgs.PathToAssembly, runtimeArgs.PathToMetadata, runtimeArgs.UnityVersion, runtimeArgs.EnableRegistrationPrompts); Cpp2IlApi.MakeDummyDLLs(runtimeArgs.SuppressAttributes); -#if NET6_0 - //Fix capstone native library loading on non-windows - - try - { - var allInstructionsField = typeof(Arm64KeyFunctionAddresses).GetField("_allInstructions", BindingFlags.Instance | BindingFlags.NonPublic); - var arm64InstructionType = allInstructionsField!.FieldType.GenericTypeArguments.First(); - NativeLibrary.SetDllImportResolver(arm64InstructionType.Assembly, DllImportResolver); - } - catch (Exception e) - { - Logger.WarnNewline("Unable to hook native library resolving for Capstone. If you're not on windows and analysing an ARM or ARM64 binary, expect this to crash!"); - } -#endif - if (runtimeArgs.EnableMetadataGeneration) Cpp2IlApi.GenerateMetadataForAllAssemblies(runtimeArgs.OutputRootDirectory); @@ -321,14 +461,14 @@ public static int MainWithArgs(Cpp2IlRuntimeArgs runtimeArgs) Logger.InfoNewline($"Applying type, method, and field attributes for {Cpp2IlApi.GeneratedAssemblies.Count} assemblies...This may take a couple of seconds"); var start = DateTime.Now; - Cpp2IlApi.RunAttributeRestorationForAllAssemblies(keyFunctionAddresses, parallel: LibCpp2IlMain.MetadataVersion >= 29 || LibCpp2IlMain.Binary!.InstructionSet is InstructionSet.X86_32 or InstructionSet.X86_64); + Cpp2IlApi.RunAttributeRestorationForAllAssemblies(runtimeArgs.SimpleAttributeRestoration ? null : keyFunctionAddresses, parallel: LibCpp2IlMain.MetadataVersion >= 29 || LibCpp2IlMain.Binary!.InstructionSet is InstructionSet.X86_32 or InstructionSet.X86_64); Logger.InfoNewline($"Finished Applying Attributes in {(DateTime.Now - start).TotalMilliseconds:F0}ms"); if (runtimeArgs.EnableAnalysis) Cpp2IlApi.PopulateConcreteImplementations(); - - // Cpp2IlApi.HarmonyPatchCecilForBetterExceptions(); + + Cpp2IlApi.HarmonyPatchCecilForBetterExceptions(); Cpp2IlApi.SaveAssemblies(runtimeArgs.OutputRootDirectory); @@ -376,28 +516,7 @@ private static void DoAnalysisForAssembly(string assemblyName, AnalysisLevel ana Cpp2IlApi.AnalyseAssembly(analysisLevel, targetAssembly, keyFunctionAddresses, skipDumps ? null : Path.Combine(rootDir, "types"), parallel, continueThroughErrors); if (doIlToAsm) - { - Cpp2IlApi.HarmonyPatchCecilForBetterExceptions(); Cpp2IlApi.SaveAssemblies(rootDir, new List {targetAssembly}); - } - } - - - private static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) - { - if (libraryName == "capstone") - { - // On linux, try .so.4 - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { -#if NET6_0 - return NativeLibrary.Load("lib" + libraryName + ".so.4", assembly, searchPath); -#endif - } - } - - // Otherwise, fallback to default import resolver. - return IntPtr.Zero; } } -} \ No newline at end of file +} diff --git a/Cpp2IL/Properties/AssemblyInfo.cs b/Cpp2IL/Properties/AssemblyInfo.cs index 7b12bff9..6ce423a7 100644 --- a/Cpp2IL/Properties/AssemblyInfo.cs +++ b/Cpp2IL/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Samboy063")] [assembly: AssemblyProduct("Cpp2IL")] -[assembly: AssemblyCopyright("Copyright © Samboy063 2019-2021")] +[assembly: AssemblyCopyright("Copyright © Samboy063 2019-2022")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -31,5 +31,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2021.6.1")] -[assembly: AssemblyFileVersion("2021.6.1")] \ No newline at end of file +[assembly: AssemblyVersion("2022.0.7")] +[assembly: AssemblyFileVersion("2022.0.7")] \ No newline at end of file diff --git a/LibCpp2IL/BinarySearcher.cs b/LibCpp2IL/BinarySearcher.cs index 43743f13..fa055ea8 100644 --- a/LibCpp2IL/BinarySearcher.cs +++ b/LibCpp2IL/BinarySearcher.cs @@ -147,18 +147,24 @@ internal ulong FindCodeRegistrationPost2019() else { //but in v27 it's close to the LAST codegen module (winrt.dll is an exception), so we need to work back until we find an xref. - var sanityCheckNumberOfModules = 200; + var sanityCheckNumberOfModules = 400UL; var pSomewhereInCodegenModules = pMscorlibCodegenEntryInCodegenModulesList.AsEnumerable(); - for (var backtrack = 0; backtrack < sanityCheckNumberOfModules && (pCodegenModules?.Count() ?? 0) != 1; backtrack++) + var numModuleDefs = LibCpp2IlMain.TheMetadata!.imageDefinitions.Length; + var initialBacktrack = (ulong) numModuleDefs - 5L; + + pSomewhereInCodegenModules = pSomewhereInCodegenModules.Select(va => va - ptrSize * initialBacktrack); + + //Slightly experimental, but we're gonna try backtracking most of the way through the number of modules. Not all the way because we don't want to overshoot. + for (var backtrack = initialBacktrack; backtrack < sanityCheckNumberOfModules && (pCodegenModules?.Count() ?? 0) != 1; backtrack++) { pCodegenModules = FindAllMappedWords(pSomewhereInCodegenModules).ToList(); //Sanity check the count, which is one pointer back if (pCodegenModules.Count == 1) { - var moduleCount = _binary.ReadClassAtVirtualAddress(pCodegenModules.First() - ptrSize); + var moduleCount = _binary.ReadClassAtVirtualAddress(pCodegenModules.First() - ptrSize); - if (moduleCount < 0 || moduleCount > sanityCheckNumberOfModules) + if (moduleCount > sanityCheckNumberOfModules) pCodegenModules = new(); } @@ -187,6 +193,13 @@ internal ulong FindCodeRegistrationPost2019() { //...and subtract that from our pointer. var address = pCodegenModule - bytesToGoBack; + + if (pCodegenModules.Count == 1) + { + LibLogger.VerboseNewline($"\t\t\tOnly found one codegen module pointer, so assuming it's correct and returning pCodeReg = 0x{address:X}"); + return address; + } + LibLogger.Verbose($"\t\t\tConsidering potential code registration at 0x{address:X}..."); var codeReg = LibCpp2IlMain.Binary!.ReadClassAtVirtualAddress(address); @@ -215,7 +228,8 @@ public static bool ValidateCodeRegistration(Il2CppCodeRegistration codeReg, Dict if (keyValuePair.Key.EndsWith("count", StringComparison.OrdinalIgnoreCase)) { - if (fieldValue > 0x60_000) + //19 Apr 2022: Upped to 0x70000 due to Zenith (which has genericMethodPointersCount = 0x6007B) + if (fieldValue > 0x70_000) { LibLogger.VerboseNewline($"Rejected due to unreasonable count field 0x{fieldValue:X} for field {keyValuePair.Key}"); success = false; @@ -320,8 +334,8 @@ public ulong FindMetadataRegistrationPost24_5() if (LibCpp2IlMain.MetadataVersion >= 27f && (metaReg.metadataUsagesCount != 0 || metaReg.metadataUsages != 0)) { //Too many metadata usages - should be 0 on v27 - LibLogger.VerboseNewline($"\t\t\tRejecting metadata registration 0x{va:X} because it has {metaReg.metadataUsagesCount} metadata usages at a pointer of 0x{metaReg.metadataUsages:X}. We're on v27, these should be 0."); - continue; + LibLogger.VerboseNewline($"\t\t\tWarning: metadata registration 0x{va:X} has {metaReg.metadataUsagesCount} metadata usages at a pointer of 0x{metaReg.metadataUsages:X}. We're on v27, these should be 0."); + // continue; } if (metaReg.typeDefinitionsSizesCount != LibCpp2IlMain.TheMetadata!.typeDefs.Length) @@ -335,6 +349,13 @@ public ulong FindMetadataRegistrationPost24_5() LibLogger.VerboseNewline($"\t\t\tRejecting metadata registration 0x{va:X} because it has {metaReg.numTypes} types, while we have {LibCpp2IlMain.TheMetadata!.typeDefs.Length} type defs"); continue; } + + if (metaReg.fieldOffsetsCount != LibCpp2IlMain.TheMetadata!.typeDefs.Length) + { + //If we see any cases of failing to find meta reg and this line is in verbose log, maybe the assumption (num field offsets == num type defs) is wrong. + LibLogger.VerboseNewline($"\t\t\tRejecting metadata registration 0x{va:X} because it has {metaReg.fieldOffsetsCount} field offsets, while metadata file defines {LibCpp2IlMain.TheMetadata!.typeDefs.Length} type defs"); + continue; + } LibLogger.VerboseNewline($"\t\t\tAccepting metadata reg as VA 0x{va:X}"); return va; diff --git a/LibCpp2IL/BinaryStructures/Il2CppCodeRegistration.cs b/LibCpp2IL/BinaryStructures/Il2CppCodeRegistration.cs index 9b250575..9674ecf1 100644 --- a/LibCpp2IL/BinaryStructures/Il2CppCodeRegistration.cs +++ b/LibCpp2IL/BinaryStructures/Il2CppCodeRegistration.cs @@ -3,8 +3,8 @@ namespace LibCpp2IL.BinaryStructures public class Il2CppCodeRegistration { - [Version(Max = 24.1f)] public ulong methodPointersCount; - [Version(Max = 24.1f)] public ulong methodPointers; + [Version(Max = 24.15f)] public ulong methodPointersCount; + [Version(Max = 24.15f)] public ulong methodPointers; public ulong reversePInvokeWrapperCount; public ulong reversePInvokeWrappers; @@ -25,8 +25,17 @@ public class Il2CppCodeRegistration [Version(Max = 24.5f)] //Removed in v27 public ulong customAttributeGeneratorListAddress; - public ulong unresolvedVirtualCallCount; - public ulong unresolvedVirtualCallPointers; + public ulong unresolvedVirtualCallCount; //Renamed to unresolvedIndirectCallCount in v29.1 + public ulong unresolvedVirtualCallPointers; //Renamed to unresolvedIndirectCallPointers in v29.1 + + //Not in 2021.3 version of v31, but present in 2022.3 (v31.1) + [Version(Min = 29.1f, Max = 29.1f)] + [Version(Min = 31.1f)] + public ulong unresolvedInstanceCallPointers; + + [Version(Min = 29.1f, Max = 29.1f)] + [Version(Min = 31.1f)] + public ulong unresolvedStaticCallPointers; public ulong interopDataCount; public ulong interopData; diff --git a/LibCpp2IL/ClassReadingBinaryReader.cs b/LibCpp2IL/ClassReadingBinaryReader.cs index f3dadbdc..d6ff13d1 100644 --- a/LibCpp2IL/ClassReadingBinaryReader.cs +++ b/LibCpp2IL/ClassReadingBinaryReader.cs @@ -264,7 +264,6 @@ private object ReadAndConvertPrimitive(bool overrideArchCheck, Type type) public virtual string ReadStringToNull(long offset) { - var builder = new List(); var obtained = false; PositionShiftLock.Enter(ref obtained); @@ -275,12 +274,8 @@ public virtual string ReadStringToNull(long offset) try { Position = offset; - byte b; - while ((b = (byte) _memoryStream.ReadByte()) != 0) - builder.Add(b); - - return Encoding.UTF8.GetString(builder.ToArray()); + return ReadStringToNullAtCurrentPos(); } finally { @@ -288,6 +283,18 @@ public virtual string ReadStringToNull(long offset) } } + public string ReadStringToNullAtCurrentPos() + { + var builder = new List(); + byte b; + var sanity = 0; + while ((b = (byte)_memoryStream.ReadByte()) != 0 && ++sanity < 32768) + builder.Add(b); + + + return Encoding.UTF8.GetString(builder.ToArray()); + } + public byte[] ReadByteArrayAtRawAddress(long offset, int count) { var obtained = false; @@ -374,5 +381,7 @@ private static FieldInfo[] GetFieldsCached(Type t) CachedFields[t] = ret = t.GetFields(); return ret; } + + public ulong ReadNUint() => is32Bit ? ReadUInt32() : ReadUInt64(); } } \ No newline at end of file diff --git a/LibCpp2IL/Elf/ElfDynamicType.cs b/LibCpp2IL/Elf/ElfDynamicType.cs index 6502a98a..c803d93b 100644 --- a/LibCpp2IL/Elf/ElfDynamicType.cs +++ b/LibCpp2IL/Elf/ElfDynamicType.cs @@ -2,6 +2,9 @@ { public enum ElfDynamicType : long { + DT_NULL = 0, + DT_NEEDED = 1, + DT_PLTRELSZ = 2, DT_PLTGOT = 0x3, DT_HASH = 0x4, DT_STRTAB = 0x5, @@ -9,14 +12,25 @@ public enum ElfDynamicType : long DT_RELA = 0x7, DT_RELASZ = 0x8, DT_RELAENT = 0x9, + DT_STRSZ = 0xa, + DT_SYMENT = 0xb, DT_INIT = 0xC, DT_FINI = 0xD, DT_REL = 0x11, DT_RELSZ = 0x12, DT_RELENT = 0x13, + DT_PLTREL = 0x14, + DT_DEBUG = 0x15, + DT_TEXTREL = 0x16, DT_JMPREL = 0x17, + DT_BIND_NOW = 0x18, DT_INIT_ARRAY = 0x19, DT_FINI_ARRAY = 0x1A, - DT_INIT_ARRAYSZ = 0x1B + DT_INIT_ARRAYSZ = 0x1B, + DT_FINI_ARRAYSZ = 0x1C, + DT_RUNPATH = 0x1D, + DT_FLAGS = 0x1E, + DT_PREINIT_ARRAY = 0x20, + DT_PREINIT_ARRAYSZ = 0x21, } } \ No newline at end of file diff --git a/LibCpp2IL/Elf/ElfFile.cs b/LibCpp2IL/Elf/ElfFile.cs index c1dcc99c..b98c0734 100644 --- a/LibCpp2IL/Elf/ElfFile.cs +++ b/LibCpp2IL/Elf/ElfFile.cs @@ -290,6 +290,7 @@ private void ProcessRelocations() (ElfRelocationType.R_386_JMP_SLOT, InstructionSet.X86_32) => (symValue, true), // S (ElfRelocationType.R_AMD64_64, InstructionSet.X86_64) => (symValue + addend, true), // S + A + (ElfRelocationType.R_AMD64_RELATIVE, InstructionSet.X86_64) => (addend, true), //Base address + A _ => (0, false) }; @@ -591,15 +592,17 @@ private void ProcessInitializers() { LibLogger.VerboseNewline("Searching for il2cpp structures in an ELF binary using non-arch-specific method..."); - var searcher = new BinarySearcher(this, LibCpp2IlMain.TheMetadata!.methodDefs.Count(x => x.methodIndex >= 0), LibCpp2IlMain.TheMetadata!.typeDefs.Length); - - LibLogger.VerboseNewline("\tLooking for code reg (this might take a while)..."); - var codeReg = LibCpp2IlMain.MetadataVersion >= 24.2f ? searcher.FindCodeRegistrationPost2019() : searcher.FindCodeRegistrationPre2019(); - LibLogger.VerboseNewline($"\tGot code reg 0x{codeReg:X}"); + // var searcher = new BinarySearcher(this, LibCpp2IlMain.TheMetadata!.methodDefs.Count(x => x.methodIndex >= 0), LibCpp2IlMain.TheMetadata!.typeDefs.Length); - LibLogger.VerboseNewline($"\tLooking for meta reg ({(LibCpp2IlMain.MetadataVersion >= 27f ? "post-27" : "pre-27")})..."); - var metaReg = LibCpp2IlMain.MetadataVersion >= 27f ? searcher.FindMetadataRegistrationPost24_5() : searcher.FindMetadataRegistrationPre24_5(); - LibLogger.VerboseNewline($"\tGot meta reg 0x{metaReg:x}"); + var (codeReg, metaReg) = PlusSearch(LibCpp2IlMain.TheMetadata!.methodDefs.Count(x => x.methodIndex >= 0), LibCpp2IlMain.TheMetadata!.typeDefs.Length); + + // LibLogger.VerboseNewline("\tLooking for code reg (this might take a while)..."); + // var codeReg = LibCpp2IlMain.MetadataVersion >= 24.2f ? searcher.FindCodeRegistrationPost2019() : searcher.FindCodeRegistrationPre2019(); + // LibLogger.VerboseNewline($"\tGot code reg 0x{codeReg:X}"); + // + // LibLogger.VerboseNewline($"\tLooking for meta reg ({(LibCpp2IlMain.MetadataVersion >= 27f ? "post-27" : "pre-27")})..."); + // var metaReg = LibCpp2IlMain.MetadataVersion >= 27f ? searcher.FindMetadataRegistrationPost24_5() : searcher.FindMetadataRegistrationPre24_5(); + // LibLogger.VerboseNewline($"\tGot meta reg 0x{metaReg:x}"); return (codeReg, metaReg); @@ -633,6 +636,12 @@ public override ulong MapRawAddressToVirtual(uint offset) public override byte[] GetRawBinaryContent() => _raw; + public override ulong[] GetAllExportedIl2CppFunctionPointers() => _exportTable + .Where(p => p.Key.StartsWith("il2cpp_")) + .Select(p => p.Value.VirtualAddress) + .Where(va => va > 0) + .ToArray(); + public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind) { if (!_exportTable.TryGetValue(toFind, out var exportedSymbol)) diff --git a/LibCpp2IL/Elf/ElfRelaEntry.cs b/LibCpp2IL/Elf/ElfRelaEntry.cs index f82a67a4..c3b2287c 100644 --- a/LibCpp2IL/Elf/ElfRelaEntry.cs +++ b/LibCpp2IL/Elf/ElfRelaEntry.cs @@ -5,5 +5,8 @@ public class ElfRelaEntry public ulong Offset; public ulong Info; public ulong Addend; + + public ElfRelocationType Type => (ElfRelocationType) (Info & 0xFFFF_FFFF); + public ulong Symbol => Info >> 32; } } \ No newline at end of file diff --git a/LibCpp2IL/Elf/ElfRelocationType.cs b/LibCpp2IL/Elf/ElfRelocationType.cs index 3d11ead9..9ac22449 100644 --- a/LibCpp2IL/Elf/ElfRelocationType.cs +++ b/LibCpp2IL/Elf/ElfRelocationType.cs @@ -18,6 +18,7 @@ public enum ElfRelocationType : uint R_386_GLOB_DAT = 6, R_386_JMP_SLOT = 7, - R_AMD64_64 = 1 + R_AMD64_64 = 1, + R_AMD64_RELATIVE = 8, } } \ No newline at end of file diff --git a/LibCpp2IL/IIl2CppTokenProvider.cs b/LibCpp2IL/IIl2CppTokenProvider.cs new file mode 100644 index 00000000..b00ba001 --- /dev/null +++ b/LibCpp2IL/IIl2CppTokenProvider.cs @@ -0,0 +1,7 @@ +namespace LibCpp2IL +{ + public interface IIl2CppTokenProvider + { + public uint Token { get; } + } +} \ No newline at end of file diff --git a/LibCpp2IL/Il2CppBinary.cs b/LibCpp2IL/Il2CppBinary.cs index 07a0a17f..f4de9207 100644 --- a/LibCpp2IL/Il2CppBinary.cs +++ b/LibCpp2IL/Il2CppBinary.cs @@ -387,6 +387,8 @@ public ulong GetMethodPointer(int methodIndex, int methodDefinitionIndex, int im public abstract byte[] GetRawBinaryContent(); public abstract ulong GetVirtualAddressOfExportedFunctionByName(string toFind); + + public abstract ulong[] GetAllExportedIl2CppFunctionPointers(); public abstract byte[] GetEntirePrimaryExecutableSection(); diff --git a/LibCpp2IL/LibCpp2IL.csproj b/LibCpp2IL/LibCpp2IL.csproj index b3e0a380..ecf32712 100644 --- a/LibCpp2IL/LibCpp2IL.csproj +++ b/LibCpp2IL/LibCpp2IL.csproj @@ -2,11 +2,10 @@ enable - ubuntu-x64;win-x64;osx-x64 9 netstandard2.0 Samboy063.LibCpp2IL - 2021.6.1 + 2022.0.7 true MIT git diff --git a/LibCpp2IL/LibCpp2IlMain.cs b/LibCpp2IL/LibCpp2IlMain.cs index 5fd8e51c..bd0d3e71 100644 --- a/LibCpp2IL/LibCpp2IlMain.cs +++ b/LibCpp2IL/LibCpp2IlMain.cs @@ -4,6 +4,7 @@ using System.Linq; using LibCpp2IL.Elf; using LibCpp2IL.Logging; +using LibCpp2IL.MachO; using LibCpp2IL.Metadata; using LibCpp2IL.NintendoSwitch; using LibCpp2IL.Reflection; @@ -33,6 +34,12 @@ public static void Reset() LibCpp2IlGlobalMapper.Reset(); LibCpp2ILUtils.Reset(); MethodsByPtr.Clear(); + + MetadataVersion = 0f; + Binary?.Dispose(); + TheMetadata?.Dispose(); + Binary = null; + TheMetadata = null; } public static List? GetManagedMethodImplementationsAtAddress(ulong addr) @@ -165,7 +172,7 @@ public static bool Initialize(byte[] binaryBytes, byte[] metadataBytes, int[] un } else if (BitConverter.ToInt32(binaryBytes, 0) == 0x304F534E) //NSO0 { - var nso = new NsoFile(new MemoryStream(binaryBytes, 0, binaryBytes.Length, false, true), TheMetadata.maxMetadataUsages); + var nso = new NsoFile(new MemoryStream(binaryBytes, 0, binaryBytes.Length, true, true), TheMetadata.maxMetadataUsages); nso = nso.Decompress(); Binary = nso; (codereg, metareg) = nso.PlusSearch(TheMetadata.methodDefs.Count(x => x.methodIndex >= 0), TheMetadata.typeDefs.Length); @@ -174,6 +181,11 @@ public static bool Initialize(byte[] binaryBytes, byte[] metadataBytes, int[] un var wasm = new WasmFile(new MemoryStream(binaryBytes, 0, binaryBytes.Length, false, true), TheMetadata.maxMetadataUsages); Binary = wasm; (codereg, metareg) = wasm.PlusSearch(TheMetadata.methodDefs.Count(x => x.methodIndex >= 0), TheMetadata.typeDefs.Length); + } else if (BitConverter.ToUInt32(binaryBytes, 0) is 0xFEEDFACE or 0xFEEDFACF) + { + var macho = new MachOFile(new MemoryStream(binaryBytes, 0, binaryBytes.Length, false, true), TheMetadata.maxMetadataUsages); + Binary = macho; + (codereg, metareg) = macho.PlusSearch(TheMetadata.methodDefs.Count(x => x.methodIndex >= 0), TheMetadata.typeDefs.Length); } else { diff --git a/LibCpp2IL/LibCpp2IlUtils.cs b/LibCpp2IL/LibCpp2IlUtils.cs index e17952c2..d946ba63 100644 --- a/LibCpp2IL/LibCpp2IlUtils.cs +++ b/LibCpp2IL/LibCpp2IlUtils.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Text; using LibCpp2IL.BinaryStructures; +using LibCpp2IL.Logging; using LibCpp2IL.Metadata; using LibCpp2IL.Reflection; @@ -13,42 +14,42 @@ public static class LibCpp2ILUtils { private static readonly Dictionary TypeString = new Dictionary { - {1, "void"}, - {2, "bool"}, - {3, "char"}, - {4, "sbyte"}, - {5, "byte"}, - {6, "short"}, - {7, "ushort"}, - {8, "int"}, - {9, "uint"}, - {10, "long"}, - {11, "ulong"}, - {12, "float"}, - {13, "double"}, - {14, "string"}, - {22, "TypedReference"}, - {24, "IntPtr"}, - {25, "UIntPtr"}, - {28, "object"} + { 1, "void" }, + { 2, "bool" }, + { 3, "char" }, + { 4, "sbyte" }, + { 5, "byte" }, + { 6, "short" }, + { 7, "ushort" }, + { 8, "int" }, + { 9, "uint" }, + { 10, "long" }, + { 11, "ulong" }, + { 12, "float" }, + { 13, "double" }, + { 14, "string" }, + { 22, "TypedReference" }, + { 24, "IntPtr" }, + { 25, "UIntPtr" }, + { 28, "object" } }; private static readonly Dictionary PrimitiveSizes = new() { - {"Byte", 1}, - {"SByte", 1}, - {"Boolean", 1}, - {"Int16", 2}, - {"UInt16", 2}, - {"Char", 2}, - {"Int32", 4}, - {"UInt32", 4}, - {"Single", 4}, - {"Int64", 8}, - {"UInt64", 8}, - {"Double", 8}, - {"IntPtr", 8}, - {"UIntPtr", 8}, + { "Byte", 1 }, + { "SByte", 1 }, + { "Boolean", 1 }, + { "Int16", 2 }, + { "UInt16", 2 }, + { "Char", 2 }, + { "Int32", 4 }, + { "UInt32", 4 }, + { "Single", 4 }, + { "Int64", 8 }, + { "UInt64", 8 }, + { "Double", 8 }, + { "IntPtr", 8 }, + { "UIntPtr", 8}, }; private static Dictionary _cachedVersionAttributes = new(); @@ -98,7 +99,7 @@ internal static string GetTypeName(Il2CppMetadata metadata, Il2CppBinary cppAsse if (LibCpp2IlMain.Binary == null || LibCpp2IlMain.TheMetadata == null) return null; var types = new List(); - var pointers = LibCpp2IlMain.Binary.ReadClassArrayAtVirtualAddress(genericInst.pointerStart, (long) genericInst.pointerCount); + var pointers = LibCpp2IlMain.Binary.ReadClassArrayAtVirtualAddress(genericInst.pointerStart, (long)genericInst.pointerCount); for (uint i = 0; i < genericInst.pointerCount; ++i) { var oriType = LibCpp2IlMain.Binary.GetIl2CppTypeFromPointer(pointers[i]); @@ -111,7 +112,7 @@ internal static string GetTypeName(Il2CppMetadata metadata, Il2CppBinary cppAsse internal static string GetGenericTypeParamNames(Il2CppMetadata metadata, Il2CppBinary cppAssembly, Il2CppGenericInst genericInst) { var typeNames = new List(); - var pointers = cppAssembly.ReadClassArrayAtVirtualAddress(genericInst.pointerStart, (long) genericInst.pointerCount); + var pointers = cppAssembly.ReadClassArrayAtVirtualAddress(genericInst.pointerStart, (long)genericInst.pointerCount); for (uint i = 0; i < genericInst.pointerCount; ++i) { var oriType = cppAssembly.GetIl2CppTypeFromPointer(pointers[i]); @@ -172,7 +173,7 @@ public static string GetTypeName(Il2CppMetadata metadata, Il2CppBinary cppAssemb break; } default: - ret = TypeString[(int) type.type]; + ret = TypeString[(int)type.type]; break; } @@ -227,9 +228,9 @@ public static string GetTypeName(Il2CppMetadata metadata, Il2CppBinary cppAssemb if (LibCpp2IlMain.MetadataVersion < 29) len = metadata.ReadClassAtRawAddr(pointer); else - len = (int) metadata.ReadUnityCompressedIntAtRawAddr(pointer, out lenLen); - if (len > 1024 * 32) - throw new Exception($"Unreasonable string length {len}"); + len = metadata.ReadUnityCompressedIntAtRawAddr(pointer, out lenLen); + if (len > 1024 * 64) + LibLogger.WarnNewline("[GetDefaultValue] String length is really large: " + len); return Encoding.UTF8.GetString(metadata.ReadByteArrayAtRawAddress(pointer + lenLen, len)); default: return null; @@ -312,13 +313,13 @@ public static Il2CppTypeReflectionData GetTypeReflectionData(Il2CppType forWhat) else { //This is slightly annoying, because we will have already read this type, but we have to re-read it. TODO FUTURE: Make a mapping of type definition addr => type def? - var type = LibCpp2IlMain.Binary.ReadClassAtVirtualAddress((ulong) genericClass.typeDefinitionIndex); + var type = LibCpp2IlMain.Binary.ReadClassAtVirtualAddress((ulong)genericClass.typeDefinitionIndex); type.Init(); typeDefinition = LibCpp2IlMain.TheMetadata!.typeDefs[type.data.classIndex]; } var genericInst = LibCpp2IlMain.Binary.ReadClassAtVirtualAddress(genericClass.context.class_inst); - var pointers = LibCpp2IlMain.Binary.GetPointers(genericInst.pointerStart, (long) genericInst.pointerCount); + var pointers = LibCpp2IlMain.Binary.GetPointers(genericInst.pointerStart, (long)genericInst.pointerCount); var genericParams = pointers .Select(pointer => LibCpp2IlMain.Binary.GetIl2CppTypeFromPointer(pointer)) .Select(type => GetTypeReflectionData(type)!) //Recursive call here @@ -345,7 +346,6 @@ public static Il2CppTypeReflectionData GetTypeReflectionData(Il2CppType forWhat) isType = false, isGenericType = false, variableGenericParamName = genericName, - variableGenericParamIndex = forWhat.data.genericParameterIndex, }; } case Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY: @@ -395,7 +395,7 @@ public static int VersionAwareSizeOf(Type type, bool dontCheckVersionAttributes type = type.GetEnumUnderlyingType(); if (type.IsPrimitive) - return (int) PrimitiveSizes[type.Name]; + return (int)PrimitiveSizes[type.Name]; var shouldDownsize = downsize && LibCpp2IlMain.Binary!.is32Bit; @@ -430,7 +430,7 @@ public static int VersionAwareSizeOf(Type type, bool dontCheckVersionAttributes default: if (field.FieldType == type) throw new Exception($"Infinite recursion is not allowed. Field {field} of type {type} has the same type as its parent."); - + size += VersionAwareSizeOf(field.FieldType, dontCheckVersionAttributes, downsize); break; } diff --git a/LibCpp2IL/MachO/LoadCommandId.cs b/LibCpp2IL/MachO/LoadCommandId.cs new file mode 100644 index 00000000..3321584e --- /dev/null +++ b/LibCpp2IL/MachO/LoadCommandId.cs @@ -0,0 +1,70 @@ +using System.Diagnostics.CodeAnalysis; + +namespace LibCpp2IL.MachO +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + public enum LoadCommandId : uint + { + LC_SEGMENT = 0x1, + LC_SYMTAB = 0x2, + LC_SYMSEG = 0x3, + LC_THREAD = 0x4, + LC_UNIXTHREAD = 0x5, + LC_LOADFVMLIB = 0x6, + LC_IDFVMLIB = 0x7, + LC_IDENT = 0x8, + LC_FVMFILE = 0x9, + LC_PREPAGE = 0xa, + LC_DYSYMTAB = 0xb, + LC_LOAD_DYLIB = 0xc, + LC_ID_DYLIB = 0xd, + LC_LOAD_DYLINKER = 0xe, + LC_ID_DYLINKER = 0xf, + LC_PREBOUND_DYLIB = 0x10, + LC_ROUTINES = 0x11, + LC_SUB_FRAMEWORK = 0x12, + LC_SUB_UMBRELLA = 0x13, + LC_SUB_CLIENT = 0x14, + LC_SUB_LIBRARY = 0x15, + LC_TWOLEVEL_HINTS = 0x16, + LC_PREBIND_CKSUM = 0x17, + LC_LOAD_WEAK_DYLIB = 0x18 | 0x80000000, + LC_SEGMENT_64 = 0x19, + LC_ROUTINES_64 = 0x1a, + LC_UUID = 0x1b, + LC_RPATH = 0x1c | 0x80000000, + LC_CODE_SIGNATURE = 0x1d, + LC_SEGMENT_SPLIT_INFO = 0x1e, + LC_REEXPORT_DYLIB = 0x1f | 0x80000000, + LC_LAZY_LOAD_DYLIB = 0x20, + LC_ENCRYPTION_INFO = 0x21, + LC_DYLD_INFO = 0x22, + LC_DYLD_INFO_ONLY = LC_DYLD_INFO | 0x80000000, + LC_LOAD_UPWARD_DYLIB = 0x23 | 0x80000000, + LC_VERSION_MIN_MACOSX = 0x24, + LC_VERSION_MIN_IPHONEOS = 0x25, + LC_FUNCTION_STARTS = 0x26, + LC_DYLD_ENVIRONMENT = 0x27, + LC_MAIN = 0x28 | 0x80000000, + LC_DATA_IN_CODE = 0x29, + LC_SOURCE_VERSION = 0x2a, + LC_DYLIB_CODE_SIGN_DRS = 0x2b, + LC_ENCRYPTION_INFO_64 = 0x2c, + LC_LINKER_OPTION = 0x2d, + LC_LINKER_OPTIMIZATION_HINT = 0x2e, + LC_VERSION_MIN_TVOS = 0x2f, + LC_VERSION_MIN_WATCHOS = 0x30, + LC_NOTE = 0x31, + LC_BUILD_VERSION = 0x32, + LC_DYLD_EXPORTS_TRIE = 0x33 | 0x80000000, + LC_DYLD_CHAINED_FIXUPS = 0x34 | 0x80000000, + LC_LOAD_WEAK_DYLIB_UUID_REEXPORT_DYLIB = 0x35, + LC_UUID_DYLIB_CODE_SIGN_DRS = 0x36, + LC_DYLD_CHAINED_FIXUPS_COUNT = 0x37, + LC_FUNCTION_STARTS_64 = 0x38, + LC_DYLD_DEBUG = 0x39, + LC_DYLD_CHAINED_FRAMEWORK = 0x3a, + LC_DYLD_CHAINED_DYLIB = 0x3b, + LC_LOAD_DYLINKER_WITH_LIBRARY_PATH = 0x3c, + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOCpuSubtype.cs b/LibCpp2IL/MachO/MachOCpuSubtype.cs new file mode 100644 index 00000000..2ff9500a --- /dev/null +++ b/LibCpp2IL/MachO/MachOCpuSubtype.cs @@ -0,0 +1,11 @@ +namespace LibCpp2IL.MachO +{ + public enum MachOCpuSubtype + { + CPU_SUBTYPE_ANY = 100, + + CPU_SUBTYPE_ARM64_ALL = 0, + CPU_SUBTYPE_ARM64_V8 = 1, + CPU_SUBTYPE_ARM64E = 2, + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOCpuType.cs b/LibCpp2IL/MachO/MachOCpuType.cs new file mode 100644 index 00000000..37af6db9 --- /dev/null +++ b/LibCpp2IL/MachO/MachOCpuType.cs @@ -0,0 +1,31 @@ +namespace LibCpp2IL.MachO +{ + public enum MachOCpuType + { + CPU_TYPE_ANY = -1, + CPU_TYPE_VAX = 1, + //Skip 2-5 + CPU_TYPE_MC680x0 = 6, + CPU_TYPE_I386 = 7, + CPU_TYPE_X86_64 = CPU_TYPE_I386 | CPU_ARCH_ABI64, + CPU_TYPE_MIPS = 8, + //Skip 9 + CPU_TYPE_MC98000 = 10, + CPU_TYPE_HPPA = 11, + CPU_TYPE_ARM = 12, + CPU_TYPE_ARM64 = CPU_TYPE_ARM | CPU_ARCH_ABI64, + CPU_TYPE_ARM64_32 = CPU_TYPE_ARM | CPU_ARCH_ABI64_32, + CPU_TYPE_MC88000 = 13, + CPU_TYPE_SPARC = 14, + CPU_TYPE_I860 = 15, + CPU_TYPE_ALPHA = 16, + //Skip 17 + CPU_TYPE_POWERPC = 18, + CPU_TYPE_POWERPC64 = CPU_TYPE_POWERPC | CPU_ARCH_ABI64, + + + //Helper values + CPU_ARCH_ABI64 = 0x01000000, + CPU_ARCH_ABI64_32 = 0x02000000, + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachODynamicLinkerCommand.cs b/LibCpp2IL/MachO/MachODynamicLinkerCommand.cs new file mode 100644 index 00000000..39821712 --- /dev/null +++ b/LibCpp2IL/MachO/MachODynamicLinkerCommand.cs @@ -0,0 +1,43 @@ +using System; + +namespace LibCpp2IL.MachO +{ + public class MachODynamicLinkerCommand + { + public int RebaseOffset; + public int RebaseSize; + public int BindOffset; + public int BindSize; + public int WeakBindOffset; + public int WeakBindSize; + public int LazyBindOffset; + public int LazyBindSize; + public int ExportOffset; + public int ExportSize; + + public MachOExportEntry[] Exports = Array.Empty(); + + public void Read(ClassReadingBinaryReader reader) + { + RebaseOffset = reader.ReadInt32(); + RebaseSize = reader.ReadInt32(); + BindOffset = reader.ReadInt32(); + BindSize = reader.ReadInt32(); + WeakBindOffset = reader.ReadInt32(); + WeakBindSize = reader.ReadInt32(); + LazyBindOffset = reader.ReadInt32(); + LazyBindSize = reader.ReadInt32(); + ExportOffset = reader.ReadInt32(); + ExportSize = reader.ReadInt32(); + + var returnTo = reader.BaseStream.Position; + + reader.BaseStream.Position = ExportOffset; + + var exports = new MachOExportTrie(reader); + Exports = exports.Entries.ToArray(); + + reader.BaseStream.Position = returnTo; + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOExportEntry.cs b/LibCpp2IL/MachO/MachOExportEntry.cs new file mode 100644 index 00000000..872bce8e --- /dev/null +++ b/LibCpp2IL/MachO/MachOExportEntry.cs @@ -0,0 +1,20 @@ +namespace LibCpp2IL.MachO +{ + public class MachOExportEntry + { + public string Name; + public long Address; + public long Flags; + public long Other; + public string? ImportName; + + public MachOExportEntry(string name, long address, long flags, long other, string? importName) + { + Name = name; + Address = address; + Flags = flags; + Other = other; + ImportName = importName; + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOExportTrie.cs b/LibCpp2IL/MachO/MachOExportTrie.cs new file mode 100644 index 00000000..97ac3e6a --- /dev/null +++ b/LibCpp2IL/MachO/MachOExportTrie.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; + +namespace LibCpp2IL.MachO +{ + public class MachOExportTrie + { + public List Entries = new(); + + private ClassReadingBinaryReader _reader; + private long _basePtr; + + public MachOExportTrie(ClassReadingBinaryReader reader) + { + _reader = reader; + _basePtr = reader.BaseStream.Position; + + var children = ParseNode("", 0); + while (children.Count > 0) + { + var current = children[0]; + children.RemoveAt(0); + children.AddRange(ParseNode(current.Name, current.Offset)); + } + } + + private List ParseNode(string name, int offset) + { + var children = new List(); + _reader.BaseStream.Position = _basePtr + offset; + + var terminalSize = _reader.BaseStream.ReadLEB128Unsigned(); + var childrenIndex = _reader.BaseStream.Position + (long)terminalSize; + if (terminalSize != 0) + { + var flags = (ExportFlags) _reader.BaseStream.ReadLEB128Unsigned(); + var address = 0L; + var other = 0L; + string? importName = null; + + if ((flags & ExportFlags.ReExport) != 0) + { + other = _reader.BaseStream.ReadLEB128Signed(); + importName = _reader.ReadStringToNullAtCurrentPos(); + } + else + { + address = _reader.BaseStream.ReadLEB128Signed(); + if ((flags & ExportFlags.StubAndResolver) != 0) + other = _reader.BaseStream.ReadLEB128Signed(); + } + + Entries.Add(new(name, address, (long) flags, other, importName)); + } + + _reader.BaseStream.Position = childrenIndex; + var numChildren = _reader.BaseStream.ReadLEB128Unsigned(); + for (var i = 0ul; i < numChildren; i++) + { + var childName = _reader.ReadStringToNullAtCurrentPos(); + var childOffset = _reader.BaseStream.ReadLEB128Unsigned(); + children.Add(new Node {Name = name + childName, Offset = (int) childOffset}); + } + + return children; + } + + [Flags] + private enum ExportFlags + { + KindRegular = 0, + KindThreadLocal = 1, + KindAbsolute = 2, + WeakDefinition = 4, + ReExport = 8, + StubAndResolver = 0x10 + } + + private struct Node + { + public string Name; + public int Offset; + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOFile.cs b/LibCpp2IL/MachO/MachOFile.cs new file mode 100644 index 00000000..e2f5d61d --- /dev/null +++ b/LibCpp2IL/MachO/MachOFile.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using LibCpp2IL.Logging; + +namespace LibCpp2IL.MachO +{ + public class MachOFile : Il2CppBinary + { + private byte[] _raw; + + private readonly MachOHeader _header; + private readonly MachOLoadCommand[] _loadCommands; + + private readonly MachOSegmentCommand[] Segments64; + private readonly MachOSection[] Sections64; + private Dictionary _exportsDict; + + public MachOFile(MemoryStream input, long maxMetadataUsages) : base(input, maxMetadataUsages) + { + _raw = input.GetBuffer(); + + LibLogger.Verbose("\tReading Mach-O header..."); + _header = new(); + _header.Read(this); + + switch (_header.Magic) + { + case MachOHeader.MAGIC_32_BIT: + LibLogger.Verbose("Mach-O is 32-bit..."); + is32Bit = true; + break; + case MachOHeader.MAGIC_64_BIT: + LibLogger.Verbose("Mach-O is 64-bit..."); + is32Bit = false; + break; + default: + throw new($"Unknown Mach-O Magic: {_header.Magic}"); + } + + switch (_header.CpuType) + { + case MachOCpuType.CPU_TYPE_I386: + LibLogger.VerboseNewline("Mach-O contains x86_32 instructions."); + InstructionSet = InstructionSet.X86_32; + break; + case MachOCpuType.CPU_TYPE_X86_64: + LibLogger.VerboseNewline("Mach-O contains x86_64 instructions."); + InstructionSet = InstructionSet.X86_64; + break; + case MachOCpuType.CPU_TYPE_ARM: + LibLogger.VerboseNewline("Mach-O contains ARM (32-bit) instructions."); + InstructionSet = InstructionSet.ARM32; + break; + case MachOCpuType.CPU_TYPE_ARM64: + LibLogger.VerboseNewline("Mach-O contains ARM64 instructions."); + InstructionSet = InstructionSet.ARM64; + break; + default: + throw new($"Don't know how to handle a Mach-O CPU Type of {_header.CpuType}"); + } + + if(_header.Magic == MachOHeader.MAGIC_32_BIT) + LibLogger.ErrorNewline("32-bit MACH-O files have not been tested! Please report any issues."); + else + LibLogger.WarnNewline("Mach-O Support is experimental. Please open an issue if anything seems incorrect."); + + LibLogger.Verbose("\tReading Mach-O load commands..."); + _loadCommands = new MachOLoadCommand[_header.NumLoadCommands]; + for (var i = 0; i < _header.NumLoadCommands; i++) + { + _loadCommands[i] = new(); + _loadCommands[i].Read(this); + } + LibLogger.VerboseNewline($"Read {_loadCommands.Length} load commands."); + + Segments64 = _loadCommands.Where(c => c.Command == LoadCommandId.LC_SEGMENT_64).Select(c => c.CommandData).Cast().ToArray(); + Sections64 = Segments64.SelectMany(s => s.Sections).ToArray(); + + var dyldData = _loadCommands.FirstOrDefault(c => c.Command is LoadCommandId.LC_DYLD_INFO or LoadCommandId.LC_DYLD_INFO_ONLY)?.CommandData as MachODynamicLinkerCommand; + var exports = dyldData?.Exports ?? Array.Empty(); + _exportsDict = exports.ToDictionary(e => e.Name[1..], e => e.Address); //Skip the first character, which is a leading underscore inserted by the compiler + + LibLogger.VerboseNewline($"Found {_exportsDict.Count} exports in the DYLD info load command."); + + LibLogger.VerboseNewline($"\tMach-O contains {Segments64.Length} segments, split into {Sections64.Length} sections."); + } + + public override long RawLength => _raw.Length; + public override byte GetByteAtRawAddress(ulong addr) => _raw[addr]; + + public override long MapVirtualAddressToRaw(ulong uiAddr) + { + var sec = Sections64.FirstOrDefault(s => s.Address <= uiAddr && uiAddr < s.Address + s.Size); + + if (sec == null) + throw new($"Could not find section for virtual address 0x{uiAddr:X}. Lowest section address is 0x{Sections64.Min(s => s.Address):X}, highest section address is 0x{Sections64.Max(s => s.Address + s.Size):X}"); + + return (long) (sec.Offset + (uiAddr - sec.Address)); + } + + public override ulong MapRawAddressToVirtual(uint offset) + { + var sec = Sections64.FirstOrDefault(s => s.Offset <= offset && offset < s.Offset + s.Size); + + if (sec == null) + throw new($"Could not find section for raw address 0x{offset:X}"); + + return sec.Address + (offset - sec.Offset); + } + + public override ulong GetRVA(ulong pointer) + { + return pointer; //TODO? + } + + public override byte[] GetRawBinaryContent() => _raw; + + public override ulong[] GetAllExportedIl2CppFunctionPointers() => _exportsDict.Values.Select(v => (ulong) v).ToArray(); + + public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind) + { + if (!_exportsDict.TryGetValue(toFind, out var addr)) + return 0; + + return (ulong) addr; + } + + private MachOSection GetTextSection64() + { + var textSection = Sections64.FirstOrDefault(s => s.SectionName == "__text"); + + if (textSection == null) + throw new("Could not find __text section"); + + return textSection; + } + + public override byte[] GetEntirePrimaryExecutableSection() + { + var textSection = GetTextSection64(); + + return _raw.SubArray((int) textSection.Offset, (int) textSection.Size); + } + + public override ulong GetVirtualAddressOfPrimaryExecutableSection() => GetTextSection64().Address; + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOFileType.cs b/LibCpp2IL/MachO/MachOFileType.cs new file mode 100644 index 00000000..59343d90 --- /dev/null +++ b/LibCpp2IL/MachO/MachOFileType.cs @@ -0,0 +1,15 @@ +namespace LibCpp2IL.MachO +{ + public enum MachOFileType : uint + { + MH_OBJECT = 0x1, + MH_EXECUTE = 0x2, + MH_DYLIB = 0x6, //Dynamic library + MH_BUNDLE = 0x8, + MH_DSYM = 0xA, + MH_KEXT_BUNDLE = 0xB, //Kernel Extension Bundle + MH_APP_EXTENSION_SAFE = 0x02000000, + MY_SIM_SUPPORT = 0x08000000, + MH_DYLIB_IN_CACHE = 0x80000000, + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOHeader.cs b/LibCpp2IL/MachO/MachOHeader.cs new file mode 100644 index 00000000..2f665fd8 --- /dev/null +++ b/LibCpp2IL/MachO/MachOHeader.cs @@ -0,0 +1,32 @@ +namespace LibCpp2IL.MachO +{ + public class MachOHeader + { + public const uint MAGIC_32_BIT = 0xFEEDFACE; + public const uint MAGIC_64_BIT = 0xFEEDFACF; + + public uint Magic; //0xFEEDFACE for 32-bit, 0xFEEDFACF for 64-bit + public MachOCpuType CpuType; //cpu specifier + public MachOCpuSubtype CpuSubtype; //cpu specifier + public MachOFileType FileType; //type of file + public uint NumLoadCommands; //number of load commands + public uint SizeOfLoadCommands; //size of load commands + public MachOHeaderFlags Flags; //flags + + public uint Reserved; //Only on 64-bit + + public void Read(ClassReadingBinaryReader reader) + { + Magic = reader.ReadUInt32(); + CpuType = (MachOCpuType) reader.ReadUInt32(); + CpuSubtype = (MachOCpuSubtype) reader.ReadUInt32(); + FileType = (MachOFileType) reader.ReadUInt32(); + NumLoadCommands = reader.ReadUInt32(); + SizeOfLoadCommands = reader.ReadUInt32(); + Flags = (MachOHeaderFlags) reader.ReadUInt32(); + + if(Magic == MAGIC_64_BIT) + Reserved = reader.ReadUInt32(); + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOHeaderFlags.cs b/LibCpp2IL/MachO/MachOHeaderFlags.cs new file mode 100644 index 00000000..a0131c30 --- /dev/null +++ b/LibCpp2IL/MachO/MachOHeaderFlags.cs @@ -0,0 +1,37 @@ +using System; + +namespace LibCpp2IL.MachO +{ + [Flags] + public enum MachOHeaderFlags + { + MH_NO_UNDEFINED_SYMBOLS = 0x1, + MH_INCREMENTAL_LINK = 0x2, + MH_DYLDLINK = 0x4, // this file is input for the dynamic linker + MH_BINDATLOAD = 0x8, // this file's undefined symbols are bound at load time + MH_PREBOUND = 0x10, // this file has its undefined symbols prebound + MH_SPLIT_SEGS = 0x20, // this file has its read-only and read-write segments split + MH_LAZY_INIT = 0x40, // the shared library init routine should be run lazily. Obsolete. + MH_TWOLEVEL = 0x80, // this image is using two-level name space bindings + MH_FORCE_FLAT = 0x100, // this executable is forcing all images to use flat name space bindings + MH_NOMULTIDEFS = 0x200, // guarantee of no multiple definitions of symbols in sub-images, so two-level namespace bindings can work + MH_NOFIXPREBINDING = 0x400, // do not have dyld notify the prebinding agent about this executable + MH_PREBINDABLE = 0x800, // the binary is not prebound but can have its prebinding redone. only used when MH_PREBOUND is not set. + MH_ALLMODSBOUND = 0x1000, // this binary binds to all two-level namespace modules of its dependent libraries. only used when MH_PREBINDABLE and MH_TWOLEVEL are both set. + MH_SUBSECTIONS_VIA_SYMBOLS = 0x2000, // safe to divide up the sections into sub-sections via symbols for dead code stripping + MH_CANONICAL = 0x4000, // the binary has been canonicalized via the unprebind operation + MH_WEAK_DEFINES = 0x8000, // the final linked image contains external weak symbols + MH_BINDS_TO_WEAK = 0x10000, // the final linked image uses weak symbols + MH_ALLOW_STACK_EXECUTION = 0x20000, // When this bit is set, all stacks in the task will be given stack execution privilege. Only used in MH_EXECUTE filetypes. + MH_ROOT_SAFE = 0x40000, // When this bit is set, the binary declares it is safe for use in processes with uid zero + MH_SETUID_SAFE = 0x80000, // When this bit is set, the binary declares it is safe for use in processes when issetugid() is true + MH_NO_REEXPORTED_DYLIBS = 0x100000, // When this bit is set on a dylib, the static linker does not need to examine dependent dylibs to see if any are re-exported + MH_PIE = 0x200000, // When this bit is set, the OS will load the main executable at a random address. Only used in MH_EXECUTE filetypes. + MH_DEAD_STRIPPABLE_DYLIB = 0x400000, // Only for use on dylibs. Allows the linker to not link to this dylib if it's not referenced. + MH_HAS_TLV_DESCRIPTORS = 0x800000, // Contains a section of type S_THREAD_LOCAL_VARIABLES.. + MH_NO_HEAP_EXECUTION = 0x1000000, // When this bit is set, the OS will run the main executable with a non-executable heap even on platforms (e.g. i386) that don't require it. Only used in MH_EXECUTE filetypes. + MH_APP_EXTENSION_SAFE = 0x02000000, // The code was linked for use in an application extension. + MH_NLIST_OUTOFSYNC_WITH_DYLDINFO = 0x04000000, // The external symbols listed in the nlist symtab do not include all the symbols that are used by the dynamic linker. + MH_SIM_SUPPORT = 0x08000000, // The binary is suitable for a simulator. + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOLoadCommand.cs b/LibCpp2IL/MachO/MachOLoadCommand.cs new file mode 100644 index 00000000..60a40943 --- /dev/null +++ b/LibCpp2IL/MachO/MachOLoadCommand.cs @@ -0,0 +1,53 @@ +using System; +using System.Text; + +namespace LibCpp2IL.MachO +{ + public class MachOLoadCommand + { + public LoadCommandId Command; + public uint CommandSize; + + public object? CommandData; + public byte[]? UnknownCommandData = null; + + + public string? UnknownDataAsString => UnknownCommandData == null ? null : Encoding.UTF8.GetString(UnknownCommandData); + + public void Read(ClassReadingBinaryReader reader) + { + Command = (LoadCommandId) reader.ReadUInt32(); + CommandSize = reader.ReadUInt32(); + + switch (Command) + { + case LoadCommandId.LC_SEGMENT: + case LoadCommandId.LC_SEGMENT_64: + { + var cmd = new MachOSegmentCommand(); + cmd.Read(reader); + CommandData = cmd; + break; + } + case LoadCommandId.LC_SYMTAB: + { + var cmd = new MachOSymtabCommand(); + cmd.Read(reader); + CommandData = cmd; + break; + } + case LoadCommandId.LC_DYLD_INFO: + case LoadCommandId.LC_DYLD_INFO_ONLY: + { + var cmd = new MachODynamicLinkerCommand(); + cmd.Read(reader); + CommandData = cmd; + break; + } + default: + UnknownCommandData = reader.ReadBytes((int) CommandSize - 8); // -8 because we've already read the 8 bytes of the header + break; + } + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOSection.cs b/LibCpp2IL/MachO/MachOSection.cs new file mode 100644 index 00000000..96eb2b62 --- /dev/null +++ b/LibCpp2IL/MachO/MachOSection.cs @@ -0,0 +1,45 @@ +using System.Text; + +namespace LibCpp2IL.MachO +{ + public class MachOSection + { + public string SectionName; // 16 bytes + public string ContainingSegmentName; // 16 bytes + + public ulong Address; + public ulong Size; + + public uint Offset; + public uint Alignment; + public uint RelocationOffset; + public uint NumberOfRelocations; + public MachOSectionFlags Flags; + + public uint Reserved1; + public uint Reserved2; + + public uint Reserved3; //64-bit only + + public void Read(ClassReadingBinaryReader reader) + { + SectionName = Encoding.UTF8.GetString(reader.ReadBytes(16)).TrimEnd('\0'); + ContainingSegmentName = Encoding.UTF8.GetString(reader.ReadBytes(16)).TrimEnd('\0'); + + Address = reader.ReadNUint(); + Size = reader.ReadNUint(); + + Offset = reader.ReadUInt32(); + Alignment = reader.ReadUInt32(); + RelocationOffset = reader.ReadUInt32(); + NumberOfRelocations = reader.ReadUInt32(); + Flags = (MachOSectionFlags) reader.ReadUInt32(); + + Reserved1 = reader.ReadUInt32(); + Reserved2 = reader.ReadUInt32(); + + if(!reader.is32Bit) + Reserved3 = reader.ReadUInt32(); + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOSectionFlags.cs b/LibCpp2IL/MachO/MachOSectionFlags.cs new file mode 100644 index 00000000..6bfc7102 --- /dev/null +++ b/LibCpp2IL/MachO/MachOSectionFlags.cs @@ -0,0 +1,49 @@ +using System; + +namespace LibCpp2IL.MachO +{ + [Flags] + public enum MachOSectionFlags : uint + { + TYPE_BITMASK = 0x000000FF, + ATTRIBUTES_BITMASK = 0xFFFFF00, + USER_ATTRIBUTES_BITMASK = 0xFF000000, //User-set attributes + SYSTEM_ATTRIBUTES_BITMASK = 0x00FFFF00, //System-set attributes + + TYPE_REGULAR = 0x0, + TYPE_ZEROFILL = 0x1, //Zero-fill on demand + TYPE_CSTRING_LITERALS = 0x2, //Only literal C strings + TYPE_4BYTE_LITERALS = 0x3, //Only 4-byte literals + TYPE_8BYTE_LITERALS = 0x4, //Only 8-byte literals + TYPE_LITERAL_POINTERS = 0x5, //Only pointers to literals + TYPE_NON_LAZY_SYMBOL_POINTERS = 0x6, //Only non-lazy symbol pointers + TYPE_LAZY_SYMBOL_POINTERS = 0x7, //Only lazy symbol pointers + TYPE_SYMBOL_STUBS = 0x8, //Only symbol stubs + TYPE_MOD_INIT_FUNC_POINTERS = 0x9, //Only function pointers for initialization + TYPE_MOD_TERM_FUNC_POINTERS = 0xA, //Only function pointers for termination + TYPE_COALESCED = 0xB, //Contains symbols that are to be coalesced + TYPE_GB_ZEROFILL = 0xC, //Zero-fill on demand, can be > 4gb + TYPE_INTERPOSING = 0xD, //Only pairs of function pointers for interposing + TYPE_16BYTE_LITERALS = 0xE, //Only 16-byte literals + TYPE_DTRACE_DOF = 0xF, //Contains DTrace Object Format + TYPE_LAZY_DYLIB_SYMBOL_POINTERS = 0x10, //Only lazy symbol pointers to lazy dylibs + TYPE_THREAD_LOCAL_REGULAR = 0x11, // Template of initial values for thread local variables + TYPE_THREAD_LOCAL_ZEROFILL = 0x12, // Zero-fill on demand template for thread local variables + TYPE_THREAD_LOCAL_VARIABLES = 0x13, // Thread local variable descriptors + TYPE_THREAD_LOCAL_VARIABLE_POINTERS = 0x14, // Pointers to thread local variable descriptors + TYPE_THREAD_LOCAL_INIT_FUNCTION_POINTERS = 0x15, // Pointers functions to call to initialize TLV values + TYPE_INIT_FUNC_OFFSETS = 0x16, // 32-bit offsets to initialization functions + + ATTR_PURE_INSTRUCTIONS = 0x80000000, // Section contains only true machine instructions + ATTR_NO_TOC = 0x40000000, // Section contains coalesced symbols that are not to be in a ranlib table of contents + ATTR_STRIP_STATIC_SYMS = 0x20000000, // Ok to strip static symbols in this section in files with the MH_DYLDLINK flag + ATTR_NO_DEAD_STRIP = 0x10000000, // No dead stripping + ATTR_LIVE_SUPPORT = 0x08000000, // Blocks are live if they reference live blocks + ATTR_SELF_MODIFYING_CODE = 0x04000000, // Used with i386 code stubs written on by dyld + ATTR_DEBUG = 0x02000000, // A debug section + + ATTR_SOME_INSTRUCTIONS = 0x00000400, // Section contains some machine instructions + ATTR_EXT_RELOC = 0x00000200, // Section has external relocation entries + ATTR_LOC_RELOC = 0x00000100, // Section has local relocation entries + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOSegmentCommand.cs b/LibCpp2IL/MachO/MachOSegmentCommand.cs new file mode 100644 index 00000000..c7646e9c --- /dev/null +++ b/LibCpp2IL/MachO/MachOSegmentCommand.cs @@ -0,0 +1,48 @@ +using System; +using System.Text; + +namespace LibCpp2IL.MachO +{ + public class MachOSegmentCommand + { + public string SegmentName; // 16 bytes + + public ulong VirtualAddress; + public ulong VirtualSize; + public ulong FileOffset; + public ulong FileSize; + + public MachOVmProtection MaxProtection; + public MachOVmProtection InitialProtection; + + public uint NumSections; + + public MachOSegmentFlags Flags; + + public MachOSection[] Sections = Array.Empty(); + + public void Read(ClassReadingBinaryReader reader) + { + SegmentName = Encoding.UTF8.GetString(reader.ReadBytes(16)).TrimEnd('\0'); + + VirtualAddress = reader.ReadNUint(); + VirtualSize = reader.ReadNUint(); + FileOffset = reader.ReadNUint(); + FileSize = reader.ReadNUint(); + + MaxProtection = (MachOVmProtection) reader.ReadInt32(); + InitialProtection = (MachOVmProtection) reader.ReadInt32(); + + NumSections = reader.ReadUInt32(); + + Flags = (MachOSegmentFlags) reader.ReadUInt32(); + + Sections = new MachOSection[NumSections]; + for (var i = 0; i < NumSections; i++) + { + Sections[i] = new(); + Sections[i].Read(reader); + } + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOSegmentFlags.cs b/LibCpp2IL/MachO/MachOSegmentFlags.cs new file mode 100644 index 00000000..28d336e9 --- /dev/null +++ b/LibCpp2IL/MachO/MachOSegmentFlags.cs @@ -0,0 +1,15 @@ +using System; + +namespace LibCpp2IL.MachO +{ + [Flags] + public enum MachOSegmentFlags + { + SG_NONE = 0x0, + + SG_HIGHVM = 0x1, // The file contents for this segment are for the high part of the virtual space - push the raw data as far to the end of the segment as possible. + SG_FVMLIB = 0x2, // This segment is the VM that is allocated by a fixed VM library, for overlap checking. + SG_NORELOC = 0x4, // This segment has nothing that was relocated in it and nothing relocated to it, so no relocations are needed. + SG_PROTECTED_VERSION_1 = 0x8, // This segment is protected. If the segment starts at file offset 0, the first page of the segment is not protected. All other pages of the segment are protected. + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOSymtabCommand.cs b/LibCpp2IL/MachO/MachOSymtabCommand.cs new file mode 100644 index 00000000..efd3818c --- /dev/null +++ b/LibCpp2IL/MachO/MachOSymtabCommand.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace LibCpp2IL.MachO +{ + public class MachOSymtabCommand + { + public uint SymbolTableOffset; + public uint NumSymbols; + public uint StringTableOffset; + public uint StringTableSize; + + public MachOSymtabEntry[] Symbols = Array.Empty(); + + public void Read(ClassReadingBinaryReader reader) + { + SymbolTableOffset = reader.ReadUInt32(); + NumSymbols = reader.ReadUInt32(); + StringTableOffset = reader.ReadUInt32(); + StringTableSize = reader.ReadUInt32(); + + var returnTo = reader.BaseStream.Position; + + reader.BaseStream.Position = SymbolTableOffset; + + Symbols = new MachOSymtabEntry[NumSymbols]; + for (var i = 0; i < NumSymbols; i++) + { + Symbols[i] = new(); + Symbols[i].Read(reader, this); + } + + reader.BaseStream.Position = returnTo; + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOSymtabEntry.cs b/LibCpp2IL/MachO/MachOSymtabEntry.cs new file mode 100644 index 00000000..7175709c --- /dev/null +++ b/LibCpp2IL/MachO/MachOSymtabEntry.cs @@ -0,0 +1,38 @@ +namespace LibCpp2IL.MachO +{ + public class MachOSymtabEntry + { + public uint NameOffset; + public byte Type; + public byte Section; + public ushort Description; + public ulong Value; // Architecture sized + + public string Name; + + public bool IsExternal => (Type & 0b1) == 0b1; + public bool IsSymbolicDebugging => (Type & 0b1110_0000) != 0; + public bool IsPrivateExternal => (Type & 0b0001_0000) == 0b0001_0000; + + private byte TypeBits => (byte) (Type & 0b1110); + + public bool IsTypeUndefined => Section == 0 && TypeBits == 0b0000; + public bool IsTypeAbsolute => Section == 0 && TypeBits == 0b0010; + public bool IsTypePreboundUndefined => Section == 0 && TypeBits == 0b1100; + public bool IsTypeIndirect => Section == 0 && TypeBits == 0b1010; + public bool IsTypeSection => TypeBits == 0b1110; + + public void Read(ClassReadingBinaryReader reader, MachOSymtabCommand machOSymtabCommand) + { + NameOffset = reader.ReadUInt32(); + Type = reader.ReadByte(); + Section = reader.ReadByte(); + Description = reader.ReadUInt16(); + Value = reader.ReadNUint(); + + var returnTo = reader.BaseStream.Position; + Name = reader.ReadStringToNull(machOSymtabCommand.StringTableOffset + NameOffset); + reader.BaseStream.Position = returnTo; + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOVmProtection.cs b/LibCpp2IL/MachO/MachOVmProtection.cs new file mode 100644 index 00000000..395c82c0 --- /dev/null +++ b/LibCpp2IL/MachO/MachOVmProtection.cs @@ -0,0 +1,19 @@ +using System; + +namespace LibCpp2IL.MachO +{ + [Flags] + public enum MachOVmProtection + { + PROT_NONE = 0x0, + PROT_READ = 0x1, + PROT_WRITE = 0x2, + PROT_EXEC = 0x4, + PROT_NO_CHANGE = 0x8, + PROT_COPY = 0x10, + PROT_TRUSTED = 0x20, + PROT_IS_MASK = 0x40, + PROT_STRIP_READ = 0x80, + PROT_COPY_FAIL_IF_EXECUTABLE = 0x100, + } +} \ No newline at end of file diff --git a/LibCpp2IL/Metadata/Il2CppAssemblyNameDefinition.cs b/LibCpp2IL/Metadata/Il2CppAssemblyNameDefinition.cs index 4f29a2f4..38503d18 100644 --- a/LibCpp2IL/Metadata/Il2CppAssemblyNameDefinition.cs +++ b/LibCpp2IL/Metadata/Il2CppAssemblyNameDefinition.cs @@ -7,7 +7,9 @@ public class Il2CppAssemblyNameDefinition { public int nameIndex; public int cultureIndex; - [Version(Max = 24.3f)] public int hashValueIndex; + [Version(Max = 24.1f)] + [Version(Min = 24.2f, Max=24.3f)] //Not present in 24.15 + public int hashValueIndex; public int publicKeyIndex; public uint hash_alg; public int hash_len; diff --git a/LibCpp2IL/Metadata/Il2CppCustomAttributeDataRange.cs b/LibCpp2IL/Metadata/Il2CppCustomAttributeDataRange.cs index 9d2e933c..c945ab31 100644 --- a/LibCpp2IL/Metadata/Il2CppCustomAttributeDataRange.cs +++ b/LibCpp2IL/Metadata/Il2CppCustomAttributeDataRange.cs @@ -1,9 +1,11 @@ namespace LibCpp2IL.Metadata { - public class Il2CppCustomAttributeDataRange + public class Il2CppCustomAttributeDataRange : IIl2CppTokenProvider { //Since v29 public uint token; public uint startOffset; + + public uint Token => token; } } \ No newline at end of file diff --git a/LibCpp2IL/Metadata/Il2CppCustomAttributeTypeRange.cs b/LibCpp2IL/Metadata/Il2CppCustomAttributeTypeRange.cs index 2936a8e0..d1a435aa 100644 --- a/LibCpp2IL/Metadata/Il2CppCustomAttributeTypeRange.cs +++ b/LibCpp2IL/Metadata/Il2CppCustomAttributeTypeRange.cs @@ -1,9 +1,11 @@ namespace LibCpp2IL.Metadata { - public class Il2CppCustomAttributeTypeRange + public class Il2CppCustomAttributeTypeRange : IIl2CppTokenProvider { [Version(Min = 24.1f)] public uint token; public int start; [Version(Max = 27.1f)] public int count; + + public uint Token => token; } } \ No newline at end of file diff --git a/LibCpp2IL/Metadata/Il2CppGlobalMetadataHeader.cs b/LibCpp2IL/Metadata/Il2CppGlobalMetadataHeader.cs index 00759418..dea6c0d7 100644 --- a/LibCpp2IL/Metadata/Il2CppGlobalMetadataHeader.cs +++ b/LibCpp2IL/Metadata/Il2CppGlobalMetadataHeader.cs @@ -44,8 +44,8 @@ public class Il2CppGlobalMetadataHeader public int interfaceOffsetsCount; public int typeDefinitionsOffset; // Il2CppTypeDefinition public int typeDefinitionsCount; - [Version(Max = 24.1f)] public int rgctxEntriesOffset; // Il2CppRGCTXDefinition - [Version(Max = 24.1f)] public int rgctxEntriesCount; + [Version(Max = 24.15f)] public int rgctxEntriesOffset; // Il2CppRGCTXDefinition + [Version(Max = 24.15f)] public int rgctxEntriesCount; public int imagesOffset; // Il2CppImageDefinition public int imagesCount; public int assembliesOffset; // Il2CppAssemblyDefinition diff --git a/LibCpp2IL/Metadata/Il2CppMetadata.cs b/LibCpp2IL/Metadata/Il2CppMetadata.cs index 1564d45b..d3f56905 100644 --- a/LibCpp2IL/Metadata/Il2CppMetadata.cs +++ b/LibCpp2IL/Metadata/Il2CppMetadata.cs @@ -26,7 +26,7 @@ public class Il2CppMetadata : ClassReadingBinaryReader private Il2CppFieldDefaultValue[] fieldDefaultValues; private Il2CppParameterDefaultValue[] parameterDefaultValues; public Il2CppPropertyDefinition[] propertyDefs; - public Il2CppCustomAttributeTypeRange[] attributeTypeRanges; + public Il2CppCustomAttributeTypeRange[] attributeTypeRanges; //Pre-29 private Il2CppStringLiteral[] stringLiterals; public Il2CppMetadataUsageList[] metadataUsageLists; private Il2CppMetadataUsagePair[] metadataUsagePairs; @@ -55,16 +55,16 @@ public class Il2CppMetadata : ClassReadingBinaryReader public static Il2CppMetadata? ReadFrom(byte[] bytes, int[] unityVer) { - if (BitConverter.ToUInt32(bytes, 0) != 0xFAB11BAF) + if (BitConverter.ToUInt32(bytes, 0) is var magic && magic != 0xFAB11BAF) { //Magic number is wrong - throw new FormatException("Invalid or corrupt metadata (magic number check failed)"); + throw new FormatException($"Invalid or corrupt metadata. Magic number was 0x{magic:X}, expected 0xFAB11BAF."); } var version = BitConverter.ToInt32(bytes, 4); - if (version is < 24 or > 29) + if (version is < 24 or > 31) { - throw new FormatException("Unsupported metadata version found! We support 24-29, got " + version); + throw new FormatException("Unsupported metadata version found! We support 24-31, got " + version); } LibLogger.VerboseNewline($"\tIL2CPP Metadata Declares its version as {version}"); @@ -91,12 +91,32 @@ public class Il2CppMetadata : ClassReadingBinaryReader actualVersion = 24.3f; //2019.3.7 introduces v24.3 else if (unityVersion.IsGreaterEqual(2019)) actualVersion = 24.2f; //2019.1.0 introduces v24.2 + else if (unityVersion.IsGreaterEqual(2018, 4, 34)) + actualVersion = 24.15f; //2018.4.34 made a tiny little change which just removes HashValueIndex from AssemblyNameDefinition else if (unityVersion.IsGreaterEqual(2018, 3)) actualVersion = 24.1f; //2018.3.0 introduces v24.1 else actualVersion = version; //2017.1.0 was the first v24 version } - else actualVersion = version; //Covers v29 + else if (version == 29) + { + if (unityVersion.IsGreaterEqual(2022, 1, 0, UnityVersionType.Beta, 7)) + actualVersion = 29.1f; //2022.1.0b7 introduces v29.1 which adds two new pointers to codereg + else + actualVersion = 29; //2021.3.0 introduces v29 + } else if (version == 31) + { + //2022.3.33 introduces v31. Unity why would you bump this on a minor version. + //Adds one new field (return type token) to method def + //2021.3.40 backported the new field but NOT the changes from v29.1, so there's a 31.1 now. + if (unityVersion.IsGreaterEqual(2022, 3, 33, UnityVersionType.Final, 1)) + //V31 with changes in codereg + actualVersion = 31.1f; + else + //v31 WITHOUT changes in codereg + actualVersion = 31; + } + else actualVersion = version; LibLogger.InfoNewline($"\tUsing actual IL2CPP Metadata version {actualVersion}"); @@ -385,8 +405,8 @@ public string GetStringFromIndex(int index) public string GetStringLiteralFromIndex(uint index) { var stringLiteral = stringLiterals[index]; - Position = metadataHeader.stringLiteralDataOffset + stringLiteral.dataIndex; - return Encoding.UTF8.GetString(ReadBytes((int) stringLiteral.length)); + + return Encoding.UTF8.GetString(ReadByteArrayAtRawAddress(metadataHeader.stringLiteralDataOffset + stringLiteral.dataIndex, (int) stringLiteral.length)); } } } \ No newline at end of file diff --git a/LibCpp2IL/Metadata/Il2CppMethodDefinition.cs b/LibCpp2IL/Metadata/Il2CppMethodDefinition.cs index e41ef3be..036a427b 100644 --- a/LibCpp2IL/Metadata/Il2CppMethodDefinition.cs +++ b/LibCpp2IL/Metadata/Il2CppMethodDefinition.cs @@ -13,14 +13,15 @@ public class Il2CppMethodDefinition public int nameIndex; public int declaringTypeIdx; public int returnTypeIdx; + [Version(Min = 31)] public uint returnParameterToken; public int parameterStart; [Version(Max = 24)] public int customAttributeIndex; public int genericContainerIndex; - [Version(Max = 24.1f)] public int methodIndex; - [Version(Max = 24.1f)] public int invokerIndex; - [Version(Max = 24.1f)] public int delegateWrapperIndex; - [Version(Max = 24.1f)] public int rgctxStartIndex; - [Version(Max = 24.1f)] public int rgctxCount; + [Version(Max = 24.15f)] public int methodIndex; + [Version(Max = 24.15f)] public int invokerIndex; + [Version(Max = 24.15f)] public int delegateWrapperIndex; + [Version(Max = 24.15f)] public int rgctxStartIndex; + [Version(Max = 24.15f)] public int rgctxCount; public uint token; public ushort flags; public ushort iflags; diff --git a/LibCpp2IL/Metadata/Il2CppTypeDefinition.cs b/LibCpp2IL/Metadata/Il2CppTypeDefinition.cs index ae2d6163..9ca5aa01 100644 --- a/LibCpp2IL/Metadata/Il2CppTypeDefinition.cs +++ b/LibCpp2IL/Metadata/Il2CppTypeDefinition.cs @@ -20,8 +20,8 @@ public class Il2CppTypeDefinition public int parentIndex; public int elementTypeIndex; // we can probably remove this one. Only used for enums - [Version(Max = 24.1f)] public int rgctxStartIndex; - [Version(Max = 24.1f)] public int rgctxCount; + [Version(Max = 24.15f)] public int rgctxStartIndex; + [Version(Max = 24.15f)] public int rgctxCount; public int genericContainerIndex; diff --git a/LibCpp2IL/NintendoSwitch/NsoFile.cs b/LibCpp2IL/NintendoSwitch/NsoFile.cs index e33c45c7..32a20929 100644 --- a/LibCpp2IL/NintendoSwitch/NsoFile.cs +++ b/LibCpp2IL/NintendoSwitch/NsoFile.cs @@ -2,19 +2,25 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using LibCpp2IL.Elf; using LibCpp2IL.Logging; namespace LibCpp2IL.NintendoSwitch { - public class NsoFile : Il2CppBinary + public sealed class NsoFile : Il2CppBinary { + private const ulong NSO_GLOBAL_OFFSET = 0; + private byte[] _raw; private NsoHeader header; + private NsoModHeader _modHeader; private bool isTextCompressed; private bool isRoDataCompressed; private bool isDataCompressed; private List segments = new(); + private List dynamicEntries = new(); + public ElfDynamicSymbol64[] SymbolTable; private bool isCompressed => isTextCompressed || isRoDataCompressed || isDataCompressed; @@ -81,7 +87,7 @@ public NsoFile(MemoryStream input, long maxMetadataUsages) : base(input, maxMeta header.TextCompressedSize = ReadUInt32(); header.RoDataCompressedSize = ReadUInt32(); header.DataCompressedSize = ReadUInt32(); - header.Padding = ReadBytes(0x1C); + header.NsoHeaderReserved = ReadBytes(0x1C); LibLogger.VerboseNewline("\tRead post-segment fields OK. Reading Dynamic section and Api Info offsets..."); @@ -111,121 +117,222 @@ public NsoFile(MemoryStream input, long maxMetadataUsages) : base(input, maxMeta if (!isCompressed) { - LibLogger.VerboseNewline($"\tBinary is not compressed. Reading BSS segment header..."); - - Position = header.TextSegment.FileOffset + 4; - var modOffset = ReadUInt32(); - Position = header.TextSegment.FileOffset + modOffset + 4; - var dynamicOffset = ReadUInt32(); - var bssStart = ReadUInt32(); - var bssEnd = ReadUInt32(); - header.BssSegment = new() - { - FileOffset = bssStart, - MemoryOffset = bssStart, - DecompressedSize = bssEnd - bssStart - }; - var ehFrameHdrStart = ReadUInt32(); - var ehFrameHdrEnd = ReadUInt32(); + ReadModHeader(); + ReadDynamicSection(); + ReadSymbolTable(); + ApplyRelocations(); } LibLogger.VerboseNewline($"\tNSO Read completed OK."); } - public NsoFile Decompress() + private void ReadModHeader() { - LibLogger.InfoNewline("\tDecompressing NSO file..."); - if (isTextCompressed || isRoDataCompressed || isDataCompressed) + LibLogger.VerboseNewline($"\tNSO is decompressed. Reading MOD segment header..."); + + _modHeader = new(); + + //Location of real mod header must be at .text + 4 + //Which allows .text + 0 to be a jump over it + Position = header.TextSegment.FileOffset + 4; + _modHeader.ModOffset = ReadUInt32(); + + //Now we have the real mod header position, go to it and read + Position = header.TextSegment.FileOffset + _modHeader.ModOffset + 4; + _modHeader.DynamicOffset = ReadUInt32() + _modHeader.ModOffset; + _modHeader.BssStart = ReadUInt32(); + _modHeader.BssEnd = ReadUInt32(); + + //Construct the bss segment information from the fields we just read + _modHeader.BssSegment = new() { - var unCompressedStream = new MemoryStream(); - var writer = new BinaryWriter(unCompressedStream); - writer.Write(header.Magic); - writer.Write(header.Version); - writer.Write(header.Reserved); - writer.Write(0); //Flags - writer.Write(header.TextSegment.FileOffset); - writer.Write(header.TextSegment.MemoryOffset); - writer.Write(header.TextSegment.DecompressedSize); - writer.Write(header.ModuleOffset); - var roOffset = header.TextSegment.FileOffset + header.TextSegment.DecompressedSize; - writer.Write(roOffset); //header.RoDataSegment.FileOffset - writer.Write(header.RoDataSegment.MemoryOffset); - writer.Write(header.RoDataSegment.DecompressedSize); - writer.Write(header.ModuleFileSize); - writer.Write(roOffset + header.RoDataSegment.DecompressedSize); //header.DataSegment.FileOffset - writer.Write(header.DataSegment.MemoryOffset); - writer.Write(header.DataSegment.DecompressedSize); - writer.Write(header.BssSize); - writer.Write(header.DigestBuildID); - writer.Write(header.TextCompressedSize); - writer.Write(header.RoDataCompressedSize); - writer.Write(header.DataCompressedSize); - writer.Write(header.Padding); - writer.Write(header.APIInfo.RegionRoDataOffset); - writer.Write(header.APIInfo.RegionSize); - writer.Write(header.DynStr.RegionRoDataOffset); - writer.Write(header.DynStr.RegionSize); - writer.Write(header.DynSym.RegionRoDataOffset); - writer.Write(header.DynSym.RegionSize); - writer.Write(header.TextHash); - writer.Write(header.RoDataHash); - writer.Write(header.DataHash); - writer.BaseStream.Position = header.TextSegment.FileOffset; - Position = header.TextSegment.FileOffset; - var textBytes = ReadBytes((int)header.TextCompressedSize); - if (isTextCompressed) - { - var unCompressedData = new byte[header.TextSegment.DecompressedSize]; - using (var decoder = new Lz4DecodeStream(new MemoryStream(textBytes))) - { - decoder.Read(unCompressedData, 0, unCompressedData.Length); - } + FileOffset = _modHeader.BssStart, + MemoryOffset = _modHeader.BssStart, + DecompressedSize = _modHeader.BssEnd - _modHeader.BssStart + }; + + _modHeader.EhFrameHdrStart = ReadUInt32(); + _modHeader.EhFrameHdrEnd = ReadUInt32(); + } - writer.Write(unCompressedData); - } - else + private void ReadDynamicSection() + { + LibLogger.VerboseNewline($"\tReading NSO Dynamic section..."); + + Position = MapVirtualAddressToRaw(_modHeader.DynamicOffset); + + //This is mostly a sanity check so we don't read the entire damn file 16 bytes at a time + //This will be way more than we need in general (like, 100 times more) + var endOfData = header.DataSegment.MemoryOffset + header.DataSegment.DecompressedSize; + var maxPossibleDynSectionEntryCount = (endOfData - _modHeader.DynamicOffset) / 16; //16 being sizeof(ElfDynamicEntry) on 64-bit + + for (var i = 0; i < maxPossibleDynSectionEntryCount; i++) + { + var dynEntry = ReadClassAtRawAddr(-1); + if (dynEntry.Tag == ElfDynamicType.DT_NULL) + //End of dynamic section + break; + dynamicEntries.Add(dynEntry); + } + } + + private void ReadSymbolTable() + { + LibLogger.Verbose($"\tReading NSO symbol table..."); + + var hash = GetDynamicEntry(ElfDynamicType.DT_HASH); + + if (hash == null) + { + LibLogger.WarnNewline("\tNo DT_HASH found in NSO, symbols will not be resolved"); + return; + } + + Position = MapVirtualAddressToRaw(hash.Value); + ReadUInt32(); //Ignored + var symbolCount = ReadUInt32(); + + var symTab = GetDynamicEntry(ElfDynamicType.DT_SYMTAB); + + if (symTab == null) + { + LibLogger.WarnNewline("\tNo DT_SYMTAB found in NSO, symbols will not be resolved"); + return; + } + + SymbolTable = ReadClassArrayAtVirtualAddress((ulong) MapVirtualAddressToRaw(symTab.Value), symbolCount); + + LibLogger.VerboseNewline($"\tGot {SymbolTable.Length} symbols"); + } + + private void ApplyRelocations() + { + ElfRelaEntry[] relaEntries; + + try + { + var dtRela = GetDynamicEntry(ElfDynamicType.DT_RELA) ?? throw new(); //Using exceptions as control flow :) + var dtRelaSize = GetDynamicEntry(ElfDynamicType.DT_RELASZ) ?? throw new(); + relaEntries = ReadClassArrayAtVirtualAddress(dtRela.Value, (long) (dtRelaSize.Value / 24)); //24 being sizeof(ElfRelaEntry) on 64-bit + } + catch + { + //If we don't have relocations, that's fine. + return; + } + + LibLogger.VerboseNewline($"\tApplying {relaEntries.Length} relocations from DT_RELA..."); + + foreach (var elfRelaEntry in relaEntries) + { + switch (elfRelaEntry.Type) { - writer.Write(textBytes); + case ElfRelocationType.R_AARCH64_ABS64: + var symbol = SymbolTable[elfRelaEntry.Symbol]; + WriteWord((int) MapVirtualAddressToRaw(elfRelaEntry.Offset), symbol.Value + elfRelaEntry.Addend); + break; + case ElfRelocationType.R_AARCH64_RELATIVE: + WriteWord((int) MapVirtualAddressToRaw(elfRelaEntry.Offset), elfRelaEntry.Addend); + break; } + } + } + + public ElfDynamicEntry? GetDynamicEntry(ElfDynamicType tag) => dynamicEntries.Find(x => x.Tag == tag); - var roDataBytes = ReadBytes((int)header.RoDataCompressedSize); - if (isRoDataCompressed) - { - var unCompressedData = new byte[header.RoDataSegment.DecompressedSize]; - using (var decoder = new Lz4DecodeStream(new MemoryStream(roDataBytes))) - { - decoder.Read(unCompressedData, 0, unCompressedData.Length); - } + public NsoFile Decompress() + { + if (!isCompressed) + return this; + + LibLogger.InfoNewline("\tDecompressing NSO file..."); - writer.Write(unCompressedData); - } - else + var unCompressedStream = new MemoryStream(); + var writer = new BinaryWriter(unCompressedStream); + writer.Write(header.Magic); + writer.Write(header.Version); + writer.Write(header.Reserved); + writer.Write(0); //Flags + writer.Write(header.TextSegment.FileOffset); + writer.Write(header.TextSegment.MemoryOffset); + writer.Write(header.TextSegment.DecompressedSize); + writer.Write(header.ModuleOffset); + var roOffset = header.TextSegment.FileOffset + header.TextSegment.DecompressedSize; + writer.Write(roOffset); //header.RoDataSegment.FileOffset + writer.Write(header.RoDataSegment.MemoryOffset); + writer.Write(header.RoDataSegment.DecompressedSize); + writer.Write(header.ModuleFileSize); + writer.Write(roOffset + header.RoDataSegment.DecompressedSize); //header.DataSegment.FileOffset + writer.Write(header.DataSegment.MemoryOffset); + writer.Write(header.DataSegment.DecompressedSize); + writer.Write(header.BssSize); + writer.Write(header.DigestBuildID); + writer.Write(header.TextCompressedSize); + writer.Write(header.RoDataCompressedSize); + writer.Write(header.DataCompressedSize); + writer.Write(header.NsoHeaderReserved); + writer.Write(header.APIInfo.RegionRoDataOffset); + writer.Write(header.APIInfo.RegionSize); + writer.Write(header.DynStr.RegionRoDataOffset); + writer.Write(header.DynStr.RegionSize); + writer.Write(header.DynSym.RegionRoDataOffset); + writer.Write(header.DynSym.RegionSize); + writer.Write(header.TextHash); + writer.Write(header.RoDataHash); + writer.Write(header.DataHash); + writer.BaseStream.Position = header.TextSegment.FileOffset; + Position = header.TextSegment.FileOffset; + var textBytes = ReadBytes((int)header.TextCompressedSize); + if (isTextCompressed) + { + var unCompressedData = new byte[header.TextSegment.DecompressedSize]; + using (var decoder = new Lz4DecodeStream(new MemoryStream(textBytes))) { - writer.Write(roDataBytes); + decoder.Read(unCompressedData, 0, unCompressedData.Length); } - var dataBytes = ReadBytes((int)header.DataCompressedSize); - if (isDataCompressed) - { - var unCompressedData = new byte[header.DataSegment.DecompressedSize]; - using (var decoder = new Lz4DecodeStream(new MemoryStream(dataBytes))) - { - decoder.Read(unCompressedData, 0, unCompressedData.Length); - } + writer.Write(unCompressedData); + } + else + { + writer.Write(textBytes); + } - writer.Write(unCompressedData); + var roDataBytes = ReadBytes((int)header.RoDataCompressedSize); + if (isRoDataCompressed) + { + var unCompressedData = new byte[header.RoDataSegment.DecompressedSize]; + using (var decoder = new Lz4DecodeStream(new MemoryStream(roDataBytes))) + { + decoder.Read(unCompressedData, 0, unCompressedData.Length); } - else + + writer.Write(unCompressedData); + } + else + { + writer.Write(roDataBytes); + } + + var dataBytes = ReadBytes((int)header.DataCompressedSize); + if (isDataCompressed) + { + var unCompressedData = new byte[header.DataSegment.DecompressedSize]; + using (var decoder = new Lz4DecodeStream(new MemoryStream(dataBytes))) { - writer.Write(dataBytes); + decoder.Read(unCompressedData, 0, unCompressedData.Length); } - writer.Flush(); - unCompressedStream.Position = 0; - return new NsoFile(unCompressedStream, maxMetadataUsages); + writer.Write(unCompressedData); + } + else + { + writer.Write(dataBytes); } - return this; + writer.Flush(); + unCompressedStream.Position = 0; + return new(unCompressedStream, maxMetadataUsages); } public override long RawLength => _raw.Length; @@ -233,11 +340,11 @@ public NsoFile Decompress() public override long MapVirtualAddressToRaw(ulong addr) { - var segment = segments.FirstOrDefault(x => addr >= x.MemoryOffset && addr <= x.MemoryOffset + x.DecompressedSize); + var segment = segments.FirstOrDefault(x => addr - NSO_GLOBAL_OFFSET >= x.MemoryOffset && addr - NSO_GLOBAL_OFFSET <= x.MemoryOffset + x.DecompressedSize); if (segment == null) throw new InvalidOperationException($"NSO: Address 0x{addr:X} is not present in any of the segments. Known segment ends are (hex) {string.Join(", ", segments.Select(s => (s.MemoryOffset + s.DecompressedSize).ToString("X")))}"); - return (long)(addr - segment.MemoryOffset + segment.FileOffset); + return (long)(addr - (segment.MemoryOffset + NSO_GLOBAL_OFFSET) + segment.FileOffset); } public override ulong MapRawAddressToVirtual(uint offset) @@ -247,7 +354,7 @@ public override ulong MapRawAddressToVirtual(uint offset) { return 0; } - return offset - segment.FileOffset + segment.MemoryOffset; + return offset - segment.FileOffset + (NSO_GLOBAL_OFFSET + segment.MemoryOffset); } public override ulong GetRVA(ulong pointer) @@ -256,6 +363,8 @@ public override ulong GetRVA(ulong pointer) } public override byte[] GetRawBinaryContent() => _raw; + + public override ulong[] GetAllExportedIl2CppFunctionPointers() => Array.Empty(); public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind) { diff --git a/LibCpp2IL/NintendoSwitch/NsoHeader.cs b/LibCpp2IL/NintendoSwitch/NsoHeader.cs index 80185be9..13df8b7b 100644 --- a/LibCpp2IL/NintendoSwitch/NsoHeader.cs +++ b/LibCpp2IL/NintendoSwitch/NsoHeader.cs @@ -16,14 +16,12 @@ public class NsoHeader public uint TextCompressedSize; public uint RoDataCompressedSize; public uint DataCompressedSize; - public byte[] Padding; + public byte[] NsoHeaderReserved; public NsoRelativeExtent APIInfo; public NsoRelativeExtent DynStr; public NsoRelativeExtent DynSym; public byte[] TextHash; public byte[] RoDataHash; public byte[] DataHash; - - public NsoSegmentHeader BssSegment; } } \ No newline at end of file diff --git a/LibCpp2IL/NintendoSwitch/NsoModHeader.cs b/LibCpp2IL/NintendoSwitch/NsoModHeader.cs new file mode 100644 index 00000000..ddadb308 --- /dev/null +++ b/LibCpp2IL/NintendoSwitch/NsoModHeader.cs @@ -0,0 +1,14 @@ +namespace LibCpp2IL.NintendoSwitch +{ + public class NsoModHeader + { + public uint ModOffset; + public uint DynamicOffset; + public uint BssStart; + public uint BssEnd; + public uint EhFrameHdrStart; + public uint EhFrameHdrEnd; + + public NsoSegmentHeader BssSegment; + } +} \ No newline at end of file diff --git a/LibCpp2IL/PE/PE.cs b/LibCpp2IL/PE/PE.cs index f5f07589..24ed0c88 100644 --- a/LibCpp2IL/PE/PE.cs +++ b/LibCpp2IL/PE/PE.cs @@ -154,6 +154,24 @@ private void LoadPeExportTable() } } + public override ulong[] GetAllExportedIl2CppFunctionPointers() + { + if(peExportedFunctionPointers == null) + LoadPeExportTable(); + + return peExportedFunctionPointers!.Where((e, i) => GetExportedFunctionName(i).StartsWith("il2cpp_")).Select(e => (ulong)e).ToArray(); + } + + private string GetExportedFunctionName(int index) + { + if (peExportedFunctionPointers == null) + LoadPeExportTable(); + + var stringAddress = peExportedFunctionNamePtrs[index]; + var rawStringAddress = MapVirtualAddressToRaw(stringAddress + peImageBase); + return ReadStringToNull(rawStringAddress); + } + public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind) { if (peExportedFunctionPointers == null) diff --git a/LibCpp2IL/Properties/AssemblyInfo.cs b/LibCpp2IL/Properties/AssemblyInfo.cs index 72ca059e..3944f978 100644 --- a/LibCpp2IL/Properties/AssemblyInfo.cs +++ b/LibCpp2IL/Properties/AssemblyInfo.cs @@ -5,11 +5,11 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyDescription("Library for reversing Unity's il2cpp build process")] -[assembly: AssemblyCopyright("Copyright © Samboy063 2019-2020")] +[assembly: AssemblyCopyright("Copyright © Samboy063 2019-2022")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("2021.6.1")] -[assembly: AssemblyFileVersion("2021.6.1")] +[assembly: AssemblyVersion("2022.0.7")] +[assembly: AssemblyFileVersion("2022.0.7")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from diff --git a/LibCpp2IL/Reflection/Il2CppTypeReflectionData.cs b/LibCpp2IL/Reflection/Il2CppTypeReflectionData.cs index 7cb5f04b..8793be9f 100644 --- a/LibCpp2IL/Reflection/Il2CppTypeReflectionData.cs +++ b/LibCpp2IL/Reflection/Il2CppTypeReflectionData.cs @@ -24,7 +24,6 @@ public class Il2CppTypeReflectionData public Il2CppTypeReflectionData? arrayType; public byte arrayRank; public string variableGenericParamName; - public long variableGenericParamIndex; public bool isPointer; #pragma warning restore 8618 diff --git a/LibCpp2IL/Wasm/WasmExportSection.cs b/LibCpp2IL/Wasm/WasmExportSection.cs index 43ab8847..2ca2c154 100644 --- a/LibCpp2IL/Wasm/WasmExportSection.cs +++ b/LibCpp2IL/Wasm/WasmExportSection.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using LibCpp2IL.Logging; namespace LibCpp2IL.Wasm { @@ -12,8 +13,12 @@ internal WasmExportSection(WasmSectionId type, long pointer, ulong size, WasmFil ExportCount = file.BaseStream.ReadLEB128Unsigned(); for (var i = 0UL; i < ExportCount; i++) { - Exports.Add(new(file)); + var export = new WasmExportEntry(file); + if(export.Kind == WasmExternalKind.EXT_FUNCTION) + LibLogger.VerboseNewline($"\t\t\t- Found exported function {export.Name}"); + Exports.Add(export); } + LibLogger.VerboseNewline($"\t\tRead {Exports.Count} exported functions"); } } } \ No newline at end of file diff --git a/LibCpp2IL/Wasm/WasmFile.cs b/LibCpp2IL/Wasm/WasmFile.cs index 6f4f5cfd..dd09e967 100644 --- a/LibCpp2IL/Wasm/WasmFile.cs +++ b/LibCpp2IL/Wasm/WasmFile.cs @@ -10,6 +10,8 @@ namespace LibCpp2IL.Wasm { public sealed class WasmFile : Il2CppBinary { + public static Dictionary? RemappedDynCallFunctions; + public readonly List FunctionTable = new(); internal readonly List Sections = new(); @@ -102,7 +104,20 @@ public WasmFunctionDefinition GetFunctionFromIndexAndSignature(ulong index, stri private void CalculateDynCallOffsets() { - var codeSec = CodeSection; + //Remap any exported functions we have remaps for + + if (RemappedDynCallFunctions != null) + { + foreach (var exportSectionExport in ExportSection.Exports.Where(e => e.Kind == WasmExternalKind.EXT_FUNCTION)) + { + if (!RemappedDynCallFunctions.TryGetValue(exportSectionExport.Name, out var remappedName)) + continue; + + LibLogger.VerboseNewline($"\t\tRemapped exported function {exportSectionExport.Name} to {remappedName}"); + exportSectionExport.Name.Value = remappedName; + } + } + foreach (var exportedDynCall in ExportSection.Exports.Where(e => e.Kind == WasmExternalKind.EXT_FUNCTION && e.Name.Value.StartsWith("dynCall_"))) { var signature = exportedDynCall.Name.Value["dynCall_".Length..]; @@ -144,6 +159,17 @@ private void CalculateDynCallOffsets() andWith = (ulong) relevantInstructions[0].Operands[0]; add = (ulong) relevantInstructions[2].Operands[0]; + } else if (disassembled.All(d => d.Mnemonic is WasmMnemonic.LocalGet or WasmMnemonic.CallIndirect or WasmMnemonic.End)) + { + //No remapping + andWith = int.MaxValue; + add = 0; + } else if (disassembled[^1].Mnemonic == WasmMnemonic.End && disassembled[^2].Mnemonic == WasmMnemonic.CallIndirect && disassembled[^3].Mnemonic == WasmMnemonic.LocalGet && (byte) disassembled[^3].Operands[0] == 0) + { + //Tentatively assume we're doing shenanigans only to the params and we don't touch the index + LibLogger.WarnNewline($"\t\tAssuming index is not touched, but couldn't get a proper calculation for dynCall_{signature}. Might cause issues later down the line."); + andWith = int.MaxValue; + add = 0; } else { @@ -201,6 +227,8 @@ public override ulong GetRVA(ulong pointer) { return pointer; } + + public override ulong[] GetAllExportedIl2CppFunctionPointers() => Array.Empty(); public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind) { diff --git a/LibCpp2IL/Wasm/WasmMemoryBlock.cs b/LibCpp2IL/Wasm/WasmMemoryBlock.cs index abdb386e..ddfc4ccb 100644 --- a/LibCpp2IL/Wasm/WasmMemoryBlock.cs +++ b/LibCpp2IL/Wasm/WasmMemoryBlock.cs @@ -16,7 +16,7 @@ private static MemoryStream BuildStream(WasmFile file) .Max(); //Add an extra buffer beyond that just to be safe - var toAlloc = maxByte + 0x1000; + var toAlloc = (maxByte + 0x1000) * 2; var memoryBlock = new byte[toAlloc]; var stream = new MemoryStream(memoryBlock, 0, (int) toAlloc, true, true); diff --git a/README.md b/README.md index e82cd238..7e1da331 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ [![NuGet](https://img.shields.io/nuget/v/Samboy063.Cpp2IL.Core)](https://www.nuget.org/packages/Samboy063.Cpp2IL.Core/) +# Important: Project is being rewritten + +Due to, well, very much forseen circumstances, we are in the process of rewriting most of cpp2il from the ground up over in the development branch. + +As such, bug fixes, and new updates, are likely to be sparse until the work is done, which ~~I expect to be sometime in January 2022~~ will be when it will be, basically. + ### Need Help? Join [the discord](https://discord.gg/XdggT7XZXm)! WIP Tool to reverse Unity's IL2CPP build process back to the original managed DLLs. @@ -47,6 +53,7 @@ same argument as above but pass in the path to the APK, and cpp2il will extract | --skip-method-dumps | <None> | Suppress creation of method_dumps folder and files, if you don't intend to use them. | | --just-give-me-dlls-asap-dammit | <None> | Shorthand for `--parallel --skip-method-dumps --experimental-enable-il-to-assembly-please --throw-safety-out-the-window --skip-metadata-txts`, if you don't want to type that much, but still want fast, complete, IL generation | | --simple-attribute-restoration | <None> | Don't use analysis to restore attributes, meaning any attributes with constructor parameters won't be recovered. Has no effect on metadata v29+ | +| --wasm-framework-file | C:\Path\To\webgl.framework.js | Only used in conjunction with WASM binaries. Some of these have obfuscated exports but they can be recovered via a framework.js file, which you can provide the path to using this argument. | ## Release Structure @@ -107,7 +114,7 @@ If you do not wish for the output to be coloured, set the Environment Variable ` | NSO (Switch) | N/A | N/A | N/A | ✔️ | Switch is ArmV8, that is the only supported instruction set. Compression supported. | | APK (Android) | ✔ | ❌ | ✔️ | ✔️ | Unpacks the APK, then delegates to ELF loader. | | WASM (WebAssembly) | N/A | N/A | N/A | N/A | WASM is its own instruction set, which **is** supported for dumps but not analyzed yet | -| Mach-O (Mac OS)| ❌ | ❌ | N/A? | ❌ | Not supported yet, but planned | +| Mach-O (Mac OS)| ❌ | ❌ | N/A | ❌ | Not supported yet, but planned | ## Supported Analysis Features Table @@ -156,7 +163,7 @@ If you do not wish for the output to be coloured, set the Environment Variable ` ## Credits -This application is built using .NET 5.0. +This application is built primarily using .NET 6.0, but a .NET Framework 4.7.2 build is also published for legacy purposes. It uses the following libraries, for which I am very thankful: diff --git a/WasmDisassembler/Disassembler.cs b/WasmDisassembler/Disassembler.cs index 1a7f12b5..6633ec94 100644 --- a/WasmDisassembler/Disassembler.cs +++ b/WasmDisassembler/Disassembler.cs @@ -34,7 +34,10 @@ private static WasmInstruction ReadInstruction(this BinaryReader reader, WasmMne var opTypes = mnemonic.GetOperandTypes(); if (opTypes.Length == 0) + { + ret.Operands = Array.Empty(); return ret; + } ret.Operands = opTypes.Select(reader.ReadPrimitive).ToArray(); diff --git a/WasmDisassembler/WasmDisassembler.csproj b/WasmDisassembler/WasmDisassembler.csproj index c7f89b48..3a385a05 100644 --- a/WasmDisassembler/WasmDisassembler.csproj +++ b/WasmDisassembler/WasmDisassembler.csproj @@ -5,6 +5,14 @@ enable enable 10 + Samboy063.WasmDisassembler + 2022.0.2 + true + MIT + git + https://github.com/SamboyCoding/Cpp2IL.git + true + Simple, zero-dependency disassembler for WebAssembly bytecode diff --git a/WasmDisassembler/WasmInstruction.cs b/WasmDisassembler/WasmInstruction.cs index 54d6daad..b3a34ab4 100644 --- a/WasmDisassembler/WasmInstruction.cs +++ b/WasmDisassembler/WasmInstruction.cs @@ -5,7 +5,7 @@ public struct WasmInstruction public uint Ip; public uint NextIp; public WasmMnemonic Mnemonic; - public object[] Operands = Array.Empty(); + public object[] Operands; public override string ToString() { @@ -14,4 +14,4 @@ public override string ToString() return $"0x{Ip:X} {Mnemonic} {string.Join(", ", Operands)}"; } -} \ No newline at end of file +} diff --git a/global.json b/global.json new file mode 100644 index 00000000..13300bb9 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "6.0.0", + "rollForward": "latestMinor", + "allowPrerelease": false + } +}