From ed51fa1881948cb4520d70a625d0235515dbb8f0 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 7 Sep 2020 11:51:00 +0100 Subject: [PATCH] Flesh out the LibCpp2Il Api quite heavily --- Cpp2IL.sln | 10 +- Cpp2IL/Analysis/ASMAnalyzer.cs | 74 ++-- .../GlobalMethodRefToConstantAction.cs | 1 + .../GlobalStringRefToConstantAction.cs | 1 + .../Actions/GlobalTypeRefToConstantAction.cs | 2 +- Cpp2IL/AssemblyBuilder.cs | 124 +----- Cpp2IL/Program.cs | 52 +-- Cpp2IL/SharedState.cs | 1 + Cpp2IL/Utils.cs | 176 +------- LibCpp2IL/ClassReadingBinaryReader.cs | 33 +- {Cpp2IL => LibCpp2IL}/GlobalIdentifier.cs | 12 +- LibCpp2IL/LibCpp2IL.cs | 27 -- LibCpp2IL/LibCpp2ILUtils.cs | 131 ------ LibCpp2IL/LibCpp2IlGlobalMapper.cs | 102 +++++ LibCpp2IL/LibCpp2IlMain.cs | 68 +++ LibCpp2IL/LibCpp2IlUtils.cs | 405 ++++++++++++++++++ .../Metadata/Il2CppAssemblyDefinition.cs | 6 + LibCpp2IL/Metadata/Il2CppEventDefinition.cs | 28 ++ LibCpp2IL/Metadata/Il2CppFieldDefinition.cs | 13 + LibCpp2IL/Metadata/Il2CppInterfaceOffset.cs | 15 +- LibCpp2IL/Metadata/Il2CppMetadata.cs | 69 +-- LibCpp2IL/Metadata/Il2CppMethodDefinition.cs | 44 +- .../Metadata/Il2CppPropertyDefinition.cs | 26 ++ LibCpp2IL/Metadata/Il2CppTypeDefinition.cs | 91 +++- LibCpp2IL/PE/Il2CppGenericClass.cs | 2 + ...Il2CppGenericMethodFunctionsDefinitions.cs | 2 + LibCpp2IL/PE/Il2CppType.cs | 2 + LibCpp2IL/PE/OptionalHeader.cs | 2 + LibCpp2IL/PE/OptionalHeader64.cs | 2 + LibCpp2IL/PE/PE.cs | 19 +- LibCpp2IL/PE/SectionHeader.cs | 2 + LibCpp2IL/Properties/AssemblyInfo.cs | 20 + .../Reflection/Il2CppFieldReflectionData.cs | 12 + .../Il2CppParameterReflectionData.cs | 12 + .../Reflection/Il2CppTypeReflectionData.cs | 43 ++ LibCpp2IL/Reflection/LibCpp2IlReflection.cs | 68 +++ 36 files changed, 1119 insertions(+), 578 deletions(-) rename {Cpp2IL => LibCpp2IL}/GlobalIdentifier.cs (51%) delete mode 100644 LibCpp2IL/LibCpp2IL.cs delete mode 100644 LibCpp2IL/LibCpp2ILUtils.cs create mode 100644 LibCpp2IL/LibCpp2IlGlobalMapper.cs create mode 100644 LibCpp2IL/LibCpp2IlMain.cs create mode 100644 LibCpp2IL/LibCpp2IlUtils.cs create mode 100644 LibCpp2IL/Properties/AssemblyInfo.cs create mode 100644 LibCpp2IL/Reflection/Il2CppFieldReflectionData.cs create mode 100644 LibCpp2IL/Reflection/Il2CppParameterReflectionData.cs create mode 100644 LibCpp2IL/Reflection/Il2CppTypeReflectionData.cs create mode 100644 LibCpp2IL/Reflection/LibCpp2IlReflection.cs diff --git a/Cpp2IL.sln b/Cpp2IL.sln index de957db7..a83dac35 100644 --- a/Cpp2IL.sln +++ b/Cpp2IL.sln @@ -2,7 +2,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cpp2IL", "Cpp2IL\Cpp2IL.csproj", "{E8BA3E8E-3CDC-4562-AE61-CA48ECA270DE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibCpp2IL", "LibCpp2IL\LibCpp2IL.csproj", "{24E00021-D8E3-4CD7-9324-091EAC3FF48A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibCpp2IL", "LibCpp2IL\LibCpp2IL.csproj", "{7C9601B4-B53B-48CD-866F-DB908B3BF54D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -14,9 +14,9 @@ Global {E8BA3E8E-3CDC-4562-AE61-CA48ECA270DE}.Release|Any CPU.Build.0 = Release|Any CPU {E8BA3E8E-3CDC-4562-AE61-CA48ECA270DE}.Debug|Any CPU.ActiveCfg = Debug|x64 {E8BA3E8E-3CDC-4562-AE61-CA48ECA270DE}.Debug|Any CPU.Build.0 = Debug|x64 - {24E00021-D8E3-4CD7-9324-091EAC3FF48A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {24E00021-D8E3-4CD7-9324-091EAC3FF48A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {24E00021-D8E3-4CD7-9324-091EAC3FF48A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {24E00021-D8E3-4CD7-9324-091EAC3FF48A}.Release|Any CPU.Build.0 = Release|Any CPU + {7C9601B4-B53B-48CD-866F-DB908B3BF54D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C9601B4-B53B-48CD-866F-DB908B3BF54D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C9601B4-B53B-48CD-866F-DB908B3BF54D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C9601B4-B53B-48CD-866F-DB908B3BF54D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Cpp2IL/Analysis/ASMAnalyzer.cs b/Cpp2IL/Analysis/ASMAnalyzer.cs index 4d4602a0..2069b2cd 100644 --- a/Cpp2IL/Analysis/ASMAnalyzer.cs +++ b/Cpp2IL/Analysis/ASMAnalyzer.cs @@ -550,13 +550,13 @@ private void CheckForTwoOpInstruction(Instruction instruction) //Have a global here. switch (global.IdentifierType) { - case GlobalIdentifier.Type.TYPE: + case GlobalIdentifier.Type.TYPEREF: _analysis.Actions.Add(new GlobalTypeRefToConstantAction(_analysis, instruction)); break; - case GlobalIdentifier.Type.METHOD: + case GlobalIdentifier.Type.METHODREF: _analysis.Actions.Add(new GlobalMethodRefToConstantAction(_analysis, instruction)); break; - case GlobalIdentifier.Type.FIELD: + case GlobalIdentifier.Type.FIELDREF: //needed? break; case GlobalIdentifier.Type.LITERAL: @@ -782,8 +782,8 @@ private void HandleFunctionCall(MethodDefinition target, bool processReturnType, var global = GetGlobalInReg(possibility); if (global.HasValue) { - args.Add($"'{global.Value.Name}' (LITERAL type System.String) as {parameter.Name} in register {possibility}"); - paramNames.Add($"'{global.Value.Name}'"); + args.Add($"'{global.Value.Value}' (LITERAL type System.String) as {parameter.Name} in register {possibility}"); + paramNames.Add($"'{global.Value.Value}'"); if (!parameter.ParameterType.IsAssignableFrom(StringReference)) { @@ -853,10 +853,10 @@ private void HandleFunctionCall(MethodDefinition target, bool processReturnType, foreach (var possibility in possibilities) { _registerContents.TryGetValue(possibility, out var potentialGlob); - if (potentialGlob is GlobalIdentifier g && g.Offset != 0 && g.IdentifierType == GlobalIdentifier.Type.METHOD) + if (potentialGlob is GlobalIdentifier g && g.Offset != 0 && g.IdentifierType == GlobalIdentifier.Type.METHODREF) { - _typeDump.Append($" ; - generic method def located, is {g.Name}"); - var genericParams = g.Name.Substring(g.Name.LastIndexOf("<", StringComparison.Ordinal) + 1); + _typeDump.Append($" ; - generic method def located, is {g.Value}"); + var genericParams = g.Value.Substring(g.Value.LastIndexOf("<", StringComparison.Ordinal) + 1); genericParams = genericParams.Remove(genericParams.Length - 1); var genericCount = genericParams.Split(',').Length; @@ -955,8 +955,8 @@ private List DetectPotentialLoops(List instructions) if (glob2.IdentifierType == GlobalIdentifier.Type.LITERAL) { objectType = StringReference; - objectName = $"'{glob2.Name}'"; - constant = glob2.Name; + objectName = $"'{glob2.Value}'"; + constant = glob2.Value; break; } } @@ -1021,13 +1021,13 @@ private List DetectPotentialLoops(List instructions) { if (glob.IdentifierType == GlobalIdentifier.Type.LITERAL) { - objectName = $"\"{glob.Name}\""; + objectName = $"\"{glob.Value}\""; objectType = StringReference; - constant = glob.Name; + constant = glob.Value; } else { - objectName = $"global_{glob.IdentifierType}_{glob.Name}"; + objectName = $"global_{glob.IdentifierType}_{glob.Value}"; objectType = LongReference; constant = glob; } @@ -1113,7 +1113,7 @@ private List DetectPotentialLoops(List instructions) //Ok we have a global, resolve it - var (type, _) = Utils.TryLookupTypeDefByName(global.Name); + var (type, _) = Utils.TryLookupTypeDefByName(global.Value); if (type == null) return null; var fields = type.Fields.Where(f => f.IsStatic).ToList(); @@ -1989,18 +1989,18 @@ private void CheckForMoveIntoRegister(Instruction instruction) _typeDump.Append($"; - Read on memory location 0x{addr:X}"); if (SharedState.GlobalsByOffset.TryGetValue(addr, out var glob)) { - _typeDump.Append($" - this is global value {glob.Name} of type {glob.IdentifierType}"); - _registerAliases[destReg] = $"global_{glob.IdentifierType}_{glob.Name}"; + _typeDump.Append($" - this is global value {glob.Value} of type {glob.IdentifierType}"); + _registerAliases[destReg] = $"global_{glob.IdentifierType}_{glob.Value}"; _registerContents[destReg] = glob; switch (glob.IdentifierType) { - case GlobalIdentifier.Type.TYPE: - _registerTypes[destReg] = Utils.TryLookupTypeDefByName(glob.Name).Item1; + case GlobalIdentifier.Type.TYPEREF: + _registerTypes[destReg] = Utils.TryLookupTypeDefByName(glob.Value).Item1; break; - case GlobalIdentifier.Type.METHOD: + case GlobalIdentifier.Type.METHODREF: _registerTypes.TryRemove(destReg, out _); break; - case GlobalIdentifier.Type.FIELD: + case GlobalIdentifier.Type.FIELDREF: _registerTypes.TryRemove(destReg, out _); break; case GlobalIdentifier.Type.LITERAL: @@ -2199,10 +2199,10 @@ private void CheckForCallAddress(Instruction instruction) if (g is GlobalIdentifier glob) { //Check it's valid (which it should be?) - if (glob.Offset != 0 && glob.IdentifierType == GlobalIdentifier.Type.TYPE) + if (glob.Offset != 0 && glob.IdentifierType == GlobalIdentifier.Type.TYPEREF) { //Look up type - var (definedType, genericParams) = Utils.TryLookupTypeDefByName(glob.Name); + var (definedType, genericParams) = Utils.TryLookupTypeDefByName(glob.Value); if (definedType != null) { @@ -2219,7 +2219,7 @@ private void CheckForCallAddress(Instruction instruction) } else { - _methodFunctionality.Append($"{Utils.Repeat("\t", _blockDepth + 2)}Creates an instance of (unresolved) type {glob.Name}\n"); + _methodFunctionality.Append($"{Utils.Repeat("\t", _blockDepth + 2)}Creates an instance of (unresolved) type {glob.Value}\n"); success = true; } } @@ -2255,10 +2255,10 @@ private void CheckForCallAddress(Instruction instruction) if (match.Success) { Enum.TryParse(match.Groups[1].Value, out var type); - var global = SharedState.Globals.Find(g => g.Name == match.Groups[2].Value && g.IdentifierType == type); + var global = SharedState.Globals.Find(g => g.Value == match.Groups[2].Value && g.IdentifierType == type); if (global.Offset != 0) { - var (definedType, genericParams) = Utils.TryLookupTypeDefByName(global.Name.Replace("[]", "")); + var (definedType, genericParams) = Utils.TryLookupTypeDefByName(global.Value.Replace("[]", "")); if (definedType != null) { @@ -2270,8 +2270,8 @@ private void CheckForCallAddress(Instruction instruction) } else { - _typeDump.Append($" - got expected type name {global.Name} for array but could not resolve to an actual type"); - _methodFunctionality.Append($"{Utils.Repeat("\t", _blockDepth + 2)}Creates an array of (unresolved) type {global.Name} and size {arraySize}\n"); + _typeDump.Append($" - got expected type name {global.Value} for array but could not resolve to an actual type"); + _methodFunctionality.Append($"{Utils.Repeat("\t", _blockDepth + 2)}Creates an array of (unresolved) type {global.Value} and size {arraySize}\n"); TaintMethod(TaintReason.FAILED_TYPE_RESOLVE); success = true; } @@ -2352,10 +2352,10 @@ private void CheckForCallAddress(Instruction instruction) } } - if (g is GlobalIdentifier glob && glob.Offset != 0 && glob.IdentifierType == GlobalIdentifier.Type.TYPE) + if (g is GlobalIdentifier glob && glob.Offset != 0 && glob.IdentifierType == GlobalIdentifier.Type.TYPEREF) { - var destType = Utils.TryLookupTypeDefByName(glob.Name).Item1; - _typeDump.Append($" - Boxes the primitive value {castTarget} to {destType?.FullName} (resolved from {glob.Name})"); + var destType = Utils.TryLookupTypeDefByName(glob.Value).Item1; + _typeDump.Append($" - Boxes the primitive value {castTarget} to {destType?.FullName} (resolved from {glob.Value})"); _registerAliases["rax"] = castTarget; if (destType != null) _registerTypes["rax"] = destType; @@ -2376,10 +2376,10 @@ private void CheckForCallAddress(Instruction instruction) //Try to directly resolve a destination type constant (as a global) in rdx _registerContents.TryGetValue("rdx", out var t); - if (t is GlobalIdentifier typeGlobal && typeGlobal.IdentifierType == GlobalIdentifier.Type.TYPE) + if (t is GlobalIdentifier typeGlobal && typeGlobal.IdentifierType == GlobalIdentifier.Type.TYPEREF) { //got one? look it up and re-set T to the type def. - (t, _) = Utils.TryLookupTypeDefByName(typeGlobal.Name); + (t, _) = Utils.TryLookupTypeDefByName(typeGlobal.Value); } object castTarget; @@ -2393,7 +2393,7 @@ private void CheckForCallAddress(Instruction instruction) _registerContents.TryGetValue("rcx", out var g); if (g is GlobalIdentifier glob) { - castTarget = glob.Name; + castTarget = glob.Value; globalIdentifier = glob; } else @@ -2425,11 +2425,11 @@ private void CheckForCallAddress(Instruction instruction) if (globalIdentifier.Offset == 0 || globalIdentifier.IdentifierType != GlobalIdentifier.Type.LITERAL) { - _psuedoCode.Append(globalIdentifier.Name ?? castTarget); + _psuedoCode.Append(globalIdentifier.Value ?? castTarget); } else { - _psuedoCode.Append($"'{globalIdentifier.Name}'"); + _psuedoCode.Append($"'{globalIdentifier.Value}'"); } _psuedoCode.Append("\n"); @@ -2948,7 +2948,7 @@ private void CheckForReturn(Instruction instruction) if (returnConstant != null) { if (returnConstant is GlobalIdentifier glob && glob.IdentifierType == GlobalIdentifier.Type.LITERAL) - returnConstant = glob.Name; + returnConstant = glob.Value; if (returnConstant is string) returnConstant = $"\"{returnConstant}\""; @@ -2979,7 +2979,7 @@ private void CheckForReturn(Instruction instruction) if (match.Success) { Enum.TryParse(match.Groups[1].Value, out var type); - var global = SharedState.Globals.Find(g2 => g2.Name == match.Groups[2].Value && g2.IdentifierType == type); + var global = SharedState.Globals.Find(g2 => g2.Value == match.Groups[2].Value && g2.IdentifierType == type); if (global.Offset != 0) { return global; diff --git a/Cpp2IL/Analysis/Actions/GlobalMethodRefToConstantAction.cs b/Cpp2IL/Analysis/Actions/GlobalMethodRefToConstantAction.cs index d81812e4..4261063e 100644 --- a/Cpp2IL/Analysis/Actions/GlobalMethodRefToConstantAction.cs +++ b/Cpp2IL/Analysis/Actions/GlobalMethodRefToConstantAction.cs @@ -1,4 +1,5 @@ using Cpp2IL.Analysis.ResultModels; +using LibCpp2IL; using Mono.Cecil; using SharpDisasm; diff --git a/Cpp2IL/Analysis/Actions/GlobalStringRefToConstantAction.cs b/Cpp2IL/Analysis/Actions/GlobalStringRefToConstantAction.cs index affc6d26..0d553a8b 100644 --- a/Cpp2IL/Analysis/Actions/GlobalStringRefToConstantAction.cs +++ b/Cpp2IL/Analysis/Actions/GlobalStringRefToConstantAction.cs @@ -1,4 +1,5 @@ using Cpp2IL.Analysis.ResultModels; +using LibCpp2IL; using Mono.Cecil; using SharpDisasm; diff --git a/Cpp2IL/Analysis/Actions/GlobalTypeRefToConstantAction.cs b/Cpp2IL/Analysis/Actions/GlobalTypeRefToConstantAction.cs index f00fb364..43f4b1ff 100644 --- a/Cpp2IL/Analysis/Actions/GlobalTypeRefToConstantAction.cs +++ b/Cpp2IL/Analysis/Actions/GlobalTypeRefToConstantAction.cs @@ -17,7 +17,7 @@ public GlobalTypeRefToConstantAction(MethodAnalysis context, Instruction instruc { var globalAddress = context.MethodStart + LibCpp2ILUtils.GetOffsetFromMemoryAccess(instruction, instruction.Operands[1]); GlobalRead = SharedState.GlobalsByOffset[globalAddress]; - var (type, genericParams) = Utils.TryLookupTypeDefByName(GlobalRead.Name); + var (type, genericParams) = Utils.TryLookupTypeDefByName(GlobalRead.Value); ResolvedType = type; if (ResolvedType == null) return; diff --git a/Cpp2IL/AssemblyBuilder.cs b/Cpp2IL/AssemblyBuilder.cs index 0e71a1e9..e04f6e8c 100644 --- a/Cpp2IL/AssemblyBuilder.cs +++ b/Cpp2IL/AssemblyBuilder.cs @@ -226,6 +226,16 @@ private static List ProcessTypeContents(Il2CppMetadata metadata, typeMetaText.Append($"\t\t{iface.InterfaceType.FullName}\n"); } + if (ilTypeDefinition.NestedTypes.Count > 0) + { + typeMetaText.Append("\n\tNested Types:\n"); + + foreach (var nestedType in ilTypeDefinition.NestedTypes) + { + typeMetaText.Append($"\t\t{nestedType.FullName}\n"); + } + } + var addressAttribute = ilTypeDefinition.Module.Types.First(x => x.Name == "AddressAttribute").Methods[0]; var fieldOffsetAttribute = ilTypeDefinition.Module.Types.First(x => x.FullName == "Cpp2IlInjected.FieldOffsetAttribute").Methods[0]; var attributeAttribute = ilTypeDefinition.Module.Types.First(x => x.Name == "AttributeAttribute").Methods[0]; @@ -281,7 +291,7 @@ private static List ProcessTypeContents(Il2CppMetadata metadata, var fieldDefault = metadata.GetFieldDefaultValueFromIndex(fieldIdx); if (fieldDefault != null && fieldDefault.dataIndex != -1) { - fieldDefinition.Constant = Utils.GetDefaultValue(fieldDefault.dataIndex, + fieldDefinition.Constant = LibCpp2ILUtils.GetDefaultValue(fieldDefault.dataIndex, fieldDefault.typeIndex, metadata, cppAssembly); } } @@ -303,13 +313,13 @@ private static List ProcessTypeContents(Il2CppMetadata metadata, SharedState.FieldsByType[ilTypeDefinition] = fields; //Methods - var lastMethodId = cppTypeDefinition.firstMethodId + cppTypeDefinition.method_count; + var lastMethodId = cppTypeDefinition.firstMethodIdx + cppTypeDefinition.method_count; var typeMethods = new List(); Il2CppGenericContainer genericContainer; - for (var methodId = cppTypeDefinition.firstMethodId; methodId < lastMethodId; ++methodId) + for (var methodId = cppTypeDefinition.firstMethodIdx; methodId < lastMethodId; ++methodId) { var methodDef = metadata.methodDefs[methodId]; - var methodReturnType = cppAssembly.types[methodDef.returnType]; + var methodReturnType = cppAssembly.types[methodDef.returnTypeIdx]; var methodName = metadata.GetStringFromIndex(methodDef.nameIndex); var methodDefinition = new MethodDefinition(methodName, (MethodAttributes) methodDef.flags, ilTypeDefinition.Module.ImportReference(Utils.TryLookupTypeDefByName("System.Void").Item1)); @@ -391,7 +401,7 @@ private static List ProcessTypeContents(Il2CppMetadata metadata, var parameterDefault = metadata.GetParameterDefaultValueFromIndex(methodDef.parameterStart + paramIdx); if (parameterDefault != null && parameterDefault.dataIndex != -1) { - parameterDefinition.Constant = Utils.GetDefaultValue(parameterDefault.dataIndex, parameterDefault.typeIndex, metadata, cppAssembly); + parameterDefinition.Constant = LibCpp2ILUtils.GetDefaultValue(parameterDefault.dataIndex, parameterDefault.typeIndex, metadata, cppAssembly); } } @@ -469,13 +479,13 @@ private static List ProcessTypeContents(Il2CppMetadata metadata, MethodDefinition setter = null; if (propertyDef.get >= 0) { - getter = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodId + propertyDef.get]; + getter = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodIdx + propertyDef.get]; propertyType = getter.ReturnType; } if (propertyDef.set >= 0) { - setter = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodId + propertyDef.set]; + setter = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodIdx + propertyDef.set]; if (propertyType == null) propertyType = setter.Parameters[0].ParameterType; } @@ -503,11 +513,11 @@ private static List ProcessTypeContents(Il2CppMetadata metadata, var eventTypeRef = Utils.ImportTypeInto(ilTypeDefinition, eventType, cppAssembly, metadata); var eventDefinition = new EventDefinition(eventName, (EventAttributes) eventType.attrs, eventTypeRef); if (eventDef.add >= 0) - eventDefinition.AddMethod = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodId + eventDef.add]; + eventDefinition.AddMethod = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodIdx + eventDef.add]; if (eventDef.remove >= 0) - eventDefinition.RemoveMethod = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodId + eventDef.remove]; + eventDefinition.RemoveMethod = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodIdx + eventDef.remove]; if (eventDef.raise >= 0) - eventDefinition.InvokeMethod = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodId + eventDef.raise]; + eventDefinition.InvokeMethod = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodIdx + eventDef.raise]; customTokenAttribute = new CustomAttribute(ilTypeDefinition.Module.ImportReference(tokenAttribute)); customTokenAttribute.Fields.Add(new CustomAttributeNamedArgument("Token", new CustomAttributeArgument(stringType, $"0x{eventDef.token:X}"))); @@ -582,98 +592,6 @@ private static ulong HandleField(TypeReference fieldTypeRef, ulong fieldOffset, return fieldOffset; } - internal static List MapGlobalIdentifiers(Il2CppMetadata metadata, PE cppAssembly) - { - //Classes - var ret = metadata.metadataUsageDic[1] - .Select(kvp => new {kvp, type = cppAssembly.types[kvp.Value]}) - .Select(t => new GlobalIdentifier - { - Name = Utils.GetTypeName(metadata, cppAssembly, t.type, true), - Offset = cppAssembly.metadataUsages[t.kvp.Key], - IdentifierType = GlobalIdentifier.Type.TYPE - }).ToList(); - - //Idx 2 is exactly the same thing - ret.AddRange(metadata.metadataUsageDic[2] - .Select(kvp => new {kvp, type = cppAssembly.types[kvp.Value]}) - .Select(t => new GlobalIdentifier - { - Name = Utils.GetTypeName(metadata, cppAssembly, t.type, true), - Offset = cppAssembly.metadataUsages[t.kvp.Key], - IdentifierType = GlobalIdentifier.Type.TYPE - }) - ); - - //Methods is idx 3 - //Don't @ me, i prefer LINQ to foreach loops. - //But that said this could be optimised to less t-ing - ret.AddRange(metadata.metadataUsageDic[3] - .Select(kvp => new {kvp, method = metadata.methodDefs[kvp.Value]}) - .Select(t => new {t.kvp, t.method, type = metadata.typeDefs[t.method.declaringType]}) - .Select(t => new {t.kvp, t.method, typeName = Utils.GetTypeName(metadata, cppAssembly, t.type)}) - .Select(t => new {t.kvp, methodName = t.typeName + "." + metadata.GetStringFromIndex(t.method.nameIndex) + "()"}) - .Select(t => new GlobalIdentifier - { - IdentifierType = GlobalIdentifier.Type.METHOD, - Name = t.methodName, - Offset = cppAssembly.metadataUsages[t.kvp.Key] - }) - ); - - //Fields is idx 4 - ret.AddRange(metadata.metadataUsageDic[4] - .Select(kvp => new {kvp, fieldRef = metadata.fieldRefs[kvp.Value]}) - .Select(t => new {t.kvp, t.fieldRef, type = cppAssembly.types[t.fieldRef.typeIndex]}) - .Select(t => new {t.type, t.kvp, t.fieldRef, typeDef = metadata.typeDefs[t.type.data.classIndex]}) - .Select(t => new {t.type, t.kvp, fieldDef = metadata.fieldDefs[t.typeDef.firstFieldIdx + t.fieldRef.fieldIndex]}) - .Select(t => new {t.kvp, fieldName = Utils.GetTypeName(metadata, cppAssembly, t.type, true) + "." + metadata.GetStringFromIndex(t.fieldDef.nameIndex)}) - .Select(t => new GlobalIdentifier - { - IdentifierType = GlobalIdentifier.Type.FIELD, - Name = t.fieldName, - Offset = cppAssembly.metadataUsages[t.kvp.Key] - }) - ); - - //Literals - ret.AddRange(metadata.metadataUsageDic[5] - .Select(kvp => new GlobalIdentifier - { - IdentifierType = GlobalIdentifier.Type.LITERAL, - Offset = cppAssembly.metadataUsages[kvp.Key], - Name = $"{metadata.GetStringLiteralFromIndex(kvp.Value)}" - }) - ); - - foreach (var kvp in metadata.metadataUsageDic[6]) //kIl2CppMetadataUsageMethodRef - { - var methodSpec = cppAssembly.methodSpecs[kvp.Value]; - var methodDef = metadata.methodDefs[methodSpec.methodDefinitionIndex]; - var typeDef = metadata.typeDefs[methodDef.declaringType]; - var typeName = Utils.GetTypeName(metadata, cppAssembly, typeDef); - if (methodSpec.classIndexIndex != -1) - { - var classInst = cppAssembly.genericInsts[methodSpec.classIndexIndex]; - typeName += Utils.GetGenericTypeParams(metadata, cppAssembly, classInst); - } - - var methodName = typeName + "." + metadata.GetStringFromIndex(methodDef.nameIndex) + "()"; - if (methodSpec.methodIndexIndex != -1) - { - var methodInst = cppAssembly.genericInsts[methodSpec.methodIndexIndex]; - methodName += Utils.GetGenericTypeParams(metadata, cppAssembly, methodInst); - } - - ret.Add(new GlobalIdentifier - { - Name = methodName, - IdentifierType = GlobalIdentifier.Type.METHOD, - Offset = cppAssembly.metadataUsages[kvp.Key] - }); - } - - return ret; - } + } } \ No newline at end of file diff --git a/Cpp2IL/Program.cs b/Cpp2IL/Program.cs index 08015698..2c97fa26 100644 --- a/Cpp2IL/Program.cs +++ b/Cpp2IL/Program.cs @@ -51,17 +51,14 @@ internal class Options private static List Assemblies = new List(); internal static Options? CommandLineOptions; - public static void PrintUsage() - { - Console.WriteLine("Usage: Cpp2IL [name of game exe]"); - } - public static void Main(string[] args) { Console.WriteLine("===Cpp2IL by Samboy063==="); Console.WriteLine("A Tool to Reverse Unity's \"il2cpp\" Build Process."); Console.WriteLine("Running on " + Environment.OSVersion.Platform); + #region Command Line Parsing + CommandLineOptions = null; Parser.Default.ParseArguments(args).WithParsed(options => { CommandLineOptions = options; }); @@ -77,8 +74,7 @@ public static void Main(string[] args) if (!Directory.Exists(baseGamePath)) { - Console.WriteLine("Specified path does not exist: " + baseGamePath); - PrintUsage(); + Console.WriteLine("Specified game-path does not exist: " + baseGamePath); return; } @@ -97,22 +93,24 @@ public static void Main(string[] args) } var unityPlayerPath = Path.Combine(baseGamePath, $"{exeName}.exe"); - var metadataPath = Path.Combine(baseGamePath, $"{exeName}_Data", "il2cpp_data", "Metadata", - "global-metadata.dat"); + var metadataPath = Path.Combine(baseGamePath, $"{exeName}_Data", "il2cpp_data", "Metadata", "global-metadata.dat"); if (!File.Exists(assemblyPath) || !File.Exists(unityPlayerPath) || !File.Exists(metadataPath)) { - Console.WriteLine("Invalid path specified. Failed to find one of the following:\n" + + Console.WriteLine("Invalid game-path or exe-name specified. Failed to find one of the following:\n" + $"\t{assemblyPath}\n" + $"\t{unityPlayerPath}\n" + $"\t{metadataPath}\n"); - PrintUsage(); return; } + #endregion + Console.WriteLine($"Located game EXE: {unityPlayerPath}"); Console.WriteLine($"Located global-metadata: {metadataPath}"); + #region Unity Version Determination + Console.WriteLine("\nAttempting to determine Unity version..."); int[] unityVerUseful; @@ -149,6 +147,10 @@ public static void Main(string[] args) return; } + #endregion + + LibCpp2IlMain.Settings.AllowManualMetadataAndCodeRegInput = true; + if (!LibCpp2IlMain.LoadFromFile(assemblyPath, metadataPath, unityVerUseful)) { Console.WriteLine("Initialization with LibCpp2IL failed."); @@ -249,16 +251,18 @@ public static void Main(string[] args) { Console.WriteLine("\tPass 5: Locating Globals..."); - var globals = AssemblyBuilder.MapGlobalIdentifiers(LibCpp2IlMain.TheMetadata, LibCpp2IlMain.ThePe); - - Console.WriteLine($"\t\tFound {globals.Count(g => g.IdentifierType == GlobalIdentifier.Type.TYPE)} type globals"); - Console.WriteLine($"\t\tFound {globals.Count(g => g.IdentifierType == GlobalIdentifier.Type.METHOD)} method globals"); - Console.WriteLine($"\t\tFound {globals.Count(g => g.IdentifierType == GlobalIdentifier.Type.FIELD)} field globals"); - Console.WriteLine($"\t\tFound {globals.Count(g => g.IdentifierType == GlobalIdentifier.Type.LITERAL)} string literals"); + Console.WriteLine($"\t\tFound {LibCpp2IlGlobalMapper.TypeRefs.Count} type globals"); + Console.WriteLine($"\t\tFound {LibCpp2IlGlobalMapper.MethodRefs.Count} method globals"); + Console.WriteLine($"\t\tFound {LibCpp2IlGlobalMapper.FieldRefs.Count} field globals"); + Console.WriteLine($"\t\tFound {LibCpp2IlGlobalMapper.Literals.Count} string literals"); - SharedState.Globals.AddRange(globals); + //TODO: Don't do this. Rework everything to use the API surface. + SharedState.Globals.AddRange(LibCpp2IlGlobalMapper.TypeRefs); + SharedState.Globals.AddRange(LibCpp2IlGlobalMapper.MethodRefs); + SharedState.Globals.AddRange(LibCpp2IlGlobalMapper.FieldRefs); + SharedState.Globals.AddRange(LibCpp2IlGlobalMapper.Literals); - foreach (var globalIdentifier in globals) + foreach (var globalIdentifier in SharedState.Globals) SharedState.GlobalsByOffset[globalIdentifier.Offset] = globalIdentifier; Console.WriteLine("\tPass 6: Looking for key functions..."); @@ -292,9 +296,9 @@ public static void Main(string[] args) var dllPath = Path.Combine(outputPath, assembly.MainModule.Name); var reference = assembly.MainModule.AssemblyReferences.FirstOrDefault(a => a.Name == "System.Private.CoreLib"); - if(reference != null) + if (reference != null) assembly.MainModule.AssemblyReferences.Remove(reference); - + assembly.Write(dllPath); if (assembly.Name.Name != "Assembly-CSharp" || CommandLineOptions.SkipAnalysis) continue; @@ -354,9 +358,9 @@ public static void Main(string[] args) foreach (var method in methodData) { var methodStart = method.MethodOffsetRam; - - if(methodStart == 0) continue; - + + if (methodStart == 0) continue; + var methodDefinition = SharedState.MethodsByIndex[method.MethodId]; var taintResult = new AsmDumper(methodDefinition, method, methodStart, keyFunctionAddresses!, LibCpp2IlMain.ThePe) diff --git a/Cpp2IL/SharedState.cs b/Cpp2IL/SharedState.cs index f64b57b5..c3d09992 100644 --- a/Cpp2IL/SharedState.cs +++ b/Cpp2IL/SharedState.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Cpp2IL.Analysis; +using LibCpp2IL; using LibCpp2IL.Metadata; using Mono.Cecil; diff --git a/Cpp2IL/Utils.cs b/Cpp2IL/Utils.cs index addafe33..621aa49d 100644 --- a/Cpp2IL/Utils.cs +++ b/Cpp2IL/Utils.cs @@ -145,8 +145,7 @@ public static TypeReference ImportTypeInto(MemberReference importInto, Il2CppTyp case Il2CppTypeEnum.IL2CPP_TYPE_VAR: { - if (SharedState.GenericParamsByIndex.TryGetValue(toImport.data.genericParameterIndex, - out var genericParameter)) + if (SharedState.GenericParamsByIndex.TryGetValue(toImport.data.genericParameterIndex, out var genericParameter)) { return genericParameter; } @@ -196,176 +195,6 @@ public static TypeReference ImportTypeInto(MemberReference importInto, Il2CppTyp } } - internal static object GetDefaultValue(int dataIndex, int typeIndex, Il2CppMetadata metadata, PE theDll) - { - var pointer = metadata.GetDefaultValueFromIndex(dataIndex); - if (pointer > 0) - { - var defaultValueType = theDll.types[typeIndex]; - metadata.Position = pointer; - switch (defaultValueType.type) - { - case Il2CppTypeEnum.IL2CPP_TYPE_BOOLEAN: - return metadata.ReadBoolean(); - case Il2CppTypeEnum.IL2CPP_TYPE_U1: - return metadata.ReadByte(); - case Il2CppTypeEnum.IL2CPP_TYPE_I1: - return metadata.ReadSByte(); - case Il2CppTypeEnum.IL2CPP_TYPE_CHAR: - return BitConverter.ToChar(metadata.ReadBytes(2), 0); - case Il2CppTypeEnum.IL2CPP_TYPE_U2: - return metadata.ReadUInt16(); - case Il2CppTypeEnum.IL2CPP_TYPE_I2: - return metadata.ReadInt16(); - case Il2CppTypeEnum.IL2CPP_TYPE_U4: - return metadata.ReadUInt32(); - case Il2CppTypeEnum.IL2CPP_TYPE_I4: - return metadata.ReadInt32(); - case Il2CppTypeEnum.IL2CPP_TYPE_U8: - return metadata.ReadUInt64(); - case Il2CppTypeEnum.IL2CPP_TYPE_I8: - return metadata.ReadInt64(); - case Il2CppTypeEnum.IL2CPP_TYPE_R4: - return metadata.ReadSingle(); - case Il2CppTypeEnum.IL2CPP_TYPE_R8: - return metadata.ReadDouble(); - case Il2CppTypeEnum.IL2CPP_TYPE_STRING: - var len = metadata.ReadInt32(); - return Encoding.UTF8.GetString(metadata.ReadBytes(len)); - } - } - - return null; - } - - 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"} - }; - - internal static string GetTypeName(Il2CppMetadata metadata, PE cppAssembly, Il2CppType type, bool fullName = false) - { - string ret; - switch (type.type) - { - case Il2CppTypeEnum.IL2CPP_TYPE_CLASS: - case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE: - { - var typeDef = metadata.typeDefs[type.data.classIndex]; - ret = String.Empty; - if (fullName) - { - ret = metadata.GetStringFromIndex(typeDef.namespaceIndex); - if (ret != String.Empty) - { - ret += "."; - } - } - - ret += GetTypeName(metadata, cppAssembly, typeDef); - break; - } - case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST: - { - var genericClass = cppAssembly.ReadClassAtVirtualAddress(type.data.generic_class); - var typeDef = metadata.typeDefs[genericClass.typeDefinitionIndex]; - ret = metadata.GetStringFromIndex(typeDef.nameIndex); - var genericInst = cppAssembly.ReadClassAtVirtualAddress(genericClass.context.class_inst); - ret = ret.Replace($"`{genericInst.type_argc}", ""); - ret += GetGenericTypeParams(metadata, cppAssembly, genericInst); - break; - } - case Il2CppTypeEnum.IL2CPP_TYPE_VAR: - case Il2CppTypeEnum.IL2CPP_TYPE_MVAR: - { - var param = metadata.genericParameters[type.data.genericParameterIndex]; - ret = metadata.GetStringFromIndex(param.nameIndex); - break; - } - case Il2CppTypeEnum.IL2CPP_TYPE_ARRAY: - { - var arrayType = cppAssembly.ReadClassAtVirtualAddress(type.data.array); - var oriType = cppAssembly.GetIl2CppType(arrayType.etype); - ret = $"{GetTypeName(metadata, cppAssembly, oriType)}[{new string(',', arrayType.rank - 1)}]"; - break; - } - case Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY: - { - var oriType = cppAssembly.GetIl2CppType(type.data.type); - ret = $"{GetTypeName(metadata, cppAssembly, oriType)}[]"; - break; - } - case Il2CppTypeEnum.IL2CPP_TYPE_PTR: - { - var oriType = cppAssembly.GetIl2CppType(type.data.type); - ret = $"{GetTypeName(metadata, cppAssembly, oriType)}*"; - break; - } - default: - ret = TypeString[(int) type.type]; - break; - } - - return ret; - } - - internal static string GetTypeName(Il2CppMetadata metadata, PE cppAssembly, Il2CppTypeDefinition typeDef) - { - var ret = String.Empty; - if (typeDef.declaringTypeIndex != -1) - { - ret += GetTypeName(metadata, cppAssembly, cppAssembly.types[typeDef.declaringTypeIndex]) + "."; - } - - ret += metadata.GetStringFromIndex(typeDef.nameIndex); - var names = new List(); - if (typeDef.genericContainerIndex >= 0) - { - var genericContainer = metadata.genericContainers[typeDef.genericContainerIndex]; - for (int i = 0; i < genericContainer.type_argc; i++) - { - var genericParameterIndex = genericContainer.genericParameterStart + i; - var param = metadata.genericParameters[genericParameterIndex]; - names.Add(metadata.GetStringFromIndex(param.nameIndex)); - } - - ret = ret.Replace($"`{genericContainer.type_argc}", ""); - ret += $"<{String.Join(", ", names)}>"; - } - - return ret; - } - - internal static string GetGenericTypeParams(Il2CppMetadata metadata, PE cppAssembly, Il2CppGenericInst genericInst) - { - var typeNames = new List(); - var pointers = cppAssembly.ReadClassArrayAtVirtualAddress(genericInst.type_argv, (long) genericInst.type_argc); - for (uint i = 0; i < genericInst.type_argc; ++i) - { - var oriType = cppAssembly.GetIl2CppType(pointers[i]); - typeNames.Add(GetTypeName(metadata, cppAssembly, oriType)); - } - - return $"<{String.Join(", ", typeNames)}>"; - } - public static bool CheckForNullCheckAtIndex(ulong offsetInRam, PE cppAssembly, List instructions, int idx, KeyFunctionAddresses kfe) { if (instructions.Count - idx < 2) return false; @@ -541,7 +370,8 @@ private static Tuple InternalTryLookupTypeDefByName(st if (definedType != null || !name.Contains(".")) return new Tuple(definedType, genericParams); //Try subclasses - searchString = name.Replace(".", "/"); + var lastIdx = name.LastIndexOf(".", StringComparison.Ordinal); + searchString = name.Substring(0, lastIdx) + "/" + name.Substring(lastIdx + 1); matches = SharedState.AllTypeDefinitions.Where(t => t.FullName.EndsWith(searchString)).ToList(); if (matches.Count == 1) definedType = matches.First(); diff --git a/LibCpp2IL/ClassReadingBinaryReader.cs b/LibCpp2IL/ClassReadingBinaryReader.cs index f23ac707..08bf5866 100644 --- a/LibCpp2IL/ClassReadingBinaryReader.cs +++ b/LibCpp2IL/ClassReadingBinaryReader.cs @@ -13,16 +13,16 @@ public class ClassReadingBinaryReader : BinaryReader public ClassReadingBinaryReader(Stream input) : base(input) { - readClass = GetType().GetMethod("ReadClass"); + readClass = GetType().GetMethod("ReadClass")!; } - + public long Position { get => BaseStream.Position; set => BaseStream.Position = value; } - - private object ReadPrimitive(Type type) + + private object? ReadPrimitive(MemberInfo type) { var typename = type.Name; switch (typename) @@ -50,33 +50,38 @@ private object ReadPrimitive(Type type) } } - public T ReadClass(long offset) where T: new() + public T ReadClass(long offset) where T : new() { if (offset >= 0) Position = offset; - + var type = typeof(T); if (type.IsPrimitive) { var value = ReadPrimitive(type); - + //32-bit fixes... if (value is uint && typeof(T).Name == "UInt64") value = Convert.ToUInt64(value); if (value is int && typeof(T).Name == "Int64") value = Convert.ToInt64(value); - - return (T) value; + + return (T) value!; } var t = new T(); foreach (var i in t.GetType().GetFields()) { - var attr = (VersionAttribute)Attribute.GetCustomAttribute(i, typeof(VersionAttribute)); + var attr = (VersionAttribute?) Attribute.GetCustomAttribute(i, typeof(VersionAttribute)); + var nonSerializedAttribute = (NonSerializedAttribute?) Attribute.GetCustomAttribute(i, typeof(NonSerializedAttribute)); + + if(nonSerializedAttribute != null) continue; + if (attr != null) { if (LibCpp2IlMain.MetadataVersion < attr.Min || LibCpp2IlMain.MetadataVersion > attr.Max) continue; } + if (i.FieldType.IsPrimitive) { i.SetValue(t, ReadPrimitive(i.FieldType)); @@ -89,21 +94,23 @@ private object ReadPrimitive(Type type) break; } } + return t; } - + public T[] ReadClassArray(long offset, long count) where T : new() { if (offset != -1) Position = offset; - + var t = new T[count]; for (var i = 0; i < count; i++) { t[i] = ReadClass(-1); } + return t; } - + public string ReadStringToNull(long offset) { Position = offset; diff --git a/Cpp2IL/GlobalIdentifier.cs b/LibCpp2IL/GlobalIdentifier.cs similarity index 51% rename from Cpp2IL/GlobalIdentifier.cs rename to LibCpp2IL/GlobalIdentifier.cs index 6b1e26f7..2278d07c 100644 --- a/Cpp2IL/GlobalIdentifier.cs +++ b/LibCpp2IL/GlobalIdentifier.cs @@ -1,21 +1,21 @@ -namespace Cpp2IL +namespace LibCpp2IL { public struct GlobalIdentifier { public ulong Offset; - public string Name; + public string Value; public Type IdentifierType; public override string ToString() { - return $"Cpp2IL Global Identifier (Name = {Name}, Offset = 0x{Offset:X}, Type = {IdentifierType})"; + return $"LibCpp2IL Global Identifier (Name = {Value}, Offset = 0x{Offset:X}, Type = {IdentifierType})"; } public enum Type { - TYPE, - METHOD, - FIELD, + TYPEREF, + METHODREF, + FIELDREF, LITERAL } } diff --git a/LibCpp2IL/LibCpp2IL.cs b/LibCpp2IL/LibCpp2IL.cs deleted file mode 100644 index c11be6ae..00000000 --- a/LibCpp2IL/LibCpp2IL.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using LibCpp2IL.Metadata; - -namespace LibCpp2IL -{ - public static class LibCpp2IlMain - { - public static float MetadataVersion = 24f; - public static PE.PE? ThePe; - public static Il2CppMetadata? TheMetadata; - - public static bool LoadFromFile(string pePath, string metadataPath, int[] unityVersion) - { - TheMetadata = Il2CppMetadata.ReadFrom(metadataPath, unityVersion); - - if (TheMetadata == null) - return false; - - var peBytes = File.ReadAllBytes(pePath); - ThePe = new PE.PE(new MemoryStream(peBytes, 0, peBytes.Length, false, true), TheMetadata.maxMetadataUsages); - - return ThePe.PlusSearch(TheMetadata.methodDefs.Count(x => x.methodIndex >= 0), TheMetadata.typeDefs.Length); - } - } -} \ No newline at end of file diff --git a/LibCpp2IL/LibCpp2ILUtils.cs b/LibCpp2IL/LibCpp2ILUtils.cs deleted file mode 100644 index a2719123..00000000 --- a/LibCpp2IL/LibCpp2ILUtils.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using SharpDisasm; -using SharpDisasm.Udis86; - -namespace LibCpp2IL -{ - public static class LibCpp2ILUtils - { - public static List DisassembleBytes(bool is32Bit, byte[] bytes) - { - return new List(new Disassembler(bytes, is32Bit ? ArchitectureMode.x86_32 : ArchitectureMode.x86_64, 0, true).Disassemble()); - } - - public static List GetMethodBodyAtRawAddress(PE.PE theDll, long addr, bool peek) - { - var ret = new List(); - var con = true; - var buff = new List(); - while (con) - { - buff.Add(theDll.raw[addr]); - - ret = DisassembleBytes(theDll.is32Bit, buff.ToArray()); - - if (ret.All(i => !i.Error) && ret.Any(i => i.Mnemonic == ud_mnemonic_code.UD_Iint3)) - con = false; - - if (peek && buff.Count > 50) - con = false; - else if (buff.Count > 1000) - con = false; //Sanity breakout. - - addr++; - } - - return ret /*.Where(i => !i.Error).ToList()*/; - } - - public static ulong GetJumpTarget(Instruction insn, ulong start) - { - var opr = insn.Operands[0]; - - var mode = GetOprMode(insn); - - var num = UInt64.MaxValue >> 64 - mode; - return opr.Size switch - { - 8 => (start + (ulong) opr.LvalSByte & num), - 16 => (start + (ulong) opr.LvalSWord & num), - 32 => (start + (ulong) opr.LvalSDWord & num), - 64 => (start + (ulong) opr.LvalSQWord & num), - _ => throw new InvalidOperationException($"invalid relative offset size {opr.Size}.") - }; - } - - private static FieldInfo oprMode = typeof(Instruction).GetField("opr_mode", BindingFlags.Instance | BindingFlags.NonPublic); - - private static byte GetOprMode(Instruction instruction) - { - return (byte) oprMode.GetValue(instruction); - } - - public static ulong GetImmediateValue(Instruction insn, Operand op) - { - ulong num; - if (op.Opcode == ud_operand_code.OP_sI && op.Size != GetOprMode(insn)) - { - if (op.Size == 8) - { - num = (ulong) op.LvalSByte; - } - else - { - if (op.Size != 32) - throw new InvalidOperationException("Operand size must be 32"); - num = (ulong) op.LvalSDWord; - } - - if (GetOprMode(insn) < 64) - num &= (ulong) ((1L << GetOprMode(insn)) - 1L); - } - else - { - switch (op.Size) - { - case 8: - num = op.LvalByte; - break; - case 16: - num = op.LvalUWord; - break; - case 32: - num = op.LvalUDWord; - break; - case 64: - num = op.LvalUQWord; - break; - default: - throw new InvalidOperationException($"Invalid size for operand: {op.Size}"); - } - } - - return num; - } - - public static ulong GetOffsetFromMemoryAccess(Instruction insn, Operand op) - { - var num1 = (ulong) GetOperandMemoryOffset(op); - - if (num1 == 0) return 0; - - return num1 + insn.PC; - } - - public static int GetOperandMemoryOffset(Operand op) - { - if (op.Type != ud_type.UD_OP_MEM) return 0; - var num1 = op.Offset switch - { - 8 => op.LvalSByte, - 16 => op.LvalSWord, - 32 => op.LvalSDWord, - _ => 0 - }; - return num1; - } - } -} \ No newline at end of file diff --git a/LibCpp2IL/LibCpp2IlGlobalMapper.cs b/LibCpp2IL/LibCpp2IlGlobalMapper.cs new file mode 100644 index 00000000..cf5959e7 --- /dev/null +++ b/LibCpp2IL/LibCpp2IlGlobalMapper.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; +using System.Linq; +using LibCpp2IL.Metadata; + +namespace LibCpp2IL +{ + public static class LibCpp2IlGlobalMapper + { + internal static List TypeRefs = new List(); + internal static List MethodRefs = new List(); + internal static List FieldRefs = new List(); + internal static List Literals = new List(); + + internal static void MapGlobalIdentifiers(Il2CppMetadata metadata, PE.PE cppAssembly) + { + //Type references + TypeRefs = metadata.metadataUsageDic[1] + .Select(kvp => new {kvp, type = cppAssembly.types[kvp.Value]}) + .Select(t => new GlobalIdentifier + { + Value = LibCpp2ILUtils.GetTypeName(metadata, cppAssembly, t.type, true), + Offset = cppAssembly.metadataUsages[t.kvp.Key], + IdentifierType = GlobalIdentifier.Type.TYPEREF + }).ToList(); + + //More type references + TypeRefs.AddRange(metadata.metadataUsageDic[2] + .Select(kvp => new {kvp, type = cppAssembly.types[kvp.Value]}) + .Select(t => new GlobalIdentifier + { + Value = LibCpp2ILUtils.GetTypeName(metadata, cppAssembly, t.type, true), + Offset = cppAssembly.metadataUsages[t.kvp.Key], + IdentifierType = GlobalIdentifier.Type.TYPEREF + }) + ); + + //Method references + MethodRefs = metadata.metadataUsageDic[3] + .Select(kvp => new {kvp, method = metadata.methodDefs[kvp.Value]}) + .Select(t => new {t.kvp, t.method, type = metadata.typeDefs[t.method.declaringTypeIdx]}) + .Select(t => new {t.kvp, t.method, typeName = LibCpp2ILUtils.GetTypeName(metadata, cppAssembly, t.type)}) + .Select(t => new {t.kvp, methodName = t.typeName + "." + metadata.GetStringFromIndex(t.method.nameIndex) + "()"}) + .Select(t => new GlobalIdentifier + { + IdentifierType = GlobalIdentifier.Type.METHODREF, + Value = t.methodName, + Offset = cppAssembly.metadataUsages[t.kvp.Key] + }).ToList(); + + //Field references + FieldRefs = metadata.metadataUsageDic[4] + .Select(kvp => new {kvp, fieldRef = metadata.fieldRefs[kvp.Value]}) + .Select(t => new {t.kvp, t.fieldRef, type = cppAssembly.types[t.fieldRef.typeIndex]}) + .Select(t => new {t.type, t.kvp, t.fieldRef, typeDef = metadata.typeDefs[t.type.data.classIndex]}) + .Select(t => new {t.type, t.kvp, fieldDef = metadata.fieldDefs[t.typeDef.firstFieldIdx + t.fieldRef.fieldIndex]}) + .Select(t => new {t.kvp, fieldName = LibCpp2ILUtils.GetTypeName(metadata, cppAssembly, t.type, true) + "." + metadata.GetStringFromIndex(t.fieldDef.nameIndex)}) + .Select(t => new GlobalIdentifier + { + IdentifierType = GlobalIdentifier.Type.FIELDREF, + Value = t.fieldName, + Offset = cppAssembly.metadataUsages[t.kvp.Key] + }).ToList(); + + //Literals + Literals = metadata.metadataUsageDic[5] + .Select(kvp => new GlobalIdentifier + { + IdentifierType = GlobalIdentifier.Type.LITERAL, + Offset = cppAssembly.metadataUsages[kvp.Key], + Value = $"{metadata.GetStringLiteralFromIndex(kvp.Value)}" + }).ToList(); + + //More method references + foreach (var (metadataUsageIdx, methodSpecIdx) in metadata.metadataUsageDic[6]) //kIl2CppMetadataUsageMethodRef + { + var methodSpec = cppAssembly.methodSpecs[methodSpecIdx]; + var methodDef = metadata.methodDefs[methodSpec.methodDefinitionIndex]; + var typeDef = metadata.typeDefs[methodDef.declaringTypeIdx]; + var typeName = LibCpp2ILUtils.GetTypeName(metadata, cppAssembly, typeDef); + if (methodSpec.classIndexIndex != -1) + { + var classInst = cppAssembly.genericInsts[methodSpec.classIndexIndex]; + typeName += LibCpp2ILUtils.GetGenericTypeParams(metadata, cppAssembly, classInst); + } + + var methodName = typeName + "." + metadata.GetStringFromIndex(methodDef.nameIndex) + "()"; + if (methodSpec.methodIndexIndex != -1) + { + var methodInst = cppAssembly.genericInsts[methodSpec.methodIndexIndex]; + methodName += LibCpp2ILUtils.GetGenericTypeParams(metadata, cppAssembly, methodInst); + } + + MethodRefs.Add(new GlobalIdentifier + { + Value = methodName, + IdentifierType = GlobalIdentifier.Type.METHODREF, + Offset = cppAssembly.metadataUsages[metadataUsageIdx] + }); + } + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/LibCpp2IlMain.cs b/LibCpp2IL/LibCpp2IlMain.cs new file mode 100644 index 00000000..722f1e25 --- /dev/null +++ b/LibCpp2IL/LibCpp2IlMain.cs @@ -0,0 +1,68 @@ +using System.IO; +using System.Linq; +using LibCpp2IL.Metadata; + +namespace LibCpp2IL +{ + public static class LibCpp2IlMain + { + public class LibCpp2IlSettings + { + public bool AllowManualMetadataAndCodeRegInput; + } + + public static readonly LibCpp2IlSettings Settings = new LibCpp2IlSettings(); + + public static float MetadataVersion = 24f; + public static PE.PE? ThePe; + public static Il2CppMetadata? TheMetadata; + + public static string? GetLiteralByAddress(ulong address) + { + var literal = LibCpp2IlGlobalMapper.Literals.FirstOrDefault(lit => lit.Offset == address); + return literal.Offset == address ? literal.Value : null; + } + + /// + /// Initialize the metadata and PE from a pair of byte arrays. + /// + /// The content of the GameAssembly.dll file. + /// The content of the global-metadata.dat file + /// The unity version, split on periods, with the patch version (e.g. f1) stripped out. For example, [2018, 2, 0] + /// True if the initialize succeeded, else false + /// if the metadata is invalid (bad magic number, bad version), or if the PE is invalid (bad header signature, bad magic number)
+ /// if the PE file specifies it is neither for AMD64 or i386 architecture + public static bool Initialize(byte[] peBytes, byte[] metadataBytes, int[] unityVersion) + { + TheMetadata = Il2CppMetadata.ReadFrom(metadataBytes, unityVersion); + + if (TheMetadata == null) + return false; + + ThePe = new PE.PE(new MemoryStream(peBytes, 0, peBytes.Length, false, true), TheMetadata.maxMetadataUsages); + if (!ThePe.PlusSearch(TheMetadata.methodDefs.Count(x => x.methodIndex >= 0), TheMetadata.typeDefs.Length)) + return false; + + LibCpp2IlGlobalMapper.MapGlobalIdentifiers(TheMetadata, ThePe); + + return true; + } + + /// + /// Initialize the metadata and PE from their respective file locations. + /// + /// The path to the GameAssembly.dll file + /// The path to the global-metadata.dat file + /// The unity version, split on periods, with the patch version (e.g. f1) stripped out. For example, [2018, 2, 0] + /// True if the initialize succeeded, else false + /// if the metadata is invalid (bad magic number, bad version), or if the PE is invalid (bad header signature, bad magic number)
+ /// if the PE file specifies it is neither for AMD64 or i386 architecture + public static bool LoadFromFile(string pePath, string metadataPath, int[] unityVersion) + { + var metadataBytes = File.ReadAllBytes(metadataPath); + var peBytes = File.ReadAllBytes(pePath); + + return Initialize(peBytes, metadataBytes, unityVersion); + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/LibCpp2IlUtils.cs b/LibCpp2IL/LibCpp2IlUtils.cs new file mode 100644 index 00000000..4c701ca0 --- /dev/null +++ b/LibCpp2IL/LibCpp2IlUtils.cs @@ -0,0 +1,405 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using LibCpp2IL.Metadata; +using LibCpp2IL.PE; +using LibCpp2IL.Reflection; +using SharpDisasm; +using SharpDisasm.Udis86; + +namespace LibCpp2IL +{ + 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"} + }; + + public static List DisassembleBytes(bool is32Bit, byte[] bytes) + { + return new List(new Disassembler(bytes, is32Bit ? ArchitectureMode.x86_32 : ArchitectureMode.x86_64, 0, true).Disassemble()); + } + + public static List GetMethodBodyAtRawAddress(PE.PE theDll, long addr, bool peek) + { + var ret = new List(); + var con = true; + var buff = new List(); + while (con) + { + buff.Add(theDll.raw[addr]); + + ret = DisassembleBytes(theDll.is32Bit, buff.ToArray()); + + if (ret.All(i => !i.Error) && ret.Any(i => i.Mnemonic == ud_mnemonic_code.UD_Iint3)) + con = false; + + if (peek && buff.Count > 50) + con = false; + else if (buff.Count > 1000) + con = false; //Sanity breakout. + + addr++; + } + + return ret /*.Where(i => !i.Error).ToList()*/; + } + + public static ulong GetJumpTarget(Instruction insn, ulong start) + { + var opr = insn.Operands[0]; + + var mode = GetOprMode(insn); + + var num = UInt64.MaxValue >> 64 - mode; + return opr.Size switch + { + 8 => (start + (ulong) opr.LvalSByte & num), + 16 => (start + (ulong) opr.LvalSWord & num), + 32 => (start + (ulong) opr.LvalSDWord & num), + 64 => (start + (ulong) opr.LvalSQWord & num), + _ => throw new InvalidOperationException($"invalid relative offset size {opr.Size}.") + }; + } + + private static FieldInfo oprMode = typeof(Instruction).GetField("opr_mode", BindingFlags.Instance | BindingFlags.NonPublic)!; + + private static byte GetOprMode(Instruction instruction) + { + return (byte) oprMode.GetValue(instruction); + } + + public static ulong GetImmediateValue(Instruction insn, Operand op) + { + ulong num; + if (op.Opcode == ud_operand_code.OP_sI && op.Size != GetOprMode(insn)) + { + if (op.Size == 8) + { + num = (ulong) op.LvalSByte; + } + else + { + if (op.Size != 32) + throw new InvalidOperationException("Operand size must be 32"); + num = (ulong) op.LvalSDWord; + } + + if (GetOprMode(insn) < 64) + num &= (ulong) ((1L << GetOprMode(insn)) - 1L); + } + else + { + switch (op.Size) + { + case 8: + num = op.LvalByte; + break; + case 16: + num = op.LvalUWord; + break; + case 32: + num = op.LvalUDWord; + break; + case 64: + num = op.LvalUQWord; + break; + default: + throw new InvalidOperationException($"Invalid size for operand: {op.Size}"); + } + } + + return num; + } + + public static ulong GetOffsetFromMemoryAccess(Instruction insn, Operand op) + { + var num1 = (ulong) GetOperandMemoryOffset(op); + + if (num1 == 0) return 0; + + return num1 + insn.PC; + } + + public static int GetOperandMemoryOffset(Operand op) + { + if (op.Type != ud_type.UD_OP_MEM) return 0; + var num1 = op.Offset switch + { + 8 => op.LvalSByte, + 16 => op.LvalSWord, + 32 => op.LvalSDWord, + _ => 0 + }; + return num1; + } + + internal static string GetTypeName(Il2CppMetadata metadata, PE.PE cppAssembly, Il2CppTypeDefinition typeDef) + { + var ret = String.Empty; + if (typeDef.declaringTypeIndex != -1) + { + ret += GetTypeName(metadata, cppAssembly, cppAssembly.types[typeDef.declaringTypeIndex]) + "."; + } + + ret += metadata.GetStringFromIndex(typeDef.nameIndex); + var names = new List(); + if (typeDef.genericContainerIndex < 0) return ret; + + var genericContainer = metadata.genericContainers[typeDef.genericContainerIndex]; + for (var i = 0; i < genericContainer.type_argc; i++) + { + var genericParameterIndex = genericContainer.genericParameterStart + i; + var param = metadata.genericParameters[genericParameterIndex]; + names.Add(metadata.GetStringFromIndex(param.nameIndex)); + } + + ret = ret.Replace($"`{genericContainer.type_argc}", ""); + ret += $"<{string.Join(", ", names)}>"; + + return ret; + } + + internal static string GetGenericTypeParams(Il2CppMetadata metadata, PE.PE cppAssembly, Il2CppGenericInst genericInst) + { + var typeNames = new List(); + var pointers = cppAssembly.ReadClassArrayAtVirtualAddress(genericInst.type_argv, (long) genericInst.type_argc); + for (uint i = 0; i < genericInst.type_argc; ++i) + { + var oriType = cppAssembly.GetIl2CppType(pointers[i]); + typeNames.Add(GetTypeName(metadata, cppAssembly, oriType)); + } + + return $"<{string.Join(", ", typeNames)}>"; + } + + public static string GetTypeName(Il2CppMetadata metadata, PE.PE cppAssembly, Il2CppType type, bool fullName = false) + { + string ret; + switch (type.type) + { + case Il2CppTypeEnum.IL2CPP_TYPE_CLASS: + case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE: + { + var typeDef = metadata.typeDefs[type.data.classIndex]; + ret = string.Empty; + if (fullName) + { + ret = metadata.GetStringFromIndex(typeDef.namespaceIndex); + if (ret != string.Empty) + { + ret += "."; + } + } + + ret += GetTypeName(metadata, cppAssembly, typeDef); + break; + } + case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST: + { + var genericClass = cppAssembly.ReadClassAtVirtualAddress(type.data.generic_class); + var typeDef = metadata.typeDefs[genericClass.typeDefinitionIndex]; + ret = metadata.GetStringFromIndex(typeDef.nameIndex); + var genericInst = cppAssembly.ReadClassAtVirtualAddress(genericClass.context.class_inst); + ret = ret.Replace($"`{genericInst.type_argc}", ""); + ret += GetGenericTypeParams(metadata, cppAssembly, genericInst); + break; + } + case Il2CppTypeEnum.IL2CPP_TYPE_VAR: + case Il2CppTypeEnum.IL2CPP_TYPE_MVAR: + { + var param = metadata.genericParameters[type.data.genericParameterIndex]; + ret = metadata.GetStringFromIndex(param.nameIndex); + break; + } + case Il2CppTypeEnum.IL2CPP_TYPE_ARRAY: + { + var arrayType = cppAssembly.ReadClassAtVirtualAddress(type.data.array); + var oriType = cppAssembly.GetIl2CppType(arrayType.etype); + ret = $"{GetTypeName(metadata, cppAssembly, oriType)}[{new string(',', arrayType.rank - 1)}]"; + break; + } + case Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY: + { + var oriType = cppAssembly.GetIl2CppType(type.data.type); + ret = $"{GetTypeName(metadata, cppAssembly, oriType)}[]"; + break; + } + case Il2CppTypeEnum.IL2CPP_TYPE_PTR: + { + var oriType = cppAssembly.GetIl2CppType(type.data.type); + ret = $"{GetTypeName(metadata, cppAssembly, oriType)}*"; + break; + } + default: + ret = TypeString[(int) type.type]; + break; + } + + return ret; + } + + internal static object? GetDefaultValue(int dataIndex, int typeIndex, Il2CppMetadata metadata, PE.PE theDll) + { + var pointer = metadata.GetDefaultValueFromIndex(dataIndex); + if (pointer <= 0) return null; + + var defaultValueType = theDll.types[typeIndex]; + metadata.Position = pointer; + switch (defaultValueType.type) + { + case Il2CppTypeEnum.IL2CPP_TYPE_BOOLEAN: + return metadata.ReadBoolean(); + case Il2CppTypeEnum.IL2CPP_TYPE_U1: + return metadata.ReadByte(); + case Il2CppTypeEnum.IL2CPP_TYPE_I1: + return metadata.ReadSByte(); + case Il2CppTypeEnum.IL2CPP_TYPE_CHAR: + return BitConverter.ToChar(metadata.ReadBytes(2), 0); + case Il2CppTypeEnum.IL2CPP_TYPE_U2: + return metadata.ReadUInt16(); + case Il2CppTypeEnum.IL2CPP_TYPE_I2: + return metadata.ReadInt16(); + case Il2CppTypeEnum.IL2CPP_TYPE_U4: + return metadata.ReadUInt32(); + case Il2CppTypeEnum.IL2CPP_TYPE_I4: + return metadata.ReadInt32(); + case Il2CppTypeEnum.IL2CPP_TYPE_U8: + return metadata.ReadUInt64(); + case Il2CppTypeEnum.IL2CPP_TYPE_I8: + return metadata.ReadInt64(); + case Il2CppTypeEnum.IL2CPP_TYPE_R4: + return metadata.ReadSingle(); + case Il2CppTypeEnum.IL2CPP_TYPE_R8: + return metadata.ReadDouble(); + case Il2CppTypeEnum.IL2CPP_TYPE_STRING: + var len = metadata.ReadInt32(); + return Encoding.UTF8.GetString(metadata.ReadBytes(len)); + default: + return null; + } + } + + internal static Il2CppTypeReflectionData WrapType(Il2CppTypeDefinition what) + { + return new Il2CppTypeReflectionData + { + baseType = what, + genericParams = new Il2CppTypeReflectionData[0], + isGenericType = false, + isType = true, + }; + } + + public static Il2CppTypeReflectionData? GetTypeReflectionData(Il2CppType forWhat) + { + if (LibCpp2IlMain.ThePe == null || LibCpp2IlMain.TheMetadata == null) return null; + + switch (forWhat.type) + { + case Il2CppTypeEnum.IL2CPP_TYPE_OBJECT: + return WrapType(LibCpp2IlReflection.GetType("Object", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_VOID: + return WrapType(LibCpp2IlReflection.GetType("Void", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_BOOLEAN: + return WrapType(LibCpp2IlReflection.GetType("Boolean", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_CHAR: + return WrapType(LibCpp2IlReflection.GetType("Char", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_I1: + return WrapType(LibCpp2IlReflection.GetType("SByte", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_U1: + return WrapType(LibCpp2IlReflection.GetType("Byte", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_I2: + return WrapType(LibCpp2IlReflection.GetType("Int16", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_U2: + return WrapType(LibCpp2IlReflection.GetType("UInt16", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_I4: + return WrapType(LibCpp2IlReflection.GetType("Int32", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_U4: + return WrapType(LibCpp2IlReflection.GetType("UInt32", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_I: + return WrapType(LibCpp2IlReflection.GetType("IntPtr", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_U: + return WrapType(LibCpp2IlReflection.GetType("UIntPtr", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_I8: + return WrapType(LibCpp2IlReflection.GetType("Int64", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_U8: + return WrapType(LibCpp2IlReflection.GetType("UInt64", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_R4: + return WrapType(LibCpp2IlReflection.GetType("Single", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_R8: + return WrapType(LibCpp2IlReflection.GetType("Double", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_STRING: + return WrapType(LibCpp2IlReflection.GetType("String", "System")!); + case Il2CppTypeEnum.IL2CPP_TYPE_CLASS: + case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE: + //"normal" type + return new Il2CppTypeReflectionData + { + baseType = LibCpp2IlMain.TheMetadata.typeDefs[forWhat.data.classIndex], + genericParams = new Il2CppTypeReflectionData[0], + isType = true, + isGenericType = false, + }; + case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST: + { + //Generic type + var genericClass = LibCpp2IlMain.ThePe.ReadClassAtVirtualAddress(forWhat.data.generic_class); + var typeDefinition = LibCpp2IlMain.TheMetadata.typeDefs[genericClass.typeDefinitionIndex]; + + var genericInst = LibCpp2IlMain.ThePe.ReadClassAtVirtualAddress(genericClass.context.class_inst); + var pointers = LibCpp2IlMain.ThePe.GetPointers(genericInst.type_argv, (long) genericInst.type_argc); + var genericParams = pointers + .Select(pointer => LibCpp2IlMain.ThePe.GetIl2CppType(pointer)) + .Select(type => GetTypeReflectionData(type)!) //Recursive call here + .ToList(); + + return new Il2CppTypeReflectionData + { + baseType = typeDefinition, + genericParams = genericParams.ToArray(), + isType = true, + isGenericType = true, + }; + } + case Il2CppTypeEnum.IL2CPP_TYPE_VAR: + { + var param = LibCpp2IlMain.TheMetadata.genericParameters[forWhat.data.genericParameterIndex]; + var genericName = LibCpp2IlMain.TheMetadata.GetStringFromIndex(param.nameIndex); + + return new Il2CppTypeReflectionData + { + baseType = null, + genericParams = new Il2CppTypeReflectionData[0], + isType = false, + isGenericType = false, + variableGenericParamName = genericName, + }; + } + } + + Console.WriteLine($"Unknown type {forWhat.type}"); + return null; + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/Metadata/Il2CppAssemblyDefinition.cs b/LibCpp2IL/Metadata/Il2CppAssemblyDefinition.cs index 8c415a9d..0d7c85cf 100644 --- a/LibCpp2IL/Metadata/Il2CppAssemblyDefinition.cs +++ b/LibCpp2IL/Metadata/Il2CppAssemblyDefinition.cs @@ -1,3 +1,5 @@ +using System.Linq; + namespace LibCpp2IL.Metadata { public class Il2CppAssemblyDefinition @@ -16,5 +18,9 @@ public class Il2CppAssemblyDefinition [Version(Min = 24.1f)] public int customAttributeStart; [Version(Min = 24.1f)] public uint customAttributeCount; + + public string? Name => LibCpp2IlMain.TheMetadata == null ? null : LibCpp2IlMain.TheMetadata.GetStringFromIndex(nameIndex); + + public Il2CppTypeDefinition[]? Types => LibCpp2IlMain.TheMetadata == null ? null : LibCpp2IlMain.TheMetadata.typeDefs.Skip(firstTypeIndex).Take((int) typeCount).ToArray(); } } \ No newline at end of file diff --git a/LibCpp2IL/Metadata/Il2CppEventDefinition.cs b/LibCpp2IL/Metadata/Il2CppEventDefinition.cs index 678f3d38..63acec8b 100644 --- a/LibCpp2IL/Metadata/Il2CppEventDefinition.cs +++ b/LibCpp2IL/Metadata/Il2CppEventDefinition.cs @@ -1,3 +1,7 @@ +using System; +using System.Linq; +using LibCpp2IL.Reflection; + namespace LibCpp2IL.Metadata { public class Il2CppEventDefinition @@ -9,5 +13,29 @@ public class Il2CppEventDefinition public int raise; [Version(Max = 24)] public int customAttributeIndex; //Not in 24.1 or 24.2 public uint token; + + [NonSerialized] private Il2CppTypeDefinition? _type; + + public Il2CppTypeDefinition? DeclaringType + { + get + { + if (_type != null) return _type; + if (LibCpp2IlMain.TheMetadata == null) return null; + + _type = LibCpp2IlMain.TheMetadata.typeDefs.FirstOrDefault(t => t.Events!.Contains(this)); + return _type; + } + } + + public string? Name => LibCpp2IlMain.TheMetadata == null ? null : LibCpp2IlMain.TheMetadata.GetStringFromIndex(nameIndex); + + public Il2CppTypeReflectionData? EventType => LibCpp2IlMain.ThePe == null ? null : LibCpp2ILUtils.GetTypeReflectionData(LibCpp2IlMain.ThePe.types[typeIndex]); + + public Il2CppMethodDefinition? Adder => LibCpp2IlMain.TheMetadata == null || add < 0 || DeclaringType == null ? null : LibCpp2IlMain.TheMetadata.methodDefs[DeclaringType.firstMethodIdx + add]; + + public Il2CppMethodDefinition? Remover => LibCpp2IlMain.TheMetadata == null || remove < 0 || DeclaringType == null ? null : LibCpp2IlMain.TheMetadata.methodDefs[DeclaringType.firstMethodIdx + remove]; + + public Il2CppMethodDefinition? Invoker => LibCpp2IlMain.TheMetadata == null || raise < 0 || DeclaringType == null ? null : LibCpp2IlMain.TheMetadata.methodDefs[DeclaringType.firstMethodIdx + raise]; } } \ No newline at end of file diff --git a/LibCpp2IL/Metadata/Il2CppFieldDefinition.cs b/LibCpp2IL/Metadata/Il2CppFieldDefinition.cs index f0fb56cd..ed18c09f 100644 --- a/LibCpp2IL/Metadata/Il2CppFieldDefinition.cs +++ b/LibCpp2IL/Metadata/Il2CppFieldDefinition.cs @@ -6,5 +6,18 @@ public class Il2CppFieldDefinition public int typeIndex; [Version(Max = 24)] public int customAttributeIndex; public uint token; + + public string? Name => LibCpp2IlMain.TheMetadata == null ? null : LibCpp2IlMain.TheMetadata.GetStringFromIndex(nameIndex); + + public Il2CppTypeDefinition? FieldType => LibCpp2IlReflection.GetTypeDefinitionByTypeIndex(typeIndex); + + public int FieldIndex => LibCpp2IlReflection.GetFieldIndexFromField(this); + + public override string ToString() + { + if(LibCpp2IlMain.TheMetadata == null) return base.ToString(); + + return $"Il2CppFieldDefinition[Name={Name}, FieldType={FieldType}]"; + } } } \ No newline at end of file diff --git a/LibCpp2IL/Metadata/Il2CppInterfaceOffset.cs b/LibCpp2IL/Metadata/Il2CppInterfaceOffset.cs index f07060f0..7b58986a 100644 --- a/LibCpp2IL/Metadata/Il2CppInterfaceOffset.cs +++ b/LibCpp2IL/Metadata/Il2CppInterfaceOffset.cs @@ -7,22 +7,11 @@ public class Il2CppInterfaceOffset public int typeIndex; public int offset; - public Il2CppType type => LibCpp2IlMain.ThePe!.types[typeIndex]; - - // public TypeDefinition TypeDefinition - // { - // get - // { - // if(SharedState.TypeDefsByIndex.ContainsKey(type.data.classIndex)) - // return SharedState.TypeDefsByIndex[type.data.classIndex]; - // return null; - // } - // } + public Il2CppTypeDefinition? type => LibCpp2IlReflection.GetTypeDefinitionByTypeIndex(typeIndex); public override string ToString() { - // return $"InterfaceOffsetPair({typeIndex}/{TypeDefinition?.FullName ?? "unknown type"} => {offset})"; - return $"InterfaceOffsetPair({typeIndex}/unknown type => {offset})"; + return $"InterfaceOffsetPair({typeIndex}/{type?.FullName ?? "unknown type"} => {offset})"; } } } \ No newline at end of file diff --git a/LibCpp2IL/Metadata/Il2CppMetadata.cs b/LibCpp2IL/Metadata/Il2CppMetadata.cs index 2ae4fc83..68878ca1 100644 --- a/LibCpp2IL/Metadata/Il2CppMetadata.cs +++ b/LibCpp2IL/Metadata/Il2CppMetadata.cs @@ -8,6 +8,8 @@ namespace LibCpp2IL.Metadata { public class Il2CppMetadata : ClassReadingBinaryReader { + //Disable null check as this stuff is reflected. +#pragma warning disable 8618 private Il2CppGlobalMetadataHeader metadataHeader; public Il2CppAssemblyDefinition[] assemblyDefinitions; public Il2CppTypeDefinition[] typeDefs; @@ -32,32 +34,29 @@ public class Il2CppMetadata : ClassReadingBinaryReader public Il2CppFieldRef[] fieldRefs; public Il2CppGenericParameter[] genericParameters; - public static Il2CppMetadata? ReadFrom(string path, int[] unityVer) + public static Il2CppMetadata? ReadFrom(byte[] bytes, int[] unityVer) { - var bytes = File.ReadAllBytes(path); if (BitConverter.ToUInt32(bytes, 0) != 0xFAB11BAF) { //Magic number is wrong - Console.WriteLine("Error: Invalid or corrupt metadata (magic number check failed): " + path); - return null; + throw new FormatException("Invalid or corrupt metadata (magic number check failed)"); } var version = BitConverter.ToInt32(bytes, 4); if (version != 24) { - Console.WriteLine("Unexpected non-unity metadata version found! Expected 24, got " + version); - return null; + throw new FormatException("Unexpected non-unity metadata version found! Expected 24, got " + version); } float actualVersion; if (unityVer[0] >= 2019) actualVersion = 24.2f; else if (unityVer[0] == 2018 && unityVer[1] >= 3) actualVersion = 24.1f; else actualVersion = version; - + Console.WriteLine($"Using IL2CPP Metadata version {actualVersion}"); LibCpp2IlMain.MetadataVersion = actualVersion; - + return new Il2CppMetadata(new MemoryStream(bytes)); } @@ -68,19 +67,19 @@ private Il2CppMetadata(Stream stream) : base(stream) { throw new Exception("ERROR: Magic number mismatch. Expecting " + 0xFAB11BAF + " but got " + metadataHeader.magicNumber); } - - if(metadataHeader.version != 24) throw new Exception("ERROR: Invalid metadata version, unity only uses 24, we got " + metadataHeader.version); + + if (metadataHeader.version != 24) throw new Exception("ERROR: Invalid metadata version, unity only uses 24, we got " + metadataHeader.version); Console.Write("\tReading image definitions..."); var start = DateTime.Now; assemblyDefinitions = ReadMetadataClassArray(metadataHeader.imagesOffset, metadataHeader.imagesCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + Console.Write("\tReading type definitions..."); start = DateTime.Now; typeDefs = ReadMetadataClassArray(metadataHeader.typeDefinitionsOffset, metadataHeader.typeDefinitionsCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + Console.Write("\tReading interface offsets..."); start = DateTime.Now; interfaceOffsets = ReadMetadataClassArray(metadataHeader.interfaceOffsetsOffset, metadataHeader.interfaceOffsetsCount); @@ -90,63 +89,63 @@ private Il2CppMetadata(Stream stream) : base(stream) start = DateTime.Now; methodDefs = ReadMetadataClassArray(metadataHeader.methodsOffset, metadataHeader.methodsCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + Console.Write("\tReading method parameter definitions..."); start = DateTime.Now; parameterDefs = ReadMetadataClassArray(metadataHeader.parametersOffset, metadataHeader.parametersCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + Console.Write("\tReading field definitions..."); start = DateTime.Now; fieldDefs = ReadMetadataClassArray(metadataHeader.fieldsOffset, metadataHeader.fieldsCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + Console.Write("\tReading default field values..."); start = DateTime.Now; fieldDefaultValues = ReadMetadataClassArray(metadataHeader.fieldDefaultValuesOffset, metadataHeader.fieldDefaultValuesCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + Console.Write("\tReading default parameter values..."); start = DateTime.Now; parameterDefaultValues = ReadMetadataClassArray(metadataHeader.parameterDefaultValuesOffset, metadataHeader.parameterDefaultValuesCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + Console.Write("\tReading property definitions..."); start = DateTime.Now; propertyDefs = ReadMetadataClassArray(metadataHeader.propertiesOffset, metadataHeader.propertiesCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + Console.Write("\tReading interface definitions..."); start = DateTime.Now; interfaceIndices = ReadClassArray(metadataHeader.interfacesOffset, metadataHeader.interfacesCount / 4); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + Console.Write("\tReading nested type definitions..."); start = DateTime.Now; nestedTypeIndices = ReadClassArray(metadataHeader.nestedTypesOffset, metadataHeader.nestedTypesCount / 4); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + Console.Write("\tReading event definitions..."); start = DateTime.Now; eventDefs = ReadMetadataClassArray(metadataHeader.eventsOffset, metadataHeader.eventsCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + Console.Write("\tReading generic container definitions..."); start = DateTime.Now; genericContainers = ReadMetadataClassArray(metadataHeader.genericContainersOffset, metadataHeader.genericContainersCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + Console.Write("\tReading generic parameter definitions..."); start = DateTime.Now; genericParameters = ReadMetadataClassArray(metadataHeader.genericParametersOffset, metadataHeader.genericParametersCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + //v17+ fields Console.Write("\tReading string definitions..."); start = DateTime.Now; stringLiterals = ReadMetadataClassArray(metadataHeader.stringLiteralOffset, metadataHeader.stringLiteralCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + Console.Write("\tReading usage data..."); start = DateTime.Now; metadataUsageLists = ReadMetadataClassArray(metadataHeader.metadataUsageListsOffset, metadataHeader.metadataUsageListsCount); @@ -159,7 +158,7 @@ private Il2CppMetadata(Stream stream) : base(stream) start = DateTime.Now; fieldRefs = ReadMetadataClassArray(metadataHeader.fieldRefsOffset, metadataHeader.fieldRefsCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); - + //v21+ fields Console.Write("\tReading attribute types..."); start = DateTime.Now; @@ -167,23 +166,25 @@ private Il2CppMetadata(Stream stream) : base(stream) attributeTypes = ReadClassArray(metadataHeader.attributeTypesOffset, metadataHeader.attributeTypesCount / 4); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); } - +#pragma warning restore 8618 + private T[] ReadMetadataClassArray(int offset, int length) where T : new() { return ReadClassArray(offset, length / VersionAwareSizeOf(typeof(T))); } - + private static int VersionAwareSizeOf(Type type) { var size = 0; foreach (var i in type.GetFields()) { - var attr = (VersionAttribute)Attribute.GetCustomAttribute(i, typeof(VersionAttribute)); + var attr = (VersionAttribute?) Attribute.GetCustomAttribute(i, typeof(VersionAttribute)); if (attr != null) { if (LibCpp2IlMain.MetadataVersion < attr.Min || LibCpp2IlMain.MetadataVersion > attr.Max) continue; } + switch (i.FieldType.Name) { case "Int32": @@ -196,9 +197,10 @@ private static int VersionAwareSizeOf(Type type) break; } } + return size; } - + private void DecipherMetadataUsage() { metadataUsageDic = new Dictionary>(); @@ -206,6 +208,7 @@ private void DecipherMetadataUsage() { metadataUsageDic[i] = new SortedDictionary(); } + foreach (var metadataUsageList in metadataUsageLists) { for (var i = 0; i < metadataUsageList.count; i++) @@ -217,9 +220,10 @@ private void DecipherMetadataUsage() metadataUsageDic[usage][metadataUsagePair.destinationIndex] = decodedIndex; } } + maxMetadataUsages = metadataUsageDic.Max(x => x.Value.Max(y => y.Key)) + 1; } - + private uint GetEncodedIndexType(uint index) { return (index & 0xE0000000) >> 29; @@ -229,7 +233,7 @@ private uint GetDecodedMethodIndex(uint index) { return index & 0x1FFFFFFFU; } - + //Getters for human readability public Il2CppFieldDefaultValue GetFieldDefaultValueFromIndex(int index) { @@ -263,6 +267,7 @@ public int GetCustomAttributeIndex(Il2CppAssemblyDefinition assemblyDef, int cus return i; } } + return -1; } else @@ -275,7 +280,7 @@ 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(ReadBytes((int) stringLiteral.length)); } } } \ No newline at end of file diff --git a/LibCpp2IL/Metadata/Il2CppMethodDefinition.cs b/LibCpp2IL/Metadata/Il2CppMethodDefinition.cs index 3936bf6f..128cdf9b 100644 --- a/LibCpp2IL/Metadata/Il2CppMethodDefinition.cs +++ b/LibCpp2IL/Metadata/Il2CppMethodDefinition.cs @@ -1,10 +1,14 @@ +using System.Linq; +using System.Reflection; +using LibCpp2IL.Reflection; + namespace LibCpp2IL.Metadata { public class Il2CppMethodDefinition { public int nameIndex; - public int declaringType; - public int returnType; + public int declaringTypeIdx; + public int returnTypeIdx; public int parameterStart; [Version(Max = 24)] public int customAttributeIndex; public int genericContainerIndex; @@ -18,5 +22,41 @@ public class Il2CppMethodDefinition public ushort iflags; public ushort slot; public ushort parameterCount; + + public int MethodIndex => LibCpp2IlReflection.GetMethodIndexFromMethod(this); + + public string? Name => LibCpp2IlMain.TheMetadata == null ? null : LibCpp2IlMain.TheMetadata.GetStringFromIndex(nameIndex); + + public Il2CppTypeReflectionData? ReturnType => LibCpp2IlMain.ThePe == null ? null : LibCpp2ILUtils.GetTypeReflectionData(LibCpp2IlMain.ThePe.types[returnTypeIdx]); + + public Il2CppTypeDefinition? DeclaringType => LibCpp2IlMain.TheMetadata == null ? null : LibCpp2IlMain.TheMetadata.typeDefs[declaringTypeIdx]; + + public ulong MethodPointer => LibCpp2IlMain.ThePe == null || LibCpp2IlMain.TheMetadata == null ? 0 : LibCpp2IlMain.ThePe.GetMethodPointer(methodIndex, MethodIndex, DeclaringType.DeclaringAssembly.assemblyIndex, token); + + public Il2CppParameterReflectionData[]? Parameters => LibCpp2IlMain.TheMetadata == null || LibCpp2IlMain.ThePe == null + ? null + : LibCpp2IlMain.TheMetadata.parameterDefs + .Skip(parameterStart) + .Take(parameterCount) + .Select((paramDef, idx) => + { + var paramType = LibCpp2IlMain.ThePe.types[paramDef.typeIndex]; + var paramFlags = (ParameterAttributes) paramType.attrs; + var paramDefaultData = (paramFlags & ParameterAttributes.HasDefault) != 0 ? LibCpp2IlMain.TheMetadata.GetParameterDefaultValueFromIndex(parameterStart + idx) : null; + return new Il2CppParameterReflectionData + { + Type = LibCpp2ILUtils.GetTypeReflectionData(paramType)!, + ParameterName = LibCpp2IlMain.TheMetadata.GetStringFromIndex(paramDef.nameIndex), + ParameterAttributes = paramFlags, + DefaultValue = paramDefaultData == null ? null : LibCpp2ILUtils.GetDefaultValue(paramDefaultData.dataIndex, paramDefaultData.typeIndex, LibCpp2IlMain.TheMetadata, LibCpp2IlMain.ThePe), + }; + }).ToArray(); + + public override string ToString() + { + if (LibCpp2IlMain.TheMetadata == null) return base.ToString(); + + return $"Il2CppMethodDefinition[Name='{Name}']"; + } } } \ No newline at end of file diff --git a/LibCpp2IL/Metadata/Il2CppPropertyDefinition.cs b/LibCpp2IL/Metadata/Il2CppPropertyDefinition.cs index 4947c362..437347b3 100644 --- a/LibCpp2IL/Metadata/Il2CppPropertyDefinition.cs +++ b/LibCpp2IL/Metadata/Il2CppPropertyDefinition.cs @@ -1,3 +1,7 @@ +using System; +using System.Linq; +using LibCpp2IL.Reflection; + namespace LibCpp2IL.Metadata { public class Il2CppPropertyDefinition @@ -8,5 +12,27 @@ public class Il2CppPropertyDefinition public uint attrs; [Version(Max = 24)] public int customAttributeIndex; public uint token; + + [NonSerialized] private Il2CppTypeDefinition? _type; + + public Il2CppTypeDefinition? DeclaringType + { + get + { + if (_type != null) return _type; + if (LibCpp2IlMain.TheMetadata == null) return null; + + _type = LibCpp2IlMain.TheMetadata.typeDefs.FirstOrDefault(t => t.Properties!.Contains(this)); + return _type; + } + } + + public string? Name => LibCpp2IlMain.TheMetadata == null ? null : LibCpp2IlMain.TheMetadata.GetStringFromIndex(nameIndex); + + public Il2CppMethodDefinition? Getter => LibCpp2IlMain.TheMetadata == null || get < 0 || DeclaringType == null ? null : LibCpp2IlMain.TheMetadata.methodDefs[DeclaringType.firstMethodIdx + get]; + + public Il2CppMethodDefinition? Setter => LibCpp2IlMain.TheMetadata == null || set < 0 || DeclaringType == null ? null : LibCpp2IlMain.TheMetadata.methodDefs[DeclaringType.firstMethodIdx + set]; + + public Il2CppTypeReflectionData? PropertyType => LibCpp2IlMain.TheMetadata == null ? null : Getter == null ? Setter!.Parameters![0].Type : Getter!.ReturnType; } } \ No newline at end of file diff --git a/LibCpp2IL/Metadata/Il2CppTypeDefinition.cs b/LibCpp2IL/Metadata/Il2CppTypeDefinition.cs index bd4335be..ab514311 100644 --- a/LibCpp2IL/Metadata/Il2CppTypeDefinition.cs +++ b/LibCpp2IL/Metadata/Il2CppTypeDefinition.cs @@ -1,4 +1,7 @@ using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using LibCpp2IL.Reflection; namespace LibCpp2IL.Metadata { @@ -22,7 +25,7 @@ public class Il2CppTypeDefinition public uint flags; public int firstFieldIdx; - public int firstMethodId; + public int firstMethodIdx; public int firstEventId; public int firstPropertyId; public int nestedTypesStart; @@ -54,10 +57,92 @@ public Il2CppInterfaceOffset[] InterfaceOffsets { get { - if(interfaceOffsetsStart < 0) return new Il2CppInterfaceOffset[0]; - + if (interfaceOffsetsStart < 0) return new Il2CppInterfaceOffset[0]; + return LibCpp2IlMain.TheMetadata!.interfaceOffsets.SubArray(interfaceOffsetsStart, interface_offsets_count); } } + + public int TypeIndex => LibCpp2IlReflection.GetTypeIndexFromType(this); + + public Il2CppAssemblyDefinition? DeclaringAssembly + { + get + { + if (LibCpp2IlMain.TheMetadata == null) return null; + var typeIdx = TypeIndex; + + foreach (var assemblyDefinition in LibCpp2IlMain.TheMetadata.assemblyDefinitions) + { + var lastIdx = assemblyDefinition.firstTypeIndex + assemblyDefinition.typeCount - 1; + if (assemblyDefinition.firstTypeIndex <= typeIdx && typeIdx <= lastIdx) + return assemblyDefinition; + } + + return null; + } + } + + public string? Namespace => LibCpp2IlMain.TheMetadata == null ? null : LibCpp2IlMain.TheMetadata.GetStringFromIndex(namespaceIndex); + + public string? Name => LibCpp2IlMain.TheMetadata == null ? null : LibCpp2IlMain.TheMetadata.GetStringFromIndex(nameIndex); + + public string? FullName => LibCpp2IlMain.TheMetadata == null ? null : Namespace + "." + Name; + + public Il2CppTypeDefinition? BaseType => LibCpp2IlReflection.GetTypeDefinitionByTypeIndex(parentIndex); + + public Il2CppFieldDefinition[]? Fields => LibCpp2IlMain.TheMetadata == null ? null : LibCpp2IlMain.TheMetadata.fieldDefs.Skip(firstFieldIdx).Take(field_count).ToArray(); + + public FieldAttributes[]? FieldAttributes => Fields? + .Select(f => f.typeIndex) + .Select(idx => LibCpp2IlMain.ThePe!.types[idx]) + .Select(t => (FieldAttributes) t.attrs) + .ToArray(); + + public object?[]? FieldDefaults => Fields? + .Select((f, idx) => (f.FieldIndex, FieldAttributes[idx])) + .Select(tuple => (tuple.Item2 & System.Reflection.FieldAttributes.HasDefault) != 0 ? LibCpp2IlMain.TheMetadata!.GetFieldDefaultValueFromIndex(tuple.FieldIndex) : null) + .Select(def => def == null ? null : LibCpp2ILUtils.GetDefaultValue(def.dataIndex, def.typeIndex, LibCpp2IlMain.TheMetadata!, LibCpp2IlMain.ThePe!)) + .ToArray(); + + public Il2CppFieldReflectionData[]? FieldInfos + { + get + { + var fields = Fields; + var attributes = FieldAttributes; + var defaults = FieldDefaults; + + return fields? + .Select((t, i) => new Il2CppFieldReflectionData {attributes = attributes![i], field = t, defaultValue = defaults![i]}) + .ToArray(); + } + } + + public Il2CppMethodDefinition[]? Methods => LibCpp2IlMain.TheMetadata == null ? null : LibCpp2IlMain.TheMetadata.methodDefs.Skip(firstMethodIdx).Take(method_count).ToArray(); + + public Il2CppPropertyDefinition[]? Properties => LibCpp2IlMain.TheMetadata == null ? null : LibCpp2IlMain.TheMetadata.propertyDefs.Skip(firstPropertyId).Take(propertyCount).ToArray(); + + public Il2CppEventDefinition[]? Events => LibCpp2IlMain.TheMetadata == null ? null : LibCpp2IlMain.TheMetadata.eventDefs.Skip(firstEventId).Take(eventCount).ToArray(); + + public Il2CppTypeDefinition[]? NestedTypes => LibCpp2IlMain.TheMetadata == null ? null : LibCpp2IlMain.TheMetadata.nestedTypeIndices.Skip(nestedTypesStart).Take(nested_type_count).Select(idx => LibCpp2IlMain.TheMetadata.typeDefs[idx]).ToArray(); + + public Il2CppTypeReflectionData[]? Interfaces => LibCpp2IlMain.TheMetadata == null || LibCpp2IlMain.ThePe == null + ? null + : LibCpp2IlMain.TheMetadata.interfaceIndices + .Skip(interfacesStart) + .Take(interfaces_count) + .Select(idx => LibCpp2IlMain.ThePe.types[idx]) + .Select(type => LibCpp2ILUtils.GetTypeReflectionData(type)!) + .ToArray(); + + public Il2CppTypeDefinition? DeclaringType => LibCpp2IlMain.TheMetadata == null || LibCpp2IlMain.ThePe == null || declaringTypeIndex < 0 ? null : LibCpp2IlMain.TheMetadata.typeDefs[LibCpp2IlMain.ThePe.types[declaringTypeIndex].data.classIndex]; + + public override string ToString() + { + if (LibCpp2IlMain.TheMetadata == null) return base.ToString(); + + return $"Il2CppTypeDefinition[namespace='{Namespace}', name='{Name}', parentType={BaseType?.ToString() ?? "null"}]"; + } } } \ No newline at end of file diff --git a/LibCpp2IL/PE/Il2CppGenericClass.cs b/LibCpp2IL/PE/Il2CppGenericClass.cs index 6f78f655..23655d6a 100644 --- a/LibCpp2IL/PE/Il2CppGenericClass.cs +++ b/LibCpp2IL/PE/Il2CppGenericClass.cs @@ -1,3 +1,5 @@ +#pragma warning disable 8618 +//Disable null check because this stuff is initialized by reflection namespace LibCpp2IL.PE { public class Il2CppGenericClass diff --git a/LibCpp2IL/PE/Il2CppGenericMethodFunctionsDefinitions.cs b/LibCpp2IL/PE/Il2CppGenericMethodFunctionsDefinitions.cs index 860dfcbc..ed98b288 100644 --- a/LibCpp2IL/PE/Il2CppGenericMethodFunctionsDefinitions.cs +++ b/LibCpp2IL/PE/Il2CppGenericMethodFunctionsDefinitions.cs @@ -1,3 +1,5 @@ +#pragma warning disable 8618 +//Disable null check because this stuff is initialized by reflection namespace LibCpp2IL.PE { public class Il2CppGenericMethodFunctionsDefinitions diff --git a/LibCpp2IL/PE/Il2CppType.cs b/LibCpp2IL/PE/Il2CppType.cs index ec719cfa..17582983 100644 --- a/LibCpp2IL/PE/Il2CppType.cs +++ b/LibCpp2IL/PE/Il2CppType.cs @@ -1,3 +1,5 @@ +#pragma warning disable 8618 +//Disable null check because this stuff is initialized by reflection namespace LibCpp2IL.PE { public class Il2CppType diff --git a/LibCpp2IL/PE/OptionalHeader.cs b/LibCpp2IL/PE/OptionalHeader.cs index 332f8faf..609f6c29 100644 --- a/LibCpp2IL/PE/OptionalHeader.cs +++ b/LibCpp2IL/PE/OptionalHeader.cs @@ -1,3 +1,5 @@ +#pragma warning disable 8618 +//Disable null check because this stuff is initialized by reflection namespace LibCpp2IL.PE { public class OptionalHeader diff --git a/LibCpp2IL/PE/OptionalHeader64.cs b/LibCpp2IL/PE/OptionalHeader64.cs index ed6dcf76..1c884ae1 100644 --- a/LibCpp2IL/PE/OptionalHeader64.cs +++ b/LibCpp2IL/PE/OptionalHeader64.cs @@ -1,3 +1,5 @@ +#pragma warning disable 8618 +//Disable null check because this stuff is initialized by reflection namespace LibCpp2IL.PE { public class OptionalHeader64 diff --git a/LibCpp2IL/PE/PE.cs b/LibCpp2IL/PE/PE.cs index 60a5e9ac..a0b8a434 100644 --- a/LibCpp2IL/PE/PE.cs +++ b/LibCpp2IL/PE/PE.cs @@ -11,6 +11,8 @@ namespace LibCpp2IL.PE { public sealed class PE : ClassReadingBinaryReader { +#pragma warning disable 8618 +//Disable null check because this stuff is initialized by reflection private Il2CppMetadataRegistration metadataRegistration; private Il2CppCodeRegistration codeRegistration; public ulong[] methodPointers; @@ -38,7 +40,7 @@ public sealed class PE : ClassReadingBinaryReader private OptionalHeader64 optionalHeader64; private OptionalHeader optionalHeader; - private uint[] exportFunctionPointers; + private uint[]? exportFunctionPointers; private uint[] exportFunctionNamePtrs; private ushort[] exportFunctionOrdinals; @@ -50,11 +52,11 @@ public PE(MemoryStream input, long maxMetadataUsages) : base(input) this.maxMetadataUsages = maxMetadataUsages; if (ReadUInt16() != 0x5A4D) //Magic number - throw new Exception("ERROR: Magic number mismatch."); + throw new FormatException("ERROR: Magic number mismatch."); Position = 0x3C; //Signature position position (lol) Position = ReadUInt32(); //Signature position if (ReadUInt32() != 0x00004550) //Signature - throw new Exception("ERROR: Invalid PE file signature"); + throw new FormatException("ERROR: Invalid PE file signature"); var fileHeader = ReadClass(-1); if (fileHeader.Machine == 0x014c) //Intel 386 @@ -72,7 +74,7 @@ public PE(MemoryStream input, long maxMetadataUsages) : base(input) } else { - throw new Exception("ERROR: Unsupported machine."); + throw new NotSupportedException("ERROR: Unsupported machine."); } sections = new SectionHeader[fileHeader.NumberOfSections]; @@ -97,6 +99,7 @@ public PE(MemoryStream input, long maxMetadataUsages) : base(input) Console.WriteLine($"\tImage Base at 0x{imageBase:X}"); Console.WriteLine($"\tDLL is {(is32Bit ? "32" : "64")}-bit"); } +#pragma warning restore 8618 private bool AutoInit(ulong codeRegistration, ulong metadataRegistration) { @@ -471,7 +474,7 @@ public bool PlusSearch(int methodCount, int typeDefinitionsCount) Console.WriteLine($"\tFound a call to that function at 0x{addrCallToRegCallback:X}"); var indexOfCallToRegisterCallback = allInstructionsInTextSection.IndexOf(callToRegisterCallback); - Instruction loadOfAddressToCodegenRegistrationFunction = null; + Instruction? loadOfAddressToCodegenRegistrationFunction = null; for (var i = indexOfCallToRegisterCallback; i > 0; i--) { if (!is32Bit) @@ -540,14 +543,14 @@ public bool PlusSearch(int methodCount, int typeDefinitionsCount) bailout: - if (codeRegistration == 0) + if (codeRegistration == 0 && LibCpp2IlMain.Settings.AllowManualMetadataAndCodeRegInput) { Console.Write("Couldn't identify a CodeRegistration address. If you know it, enter it now, otherwise enter nothing or zero to fail: "); var crInput = Console.ReadLine(); ulong.TryParse(crInput, NumberStyles.HexNumber, null, out codeRegistration); } - if (metadataRegistration == 0) + if (metadataRegistration == 0 && LibCpp2IlMain.Settings.AllowManualMetadataAndCodeRegInput) { Console.Write("Couldn't identify a MetadataRegistration address. If you know it, enter it now, otherwise enter nothing or zero to fail: "); var mrInput = Console.ReadLine(); @@ -661,7 +664,7 @@ public ulong GetVirtualAddressOfUnmanagedExportByName(string toFind) return 0; var ordinal = exportFunctionOrdinals[index]; - var functionPointer = exportFunctionPointers[ordinal]; + var functionPointer = exportFunctionPointers![ordinal]; return functionPointer + imageBase; } diff --git a/LibCpp2IL/PE/SectionHeader.cs b/LibCpp2IL/PE/SectionHeader.cs index 53c61afe..9bade0df 100644 --- a/LibCpp2IL/PE/SectionHeader.cs +++ b/LibCpp2IL/PE/SectionHeader.cs @@ -1,3 +1,5 @@ +#pragma warning disable 8618 +//Disable null check because this stuff is initialized by reflection namespace LibCpp2IL.PE { public class SectionHeader diff --git a/LibCpp2IL/Properties/AssemblyInfo.cs b/LibCpp2IL/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..a002fa3c --- /dev/null +++ b/LibCpp2IL/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// 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: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 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 +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7C9601B4-B53B-48CD-866F-DB908B3BF54D")] +[assembly: InternalsVisibleTo("Cpp2IL")] \ No newline at end of file diff --git a/LibCpp2IL/Reflection/Il2CppFieldReflectionData.cs b/LibCpp2IL/Reflection/Il2CppFieldReflectionData.cs new file mode 100644 index 00000000..f71bbd05 --- /dev/null +++ b/LibCpp2IL/Reflection/Il2CppFieldReflectionData.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using LibCpp2IL.Metadata; + +namespace LibCpp2IL +{ + public struct Il2CppFieldReflectionData + { + public Il2CppFieldDefinition field; + public FieldAttributes attributes; + public object? defaultValue; + } +} \ No newline at end of file diff --git a/LibCpp2IL/Reflection/Il2CppParameterReflectionData.cs b/LibCpp2IL/Reflection/Il2CppParameterReflectionData.cs new file mode 100644 index 00000000..5305cff8 --- /dev/null +++ b/LibCpp2IL/Reflection/Il2CppParameterReflectionData.cs @@ -0,0 +1,12 @@ +using System.Reflection; + +namespace LibCpp2IL.Reflection +{ + public class Il2CppParameterReflectionData + { + public string ParameterName; + public Il2CppTypeReflectionData Type; + public ParameterAttributes ParameterAttributes; + public object? DefaultValue; + } +} \ No newline at end of file diff --git a/LibCpp2IL/Reflection/Il2CppTypeReflectionData.cs b/LibCpp2IL/Reflection/Il2CppTypeReflectionData.cs new file mode 100644 index 00000000..5df241bc --- /dev/null +++ b/LibCpp2IL/Reflection/Il2CppTypeReflectionData.cs @@ -0,0 +1,43 @@ +using System.Text; +using LibCpp2IL.Metadata; + +namespace LibCpp2IL.Reflection +{ + /// + /// A wrapper around Il2CppTypeDefinition to allow expression of complex types such as Generics. Can represent one of three things: + ///
    + ///
  • If both and are false, this represents a generic parameter (such as the T in List<T>)
  • + ///
  • If is true and is false, this represents a standard Il2CppTypeDefinition - check to see what
  • + ///
  • If both and are true, this is a complex generic type, of basic type - this is where the List part would be - and with params stored in - these may be any of these three cases again.
  • + ///
+ ///
+ /// Calling on this object will return the canonical representation of this object, with generic params such as System.Collections.Generic.List`1<T> or with concrete types, like in the case of a String's interfaces, System.Collections.Generic.IEnumerable`1<System.Char> + ///
+ public class Il2CppTypeReflectionData + { + public Il2CppTypeDefinition? baseType; + public Il2CppTypeReflectionData[] genericParams; + public bool isType; + public bool isGenericType; + public string variableGenericParamName; + + public override string ToString() + { + if (!isType) + return variableGenericParamName; + + if (!isGenericType) + return baseType.FullName!; + + var builder = new StringBuilder(baseType.FullName + "<"); + foreach (var genericParam in genericParams) + { + builder.Append(genericParam).Append(", "); + } + + builder.Remove(builder.Length - 2, 2); + builder.Append(">"); + return builder.ToString(); + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/Reflection/LibCpp2IlReflection.cs b/LibCpp2IL/Reflection/LibCpp2IlReflection.cs new file mode 100644 index 00000000..f8ceaad6 --- /dev/null +++ b/LibCpp2IL/Reflection/LibCpp2IlReflection.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Linq; +using LibCpp2IL.Metadata; + +namespace LibCpp2IL +{ + public static class LibCpp2IlReflection + { + public static Il2CppTypeDefinition? GetType(string name, string? @namespace = null) + { + if (LibCpp2IlMain.TheMetadata == null) return null; + + var typeDef = LibCpp2IlMain.TheMetadata.typeDefs.FirstOrDefault(td => + td.Name == name && + (@namespace == null || @namespace == td.Namespace) + ); + + return typeDef; + } + + public static Il2CppTypeDefinition? GetTypeDefinitionByTypeIndex(int index) + { + if (LibCpp2IlMain.TheMetadata == null || LibCpp2IlMain.ThePe == null) return null; + + if (index >= LibCpp2IlMain.ThePe.types.Length || index < 0) return null; + + var type = LibCpp2IlMain.ThePe.types[index]; + + return LibCpp2IlMain.TheMetadata.typeDefs[type.data.classIndex]; + } + + public static int GetTypeIndexFromType(Il2CppTypeDefinition typeDefinition) + { + if (LibCpp2IlMain.TheMetadata == null) return -1; + + for (var i = 0; i < LibCpp2IlMain.TheMetadata.typeDefs.Length; i++) + { + if (LibCpp2IlMain.TheMetadata.typeDefs[i] == typeDefinition) return i; + } + + return -1; + } + + public static int GetMethodIndexFromMethod(Il2CppMethodDefinition methodDefinition) + { + if (LibCpp2IlMain.TheMetadata == null) return -1; + + for (var i = 0; i < LibCpp2IlMain.TheMetadata.methodDefs.Length; i++) + { + if (LibCpp2IlMain.TheMetadata.methodDefs[i] == methodDefinition) return i; + } + + return -1; + } + + public static int GetFieldIndexFromField(Il2CppFieldDefinition fieldDefinition) + { + if (LibCpp2IlMain.TheMetadata == null) return -1; + + for (var i = 0; i < LibCpp2IlMain.TheMetadata.fieldDefs.Length; i++) + { + if (LibCpp2IlMain.TheMetadata.fieldDefs[i] == fieldDefinition) return i; + } + + return -1; + } + } +} \ No newline at end of file