diff --git a/README.md b/README.md index fb55d3a..f99d540 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,19 @@ protodec ======== -A tool to decompile protobuf classes compiled by [protoc](https://github.com/protocolbuffers/protobuf), from CIL assemblies back into .proto definitions. +A tool to decompile protobuf classes compiled by [protoc](https://github.com/protocolbuffers/protobuf), from il2cpp compiled CIL assemblies back into .proto definitions. + +This branch was created as a proof-of-concept using [the development branch of LibCpp2Il](https://github.com/SamboyCoding/Cpp2IL/tree/development/LibCpp2IL) to parse the game assembly and metadata directly, without the intermediate step of generating dummy DLLs. + +I offer no guarantees that this branch functions 1:1 with master, it may explode. Usage ----- ``` -Usage: protodec(.exe) [options] +Usage: protodec(.exe) [options] Arguments: - target_assembly_path Either the path to the target assembly or a directory of assemblies, all of which be parsed. + game_assembly_path The path to the game assembly DLL. + global_metadata_path The path to the global-metadata.dat file. + unity_version The version of Unity which was used to create the metadata file or alternatively, the path to the globalgamemanagers or the data.unity3d file. out_path An existing directory to output into individual files, otherwise output to a single file. Options: --debug Drops the minimum log level to Debug. @@ -22,8 +28,9 @@ Limitations ----------- - Integers are assumed to be (u)int32/64 as CIL doesn't differentiate between them and sint32/64 and (s)fixed32/64. - Package names are not preserved in protobuf compilation so naturally we cannot recover them during decompilation, which may result in naming conflicts. -- When decompiling from [Il2CppDumper](https://github.com/Perfare/Il2CppDumper) DummyDLLs - - The `Name` parameter of `OriginalNameAttribute` is not dumped. In this case, the CIL enum field names are used after conforming them to protobuf conventions +- Due to the development branch of Cpp2Il not yet recovering method bodies + - The `Name` parameter of `OriginalNameAttribute` is not parsed. In this case, the CIL enum field names are used after conforming them to protobuf conventions. + - The `Tool` parameter of `GeneratedCodeAttribute` is not compared against when parsing gRPC service methods, which may cause false positives in the event that another tool has generated methods in the service class. License ------- diff --git a/protodec.sln b/protodec.sln index d47bba3..e17668b 100644 --- a/protodec.sln +++ b/protodec.sln @@ -12,6 +12,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibCpp2IL", "..\Cpp2IL\LibCpp2IL\LibCpp2IL.csproj", "{42B987D9-E551-48BC-A821-F7411C664ECC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WasmDisassembler", "..\Cpp2IL\WasmDisassembler\WasmDisassembler.csproj", "{07C0845C-7579-43C0-B4C9-4769573F3A24}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -26,6 +30,14 @@ Global {5F6DAD82-D9AD-4CE5-86E6-D20C9F059A4D}.Debug|Any CPU.Build.0 = Debug|Any CPU {5F6DAD82-D9AD-4CE5-86E6-D20C9F059A4D}.Release|Any CPU.ActiveCfg = Release|Any CPU {5F6DAD82-D9AD-4CE5-86E6-D20C9F059A4D}.Release|Any CPU.Build.0 = Release|Any CPU + {42B987D9-E551-48BC-A821-F7411C664ECC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42B987D9-E551-48BC-A821-F7411C664ECC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42B987D9-E551-48BC-A821-F7411C664ECC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42B987D9-E551-48BC-A821-F7411C664ECC}.Release|Any CPU.Build.0 = Release|Any CPU + {07C0845C-7579-43C0-B4C9-4769573F3A24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07C0845C-7579-43C0-B4C9-4769573F3A24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07C0845C-7579-43C0-B4C9-4769573F3A24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07C0845C-7579-43C0-B4C9-4769573F3A24}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/LibProtodec/LibProtodec.csproj b/src/LibProtodec/LibProtodec.csproj index 87a204e..4827130 100644 --- a/src/LibProtodec/LibProtodec.csproj +++ b/src/LibProtodec/LibProtodec.csproj @@ -18,6 +18,7 @@ + diff --git a/src/LibProtodec/Loaders/Il2CppAssemblyLoader.cs b/src/LibProtodec/Loaders/Il2CppAssemblyLoader.cs new file mode 100644 index 0000000..f9f900a --- /dev/null +++ b/src/LibProtodec/Loaders/Il2CppAssemblyLoader.cs @@ -0,0 +1,38 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System.Linq; +using AssetRipper.Primitives; +using CommunityToolkit.Diagnostics; +using LibCpp2IL; +using LibCpp2IL.Metadata; +using LibCpp2IL.Reflection; +using LibProtodec.Models.Cil; +using LibProtodec.Models.Cil.Il2Cpp; +using Microsoft.Extensions.Logging; + +namespace LibProtodec.Loaders; + +public sealed class Il2CppAssemblyLoader : CilAssemblyLoader +{ + public Il2CppAssemblyLoader(string assemblyPath, string metadataPath, UnityVersion unityVersion, ILogger logger) + { + if (!LibCpp2IlMain.LoadFromFile(assemblyPath, metadataPath, unityVersion)) + ThrowHelper.ThrowInvalidDataException("Failed to load il2cpp assembly!"); + + this.LoadedTypes = LibCpp2IlMain.TheMetadata!.typeDefs.Select(Il2CppType.GetOrCreate).ToList(); + + logger?.LogLoadedTypeAndAssemblyCount(this.LoadedTypes.Count, LibCpp2IlMain.TheMetadata.imageDefinitions.Length); + } + + protected override ICilType FindType(string typeFullName, string assemblySimpleName) + { + Il2CppTypeDefinition? type = LibCpp2IlReflection.GetTypeByFullName(typeFullName); + Guard.IsNotNull(type); + + return Il2CppType.GetOrCreate(type); + } +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppAttribute.cs b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppAttribute.cs new file mode 100644 index 0000000..fa61b26 --- /dev/null +++ b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppAttribute.cs @@ -0,0 +1,20 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System; +using System.Collections.Generic; +using LibCpp2IL.Reflection; + +namespace LibProtodec.Models.Cil.Il2Cpp; + +public sealed class Il2CppAttribute(Il2CppTypeReflectionData il2CppAttrType) : ICilAttribute +{ + public ICilType Type => + Il2CppType.GetOrCreate(il2CppAttrType); + + public IList ConstructorArgumentValues => + throw new NotImplementedException(); +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppField.cs b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppField.cs new file mode 100644 index 0000000..7f75411 --- /dev/null +++ b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppField.cs @@ -0,0 +1,40 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System.Reflection; +using LibCpp2IL.Metadata; + +namespace LibProtodec.Models.Cil.Il2Cpp; + +public sealed class Il2CppField(Il2CppFieldDefinition il2CppField) : Il2CppMember, ICilField +{ + private readonly FieldAttributes _attributes = + (FieldAttributes)il2CppField.RawFieldType!.Attrs; + + public string Name => + il2CppField.Name!; + + public object? ConstantValue => + il2CppField.DefaultValue!.Value; + + public bool IsLiteral => + (_attributes & FieldAttributes.Literal) != 0; + + public bool IsPublic => + (_attributes & FieldAttributes.Public) != 0; + + public bool IsStatic => + (_attributes & FieldAttributes.Static) != 0; + + protected override Il2CppImageDefinition DeclaringAssembly => + il2CppField.FieldType!.baseType!.DeclaringAssembly!; + + protected override int CustomAttributeIndex => + il2CppField.customAttributeIndex; + + protected override uint Token => + il2CppField.token; +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppMember.cs b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppMember.cs new file mode 100644 index 0000000..663237f --- /dev/null +++ b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppMember.cs @@ -0,0 +1,63 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System; +using System.Collections.Generic; +using System.Linq; +using LibCpp2IL; +using LibCpp2IL.Metadata; + +namespace LibProtodec.Models.Cil.Il2Cpp; + +public abstract class Il2CppMember +{ + private ICilAttribute[]? _customAttributes; + + public IList CustomAttributes + { + get + { + if (_customAttributes is null) + { + _customAttributes = GetCustomAttributes().ToArray(); + } + + return _customAttributes; + } + } + + public IEnumerable GetCustomAttributes() + { + if (LibCpp2IlMain.MetadataVersion >= 29) + { + throw new NotImplementedException(); + } + + Il2CppCustomAttributeTypeRange? attributeTypeRange = LibCpp2IlMain.TheMetadata!.GetCustomAttributeData( + DeclaringAssembly, CustomAttributeIndex, Token, out _); + + if (attributeTypeRange is null || attributeTypeRange.count == 0) + yield break; + + for (int attrIndex = attributeTypeRange.start, + end = attributeTypeRange.start + attributeTypeRange.count; + attrIndex < end; + attrIndex++) + { + int typeIndex = LibCpp2IlMain.TheMetadata.attributeTypes[attrIndex]; + + yield return new Il2CppAttribute( + LibCpp2ILUtils.GetTypeReflectionData( + LibCpp2IlMain.Binary!.GetType(typeIndex))); + } + } + + protected abstract Il2CppImageDefinition DeclaringAssembly { get; } + + protected abstract int CustomAttributeIndex { get; } + + protected abstract uint Token { get; } +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppMethod.cs b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppMethod.cs new file mode 100644 index 0000000..3a607ef --- /dev/null +++ b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppMethod.cs @@ -0,0 +1,57 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System.Collections.Generic; +using System.Reflection; +using LibCpp2IL; +using LibCpp2IL.Metadata; +using LibCpp2IL.Reflection; + +namespace LibProtodec.Models.Cil.Il2Cpp; + +public sealed class Il2CppMethod(Il2CppMethodDefinition il2CppMethod) : Il2CppMember, ICilMethod +{ + public string Name => + il2CppMethod.Name!; + + public bool IsInherited => + false; + + public bool IsConstructor => + false; + + public bool IsPublic => + (il2CppMethod.Attributes & MethodAttributes.Public) != 0; + + public bool IsStatic => + (il2CppMethod.Attributes & MethodAttributes.Static) != 0; + + public bool IsVirtual => + (il2CppMethod.Attributes & MethodAttributes.Virtual) != 0; + + public ICilType ReturnType => + Il2CppType.GetOrCreate( + LibCpp2ILUtils.GetTypeReflectionData( + LibCpp2IlMain.Binary!.GetType( + il2CppMethod.returnTypeIdx))); + + public IEnumerable GetParameterTypes() + { + foreach (Il2CppParameterReflectionData parameter in il2CppMethod.Parameters!) + { + yield return Il2CppType.GetOrCreate(parameter.Type); + } + } + + protected override Il2CppImageDefinition DeclaringAssembly => + il2CppMethod.DeclaringType!.DeclaringAssembly!; + + protected override int CustomAttributeIndex => + il2CppMethod.customAttributeIndex; + + protected override uint Token => + il2CppMethod.token; +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppProperty.cs b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppProperty.cs new file mode 100644 index 0000000..be515fa --- /dev/null +++ b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppProperty.cs @@ -0,0 +1,54 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System.Linq; +using LibCpp2IL; +using LibCpp2IL.Metadata; + +namespace LibProtodec.Models.Cil.Il2Cpp; + +// We take declaring type as a ctor arg because of the nonsensically inefficient way libcpp2il calculates Il2CppPropertyDefinition's DeclaringType property +public sealed class Il2CppProperty(Il2CppPropertyDefinition il2CppProperty, Il2CppTypeDefinition declaringType) : Il2CppMember, ICilProperty +{ + public string Name => + il2CppProperty.Name!; + + public bool IsInherited => + false; + + public bool CanRead => + il2CppProperty.get >= 0; + + public bool CanWrite => + il2CppProperty.set >= 0; + + public ICilMethod? Getter => + CanRead + ? new Il2CppMethod( + LibCpp2IlMain.TheMetadata!.methodDefs[ + declaringType.FirstMethodIdx + il2CppProperty.get]) + : null; + + public ICilMethod? Setter => + CanWrite + ? new Il2CppMethod( + LibCpp2IlMain.TheMetadata!.methodDefs[ + declaringType.FirstMethodIdx + il2CppProperty.set]) + : null; + + public ICilType Type => + Getter?.ReturnType + ?? Setter!.GetParameterTypes().First(); + + protected override Il2CppImageDefinition DeclaringAssembly => + declaringType.DeclaringAssembly!; + + protected override int CustomAttributeIndex => + il2CppProperty.customAttributeIndex; + + protected override uint Token => + il2CppProperty.token; +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppType.cs b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppType.cs new file mode 100644 index 0000000..3d1a07a --- /dev/null +++ b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppType.cs @@ -0,0 +1,194 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using CommunityToolkit.Diagnostics; +using LibCpp2IL; +using LibCpp2IL.Metadata; +using LibCpp2IL.Reflection; + +namespace LibProtodec.Models.Cil.Il2Cpp; + +public sealed class Il2CppType : Il2CppMember, ICilType +{ + private readonly Il2CppTypeDefinition _il2CppType; + private readonly Il2CppTypeReflectionData[] _genericArgs; + private IList? _genericTypeArguments; + + private Il2CppType(Il2CppTypeDefinition il2CppType, Il2CppTypeReflectionData[] genericArgs) => + (_il2CppType, _genericArgs) = (il2CppType, genericArgs); + + public string Name => + _il2CppType.Name!; + + public string FullName => + _il2CppType.FullName!; + + public string? Namespace => + _il2CppType.Namespace; + + public string DeclaringAssemblyName => + LibCpp2IlMain.TheMetadata!.GetStringFromIndex( + DeclaringAssembly.nameIndex); + + public ICilType? DeclaringType => + IsNested + ? GetOrCreate( + LibCpp2ILUtils.GetTypeReflectionData( + LibCpp2IlMain.Binary!.GetType( + _il2CppType.DeclaringTypeIndex))) + : null; + + public ICilType? BaseType => + _il2CppType.ParentIndex == -1 + ? null + : GetOrCreate( + LibCpp2ILUtils.GetTypeReflectionData( + LibCpp2IlMain.Binary!.GetType( + _il2CppType.ParentIndex))); + + public bool IsAbstract => + _il2CppType.IsAbstract; + + public bool IsClass => + (_il2CppType.Attributes & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Class + && !_il2CppType.IsValueType; + + public bool IsEnum => + _il2CppType.IsEnumType; + + public bool IsNested => + _il2CppType.DeclaringTypeIndex >= 0; + + public bool IsSealed => + (_il2CppType.Attributes & TypeAttributes.Sealed) != 0; + + public IList GenericTypeArguments + { + get + { + if (_genericTypeArguments is null) + { + if (_genericArgs.Length < 1) + { + _genericTypeArguments = Array.Empty(); + } + else + { + _genericTypeArguments = _genericArgs.Select(GetOrCreate).ToList(); + } + } + + return _genericTypeArguments; + } + } + + public IEnumerable GetFields() + { + for (int idx = _il2CppType.FirstFieldIdx, end = _il2CppType.FirstFieldIdx + _il2CppType.FieldCount; idx < end; idx++) + { + yield return new Il2CppField( + LibCpp2IlMain.TheMetadata!.fieldDefs[idx]); + } + } + + public IEnumerable GetMethods() + { + for (int idx = _il2CppType.FirstMethodIdx, end = _il2CppType.FirstMethodIdx + _il2CppType.MethodCount; idx < end; idx++) + { + yield return new Il2CppMethod( + LibCpp2IlMain.TheMetadata!.methodDefs[idx]); + } + } + + public IEnumerable GetNestedTypes() + { + for (int idx = _il2CppType.NestedTypesStart, end = _il2CppType.NestedTypesStart + _il2CppType.NestedTypeCount; idx < end; idx++) + { + yield return GetOrCreate( + LibCpp2IlMain.TheMetadata!.typeDefs[ + LibCpp2IlMain.TheMetadata.nestedTypeIndices[idx]]); + } + } + + public IEnumerable GetProperties() + { + for (int idx = _il2CppType.FirstPropertyId, end = _il2CppType.FirstPropertyId + _il2CppType.PropertyCount; idx < end; idx++) + { + yield return new Il2CppProperty( + LibCpp2IlMain.TheMetadata!.propertyDefs[idx], + _il2CppType); + } + } + + public bool IsAssignableTo(ICilType type) + { + if (type is Il2CppType il2CppType) + { + return IsAssignableTo(_il2CppType, il2CppType._il2CppType); + } + + return ThrowHelper.ThrowNotSupportedException(); + } + + protected override Il2CppImageDefinition DeclaringAssembly => + _il2CppType.DeclaringAssembly!; + + protected override int CustomAttributeIndex => + _il2CppType.CustomAttributeIndex; + + protected override uint Token => + _il2CppType.Token; + + + private static readonly ConcurrentDictionary TypeLookup = []; + + public static ICilType GetOrCreate(Il2CppTypeDefinition il2CppType) => + TypeLookup.GetOrAdd( + il2CppType.FullName!, + static (_, il2CppType) => + new Il2CppType(il2CppType, Array.Empty()), + il2CppType); + + public static ICilType GetOrCreate(Il2CppTypeReflectionData il2CppTypeData) + { + Guard.IsTrue(il2CppTypeData.isType); + + return TypeLookup.GetOrAdd( + il2CppTypeData.ToString(), + static (_, il2CppTypeData) => + new Il2CppType(il2CppTypeData.baseType!, il2CppTypeData.genericParams), + il2CppTypeData); + } + + private static bool IsAssignableTo(Il2CppTypeDefinition thisType, Il2CppTypeDefinition baseType) + { + if (baseType.IsInterface) + { + foreach (Il2CppTypeReflectionData @interface in thisType.Interfaces!) + { + if (@interface.baseType == baseType) + { + return true; + } + } + } + + if (thisType == baseType) + { + return true; + } + + Il2CppTypeDefinition? thisTypeBaseType = thisType.BaseType?.baseType; + + return thisTypeBaseType is not null + && IsAssignableTo(thisTypeBaseType, baseType); + } +} \ No newline at end of file diff --git a/src/LibProtodec/ProtodecContext.cs b/src/LibProtodec/ProtodecContext.cs index 5aa905e..f9a3c90 100644 --- a/src/LibProtodec/ProtodecContext.cs +++ b/src/LibProtodec/ProtodecContext.cs @@ -572,11 +572,7 @@ public class ProtodecContext protected string TranslateEnumFieldName(IEnumerable attributes, string fieldName, string enumName) { - if (attributes.SingleOrDefault(static attr => attr.Type.Name == "OriginalNameAttribute") - ?.ConstructorArgumentValues[0] is string originalName) - { - return originalName; - } + //TODO: parse original name from first parameter of OriginalNameAttribute constructor if (NameLookup?.Invoke(fieldName, out string? translatedName) == true) { @@ -624,11 +620,13 @@ public class ProtodecContext // ReSharper disable once IdentifierTypo protected static bool IsBeebyted(string name) => - name.Length == 11 && name.CountUpper() == 11; - - protected static bool HasGeneratedCodeAttribute(IEnumerable attributes, string tool) => - attributes.Any(attr => attr.Type.Name == nameof(GeneratedCodeAttribute) - && attr.ConstructorArgumentValues[0] as string == tool); + name.Length == 11 && name.CountUpper() == 11; + + protected static bool HasGeneratedCodeAttribute(IEnumerable attributes, string tool) + { + return attributes.Any(attr => attr.Type.Name == nameof(GeneratedCodeAttribute)); + //TODO: ensure the first argument of the GeneratedCodeAttribute constructor == tool parameter + } protected static bool HasNonUserCodeAttribute(IEnumerable attributes) => attributes.Any(static attr => attr.Type.Name == nameof(DebuggerNonUserCodeAttribute)); diff --git a/src/protodec/Program.cs b/src/protodec/Program.cs index cd6040a..091e8f4 100644 --- a/src/protodec/Program.cs +++ b/src/protodec/Program.cs @@ -9,6 +9,8 @@ using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Linq; +using AssetRipper.Primitives; +using LibCpp2IL; using LibProtodec; using LibProtodec.Loaders; using LibProtodec.Models.Cil; @@ -17,9 +19,11 @@ using Microsoft.Extensions.Logging; const string indent = " "; const string help = """ - Usage: protodec(.exe) [options] + Usage: protodec(.exe) [options] Arguments: - target_assembly_path Either the path to the target assembly or a directory of assemblies, all of which be parsed. + game_assembly_path The path to the game assembly DLL. + global_metadata_path The path to the global-metadata.dat file. + unity_version The version of Unity which was used to create the metadata file or alternatively, the path to the globalgamemanagers or the data.unity3d file. out_path An existing directory to output into individual files, otherwise output to a single file. Options: --debug Drops the minimum log level to Debug. @@ -30,14 +34,16 @@ const string help = """ --include_service_methods_without_generated_code_attribute Includes methods that aren't decorated with `GeneratedCode("grpc_csharp_plugin")` when parsing gRPC services. """; -if (args.Length < 2) +if (args.Length < 4) { Console.WriteLine(help); return; } string assembly = args[0]; -string outPath = Path.GetFullPath(args[1]); +string metadata = args[1]; +string uVersion = args[2]; +string outPath = Path.GetFullPath(args[3]); ParserOptions options = ParserOptions.None; LogLevel logLevel = args.Contains("--debug") ? LogLevel.Debug @@ -52,14 +58,23 @@ if (args.Contains("--include_properties_without_non_user_code_attribute")) if (args.Contains("--include_service_methods_without_generated_code_attribute")) options |= ParserOptions.IncludeServiceMethodsWithoutGeneratedCodeAttribute; +if (!UnityVersion.TryParse(uVersion, out UnityVersion unityVersion, out _)) +{ + unityVersion = uVersion.EndsWith("globalgamemanagers") + ? LibCpp2IlMain.GetVersionFromGlobalGameManagers( + File.ReadAllBytes(uVersion)) + : LibCpp2IlMain.GetVersionFromDataUnity3D( + File.OpenRead(uVersion)); +} + using ILoggerFactory loggerFactory = LoggerFactory.Create( builder => builder.AddSimpleConsole(static console => console.IncludeScopes = true) .SetMinimumLevel(logLevel)); ILogger logger = loggerFactory.CreateLogger("protodec"); logger.LogInformation("Loading target assemblies..."); -using CilAssemblyLoader loader = new ClrAssemblyLoader( - assembly, loggerFactory.CreateLogger()); +using CilAssemblyLoader loader = new Il2CppAssemblyLoader( + assembly, metadata, unityVersion, loggerFactory.CreateLogger()); ProtodecContext ctx = new() {