diff --git a/README.md b/README.md index 283af37..2cc3cd9 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: --parse_service_servers Parses gRPC service definitions from server classes. @@ -21,8 +27,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 8120bb0..29a871c 100644 --- a/src/LibProtodec/LibProtodec.csproj +++ b/src/LibProtodec/LibProtodec.csproj @@ -18,9 +18,10 @@ + - + \ No newline at end of file diff --git a/src/LibProtodec/Loaders/Il2CppAssemblyLoader.cs b/src/LibProtodec/Loaders/Il2CppAssemblyLoader.cs new file mode 100644 index 0000000..21804dd --- /dev/null +++ b/src/LibProtodec/Loaders/Il2CppAssemblyLoader.cs @@ -0,0 +1,66 @@ +// 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.Linq; +using AssetRipper.Primitives; +using CommunityToolkit.Diagnostics; +using LibCpp2IL; +using LibCpp2IL.Metadata; +using LibCpp2IL.Reflection; +using LibProtodec.Models.Cil; +using LibProtodec.Models.Cil.Il2Cpp; + +namespace LibProtodec.Loaders; + +public sealed class Il2CppAssemblyLoader : ICilAssemblyLoader +{ + public Il2CppAssemblyLoader(string assemblyPath, string metadataPath, UnityVersion unityVersion) + { + if (!LibCpp2IlMain.LoadFromFile(assemblyPath, metadataPath, unityVersion)) + ThrowHelper.ThrowInvalidDataException("Failed to load il2cpp assembly!"); + + LoadedTypes = LibCpp2IlMain.TheMetadata!.typeDefs.Select(Il2CppType.GetOrCreate).ToList(); + } + + public IReadOnlyList LoadedTypes { get; } + + public ICilType IMessage + { + get + { + Il2CppTypeDefinition? iMessage = LibCpp2IlReflection.GetTypeByFullName("Google.Protobuf.IMessage"); + Guard.IsNotNull(iMessage); + + return Il2CppType.GetOrCreate(iMessage); + } + } + + public ICilType ClientBase + { + get + { + Il2CppTypeDefinition? clientBase = LibCpp2IlReflection.GetTypeByFullName("Grpc.Core.ClientBase"); + Guard.IsNotNull(clientBase); + + return Il2CppType.GetOrCreate(clientBase); + } + } + + public ICilType BindServiceMethodAttribute + { + get + { + Il2CppTypeDefinition? attribute = LibCpp2IlReflection.GetTypeByFullName("Grpc.Core.BindServiceMethodAttribute"); + Guard.IsNotNull(attribute); + + return Il2CppType.GetOrCreate(attribute); + } + } + + public void Dispose() => + LibCpp2IlMain.Reset(); +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Clr/ClrMethod.cs b/src/LibProtodec/Models/Cil/Clr/ClrMethod.cs index 0dcbfd5..30a4cc8 100644 --- a/src/LibProtodec/Models/Cil/Clr/ClrMethod.cs +++ b/src/LibProtodec/Models/Cil/Clr/ClrMethod.cs @@ -14,9 +14,6 @@ public sealed class ClrMethod(MethodInfo clrMethod) : ClrMember(clrMethod), ICil public bool IsPublic => clrMethod.IsPublic; - public bool IsNonPublic => - (clrMethod.Attributes & MethodAttributes.Public) == 0; - public bool IsStatic => clrMethod.IsStatic; diff --git a/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppAttribute.cs b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppAttribute.cs new file mode 100644 index 0000000..eea764e --- /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 ConstructorArguments => + 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..b7ec9cb --- /dev/null +++ b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppMember.cs @@ -0,0 +1,47 @@ +// 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; +using LibCpp2IL.Metadata; + +namespace LibProtodec.Models.Cil.Il2Cpp; + +public abstract class Il2CppMember +{ + 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..7886f54 --- /dev/null +++ b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppMethod.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.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 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/Models/Protobuf/Types/IProtobufType.cs b/src/LibProtodec/Models/Protobuf/Types/IProtobufType.cs index a272204..78618c1 100644 --- a/src/LibProtodec/Models/Protobuf/Types/IProtobufType.cs +++ b/src/LibProtodec/Models/Protobuf/Types/IProtobufType.cs @@ -9,10 +9,4 @@ namespace LibProtodec.Models.Protobuf.Types; public interface IProtobufType { string Name { get; } -} - -public sealed class External(string typeName) : IProtobufType -{ - public string Name => - typeName; } \ No newline at end of file diff --git a/src/LibProtodec/Models/Protobuf/Types/Scalar.cs b/src/LibProtodec/Models/Protobuf/Types/Scalar.cs index 2feec68..e187062 100644 --- a/src/LibProtodec/Models/Protobuf/Types/Scalar.cs +++ b/src/LibProtodec/Models/Protobuf/Types/Scalar.cs @@ -7,21 +7,24 @@ namespace LibProtodec.Models.Protobuf.Types; // ReSharper disable StringLiteralTypo -public static class Scalar +public sealed class Scalar(string typeName) : IProtobufType { - public static readonly IProtobufType Bool = new External("bool"); - public static readonly IProtobufType Bytes = new External("bytes"); - public static readonly IProtobufType Double = new External("double"); - public static readonly IProtobufType Fixed32 = new External("fixed32"); - public static readonly IProtobufType Fixed64 = new External("fixed64"); - public static readonly IProtobufType Float = new External("float"); - public static readonly IProtobufType Int32 = new External("int32"); - public static readonly IProtobufType Int64 = new External("int64"); - public static readonly IProtobufType SFixed32 = new External("sfixed32"); - public static readonly IProtobufType SFixed64 = new External("sfixed64"); - public static readonly IProtobufType SInt32 = new External("sint32"); - public static readonly IProtobufType SInt64 = new External("sint64"); - public static readonly IProtobufType String = new External("string"); - public static readonly IProtobufType UInt32 = new External("uint32"); - public static readonly IProtobufType UInt64 = new External("uint64"); + public string Name => + typeName; + + public static readonly IProtobufType Bool = new Scalar("bool"); + public static readonly IProtobufType Bytes = new Scalar("bytes"); + public static readonly IProtobufType Double = new Scalar("double"); + public static readonly IProtobufType Fixed32 = new Scalar("fixed32"); + public static readonly IProtobufType Fixed64 = new Scalar("fixed64"); + public static readonly IProtobufType Float = new Scalar("float"); + public static readonly IProtobufType Int32 = new Scalar("int32"); + public static readonly IProtobufType Int64 = new Scalar("int64"); + public static readonly IProtobufType SFixed32 = new Scalar("sfixed32"); + public static readonly IProtobufType SFixed64 = new Scalar("sfixed64"); + public static readonly IProtobufType SInt32 = new Scalar("sint32"); + public static readonly IProtobufType SInt64 = new Scalar("sint64"); + public static readonly IProtobufType String = new Scalar("string"); + public static readonly IProtobufType UInt32 = new Scalar("uint32"); + public static readonly IProtobufType UInt64 = new Scalar("uint64"); } \ No newline at end of file diff --git a/src/LibProtodec/Models/Protobuf/Types/WellKnown.cs b/src/LibProtodec/Models/Protobuf/Types/WellKnown.cs index 488ac4a..e84b8f9 100644 --- a/src/LibProtodec/Models/Protobuf/Types/WellKnown.cs +++ b/src/LibProtodec/Models/Protobuf/Types/WellKnown.cs @@ -6,34 +6,40 @@ namespace LibProtodec.Models.Protobuf.Types; -public static class WellKnown +public sealed class WellKnown(string typeName, string fileName) : IProtobufType { - public static readonly IProtobufType Any = new External("google.protobuf.Any"); - public static readonly IProtobufType Api = new External("google.protobuf.Api"); - public static readonly IProtobufType BoolValue = new External("google.protobuf.BoolValue"); - public static readonly IProtobufType BytesValue = new External("google.protobuf.BytesValue"); - public static readonly IProtobufType DoubleValue = new External("google.protobuf.DoubleValue"); - public static readonly IProtobufType Duration = new External("google.protobuf.Duration"); - public static readonly IProtobufType Empty = new External("google.protobuf.Empty"); - public static readonly IProtobufType Enum = new External("google.protobuf.Enum"); - public static readonly IProtobufType EnumValue = new External("google.protobuf.EnumValue"); - public static readonly IProtobufType Field = new External("google.protobuf.Field"); - public static readonly IProtobufType FieldMask = new External("google.protobuf.FieldMask"); - public static readonly IProtobufType FloatValue = new External("google.protobuf.FloatValue"); - public static readonly IProtobufType Int32Value = new External("google.protobuf.Int32Value"); - public static readonly IProtobufType Int64Value = new External("google.protobuf.Int64Value"); - public static readonly IProtobufType ListValue = new External("google.protobuf.ListValue"); - public static readonly IProtobufType Method = new External("google.protobuf.Method"); - public static readonly IProtobufType Mixin = new External("google.protobuf.Mixin"); - public static readonly IProtobufType NullValue = new External("google.protobuf.NullValue"); - public static readonly IProtobufType Option = new External("google.protobuf.Option"); - public static readonly IProtobufType SourceContext = new External("google.protobuf.SourceContext"); - public static readonly IProtobufType StringValue = new External("google.protobuf.StringValue"); - public static readonly IProtobufType Struct = new External("google.protobuf.Struct"); - public static readonly IProtobufType Syntax = new External("google.protobuf.Syntax"); - public static readonly IProtobufType Timestamp = new External("google.protobuf.Timestamp"); - public static readonly IProtobufType Type = new External("google.protobuf.Type"); - public static readonly IProtobufType UInt32Value = new External("google.protobuf.UInt32Value"); - public static readonly IProtobufType UInt64Value = new External("google.protobuf.UInt64Value"); - public static readonly IProtobufType Value = new External("google.protobuf.Value"); + public string Name => + typeName; + + public string FileName => + fileName; + + public static readonly IProtobufType Any = new WellKnown("google.protobuf.Any", "google/protobuf/any.proto"); + public static readonly IProtobufType Api = new WellKnown("google.protobuf.Api", "google/protobuf/api.proto"); + public static readonly IProtobufType BoolValue = new WellKnown("google.protobuf.BoolValue", "google/protobuf/wrappers.proto"); + public static readonly IProtobufType BytesValue = new WellKnown("google.protobuf.BytesValue", "google/protobuf/wrappers.proto"); + public static readonly IProtobufType DoubleValue = new WellKnown("google.protobuf.DoubleValue", "google/protobuf/wrappers.proto"); + public static readonly IProtobufType Duration = new WellKnown("google.protobuf.Duration", "google/protobuf/duration.proto"); + public static readonly IProtobufType Empty = new WellKnown("google.protobuf.Empty", "google/protobuf/empty.proto"); + public static readonly IProtobufType Enum = new WellKnown("google.protobuf.Enum", "google/protobuf/type.proto"); + public static readonly IProtobufType EnumValue = new WellKnown("google.protobuf.EnumValue", "google/protobuf/type.proto"); + public static readonly IProtobufType Field = new WellKnown("google.protobuf.Field", "google/protobuf/type.proto"); + public static readonly IProtobufType FieldMask = new WellKnown("google.protobuf.FieldMask", "google/protobuf/field_mask.proto"); + public static readonly IProtobufType FloatValue = new WellKnown("google.protobuf.FloatValue", "google/protobuf/wrappers.proto"); + public static readonly IProtobufType Int32Value = new WellKnown("google.protobuf.Int32Value", "google/protobuf/wrappers.proto"); + public static readonly IProtobufType Int64Value = new WellKnown("google.protobuf.Int64Value", "google/protobuf/wrappers.proto"); + public static readonly IProtobufType ListValue = new WellKnown("google.protobuf.ListValue", "google/protobuf/struct.proto"); + public static readonly IProtobufType Method = new WellKnown("google.protobuf.Method", "google/protobuf/api.proto"); + public static readonly IProtobufType Mixin = new WellKnown("google.protobuf.Mixin", "google/protobuf/api.proto"); + public static readonly IProtobufType NullValue = new WellKnown("google.protobuf.NullValue", "google/protobuf/struct.proto"); + public static readonly IProtobufType Option = new WellKnown("google.protobuf.Option", "google/protobuf/type.proto"); + public static readonly IProtobufType SourceContext = new WellKnown("google.protobuf.SourceContext", "google/protobuf/source_context.proto"); + public static readonly IProtobufType StringValue = new WellKnown("google.protobuf.StringValue", "google/protobuf/wrappers.proto"); + public static readonly IProtobufType Struct = new WellKnown("google.protobuf.Struct", "google/protobuf/struct.proto"); + public static readonly IProtobufType Syntax = new WellKnown("google.protobuf.Syntax", "google/protobuf/type.proto"); + public static readonly IProtobufType Timestamp = new WellKnown("google.protobuf.Timestamp", "google/protobuf/timestamp.proto"); + public static readonly IProtobufType Type = new WellKnown("google.protobuf.Type", "google/protobuf/type.proto"); + public static readonly IProtobufType UInt32Value = new WellKnown("google.protobuf.UInt32Value", "google/protobuf/wrappers.proto"); + public static readonly IProtobufType UInt64Value = new WellKnown("google.protobuf.UInt64Value", "google/protobuf/wrappers.proto"); + public static readonly IProtobufType Value = new WellKnown("google.protobuf.Value", "google/protobuf/struct.proto"); } \ No newline at end of file diff --git a/src/LibProtodec/ProtodecContext.cs b/src/LibProtodec/ProtodecContext.cs index a6939d5..6f3c0d8 100644 --- a/src/LibProtodec/ProtodecContext.cs +++ b/src/LibProtodec/ProtodecContext.cs @@ -20,7 +20,7 @@ using LibProtodec.Models.Protobuf.Types; namespace LibProtodec; -public delegate bool TypeLookupFunc(ICilType cilType, [NotNullWhen(true)] out IProtobufType? protobufType, out string? import); +public delegate bool TypeLookupFunc(ICilType cilType, [NotNullWhen(true)] out IProtobufType? protobufType); public delegate bool NameLookupFunc(string name, [MaybeNullWhen(false)] out string translatedName); public sealed class ProtodecContext @@ -308,34 +308,35 @@ public sealed class ProtodecContext ParseFieldType(type.GenericTypeArguments[1], options, referencingProtobuf)); } - if (TypeLookup(type, out IProtobufType? fieldType, out string? import)) + if (!TypeLookup(type, out IProtobufType? fieldType)) { - if (import is not null) + if (type.IsEnum) { - referencingProtobuf.Imports.Add(import); + if ((options & ParserOptions.SkipEnums) > 0) + { + return Scalar.Int32; + } + + fieldType = ParseEnum(type, options); } - - return fieldType; - } - - if (type.IsEnum) - { - if ((options & ParserOptions.SkipEnums) > 0) + else { - return Scalar.Int32; + fieldType = ParseMessage(type, options); } - - fieldType = ParseEnum(type, options); - } - else - { - fieldType = ParseMessage(type, options); } - Protobuf protobuf = ((INestableType)fieldType).Protobuf!; - if (referencingProtobuf != protobuf) + switch (fieldType) { - referencingProtobuf.Imports.Add(protobuf.FileName); + case WellKnown wellKnown: + referencingProtobuf.Imports.Add( + wellKnown.FileName); + break; + case INestableType nestableType: + Protobuf protobuf = nestableType.Protobuf!; + if (referencingProtobuf != protobuf) + referencingProtobuf.Imports.Add( + protobuf.FileName); + break; } return fieldType; @@ -419,11 +420,7 @@ public sealed class ProtodecContext private string TranslateEnumFieldName(IEnumerable attributes, string fieldName, string enumName) { - if (attributes.SingleOrDefault(static attr => attr.Type.Name == "OriginalNameAttribute") - ?.ConstructorArguments[0] is string originalName) - { - return originalName; - } + //TODO: parse original name from first parameter of OriginalNameAttribute constructor if (NameLookup?.Invoke(fieldName, out string? translatedName) == true) { @@ -469,161 +466,122 @@ public sealed class ProtodecContext return translatedName; } - public static bool LookupScalarAndWellKnownTypes(ICilType cilType, [NotNullWhen(true)] out IProtobufType? protobufType, out string? import) + public static bool LookupScalarAndWellKnownTypes(ICilType cilType, [NotNullWhen(true)] out IProtobufType? protobufType) { switch (cilType.FullName) { case "System.String": - import = null; protobufType = Scalar.String; return true; case "System.Boolean": - import = null; protobufType = Scalar.Bool; return true; case "System.Double": - import = null; protobufType = Scalar.Double; return true; case "System.UInt32": - import = null; protobufType = Scalar.UInt32; return true; case "System.UInt64": - import = null; protobufType = Scalar.UInt64; return true; case "System.Int32": - import = null; protobufType = Scalar.Int32; return true; case "System.Int64": - import = null; protobufType = Scalar.Int64; return true; case "System.Single": - import = null; protobufType = Scalar.Float; return true; case "Google.Protobuf.ByteString": - import = null; protobufType = Scalar.Bytes; return true; case "Google.Protobuf.WellKnownTypes.Any": - import = "google/protobuf/any.proto"; protobufType = WellKnown.Any; return true; case "Google.Protobuf.WellKnownTypes.Api": - import = "google/protobuf/api.proto"; protobufType = WellKnown.Api; return true; case "Google.Protobuf.WellKnownTypes.BoolValue": - import = "google/protobuf/wrappers.proto"; protobufType = WellKnown.BoolValue; return true; case "Google.Protobuf.WellKnownTypes.BytesValue": - import = "google/protobuf/wrappers.proto"; protobufType = WellKnown.BytesValue; return true; case "Google.Protobuf.WellKnownTypes.DoubleValue": - import = "google/protobuf/wrappers.proto"; protobufType = WellKnown.DoubleValue; return true; case "Google.Protobuf.WellKnownTypes.Duration": - import = "google/protobuf/duration.proto"; protobufType = WellKnown.Duration; return true; case "Google.Protobuf.WellKnownTypes.Empty": - import = "google/protobuf/empty.proto"; protobufType = WellKnown.Empty; return true; case "Google.Protobuf.WellKnownTypes.Enum": - import = "google/protobuf/type.proto"; protobufType = WellKnown.Enum; return true; case "Google.Protobuf.WellKnownTypes.EnumValue": - import = "google/protobuf/type.proto"; protobufType = WellKnown.EnumValue; return true; case "Google.Protobuf.WellKnownTypes.Field": - import = "google/protobuf/type.proto"; protobufType = WellKnown.Field; return true; case "Google.Protobuf.WellKnownTypes.FieldMask": - import = "google/protobuf/field_mask.proto"; protobufType = WellKnown.FieldMask; return true; case "Google.Protobuf.WellKnownTypes.FloatValue": - import = "google/protobuf/wrappers.proto"; protobufType = WellKnown.FloatValue; return true; case "Google.Protobuf.WellKnownTypes.Int32Value": - import = "google/protobuf/wrappers.proto"; protobufType = WellKnown.Int32Value; return true; case "Google.Protobuf.WellKnownTypes.Int64Value": - import = "google/protobuf/wrappers.proto"; protobufType = WellKnown.Int64Value; return true; case "Google.Protobuf.WellKnownTypes.ListValue": - import = "google/protobuf/struct.proto"; protobufType = WellKnown.ListValue; return true; case "Google.Protobuf.WellKnownTypes.Method": - import = "google/protobuf/api.proto"; protobufType = WellKnown.Method; return true; case "Google.Protobuf.WellKnownTypes.Mixin": - import = "google/protobuf/api.proto"; protobufType = WellKnown.Mixin; return true; case "Google.Protobuf.WellKnownTypes.NullValue": - import = "google/protobuf/struct.proto"; protobufType = WellKnown.NullValue; return true; case "Google.Protobuf.WellKnownTypes.Option": - import = "google/protobuf/type.proto"; protobufType = WellKnown.Option; return true; case "Google.Protobuf.WellKnownTypes.SourceContext": - import = "google/protobuf/source_context.proto"; protobufType = WellKnown.SourceContext; return true; case "Google.Protobuf.WellKnownTypes.StringValue": - import = "google/protobuf/wrappers.proto"; protobufType = WellKnown.StringValue; return true; case "Google.Protobuf.WellKnownTypes.Struct": - import = "google/protobuf/struct.proto"; protobufType = WellKnown.Struct; return true; case "Google.Protobuf.WellKnownTypes.Syntax": - import = "google/protobuf/type.proto"; protobufType = WellKnown.Syntax; return true; case "Google.Protobuf.WellKnownTypes.Timestamp": - import = "google/protobuf/timestamp.proto"; protobufType = WellKnown.Timestamp; return true; case "Google.Protobuf.WellKnownTypes.Type": - import = "google/protobuf/type.proto"; protobufType = WellKnown.Type; return true; case "Google.Protobuf.WellKnownTypes.UInt32Value": - import = "google/protobuf/wrappers.proto"; protobufType = WellKnown.UInt32Value; return true; case "Google.Protobuf.WellKnownTypes.UInt64Value": - import = "google/protobuf/wrappers.proto"; protobufType = WellKnown.UInt64Value; return true; case "Google.Protobuf.WellKnownTypes.Value": - import = "google/protobuf/struct.proto"; protobufType = WellKnown.Value; return true; - default: - import = null; protobufType = null; return false; } @@ -633,9 +591,11 @@ public sealed class ProtodecContext private static bool IsBeebyted(string name) => name.Length == 11 && name.CountUpper() == 11; - private static bool HasGeneratedCodeAttribute(IEnumerable attributes, string tool) => - attributes.Any(attr => attr.Type.Name == nameof(GeneratedCodeAttribute) - && attr.ConstructorArguments[0] as string == tool); + private 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 + } private 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 e99321b..7011dc8 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; @@ -16,9 +18,11 @@ using LibProtodec.Models.Protobuf; 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: --parse_service_servers Parses gRPC service definitions from server classes. @@ -28,14 +32,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; if (args.Contains("--skip_enums")) @@ -47,7 +53,16 @@ if (args.Contains("--include_properties_without_non_user_code_attribute")) if (args.Contains("--include_service_methods_without_generated_code_attribute")) options |= ParserOptions.IncludeServiceMethodsWithoutGeneratedCodeAttribute; -using ICilAssemblyLoader loader = new ClrAssemblyLoader(assembly); +if (!UnityVersion.TryParse(uVersion, out UnityVersion unityVersion, out _)) +{ + unityVersion = uVersion.EndsWith("globalgamemanagers") + ? LibCpp2IlMain.GetVersionFromGlobalGameManagers( + File.ReadAllBytes(uVersion)) + : LibCpp2IlMain.GetVersionFromDataUnity3D( + File.OpenRead(uVersion)); +} + +using ICilAssemblyLoader loader = new Il2CppAssemblyLoader(assembly, metadata, unityVersion); ProtodecContext ctx = new(); foreach (ICilType message in GetProtobufMessageTypes())