├── Il2Cpp-Modding-Codegen ├── Data │ ├── TypeInfo.cs │ ├── Interfaces │ │ ├── IImage.cs │ │ ├── IAttribute.cs │ │ ├── IParsedData.cs │ │ ├── ITypeCollection.cs │ │ ├── IProperty.cs │ │ ├── IField.cs │ │ ├── ISpecifier.cs │ │ ├── IMethod.cs │ │ └── ITypeData.cs │ ├── Refness.cs │ ├── ParameterModifier.cs │ ├── ConversionOperatorKind.cs │ ├── DumpHandling │ │ ├── DumpSpecifier.cs │ │ ├── DumpImage.cs │ │ ├── DumpAttribute.cs │ │ ├── DumpField.cs │ │ ├── DumpProperty.cs │ │ ├── DumpData.cs │ │ ├── DumpMethod.cs │ │ ├── DumpTypeRef.cs │ │ └── DumpTypeData.cs │ ├── TypeEnum.cs │ ├── DllHandling │ │ ├── DllProperty.cs │ │ ├── DllAttribute.cs │ │ ├── DllField.cs │ │ ├── DllMethodDefinitionHash.cs │ │ ├── DllTypeData.cs │ │ ├── DllSpecifierHelpers.cs │ │ ├── DllData.cs │ │ └── DllMethod.cs │ ├── FieldConversionOperator.cs │ ├── FastTypeRefComparer.cs │ ├── MethodTypeContainer.cs │ └── Parameter.cs ├── Serialization │ ├── Constants.cs │ ├── Serializer.cs │ ├── DuplicateMethodException.cs │ ├── UnresolvedTypeException.cs │ ├── ApplicationMkSerializer.cs │ ├── CppSourceCreator.cs │ ├── CppHeaderCreator.cs │ ├── CppFieldSerializerInvokes.cs │ ├── CppStreamWriter.cs │ ├── AndroidMkSerializer.cs │ └── CppStaticFieldSerializer.cs ├── Parsers │ ├── IParser.cs │ ├── DumpParser.cs │ ├── DllParser.cs │ └── PeekableStreamReader.cs ├── Config │ ├── DumpConfig.cs │ ├── DllConfig.cs │ └── SerializationConfig.cs ├── Il2Cpp-Modding-Codegen.csproj ├── GlobalSuppressions.cs ├── SizeTracker.cs └── Utils.cs ├── Codegen-CLI ├── Codegen-CLI.csproj ├── SpecifierConverter.cs ├── PropertyConverter.cs ├── FieldConverter.cs ├── MethodConverter.cs ├── SimpleTypeRefConverter.cs ├── TypeDataConverter.cs └── Program.cs ├── .github └── workflows │ └── dotnet-core.yml ├── LICENSE ├── Il2Cpp-Modding-Codegen.sln ├── .gitattributes ├── AlignmentUnion.bkp.hpp ├── EventDescriptor_bkp.hpp └── .gitignore /Il2Cpp-Modding-Codegen/Data/TypeInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Il2CppModdingCodegen.Data 2 | { 3 | public class TypeInfo 4 | { 5 | internal Refness Refness { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/Interfaces/IImage.cs: -------------------------------------------------------------------------------- 1 | namespace Il2CppModdingCodegen.Data 2 | { 3 | public interface IImage 4 | { 5 | string Name { get; } 6 | int Start { get; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/Refness.cs: -------------------------------------------------------------------------------- 1 | namespace Il2CppModdingCodegen.Data 2 | { 3 | public enum Refness 4 | { 5 | Unknown, 6 | ValueType, 7 | ReferenceType 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/ParameterModifier.cs: -------------------------------------------------------------------------------- 1 | namespace Il2CppModdingCodegen.Data 2 | { 3 | public enum ParameterModifier 4 | { 5 | None, 6 | Ref, 7 | Out, 8 | In, 9 | Params 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/ConversionOperatorKind.cs: -------------------------------------------------------------------------------- 1 | namespace Il2CppModdingCodegen.Data 2 | { 3 | public enum ConversionOperatorKind 4 | { 5 | None, 6 | Invalid, 7 | Yes, 8 | Inherited, 9 | Delete 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/Interfaces/IAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Il2CppModdingCodegen.Data 2 | { 3 | public interface IAttribute 4 | { 5 | string Name { get; } 6 | int RVA { get; } 7 | int Offset { get; } 8 | int VA { get; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/Interfaces/IParsedData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Il2CppModdingCodegen.Data 4 | { 5 | public interface IParsedData : ITypeCollection 6 | { 7 | string Name { get; } 8 | List Images { get; } 9 | } 10 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Serialization/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Il2CppModdingCodegen.Serialization 2 | { 3 | internal static class Constants 4 | { 5 | public const string ObjectCppName = "Il2CppObject"; 6 | public const string StringCppName = "StringW"; 7 | public const int PointerSize = 8; 8 | } 9 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Parsers/IParser.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Data; 2 | using System.IO; 3 | 4 | namespace Il2CppModdingCodegen.Parsers 5 | { 6 | public interface IParser 7 | { 8 | bool ValidFile(string fileName); 9 | 10 | IParsedData Parse(string fileName); 11 | 12 | IParsedData Parse(Stream stream); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/Interfaces/ITypeCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Il2CppModdingCodegen.Data 5 | { 6 | public interface ITypeCollection 7 | { 8 | IEnumerable Types { get; } 9 | [ObsoleteAttribute("Please call TypeRef.Resolve(ITypeCollection) instead.")] 10 | ITypeData? Resolve(TypeRef? typeRef); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Codegen-CLI/Codegen-CLI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | Codegen_CLI 7 | 9.0 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/Interfaces/IProperty.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Il2CppModdingCodegen.Data 4 | { 5 | public interface IProperty 6 | { 7 | List Attributes { get; } 8 | List Specifiers { get; } 9 | TypeRef Type { get; } 10 | TypeRef DeclaringType { get; } 11 | string Name { get; } 12 | bool GetMethod { get; } 13 | bool SetMethod { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/Interfaces/IField.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Il2CppModdingCodegen.Data 4 | { 5 | public interface IField 6 | { 7 | List Attributes { get; } 8 | List Specifiers { get; } 9 | TypeRef Type { get; } 10 | TypeRef DeclaringType { get; } 11 | string Name { get; } 12 | int Offset { get; } 13 | int LayoutOffset { get; } 14 | object Constant { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | [push, pull_request] 5 | 6 | jobs: 7 | build: 8 | 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Setup .NET Core 14 | uses: actions/setup-dotnet@v1 15 | with: 16 | dotnet-version: 5.0.x 17 | - name: Install dependencies 18 | run: dotnet restore 19 | - name: Build 20 | run: dotnet build --configuration Release --no-restore 21 | - name: Test 22 | run: dotnet test --no-restore --verbosity normal 23 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Config/DumpConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Il2CppModdingCodegen.Config 2 | { 3 | public class DumpConfig 4 | { 5 | internal bool ParseImages { get; set; } = true; 6 | internal bool ParseTypes { get; set; } = true; 7 | internal bool ParseTypeAttributes { get; set; } = true; 8 | internal bool ParseTypeSpecifiers { get; set; } = true; 9 | internal bool ParseTypeFields { get; set; } = true; 10 | internal bool ParseTypeProperties { get; set; } = true; 11 | internal bool ParseTypeMethods { get; set; } = true; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Serialization/Serializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Il2CppModdingCodegen.Serialization 4 | { 5 | public abstract class Serializer 6 | { 7 | internal event Action? OnResolved; 8 | protected virtual void Resolved(T obj) => OnResolved?.Invoke(obj); 9 | 10 | internal event Action? OnSerialized; 11 | protected virtual void Serialized(T obj) => OnSerialized?.Invoke(obj); 12 | 13 | public abstract void PreSerialize(CppTypeContext context, T obj); 14 | 15 | public abstract void Serialize(CppStreamWriter writer, T obj, bool asHeader); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Serialization/DuplicateMethodException.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Data; 2 | using System; 3 | 4 | namespace Il2CppModdingCodegen.Serialization 5 | { 6 | public class DuplicateMethodException : Exception 7 | { 8 | private DuplicateMethodException() { } 9 | private DuplicateMethodException(string message) : base(message) { } 10 | internal DuplicateMethodException(IMethod method, string sigMatching) 11 | : this($"Method: {method} has matching signature: {sigMatching} in type: {method.DeclaringType}!") { } 12 | internal DuplicateMethodException(string message, Exception innerException) : base(message, innerException) { } 13 | } 14 | } -------------------------------------------------------------------------------- /Codegen-CLI/SpecifierConverter.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Data; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Text.Json; 6 | using System.Text.Json.Serialization; 7 | 8 | namespace Codegen_CLI 9 | { 10 | internal class SpecifierConverter : JsonConverter 11 | { 12 | public override ISpecifier Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 13 | { 14 | throw new NotImplementedException(); 15 | } 16 | 17 | public override void Write(Utf8JsonWriter writer, ISpecifier value, JsonSerializerOptions options) 18 | { 19 | writer.WriteStringValue(value.Value); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Parsers/DumpParser.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Config; 2 | using Il2CppModdingCodegen.Data; 3 | using Il2CppModdingCodegen.Data.DumpHandling; 4 | using Il2CppModdingCodegen.Parsers; 5 | using System.IO; 6 | 7 | namespace Il2CppModdingCodegen 8 | { 9 | public class DumpParser : IParser 10 | { 11 | private readonly DumpConfig _config; 12 | 13 | public DumpParser(DumpConfig config) => _config = config; 14 | 15 | public IParsedData Parse(string fileName) => new DumpData(fileName, _config); 16 | 17 | public IParsedData Parse(Stream stream) => new DumpData(stream, _config); 18 | 19 | public bool ValidFile(string filename) => Path.GetFileName(filename) == "dump.cs"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Serialization/UnresolvedTypeException.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Data; 2 | using System; 3 | 4 | namespace Il2CppModdingCodegen.Serialization 5 | { 6 | public class UnresolvedTypeException : Exception 7 | { 8 | internal UnresolvedTypeException(TypeRef declaringType, TypeRef typeFailed) : this($"{declaringType} could not find reference to type: {typeFailed}") 9 | { 10 | } 11 | 12 | private UnresolvedTypeException() 13 | { 14 | } 15 | 16 | private UnresolvedTypeException(string message) : base(message) 17 | { 18 | } 19 | 20 | private UnresolvedTypeException(string message, Exception innerException) : base(message, innerException) 21 | { 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DumpHandling/DumpSpecifier.cs: -------------------------------------------------------------------------------- 1 | namespace Il2CppModdingCodegen.Data.DumpHandling 2 | { 3 | internal class DumpSpecifier : ISpecifier 4 | { 5 | public string Value { get; } 6 | public bool Static => Value == "static"; 7 | public bool Private => Value == "private"; 8 | public bool Internal => Value == "internal"; 9 | public bool Public => Value == "public"; 10 | public bool Sealed => Value == "sealed"; 11 | public bool Override => Value == "override"; 12 | public bool Readonly => Value == "readonly"; 13 | public bool Const => Value == "const"; 14 | 15 | internal DumpSpecifier(string specifier) => Value = specifier; 16 | public override string ToString() => Value; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DumpHandling/DumpImage.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Parsers; 2 | using System; 3 | 4 | namespace Il2CppModdingCodegen.Data.DumpHandling 5 | { 6 | internal class DumpImage : IImage 7 | { 8 | public string Name { get; } 9 | public int Start { get; } 10 | 11 | internal DumpImage(PeekableStreamReader fs) 12 | { 13 | var line = fs.ReadLine() ?? ""; 14 | var split = line.Split(' '); 15 | if (split.Length < 6) 16 | throw new InvalidOperationException($"Could not create Image out of: \"{line.Trim()}\""); 17 | 18 | Start = int.Parse(split[^1]); 19 | Name = split[^3]; 20 | } 21 | 22 | public override string ToString() => $"{Name} - {Start}"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Parsers/DllParser.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Config; 2 | using Il2CppModdingCodegen.Data; 3 | using Il2CppModdingCodegen.Data.DllHandling; 4 | using Il2CppModdingCodegen.Parsers; 5 | using System; 6 | using System.IO; 7 | 8 | namespace Il2CppModdingCodegen 9 | { 10 | public class DllParser : IParser 11 | { 12 | private readonly DllConfig _config; 13 | 14 | public DllParser(DllConfig config) => _config = config; 15 | 16 | public IParsedData Parse(string dirname) => new DllData(dirname, _config); 17 | 18 | public IParsedData Parse(Stream stream) => throw new InvalidOperationException("Cannot DllParse a stream! Must DllParse a directory!"); 19 | 20 | public bool ValidFile(string filename) => Directory.Exists(filename); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Config/DllConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Il2CppModdingCodegen.Config 4 | { 5 | public class DllConfig 6 | { 7 | internal bool ParseImages { get; set; } = true; 8 | internal bool ParseTypes { get; set; } = true; 9 | internal bool ParseTypeAttributes { get; set; } = true; 10 | internal bool ParseTypeSpecifiers { get; set; } = true; 11 | internal bool ParseTypeFields { get; set; } = true; 12 | internal bool ParseTypeProperties { get; set; } = true; 13 | internal bool ParseTypeMethods { get; set; } = true; 14 | internal HashSet BlacklistDlls { get; set; } = new HashSet(); 15 | internal HashSet BlacklistTypes { get; set; } = new HashSet(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/Interfaces/ISpecifier.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Il2CppModdingCodegen.Data 5 | { 6 | public interface ISpecifier 7 | { 8 | string Value { get; } 9 | bool Static { get; } 10 | bool Private { get; } 11 | bool Internal { get; } 12 | bool Public { get; } 13 | bool Sealed { get; } 14 | bool Override { get; } 15 | bool Readonly { get; } 16 | bool Const { get; } 17 | } 18 | 19 | public static class SpecifierExtensions 20 | { 21 | internal static bool IsStatic(this List specifiers) => specifiers.Any(s => s.Static); 22 | 23 | internal static bool IsConst(this List specifiers) => specifiers.Any(s => s.Const); 24 | } 25 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Il2Cpp-Modding-Codegen.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | Il2CppModdingCodegen 6 | 9.0 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Serialization/ApplicationMkSerializer.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Il2CppModdingCodegen.Serialization 4 | { 5 | public sealed class ApplicationMkSerializer : System.IDisposable 6 | { 7 | private const string ApplicationMk = @"APP_ABI := arm64-v8a 8 | APP_PLATFORM := android-24 9 | APP_PIE := true 10 | APP_STL := c++_static 11 | APP_CFLAGS := -std=gnu18 12 | APP_CPPFLAGS := -std=gnu++2a 13 | APP_SHORT_COMMANDS := true"; 14 | 15 | private StreamWriter? _stream; 16 | 17 | internal void Write(string filename) 18 | { 19 | _stream = new StreamWriter(new MemoryStream()); 20 | _stream.WriteLine(ApplicationMk); 21 | _stream.Flush(); 22 | CppStreamWriter.WriteIfDifferent(filename, _stream.BaseStream); 23 | } 24 | 25 | public void Close() => _stream?.Close(); 26 | public void Dispose() => Close(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/TypeEnum.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Il2CppModdingCodegen.Data 4 | { 5 | public enum TypeEnum 6 | { 7 | Struct, 8 | Class, 9 | Enum, 10 | Interface 11 | } 12 | 13 | public static class TypeEnumExtensions 14 | { 15 | public static string TypeName(this TypeEnum type) 16 | { 17 | switch (type) 18 | { 19 | case TypeEnum.Class: 20 | case TypeEnum.Interface: 21 | return "class"; 22 | 23 | case TypeEnum.Struct: 24 | return "struct"; 25 | 26 | case TypeEnum.Enum: 27 | // For now, serialize enums as structs 28 | return "struct"; 29 | 30 | default: 31 | throw new InvalidOperationException($"Cannot get C++ type name of type: {type}!"); 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "language support is too much work")] 9 | [assembly: SuppressMessage("Globalization", "CA1304:Specify CultureInfo", Justification = "language support is too much work")] 10 | [assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "language support is too much work")] 11 | [assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "language support is too much work")] 12 | [assembly: SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "I ain't a part of your system, man")] -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Parsers/PeekableStreamReader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Il2CppModdingCodegen.Parsers 4 | { 5 | public class PeekableStreamReader : StreamReader 6 | { 7 | // Only buffer a maximum of one line 8 | private string? bufferedLine = null; 9 | 10 | internal ulong CurrentLineIndex { get; private set; } = 0; 11 | 12 | internal PeekableStreamReader(string path) : base(path) { } 13 | 14 | internal PeekableStreamReader(Stream stream) : base(stream) { } 15 | 16 | internal string? PeekLine() 17 | { 18 | if (bufferedLine != null) 19 | return bufferedLine; 20 | string? line = base.ReadLine(); 21 | if (line != null) 22 | bufferedLine = line; 23 | return line; 24 | } 25 | 26 | public override string? ReadLine() 27 | { 28 | CurrentLineIndex++; 29 | if (bufferedLine != null) 30 | { 31 | var tmp = bufferedLine; 32 | bufferedLine = null; 33 | return tmp; 34 | } 35 | return base.ReadLine(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DumpHandling/DumpAttribute.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Parsers; 2 | using System; 3 | 4 | namespace Il2CppModdingCodegen.Data.DumpHandling 5 | { 6 | internal class DumpAttribute : IAttribute 7 | { 8 | public string Name { get; } 9 | public int RVA { get; } = 0; 10 | public int Offset { get; } = 0; 11 | public int VA { get; } = 0; 12 | 13 | internal DumpAttribute(PeekableStreamReader fs) 14 | { 15 | var line = fs.ReadLine()?.Trim() ?? ""; 16 | // Line must start with a [ after being trimmed 17 | if (!line.StartsWith("[")) 18 | throw new InvalidOperationException($"Line {fs.CurrentLineIndex}: Could not create attribute from: \"{line.Trim()}\""); 19 | 20 | var parsed = line.Substring(1); 21 | Name = parsed.Substring(0, line.LastIndexOf(']') - 1); 22 | var split = parsed.Split(new string[] { " " }, StringSplitOptions.None); 23 | if (split.Length != 8) 24 | return; 25 | RVA = Convert.ToInt32(split[3], 16); 26 | Offset = Convert.ToInt32(split[5], 16); 27 | VA = Convert.ToInt32(split[7], 16); 28 | } 29 | 30 | public override string ToString() => $"[{Name}] // Offset: 0x{Offset:X}"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Codegen-CLI/PropertyConverter.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Data; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Text.Json; 6 | using System.Text.Json.Serialization; 7 | 8 | namespace Codegen_CLI 9 | { 10 | internal class PropertyConverter : JsonConverter 11 | { 12 | public override IProperty Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 13 | { 14 | throw new NotImplementedException(); 15 | } 16 | 17 | public override void Write(Utf8JsonWriter writer, IProperty value, JsonSerializerOptions options) 18 | { 19 | writer.WriteStartObject(); 20 | writer.WritePropertyName(nameof(value.Attributes)); 21 | JsonSerializer.Serialize(writer, value.Attributes, options); 22 | writer.WritePropertyName(nameof(value.Specifiers)); 23 | JsonSerializer.Serialize(writer, value.Specifiers, options); 24 | writer.WriteBoolean(nameof(value.GetMethod), value.GetMethod); 25 | writer.WriteBoolean(nameof(value.SetMethod), value.SetMethod); 26 | writer.WriteString(nameof(value.Name), value.Name); 27 | writer.WritePropertyName(nameof(value.Type)); 28 | JsonSerializer.Serialize(writer, value.Type, options); 29 | writer.WriteEndObject(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/Interfaces/IMethod.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Il2CppModdingCodegen.Data 4 | { 5 | public interface IMethod 6 | { 7 | bool Generic { get; } 8 | IReadOnlyList GenericParameters { get; } 9 | List Attributes { get; } 10 | List Specifiers { get; } 11 | long RVA { get; } 12 | long Offset { get; } 13 | long VA { get; } 14 | int Slot { get; } 15 | TypeRef ReturnType { get; } 16 | TypeRef DeclaringType { get; } 17 | TypeRef? ImplementedFrom { get; } 18 | List BaseMethods { get; } 19 | List ImplementingMethods { get; } 20 | bool IsVirtual { get; } 21 | 22 | // Does this method hide (by signature or override) an existing method in a base class or interface? 23 | bool HidesBase { get; } 24 | 25 | bool IsSpecialName { get; } 26 | 27 | string Name { get; } 28 | 29 | /// 30 | /// The name of the method in Il2Cpp form. 31 | /// If this is a method with the special name flag set, this will appear as a fully qualified type suffixed by the method name. 32 | /// Otherwise, this name matches 33 | /// 34 | string Il2CppName { get; } 35 | 36 | List Parameters { get; } 37 | } 38 | } -------------------------------------------------------------------------------- /Codegen-CLI/FieldConverter.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Data; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Text.Json; 6 | using System.Text.Json.Serialization; 7 | 8 | namespace Codegen_CLI 9 | { 10 | internal class FieldConverter : JsonConverter 11 | { 12 | public override IField Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 13 | { 14 | throw new NotImplementedException(); 15 | } 16 | 17 | public override void Write(Utf8JsonWriter writer, IField value, JsonSerializerOptions options) 18 | { 19 | writer.WriteStartObject(); 20 | writer.WritePropertyName(nameof(value.Attributes)); 21 | JsonSerializer.Serialize(writer, value.Attributes, options); 22 | writer.WriteString(nameof(value.Name), value.Name); 23 | writer.WriteNumber(nameof(value.Offset), value.Offset); 24 | writer.WriteNumber(nameof(value.LayoutOffset), value.LayoutOffset); 25 | writer.WritePropertyName(nameof(value.Specifiers)); 26 | JsonSerializer.Serialize(writer, value.Specifiers, options); 27 | writer.WritePropertyName(nameof(value.Type)); 28 | JsonSerializer.Serialize(writer, value.Type, options); 29 | writer.WriteString(nameof(value.Constant), value.Constant?.ToString()); 30 | writer.WriteEndObject(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DllHandling/DllProperty.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Il2CppModdingCodegen.Data.DllHandling 6 | { 7 | internal class DllProperty : IProperty 8 | { 9 | public List Attributes { get; } = new List(); 10 | public List Specifiers { get; } = new List(); 11 | public TypeRef Type { get; } 12 | public TypeRef DeclaringType { get; } 13 | public string Name { get; } 14 | public bool GetMethod { get; } 15 | public bool SetMethod { get; } 16 | 17 | internal DllProperty(PropertyDefinition p) 18 | { 19 | DeclaringType = DllTypeRef.From(p.DeclaringType); 20 | Type = DllTypeRef.From(p.PropertyType); 21 | Name = p.Name; 22 | GetMethod = p.GetMethod != null; 23 | SetMethod = p.SetMethod != null; 24 | if (p.HasCustomAttributes) 25 | Attributes.AddRange(p.CustomAttributes.Select(ca => new DllAttribute(ca)).Where(a => !string.IsNullOrEmpty(a.Name))); 26 | Specifiers.AddRange(DllSpecifierHelpers.From(p)); 27 | } 28 | 29 | public override string ToString() 30 | { 31 | var s = $"{Type} {Name}"; 32 | s += " { "; 33 | if (GetMethod) 34 | s += "get; "; 35 | if (SetMethod) 36 | s += "set; "; 37 | s += "}"; 38 | return s; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/FieldConversionOperator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using static Il2CppModdingCodegen.Data.ConversionOperatorKind; 4 | 5 | namespace Il2CppModdingCodegen.Data 6 | { 7 | class FieldConversionOperator 8 | { 9 | internal ConversionOperatorKind Kind { get; } 10 | internal IField? Field { get; } = null; 11 | 12 | public FieldConversionOperator(ITypeData type, FieldConversionOperator? parentsOperator) 13 | { 14 | var parKind = parentsOperator?.Kind ?? None; 15 | if (parKind == Delete) 16 | Kind = Invalid; 17 | else if (parKind == Yes || parKind == Inherited) 18 | { 19 | if (type.InstanceFields.Any()) 20 | Kind = Delete; 21 | else 22 | Kind = Inherited; 23 | Field = parentsOperator?.Field ?? throw new ArgumentException("Must have Field!", nameof(parentsOperator)); 24 | } 25 | else if (parKind == None && type.InstanceFields.Count == 1) 26 | { 27 | var field = type.InstanceFields.First(); 28 | if (field.Type.IsGenericParameter || field.Type.IsGenericTemplate) 29 | Kind = Invalid; // todo: resolve conversion operators properly for generic types? 30 | else 31 | { 32 | Kind = Yes; 33 | Field = field; 34 | } 35 | } 36 | else 37 | Kind = parKind; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DllHandling/DllAttribute.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using System; 3 | 4 | namespace Il2CppModdingCodegen.Data.DllHandling 5 | { 6 | internal class DllAttribute : IAttribute 7 | { 8 | // Name is the name field of the attribute 9 | public string Name { get; } = ""; 10 | 11 | public int RVA { get; } = -1; 12 | public int Offset { get; } = -1; 13 | public int VA { get; } = -1; 14 | 15 | internal DllAttribute(CustomAttribute attribute) 16 | { 17 | // These parameters are unknown for attributes besides the attribute that has all three in its constructor 18 | if (attribute.Fields.Count == 3) 19 | foreach (var f in attribute.Fields) 20 | { 21 | if (f.Name == "Name") 22 | Name = (string)f.Argument.Value; 23 | else if (f.Name == "RVA" || f.Name == "Offset" || f.Name == "VA") 24 | { 25 | var val = Convert.ToInt32(f.Argument.Value as string, 16); 26 | if (f.Name == "RVA") RVA = val; 27 | else if (f.Name == "Offset") Offset = val; 28 | else if (f.Name == "VA") VA = val; 29 | } 30 | } 31 | else if (attribute.AttributeType.FullName != "Il2CppInspector.Dll.TokenAttribute") 32 | // Ignore TokenAttribute 33 | Name = attribute.AttributeType.Name; 34 | } 35 | 36 | public override string ToString() => $"[{Name}] // Offset: 0x{Offset:X}"; 37 | } 38 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30002.166 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Il2Cpp-Modding-Codegen", "Il2Cpp-Modding-Codegen\Il2Cpp-Modding-Codegen.csproj", "{D27DC0B1-2A9B-4C33-9CF2-A85CB855810B}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codegen-CLI", "Codegen-CLI\Codegen-CLI.csproj", "{7F0FCB4B-C82D-4841-B3FC-095E1AAF20E1}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {D27DC0B1-2A9B-4C33-9CF2-A85CB855810B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {D27DC0B1-2A9B-4C33-9CF2-A85CB855810B}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {D27DC0B1-2A9B-4C33-9CF2-A85CB855810B}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {D27DC0B1-2A9B-4C33-9CF2-A85CB855810B}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {7F0FCB4B-C82D-4841-B3FC-095E1AAF20E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {7F0FCB4B-C82D-4841-B3FC-095E1AAF20E1}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {7F0FCB4B-C82D-4841-B3FC-095E1AAF20E1}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {7F0FCB4B-C82D-4841-B3FC-095E1AAF20E1}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {211A3351-2001-416D-B728-181EFDEDDF87} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/FastTypeRefComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Il2CppModdingCodegen.Data 6 | { 7 | /// 8 | /// Compares objects WITHOUT comparing their DeclaringTypes. 9 | /// This allows for faster comparison and avoids 10 | /// 11 | public class FastTypeRefComparer : IEqualityComparer 12 | { 13 | // TODO: Ensure this behaves as intended for recursive DeclaringTypes (it probably does not) 14 | public bool Equals(TypeRef? x, TypeRef? y) 15 | { 16 | if (x is null || y is null) 17 | return x is null == y is null; 18 | if (x.Namespace != y.Namespace || x.Name != y.Name) 19 | return false; 20 | if (x.KnownOffsetTypeName != y.KnownOffsetTypeName) 21 | return false; 22 | if (x.IsGeneric && y.IsGeneric) 23 | { 24 | // If both x and y are generic 25 | if (x.IsGenericInstance == y.IsGenericInstance || x.IsGenericTemplate == y.IsGenericTemplate) 26 | // If they are both an instance or both a template, return sequence equal 27 | return x.Generics is null ? y.Generics is null : x.Generics.SequenceEqual(y.Generics, this); 28 | // Otherwise, if one is a template and the other is an instance, if their counts match, consider it good enough. 29 | return x.Generics is null ? y.Generics is null : x.Generics.Count == y.Generics.Count; 30 | } 31 | return true; 32 | } 33 | 34 | public int GetHashCode(TypeRef obj) 35 | { 36 | if (obj is null) 37 | return 0; 38 | return (obj.Namespace, obj.Name, obj.KnownOffsetTypeName).GetHashCode(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/Interfaces/ITypeData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Il2CppModdingCodegen.Data 5 | { 6 | public interface ITypeData 7 | { 8 | public enum LayoutKind 9 | { 10 | Auto = 0, 11 | Sequential = 8, 12 | Explicit = 16 13 | } 14 | 15 | TypeRef This { get; } 16 | TypeEnum Type { get; } 17 | TypeInfo Info { get; } 18 | TypeRef? Parent { get; } 19 | HashSet NestedTypes { get; } 20 | List ImplementingInterfaces { get; } 21 | int TypeDefIndex { get; } 22 | List Attributes { get; } 23 | List Specifiers { get; } 24 | List InstanceFields { get; } 25 | List StaticFields { get; } 26 | IEnumerable Fields => InstanceFields.Concat(StaticFields); 27 | List Properties { get; } 28 | List Methods { get; } 29 | LayoutKind Layout { get; } 30 | 31 | string ToString() 32 | { 33 | var s = $"// Namespace: {This.Namespace}\n"; 34 | foreach (var attr in Attributes) 35 | s += $"{attr}\n"; 36 | foreach (var spec in Specifiers) 37 | s += $"{spec} "; 38 | s += $"{Type.ToString().ToLower()} {This.CppName()}"; 39 | if (Parent != null) 40 | s += $" : {Parent}"; 41 | s += "\n{"; 42 | if (Fields.Any()) 43 | { 44 | s += "\n\t// Fields\n\t"; 45 | foreach (var f in Fields) 46 | s += $"{f}\n\t"; 47 | } 48 | if (Properties.Any()) 49 | { 50 | s += "\n\t// Properties\n\t"; 51 | foreach (var p in Properties) 52 | s += $"{p}\n\t"; 53 | } 54 | if (Methods.Any()) 55 | { 56 | s += "\n\t// Methods\n\t"; 57 | foreach (var m in Methods) 58 | s += $"{m}\n\t"; 59 | } 60 | s = s.TrimEnd('\t'); 61 | s += "}"; 62 | return s; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DllHandling/DllField.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Il2CppModdingCodegen.Data.DllHandling 7 | { 8 | internal class DllField : IField 9 | { 10 | internal FieldDefinition This; 11 | public List Attributes { get; } = new List(); 12 | public List Specifiers { get; } = new List(); 13 | public TypeRef Type { get; } 14 | public TypeRef DeclaringType { get; } 15 | public string Name { get; } 16 | public int Offset { get; } 17 | public int LayoutOffset { get; } = -1; 18 | public object Constant { get; } 19 | 20 | internal DllField(FieldDefinition f, TypeDefinition info) 21 | { 22 | LayoutOffset = f.Offset; 23 | DeclaringType = DllTypeRef.From(f.DeclaringType); 24 | Type = DllTypeRef.From(f.FieldType); 25 | Name = f.Name; 26 | Offset = -1; 27 | if (f.HasCustomAttributes) 28 | foreach (var ca in f.CustomAttributes) 29 | if (ca.AttributeType.Name == "FieldOffsetAttribute" || ca.AttributeType.Name == "StaticFieldOffsetAttribute") 30 | { 31 | if (ca.Fields.Count > 0) 32 | Offset = Convert.ToInt32(ca.Fields.FirstOrDefault().Argument.Value as string, 16); 33 | //if (info.IsEnum) 34 | // // Because Il2CppInspector is bad and emits 0x10 for fields on enums. I seriously don't know why. 35 | // Offset -= 0x10; 36 | } 37 | else 38 | { 39 | // Ignore the DummyDll attributes 40 | var atr = new DllAttribute(ca); 41 | if (!string.IsNullOrEmpty(atr.Name)) 42 | Attributes.Add(atr); 43 | } 44 | 45 | Specifiers.AddRange(DllSpecifierHelpers.From(f)); 46 | 47 | This = f; 48 | Constant = f.Constant; 49 | } 50 | 51 | public override string ToString() => $"{Type} {DeclaringType}.{Name}; // Offset: 0x{Offset:X}"; 52 | } 53 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DumpHandling/DumpField.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Parsers; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Il2CppModdingCodegen.Data.DumpHandling 6 | { 7 | internal class DumpField : IField 8 | { 9 | public List Attributes { get; } = new List(); 10 | public List Specifiers { get; } = new List(); 11 | public TypeRef Type { get; } 12 | public TypeRef DeclaringType { get; } 13 | public string Name { get; } 14 | public int Offset { get; } 15 | public int LayoutOffset { get => throw new InvalidOperationException(); } 16 | public object Constant { get => throw new InvalidOperationException(); } 17 | 18 | internal DumpField(TypeRef declaring, PeekableStreamReader fs) 19 | { 20 | DeclaringType = declaring; 21 | var line = fs.PeekLine()?.Trim(); 22 | while (line != null && line.StartsWith("[")) 23 | { 24 | Attributes.Add(new DumpAttribute(fs)); 25 | line = fs.PeekLine()?.Trim(); 26 | } 27 | line = fs.ReadLine()?.Trim() ?? ""; 28 | var split = line.Split(' '); 29 | // Offset is at the end 30 | if (split.Length < 4) 31 | throw new InvalidOperationException($"Line {fs.CurrentLineIndex}: Field cannot be created from: \"{line.Trim()}\""); 32 | 33 | Offset = Convert.ToInt32(split[^1], 16); 34 | int start = split.Length - 3; 35 | for (int i = start; i > 1; i--) 36 | if (split[i] == "=") 37 | { 38 | start = i - 1; 39 | break; 40 | } 41 | 42 | Name = split[start].TrimEnd(';'); 43 | Type = new DumpTypeRef(DumpTypeRef.FromMultiple(split, start - 1, out int res, -1, " ")); 44 | for (int i = 0; i < res; i++) 45 | Specifiers.Add(new DumpSpecifier(split[i])); 46 | } 47 | 48 | public override string ToString() 49 | { 50 | var s = ""; 51 | foreach (var atr in Attributes) 52 | s += $"{atr}\n\t"; 53 | foreach (var spec in Specifiers) 54 | s += $"{spec} "; 55 | s += $"{Type} {Name}; // 0x{Offset:X}"; 56 | return s; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DllHandling/DllMethodDefinitionHash.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using System.Collections.Generic; 3 | 4 | namespace Il2CppModdingCodegen.Data.DllHandling 5 | { 6 | /// 7 | /// Provides a way of determining whether two method definitions are the same or not. 8 | /// This is important because of the cache in DllMethod, which will otherwise fail to properly map base methods. 9 | /// 10 | internal class DllMethodDefinitionHash : IEqualityComparer 11 | { 12 | /// 13 | /// Here, we check some common fields. 14 | /// This is by no means entirely complete, but if we see a duplicate, we should double hit the cache and subsequently throw when adding a key that exists to DllMethod.cache. 15 | /// This currently checks attributes, declaring type, return type, parameters, and generic parameters. 16 | /// 17 | /// 18 | /// 19 | /// 20 | public bool Equals(MethodDefinition x, MethodDefinition y) 21 | { 22 | if (x.Attributes != y.Attributes 23 | || x.FullName != y.FullName 24 | || x.ReturnType.FullName != y.ReturnType.FullName 25 | || x.Parameters.Count != y.Parameters.Count 26 | || x.DeclaringType.FullName != y.DeclaringType.FullName 27 | || x.HasGenericParameters != y.HasGenericParameters 28 | || x.GenericParameters.Count != y.GenericParameters.Count 29 | || x.DeclaringType.Module != y.DeclaringType.Module) 30 | return false; 31 | for (int i = 0; i < x.Parameters.Count; i++) 32 | if (x.Parameters[i].ParameterType.FullName != y.Parameters[i].ParameterType.FullName) 33 | return false; 34 | for (int i = 0; i < x.GenericParameters.Count; i++) 35 | if (x.GenericParameters[i].FullName != y.GenericParameters[i].FullName) 36 | return false; 37 | return true; 38 | } 39 | 40 | // This currently collides quite frequently and should probably be adjusted. 41 | public int GetHashCode(MethodDefinition obj) 42 | { 43 | var val = obj.FullName.GetHashCode() * 1361 + obj.DeclaringType.FullName.GetHashCode(); 44 | foreach (var p in obj.Parameters) 45 | val = val * 42071 + p.ParameterType.FullName.GetHashCode(); 46 | return val; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DumpHandling/DumpProperty.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Parsers; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Il2CppModdingCodegen.Data.DumpHandling 6 | { 7 | internal class DumpProperty : IProperty 8 | { 9 | public List Attributes { get; } = new List(); 10 | public List Specifiers { get; } = new List(); 11 | public TypeRef Type { get; } 12 | public TypeRef DeclaringType { get; } 13 | public string Name { get; } 14 | public bool GetMethod { get; } 15 | public bool SetMethod { get; } 16 | 17 | internal DumpProperty(TypeRef declaring, PeekableStreamReader fs) 18 | { 19 | DeclaringType = declaring; 20 | var line = fs.PeekLine()?.Trim(); 21 | while (line != null && line.StartsWith("[")) 22 | { 23 | Attributes.Add(new DumpAttribute(fs)); 24 | line = fs.PeekLine()?.Trim(); 25 | } 26 | line = fs.ReadLine()?.Trim() ?? ""; 27 | var split = line.Split(' '); 28 | if (split.Length < 5) 29 | throw new InvalidOperationException($"Line {fs.CurrentLineIndex}: Property cannot be created from: \"{line.Trim()}\""); 30 | 31 | // Start at the end (but before the }), count back until we hit a { (or we have gone 3 steps) 32 | // Keep track of how far back we count 33 | int i; 34 | for (i = 0; i < 3; i++) 35 | { 36 | var val = split[split.Length - 2 - i]; 37 | if (val == "{") 38 | break; 39 | else if (val == "get;") 40 | GetMethod = true; 41 | else if (val == "set;") 42 | SetMethod = true; 43 | } 44 | Name = split[split.Length - 3 - i]; 45 | Type = new DumpTypeRef(DumpTypeRef.FromMultiple(split, split.Length - 4 - i, out int adjust, -1, " ")); 46 | for (int j = 0; j < adjust; j++) 47 | Specifiers.Add(new DumpSpecifier(split[j])); 48 | } 49 | 50 | public override string ToString() 51 | { 52 | var s = ""; 53 | foreach (var atr in Attributes) 54 | s += $"{atr}\n\t"; 55 | foreach (var spec in Specifiers) 56 | s += $"{spec} "; 57 | s += $"{Type} {Name}"; 58 | s += " { "; 59 | if (GetMethod) 60 | s += "get; "; 61 | if (SetMethod) 62 | s += "set; "; 63 | s += "}"; 64 | return s; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/MethodTypeContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Il2CppModdingCodegen.Data 6 | { 7 | public class MethodTypeContainer 8 | { 9 | private string? _typeName; 10 | private string _suffix = ""; 11 | private string? _templatedName; 12 | 13 | // getter-only properties 14 | internal bool IsPointer => _typeName?.EndsWith("*") ?? throw new InvalidOperationException("typeName is null!"); 15 | 16 | // Contains a class or struct 17 | internal bool IsClassType => _typeName.Any(char.IsUpper); 18 | 19 | internal bool HasTemplate => !string.IsNullOrEmpty(_templatedName); 20 | internal string ElementType => Regex.Match(_typeName, @"ArrayW<(.*)>[^>]*").Groups[1].ToString(); 21 | 22 | // other properties 23 | internal bool Skip { get; set; } = false; 24 | 25 | internal bool UnPointered { get; private set; } = false; 26 | internal bool ExpandParams { get; set; } = false; 27 | internal TypeRef Type { get; } 28 | 29 | // methods 30 | internal MethodTypeContainer(string? t, TypeRef typ) 31 | { 32 | _typeName = t; 33 | Type = typ; 34 | } 35 | 36 | internal void Prefix(string prefix) => _typeName = prefix + _typeName; 37 | 38 | internal void Suffix(string suffix) => _suffix += suffix; 39 | 40 | // Make this parameter no longer a pointer, and use its value as `&val` from now on 41 | internal bool UnPointer() 42 | { 43 | if (_typeName == null) throw new InvalidOperationException("typeName is null!"); 44 | if (!IsPointer) return false; 45 | _typeName = _typeName[0..^1]; 46 | return UnPointered = true; 47 | } 48 | 49 | internal string TypeName(bool header) 50 | { 51 | // If we are a header, return a templated typename. 52 | // Otherwise, we should never return a templated typename. 53 | if (HasTemplate && header) 54 | return _templatedName!; 55 | var typeName = ExpandParams ? $"std::initializer_list<{ElementType}>" : _typeName; 56 | if (_typeName != null && (string.IsNullOrEmpty(typeName) || typeName.Contains(_typeName) && !typeName.Equals(_typeName))) 57 | throw new FormatException($"Got '{typeName}' for type name '{_typeName}'!"); 58 | return typeName + _suffix; 59 | } 60 | 61 | internal void Template(string? newName) => _templatedName = newName; 62 | 63 | [Obsolete("TypeName should be used instead!", true)] 64 | #pragma warning disable CS0809 // Obsolete member 'MethodTypeContainer.ToString()' overrides non-obsolete member 'object.ToString()' 65 | public override string ToString() => ""; 66 | 67 | #pragma warning restore CS0809 // Obsolete member 'MethodTypeContainer.ToString()' overrides non-obsolete member 'object.ToString()' 68 | } 69 | } -------------------------------------------------------------------------------- /AlignmentUnion.bkp.hpp: -------------------------------------------------------------------------------- 1 | // Autogenerated from CppHeaderCreator 2 | // Created by Sc2ad 3 | // ========================================================================= 4 | #pragma once 5 | // Begin includes 6 | #include 7 | #include "extern/beatsaber-hook/shared/utils/byref.hpp" 8 | // Including type: System.ValueType 9 | #include "System/ValueType.hpp" 10 | // Completed includes 11 | namespace System::Net::NetworkInformation { 12 | // Forward declaring type: AlignmentUnion 13 | struct AlignmentUnion; 14 | } 15 | #include "extern/beatsaber-hook/shared/utils/il2cpp-type-check.hpp" 16 | DEFINE_IL2CPP_ARG_TYPE(System::Net::NetworkInformation::AlignmentUnion, "System.Net.NetworkInformation", "AlignmentUnion"); 17 | // Type namespace: System.Net.NetworkInformation 18 | namespace System::Net::NetworkInformation { 19 | // Size: 0x8 20 | #pragma pack(push, 1) 21 | // WARNING Layout: Explicit may not be correctly taken into account! 22 | // Autogenerated type: System.Net.NetworkInformation.AlignmentUnion 23 | // [TokenAttribute] Offset: FFFFFFFF 24 | struct AlignmentUnion/*, public System::ValueType*/ { 25 | public: 26 | struct __InternalUnionData { 27 | // public System.Int32 Length 28 | // Size: 0x4 29 | // Offset: 0x0 30 | int Length; 31 | // Field size check 32 | static_assert(sizeof(int) == 0x4); 33 | // public System.Int32 IfIndex 34 | // Size: 0x4 35 | // Offset: 0x4 36 | int IfIndex; 37 | // Field size check 38 | static_assert(sizeof(int) == 0x4); 39 | }; 40 | #ifdef USE_CODEGEN_FIELDS 41 | public: 42 | #else 43 | protected: 44 | #endif 45 | // Creating union for fields at offset: 0x0 46 | union { 47 | // public System.UInt64 Alignment 48 | // Size: 0x8 49 | // Offset: 0x0 50 | uint64_t Alignment; 51 | // Field size check 52 | static_assert(sizeof(uint64_t) == 0x8); 53 | // WARNING: Manual union structure 54 | __InternalUnionData data; 55 | static_assert(sizeof(__InternalUnionData) == 0x8); 56 | }; 57 | public: 58 | // Creating value type constructor for type: AlignmentUnion 59 | constexpr AlignmentUnion(uint64_t Alignment_ = {}) noexcept : Alignment{Alignment_} {} 60 | // Creating interface conversion operator: operator System::ValueType 61 | operator System::ValueType() noexcept { 62 | return *reinterpret_cast(this); 63 | } 64 | // Get instance field reference: public System.UInt64 Alignment 65 | uint64_t& dyn_Alignment(); 66 | // Get instance field reference: public System.Int32 Length 67 | int& dyn_Length(); 68 | // Get instance field reference: public System.Int32 IfIndex 69 | int& dyn_IfIndex(); 70 | }; // System.Net.NetworkInformation.AlignmentUnion 71 | #pragma pack(pop) 72 | static check_size __System_Net_NetworkInformation_AlignmentUnionSizeCheck; 73 | static_assert(sizeof(AlignmentUnion) == 0x8); 74 | } 75 | #include "extern/beatsaber-hook/shared/utils/il2cpp-utils-methods.hpp" 76 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DllHandling/DllTypeData.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Config; 2 | using Mono.Cecil; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Il2CppModdingCodegen.Data.DllHandling 7 | { 8 | internal class DllTypeData : ITypeData 9 | { 10 | public TypeEnum Type { get; } 11 | public TypeInfo Info { get; } 12 | public TypeRef This { get; } 13 | public TypeRef? Parent { get; } 14 | public TypeRef? EnumUnderlyingType { get; } 15 | public HashSet NestedTypes { get; } = new HashSet(); 16 | public List ImplementingInterfaces { get; } = new List(); 17 | public int TypeDefIndex { get; } 18 | public List Attributes { get; } = new List(); 19 | public List Specifiers { get; } = new List(); 20 | public List InstanceFields { get; } = new List(); 21 | public List StaticFields { get; } = new List(); 22 | public List Properties { get; } = new List(); 23 | public List Methods { get; } = new List(); 24 | public ITypeData.LayoutKind Layout { get; } 25 | 26 | private readonly DllConfig _config; 27 | 28 | internal DllTypeData(TypeDefinition def, DllConfig config) 29 | { 30 | _config = config; 31 | foreach (var i in def.Interfaces) 32 | ImplementingInterfaces.Add(DllTypeRef.From(i.InterfaceType)); 33 | 34 | This = DllTypeRef.From(def); 35 | Type = def.IsEnum ? TypeEnum.Enum : (def.IsInterface ? TypeEnum.Interface : (def.IsValueType ? TypeEnum.Struct : TypeEnum.Class)); 36 | Info = new TypeInfo 37 | { 38 | Refness = def.IsValueType ? Refness.ValueType : Refness.ReferenceType 39 | }; 40 | 41 | if (def.BaseType != null) 42 | Parent = DllTypeRef.From(def.BaseType); 43 | 44 | // TODO: Parse this eventually 45 | TypeDefIndex = -1; 46 | 47 | if (_config.ParseTypeAttributes && def.HasCustomAttributes) 48 | { 49 | Attributes.AddRange(def.CustomAttributes.Select(ca => new DllAttribute(ca)).Where(a => !string.IsNullOrEmpty(a.Name))); 50 | } 51 | Layout = (ITypeData.LayoutKind)(def.Attributes & TypeAttributes.LayoutMask); 52 | if (_config.ParseTypeFields) 53 | { 54 | InstanceFields.AddRange(def.Fields.Where(f => !f.IsStatic).Select(f => new DllField(f, def))); 55 | StaticFields.AddRange(def.Fields.Where(f => f.IsStatic).Select(f => new DllField(f, def))); 56 | } 57 | if (_config.ParseTypeProperties) 58 | Properties.AddRange(def.Properties.Select(p => new DllProperty(p))); 59 | if (_config.ParseTypeMethods) 60 | { 61 | var mappedBaseMethods = new HashSet(); 62 | var methods = def.Methods.Select(m => DllMethod.From(m, ref mappedBaseMethods)).ToList(); 63 | // It's important that Foo.IBar.func() goes after func() (if present) 64 | Methods.AddRange(methods.Where(m => m.ImplementedFrom is null)); 65 | Methods.AddRange(methods.Where(m => m.ImplementedFrom != null)); 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DumpHandling/DumpData.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Config; 2 | using Il2CppModdingCodegen.Parsers; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | 8 | namespace Il2CppModdingCodegen.Data.DumpHandling 9 | { 10 | public class DumpData : IParsedData 11 | { 12 | public string Name => "Dump Data"; 13 | public List Images { get; } = new List(); 14 | public IEnumerable Types { get => _types; } 15 | private readonly DumpConfig _config; 16 | private readonly List _types = new List(); 17 | 18 | private void ParseImages(PeekableStreamReader fs) 19 | { 20 | var line = fs.PeekLine(); 21 | while (line != null && line.StartsWith("// Image")) 22 | { 23 | if (_config.ParseImages) 24 | Images.Add(new DumpImage(fs)); 25 | line = fs.PeekLine(); 26 | } 27 | } 28 | 29 | private void ParseTypes(PeekableStreamReader fs) 30 | { 31 | var line = fs.PeekLine(); 32 | while (line != null) 33 | { 34 | while (!line.StartsWith("// Namespace: ")) 35 | { 36 | // Read empty lines 37 | fs.ReadLine(); 38 | line = fs.PeekLine(); 39 | if (line is null) return; 40 | } 41 | if (_config.ParseTypes) 42 | { 43 | var typeData = new DumpTypeData(fs, _config); 44 | if (typeData.This.DeclaringType != null) 45 | { 46 | var declaringTypeData = Resolve(typeData.This.DeclaringType); 47 | if (declaringTypeData is null) 48 | throw new Exception("Failed to get declaring type ITypeData for newly parsed nested type!"); 49 | declaringTypeData.NestedTypes.AddOrThrow(typeData); 50 | } 51 | _types.Add(typeData); 52 | } 53 | line = fs.PeekLine(); 54 | } 55 | } 56 | 57 | internal void Parse(PeekableStreamReader fs) 58 | { 59 | ParseImages(fs); 60 | ParseTypes(fs); 61 | } 62 | 63 | internal DumpData(string fileName, DumpConfig config) 64 | { 65 | _config = config; 66 | using var fs = new PeekableStreamReader(fileName); 67 | Parse(fs); 68 | } 69 | 70 | internal DumpData(Stream stream, DumpConfig config) 71 | { 72 | _config = config; 73 | using var fs = new PeekableStreamReader(stream); 74 | Parse(fs); 75 | } 76 | 77 | public override string ToString() 78 | { 79 | var s = ""; 80 | for (int i = 0; i < Images.Count; i++) 81 | s += $"// Image {i}: {Images[i]}\n"; 82 | s += "\n"; 83 | foreach (var t in Types) 84 | s += $"{t}\n"; 85 | return s; 86 | } 87 | 88 | public ITypeData? Resolve(TypeRef? typeRef) 89 | { 90 | if (typeRef is null) throw new ArgumentNullException(nameof(typeRef)); 91 | // TODO: Resolve only among our types that we actually plan on serializing 92 | // Basically, check it against our whitelist/blacklist 93 | var te = Types.LastOrDefault(t => t.This.Equals(typeRef) || t.This.Name == typeRef.Name); 94 | return te; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Serialization/CppSourceCreator.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Config; 2 | using System; 3 | using System.IO; 4 | 5 | namespace Il2CppModdingCodegen.Serialization 6 | { 7 | public class CppSourceCreator 8 | { 9 | private readonly SerializationConfig _config; 10 | private readonly CppContextSerializer _serializer; 11 | private CppStreamWriter? overallStreamWriter; 12 | private string overallSourceLocation; 13 | 14 | internal CppSourceCreator(SerializationConfig config, CppContextSerializer serializer) 15 | { 16 | _config = config; 17 | _serializer = serializer; 18 | overallSourceLocation = Path.Combine(_config.OutputDirectory, _config.OutputSourceDirectory, "main.cpp"); 19 | overallStreamWriter = _config.OneSourceFile ? new CppStreamWriter(new StreamWriter(overallSourceLocation), " ") : null; 20 | } 21 | 22 | internal void SetupChunkedSerialization(int label) 23 | { 24 | if (overallStreamWriter is not null) 25 | { 26 | overallStreamWriter.Close(); 27 | overallStreamWriter.Dispose(); 28 | } 29 | overallSourceLocation = Path.Combine(_config.OutputDirectory, _config.OutputSourceDirectory, $"main_{label}.cpp"); 30 | overallStreamWriter = _config.OneSourceFile ? new CppStreamWriter(new StreamWriter(overallSourceLocation), " ") : null; 31 | } 32 | 33 | internal void Serialize(CppTypeContext context) 34 | { 35 | var sourceLocation = _config.OneSourceFile ? Path.Combine(_config.OutputDirectory, _config.OutputSourceDirectory, context.CppFileName) : overallSourceLocation; 36 | CppStreamWriter? writer = overallStreamWriter; 37 | if (writer is null) 38 | { 39 | Directory.CreateDirectory(Path.GetDirectoryName(sourceLocation)); 40 | using var ms = new MemoryStream(); 41 | using var rawWriter = new StreamWriter(ms); 42 | writer = new CppStreamWriter(rawWriter, " "); 43 | } 44 | // Write header 45 | writer.WriteComment($"Autogenerated from {nameof(CppSourceCreator)}"); 46 | writer.WriteComment($"Created by Sc2ad"); 47 | writer.WriteComment("========================================================================="); 48 | try 49 | { 50 | // Write SerializerContext and actual type 51 | _serializer.Serialize(writer, context, false); 52 | } 53 | catch (UnresolvedTypeException e) 54 | { 55 | if (_config.UnresolvedTypeExceptionHandling?.TypeHandling == UnresolvedTypeExceptionHandling.DisplayInFile) 56 | { 57 | writer.WriteLine("// Unresolved type exception!"); 58 | writer.WriteLine("/*"); 59 | writer.WriteLine(e); 60 | writer.WriteLine("*/"); 61 | } 62 | else if (_config.UnresolvedTypeExceptionHandling?.TypeHandling == UnresolvedTypeExceptionHandling.SkipIssue) 63 | return; 64 | else if (_config.UnresolvedTypeExceptionHandling?.TypeHandling == UnresolvedTypeExceptionHandling.Elevate) 65 | throw new InvalidOperationException($"Cannot elevate {e} to a parent type- there is no parent type!"); 66 | } 67 | writer.Flush(); 68 | 69 | if (overallStreamWriter is null) 70 | { 71 | writer.WriteIfDifferent(sourceLocation, context); 72 | writer.Close(); 73 | writer.Dispose(); 74 | } 75 | } 76 | 77 | internal void Close() 78 | { 79 | if (overallStreamWriter is not null) 80 | overallStreamWriter.Close(); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /Codegen-CLI/MethodConverter.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Data; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Text.Json; 6 | using System.Text.Json.Serialization; 7 | 8 | namespace Codegen_CLI 9 | { 10 | internal class MethodConverter : JsonConverter 11 | { 12 | public override IMethod Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 13 | { 14 | throw new NotImplementedException(); 15 | } 16 | 17 | private void WriteSimple(Utf8JsonWriter writer, IMethod value, JsonSerializerOptions options) 18 | { 19 | writer.WriteStartObject(); 20 | writer.WritePropertyName(nameof(value.DeclaringType)); 21 | JsonSerializer.Serialize(writer, value.DeclaringType, options); 22 | writer.WriteString(nameof(value.Name), value.Name); 23 | writer.WriteBoolean(nameof(value.IsSpecialName), value.IsSpecialName); 24 | writer.WriteEndObject(); 25 | } 26 | 27 | public override void Write(Utf8JsonWriter writer, IMethod value, JsonSerializerOptions options) 28 | { 29 | writer.WriteStartObject(); 30 | writer.WritePropertyName(nameof(value.Attributes)); 31 | JsonSerializer.Serialize(writer, value.Attributes, options); 32 | writer.WritePropertyName(nameof(value.Generic)); 33 | JsonSerializer.Serialize(writer, value.Generic, options); 34 | writer.WritePropertyName(nameof(value.GenericParameters)); 35 | JsonSerializer.Serialize(writer, value.GenericParameters, options); 36 | writer.WritePropertyName(nameof(value.HidesBase)); 37 | JsonSerializer.Serialize(writer, value.HidesBase, options); 38 | writer.WritePropertyName(nameof(value.Il2CppName)); 39 | JsonSerializer.Serialize(writer, value.Il2CppName, options); 40 | writer.WritePropertyName(nameof(value.ImplementedFrom)); 41 | JsonSerializer.Serialize(writer, value.ImplementedFrom, options); 42 | writer.WritePropertyName(nameof(value.IsSpecialName)); 43 | JsonSerializer.Serialize(writer, value.IsSpecialName, options); 44 | writer.WritePropertyName(nameof(value.IsVirtual)); 45 | JsonSerializer.Serialize(writer, value.IsVirtual, options); 46 | writer.WritePropertyName(nameof(value.Name)); 47 | JsonSerializer.Serialize(writer, value.Name, options); 48 | writer.WritePropertyName(nameof(value.Offset)); 49 | JsonSerializer.Serialize(writer, value.Offset, options); 50 | writer.WritePropertyName(nameof(value.Parameters)); 51 | JsonSerializer.Serialize(writer, value.Parameters, options); 52 | writer.WritePropertyName(nameof(value.ReturnType)); 53 | JsonSerializer.Serialize(writer, value.ReturnType, options); 54 | writer.WritePropertyName(nameof(value.RVA)); 55 | JsonSerializer.Serialize(writer, value.RVA, options); 56 | writer.WritePropertyName(nameof(value.Slot)); 57 | JsonSerializer.Serialize(writer, value.Slot, options); 58 | writer.WritePropertyName(nameof(value.Specifiers)); 59 | JsonSerializer.Serialize(writer, value.Specifiers, options); 60 | writer.WritePropertyName(nameof(value.VA)); 61 | JsonSerializer.Serialize(writer, value.VA, options); 62 | 63 | //writer.WritePropertyName(nameof(value.BaseMethods)); 64 | //writer.WriteStartArray(); 65 | //foreach (var bm in value.BaseMethods) 66 | // WriteSimple(writer, bm, options); 67 | //writer.WriteEndArray(); 68 | //writer.WritePropertyName(nameof(value.ImplementingMethods)); 69 | //writer.WriteStartArray(); 70 | //foreach (var im in value.ImplementingMethods) 71 | // WriteSimple(writer, im, options); 72 | //writer.WriteEndArray(); 73 | writer.WriteEndObject(); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DllHandling/DllSpecifierHelpers.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Data.DumpHandling; 2 | using Mono.Cecil; 3 | using Mono.Cecil.Rocks; 4 | using System.Collections.Generic; 5 | 6 | namespace Il2CppModdingCodegen.Data.DllHandling 7 | { 8 | internal class DllSpecifierHelpers 9 | { 10 | // TODO: These methods are, put simply, horrible. They should not exist, instead the interface should be more generic. 11 | // As of now, because I am far too lazy, I am simply going to do this. 12 | // But in order to ensure that we can migrate away from it easier, this class literally does not exist. 13 | // Because we are doing this, we are using DumpSpecifiers for a DllSpecifier. Why? Because there are no DllSpecifiers 14 | // and there shouldn't be any. 15 | internal static IEnumerable From(TypeDefinition def) 16 | { 17 | var list = new List(); 18 | if (def.IsSealed) 19 | // https://groups.google.com/forum/#!topic/mono-cecil/MtRTwHjPNu4 20 | if (def.IsAbstract) 21 | list.Add(new DumpSpecifier("static")); 22 | else 23 | list.Add(new DumpSpecifier("sealed")); 24 | 25 | // http://www.programmersought.com/article/6494173120/ 26 | if (def.IsPublic || def.IsNestedPublic) 27 | list.Add(new DumpSpecifier("public")); 28 | else if (!def.IsNested) 29 | list.Add(new DumpSpecifier("internal")); 30 | else if (def.IsNestedFamily || def.IsNestedFamilyOrAssembly) 31 | { 32 | list.Add(new DumpSpecifier("protected")); 33 | if (def.IsNestedFamilyOrAssembly) 34 | list.Add(new DumpSpecifier("internal")); 35 | } 36 | else if (def.IsNestedPrivate) 37 | list.Add(new DumpSpecifier("private")); 38 | 39 | // TODO: readonly struct? 40 | return list; 41 | } 42 | 43 | internal static IEnumerable From(FieldDefinition def) 44 | { 45 | var list = new List(); 46 | if (def.IsStatic) 47 | list.Add(new DumpSpecifier("static")); 48 | if (def.IsPublic) 49 | list.Add(new DumpSpecifier("public")); 50 | else if (def.IsFamily || def.IsFamilyOrAssembly) 51 | { 52 | list.Add(new DumpSpecifier("protected")); 53 | if (def.IsFamilyOrAssembly) 54 | list.Add(new DumpSpecifier("internal")); 55 | } 56 | else if (def.IsPrivate) 57 | list.Add(new DumpSpecifier("private")); 58 | 59 | if (def.IsInitOnly) 60 | // https://stackoverflow.com/questions/56179043/how-to-get-initial-value-of-field-by-mono-cecil ? 61 | if (def.HasConstant) 62 | list.Add(new DumpSpecifier("const")); 63 | else 64 | list.Add(new DumpSpecifier("readonly")); 65 | return list; 66 | } 67 | 68 | internal static IEnumerable From(MethodDefinition def) 69 | { 70 | var list = new List(); 71 | if (def.IsStatic) 72 | list.Add(new DumpSpecifier("static")); 73 | if (def.IsPublic) 74 | list.Add(new DumpSpecifier("public")); 75 | else if (def.IsFamily || def.IsFamilyOrAssembly) 76 | { 77 | list.Add(new DumpSpecifier("protected")); 78 | if (def.IsFamilyOrAssembly) 79 | list.Add(new DumpSpecifier("internal")); 80 | } 81 | else if (def.IsPrivate) 82 | list.Add(new DumpSpecifier("private")); 83 | 84 | if (def.GetBaseMethod() != def) 85 | list.Add(new DumpSpecifier("override")); 86 | return list; 87 | } 88 | 89 | internal static IEnumerable From(PropertyDefinition def) 90 | { 91 | if (def.GetMethod != null) 92 | return From(def.GetMethod); 93 | else 94 | return From(def.SetMethod); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Serialization/CppHeaderCreator.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Config; 2 | using Il2CppModdingCodegen.Data; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | 8 | namespace Il2CppModdingCodegen.Serialization 9 | { 10 | public class CppHeaderCreator 11 | { 12 | private readonly SerializationConfig _config; 13 | private readonly CppContextSerializer _serializer; 14 | 15 | internal CppHeaderCreator(SerializationConfig config, CppContextSerializer serializer) 16 | { 17 | _config = config; 18 | _serializer = serializer; 19 | } 20 | 21 | private bool hasIl2CppTypeCheckInclude; 22 | 23 | private void IncludeIl2CppTypeCheckIfNotAlready(CppStreamWriter writer) 24 | { 25 | if (hasIl2CppTypeCheckInclude) return; 26 | writer.WriteInclude("beatsaber-hook/shared/utils/il2cpp-type-check.hpp"); 27 | hasIl2CppTypeCheckInclude = true; 28 | } 29 | 30 | internal void Serialize(CppTypeContext context) 31 | { 32 | var data = context.LocalType; 33 | var headerLocation = Path.Combine(_config.OutputDirectory, _config.OutputHeaderDirectory, context.HeaderFileName); 34 | Directory.CreateDirectory(Path.GetDirectoryName(headerLocation)); 35 | using var ms = new MemoryStream(); 36 | using var rawWriter = new StreamWriter(ms); 37 | using var writer = new CppStreamWriter(rawWriter, " "); 38 | // Write header 39 | writer.WriteComment($"Autogenerated from {nameof(CppHeaderCreator)}"); 40 | writer.WriteComment("Created by Sc2ad"); 41 | writer.WriteComment("========================================================================="); 42 | writer.WriteLine("#pragma once"); 43 | // TODO: determine when/if we need this 44 | // For sizes that are valid, we ALSO want to write with pack of 1 45 | // Invalid sizes are ignored. 46 | 47 | // Write SerializerContext and actual type 48 | try 49 | { 50 | _serializer.Serialize(writer, context, true); 51 | } 52 | catch (UnresolvedTypeException e) 53 | { 54 | if (_config.UnresolvedTypeExceptionHandling?.TypeHandling == UnresolvedTypeExceptionHandling.DisplayInFile) 55 | { 56 | writer.WriteComment("Unresolved type exception!"); 57 | writer.WriteLine("/*"); 58 | writer.WriteLine(e); 59 | writer.WriteLine("*/"); 60 | } 61 | else if (_config.UnresolvedTypeExceptionHandling?.TypeHandling == UnresolvedTypeExceptionHandling.SkipIssue) 62 | return; 63 | else if (_config.UnresolvedTypeExceptionHandling?.TypeHandling == UnresolvedTypeExceptionHandling.Elevate) 64 | throw new InvalidOperationException($"Cannot elevate {e} to a parent type- there is no parent type!"); 65 | } 66 | // End the namespace 67 | writer.CloseDefinition(); 68 | hasIl2CppTypeCheckInclude = context.NeedIl2CppUtilsFunctionsInHeader; 69 | 70 | if (data.This.Namespace == "System" && data.This.Name == "ValueType") 71 | { 72 | IncludeIl2CppTypeCheckIfNotAlready(writer); 73 | writer.WriteLine("template"); 74 | writer.WriteLine("struct is_value_type>> : std::true_type{};"); 75 | } 76 | var nestedContexts = new Stack(context.NestedContexts.Where(n => n.InPlace)); 77 | while (nestedContexts.TryPop(out var nested)) 78 | { 79 | CppContextSerializer.DefineIl2CppArgTypes(writer, nested); 80 | foreach (var innerNested in nested.NestedContexts.Where(n => n.InPlace)) 81 | nestedContexts.Push(innerNested); 82 | } 83 | 84 | writer.WriteLine("#include \"beatsaber-hook/shared/utils/il2cpp-utils-methods.hpp\""); 85 | _serializer.WritePostSerializeMethods(writer, context, true); 86 | writer.Flush(); 87 | 88 | writer.WriteIfDifferent(headerLocation, context); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Serialization/CppFieldSerializerInvokes.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Config; 2 | using Il2CppModdingCodegen.Data; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace Il2CppModdingCodegen.Serialization 7 | { 8 | public class CppFieldSerializerInvokes : Serializer 9 | { 10 | private string? _declaringFullyQualified; 11 | private readonly Dictionary _resolvedTypes = new(); 12 | private bool _asHeader; 13 | private readonly SerializationConfig _config; 14 | private Refness _refness; 15 | 16 | internal CppFieldSerializerInvokes(SerializationConfig config) 17 | { 18 | _config = config; 19 | } 20 | 21 | public override void PreSerialize(CppTypeContext context, IField field) 22 | { 23 | if (context is null) throw new ArgumentNullException(nameof(context)); 24 | if (field is null) throw new ArgumentNullException(nameof(field)); 25 | _refness = context.LocalType.Info.Refness; 26 | _declaringFullyQualified = context.QualifiedTypeName.TrimStart(':'); 27 | var resolvedName = context.GetCppName(field.Type, true); 28 | _resolvedTypes.Add(field, resolvedName); 29 | if (resolvedName != null) 30 | { 31 | // Add static field to forward declares, since it is used by the static _get and _set methods 32 | Resolved(field); 33 | } 34 | } 35 | 36 | private static string SafeConfigName(string name) => Utils.SafeName(name.Replace('<', '$').Replace('>', '$').Replace('.', '_')); 37 | 38 | private string GetGetter(string fieldType, IField field, bool namespaceQualified) 39 | { 40 | var retStr = fieldType + "&"; 41 | var ns = string.Empty; 42 | if (namespaceQualified) 43 | ns = _declaringFullyQualified + "::"; 44 | // Collisions with this name are incredibly unlikely. 45 | return $"[[deprecated(\"Use field access instead!\")]] {retStr} {ns}{SafeConfigName($"dyn_{field.Name}")}()"; 46 | } 47 | 48 | public override void Serialize(CppStreamWriter writer, IField field, bool asHeader) 49 | { 50 | if (writer is null) throw new ArgumentNullException(nameof(writer)); 51 | if (field is null) throw new ArgumentNullException(nameof(field)); 52 | _asHeader = asHeader; 53 | if (_resolvedTypes[field] is null) 54 | throw new UnresolvedTypeException(field.DeclaringType, field.Type); 55 | string resolvedType = _resolvedTypes[field]!; 56 | var fieldCommentString = ""; 57 | foreach (var spec in field.Specifiers) 58 | fieldCommentString += $"{spec} "; 59 | fieldCommentString += $"{field.Type} {field.Name}"; 60 | if (_asHeader && !field.DeclaringType.IsGenericTemplate) 61 | { 62 | // Create two method declarations: 63 | // static FIELDTYPE& _get_FIELDNAME(); 64 | writer.WriteComment("Get instance field reference: " + fieldCommentString); 65 | writer.WriteDeclaration(GetGetter(resolvedType, field, !_asHeader)); 66 | } 67 | else 68 | { 69 | var classArgs = "this"; 70 | if (_refness == Refness.ValueType) 71 | classArgs = "*this"; 72 | 73 | // Write getter 74 | writer.WriteComment("Autogenerated instance field getter"); 75 | writer.WriteComment("Get instance field: " + fieldCommentString); 76 | writer.WriteDefinition(GetGetter(resolvedType, field, !_asHeader)); 77 | 78 | // TODO: Check invalid name 79 | var loggerId = "___internal__logger"; 80 | var classId = "___internal__instance"; 81 | var offsetId = "___internal__field__offset"; 82 | 83 | writer.WriteDeclaration($"static auto {loggerId} = ::Logger::get()" + 84 | $".WithContext(\"{field.DeclaringType.GetQualifiedCppName()}::{SafeConfigName($"dyn_{field.Name}")}\")"); 85 | 86 | writer.WriteDeclaration($"auto {classId} = {classArgs}"); 87 | writer.WriteDeclaration($"static auto {offsetId} = THROW_UNLESS(il2cpp_utils::FindField({classId}, \"{field.Name}\"))->offset"); 88 | writer.WriteDeclaration($"return *reinterpret_cast<{resolvedType}*>(reinterpret_cast(this) + {offsetId})"); 89 | writer.CloseDefinition(); 90 | } 91 | writer.Flush(); 92 | Serialized(field); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /Codegen-CLI/SimpleTypeRefConverter.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Data; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Text; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization; 8 | 9 | namespace Codegen_CLI 10 | { 11 | internal class SimpleTypeRefConverter : JsonConverter 12 | { 13 | private class ClearTypeRefConverter 14 | { 15 | internal bool Equals(TypeRef x, [AllowNull] TypeRef y) 16 | { 17 | if (y is null) return false; 18 | if (x.Namespace != y.Namespace || x.Name != y.Name) 19 | return false; 20 | if (x.DeclaringType != null && !Equals(x.DeclaringType, y.DeclaringType)) 21 | return false; 22 | return true; 23 | } 24 | 25 | // Determine if the type ref on the left is a reference to the right 26 | // This INCLUDES generic instances x that are of the generic template y 27 | internal bool Equals([AllowNull] TypeRef x, ITypeData y) 28 | { 29 | if (x is null || y is null) return false; 30 | // First a simple name match 31 | if (x.Namespace != y.This.Namespace || x.Name != y.This.Name) 32 | return false; 33 | // Then, check to see if the declaring types match 34 | if (x.DeclaringType != null && !Equals(x.DeclaringType, y.This.DeclaringType)) 35 | return false; 36 | // Otherwise, we return true 37 | // TODO: (this may short circuit fail on some interesting cases where generic args match?) 38 | return true; 39 | } 40 | } 41 | 42 | private readonly List types; 43 | private readonly ClearTypeRefConverter comparer = new(); 44 | 45 | public SimpleTypeRefConverter(IEnumerable types) 46 | { 47 | this.types = new List(types); 48 | } 49 | 50 | public override TypeRef Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 51 | { 52 | throw new NotImplementedException(); 53 | } 54 | 55 | public override void Write(Utf8JsonWriter writer, TypeRef value, JsonSerializerOptions options) 56 | { 57 | writer.WriteStartObject(); 58 | // Write simple here, only relevant details and a TID 59 | writer.WriteString(nameof(value.Namespace), value.Namespace); 60 | writer.WriteString(nameof(value.Name), value.Name); 61 | writer.WriteBoolean(nameof(value.IsGenericParameter), value.IsGenericParameter); 62 | writer.WriteBoolean(nameof(value.IsArray), value.IsArray()); 63 | writer.WriteBoolean(nameof(value.IsPointer), value.IsPointer()); 64 | var genericsTypedef = value; 65 | if (value.ElementType != null) 66 | { 67 | // If we have an element type, skip ourselves and go to our elements directly. 68 | genericsTypedef = value.ElementType; 69 | } 70 | while (genericsTypedef.ElementType != null) 71 | { 72 | // Continue checking our element types until we have extinguished all of our element types, then write those generics. 73 | genericsTypedef = genericsTypedef.ElementType; 74 | } 75 | writer.WritePropertyName(nameof(value.Generics)); 76 | writer.WriteStartArray(); 77 | foreach (var gp in genericsTypedef.Generics) 78 | { 79 | Write(writer, gp, options); 80 | } 81 | writer.WriteEndArray(); 82 | //// Write constraints of our bottom type 83 | //writer.WritePropertyName(nameof(value.GenericParameterConstraints)); 84 | //writer.WriteStartArray(); 85 | //foreach (var constraint in genericsTypedef.GenericParameterConstraints) 86 | //{ 87 | // Write(writer, constraint, options); 88 | //} 89 | //writer.WriteEndArray(); 90 | var ind = types.FindIndex(d => comparer.Equals(genericsTypedef, d)); 91 | bool genericParam = value.IsGenericParameter; 92 | while (ind < 0 && !genericParam) 93 | { 94 | if (value.ElementType != null) 95 | { 96 | genericParam = value.ElementType.IsGenericParameter; 97 | if (genericParam) 98 | break; 99 | ind = types.FindIndex(d => comparer.Equals(value.ElementType, d)); 100 | value = value.ElementType; 101 | } 102 | } 103 | if (ind < 0 && !genericParam) 104 | { 105 | // If index is STILL -1 (and we aren't a generic param) 106 | // We couldn't find it even after searching our element types for as long as we could! 107 | throw new InvalidOperationException("TypeRef could not be found in types! Is this a generic parameter/array/pointer?"); 108 | } 109 | if (genericParam) 110 | ind--; // VERY IMPORTANT DETAIL 111 | writer.WriteNumber("TypeId", ind); 112 | writer.WriteEndObject(); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /Codegen-CLI/TypeDataConverter.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen; 2 | using Il2CppModdingCodegen.Data; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Text.Json; 9 | using System.Text.Json.Serialization; 10 | 11 | namespace Codegen_CLI 12 | { 13 | /// 14 | /// CAN ONLY BE CALLED AFTER TYPE REGISTRATION HAS TAKEN PLACE! 15 | /// 16 | internal class TypeDataConverter : JsonConverter 17 | { 18 | public override ITypeData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 19 | { 20 | throw new NotImplementedException(); 21 | } 22 | 23 | private readonly JsonConverter simpleConv; 24 | private readonly ITypeCollection types; 25 | 26 | public TypeDataConverter(ITypeCollection types, JsonConverter simpleConv) 27 | { 28 | this.types = types; 29 | this.simpleConv = simpleConv; 30 | } 31 | 32 | private void WriteThis(Utf8JsonWriter writer, TypeRef value, JsonSerializerOptions options) 33 | { 34 | writer.WriteStartObject(); 35 | // Write simple here, only relevant details and a TID 36 | writer.WriteString(nameof(value.Namespace), value.Namespace); 37 | writer.WriteString(nameof(value.Name), value.Name); 38 | writer.WriteString("QualifiedCppName", value.GetQualifiedCppName()); 39 | writer.WriteBoolean(nameof(value.IsGenericTemplate), value.IsGenericTemplate); 40 | writer.WriteBoolean("IsNested", value.DeclaringType != null); 41 | if (value.DeclaringType != null) 42 | { 43 | // Declaring types can't have a cycle, but could be weird with generics 44 | writer.WritePropertyName(nameof(value.DeclaringType)); 45 | simpleConv.Write(writer, value.OriginalDeclaringType, options); 46 | } 47 | if (value.ElementType != null) 48 | { 49 | // Element types can have a cycle 50 | writer.WritePropertyName(nameof(value.ElementType)); 51 | simpleConv.Write(writer, value.ElementType, options); 52 | } 53 | else 54 | { 55 | writer.WriteNull(nameof(value.ElementType)); 56 | } 57 | writer.WritePropertyName(nameof(value.GenericParameterConstraints)); 58 | writer.WriteStartArray(); 59 | foreach (var gpc in value.GenericParameterConstraints) 60 | { 61 | simpleConv.Write(writer, gpc, options); 62 | } 63 | writer.WriteEndArray(); 64 | writer.WritePropertyName(nameof(value.Generics)); 65 | writer.WriteStartArray(); 66 | foreach (var gp in value.Generics) 67 | { 68 | simpleConv.Write(writer, gp, options); 69 | } 70 | writer.WriteEndArray(); 71 | writer.WriteEndObject(); 72 | } 73 | 74 | public override void Write(Utf8JsonWriter writer, ITypeData value, JsonSerializerOptions options) 75 | { 76 | // We basically want to verbosely write out our this typeref, everything else should use the simple type ref converter. 77 | // Everything else in this type should also just be serialized normally. 78 | writer.WriteStartObject(); 79 | writer.WritePropertyName(nameof(value.This)); 80 | WriteThis(writer, value.This, options); 81 | // Write each of the other properties explicitly by converting 82 | writer.WritePropertyName(nameof(value.Attributes)); 83 | JsonSerializer.Serialize(writer, value.Attributes, options); 84 | writer.WritePropertyName(nameof(value.ImplementingInterfaces)); 85 | JsonSerializer.Serialize(writer, value.ImplementingInterfaces, options); 86 | writer.WritePropertyName(nameof(value.InstanceFields)); 87 | JsonSerializer.Serialize(writer, value.InstanceFields, options); 88 | writer.WritePropertyName(nameof(value.Layout)); 89 | JsonSerializer.Serialize(writer, value.Layout, options); 90 | writer.WritePropertyName(nameof(value.Methods)); 91 | JsonSerializer.Serialize(writer, value.Methods, options); 92 | writer.WritePropertyName(nameof(value.NestedTypes)); 93 | JsonSerializer.Serialize(writer, value.NestedTypes, options); 94 | writer.WritePropertyName(nameof(value.Parent)); 95 | JsonSerializer.Serialize(writer, value.Parent, options); 96 | writer.WritePropertyName(nameof(value.Properties)); 97 | JsonSerializer.Serialize(writer, value.Properties, options); 98 | writer.WritePropertyName(nameof(value.Specifiers)); 99 | JsonSerializer.Serialize(writer, value.Specifiers, options); 100 | writer.WritePropertyName(nameof(value.StaticFields)); 101 | JsonSerializer.Serialize(writer, value.StaticFields, options); 102 | writer.WritePropertyName(nameof(value.Type)); 103 | JsonSerializer.Serialize(writer, value.Type, options); 104 | writer.WritePropertyName(nameof(value.TypeDefIndex)); 105 | JsonSerializer.Serialize(writer, value.TypeDefIndex, options); 106 | writer.WriteNumber("Size", SizeTracker.GetSize(types, value)); 107 | writer.WriteEndObject(); 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DllHandling/DllData.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Config; 2 | using Mono.Cecil; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | 8 | namespace Il2CppModdingCodegen.Data.DllHandling 9 | { 10 | public class DllData : DefaultAssemblyResolver, IParsedData 11 | { 12 | public string Name => "Dll Data"; 13 | public List Images { get; } = new List(); 14 | public IEnumerable Types => _types.Values; 15 | private readonly DllConfig _config; 16 | private readonly string _dir; 17 | private readonly ReaderParameters _readerParams; 18 | private readonly IMetadataResolver _metadataResolver; 19 | 20 | private readonly Dictionary _types = new Dictionary(); 21 | 22 | internal DllData(string dir, DllConfig config) 23 | { 24 | _config = config; 25 | _dir = dir; 26 | AddSearchDirectory(dir); 27 | _metadataResolver = new MetadataResolver(this); 28 | _readerParams = new ReaderParameters(ReadingMode.Immediate) 29 | { 30 | AssemblyResolver = this, 31 | MetadataResolver = _metadataResolver 32 | }; 33 | 34 | var modules = new List(); 35 | foreach (var file in Directory.GetFiles(dir)) 36 | if (file.EndsWith(".dll") && !_config.BlacklistDlls.Contains(file)) 37 | { 38 | var assemb = AssemblyDefinition.ReadAssembly(file, _readerParams); 39 | foreach (var module in assemb.Modules) 40 | modules.Add(module); 41 | } 42 | 43 | Queue frontier = new Queue(); 44 | modules.ForEach(m => m.Types.ToList().ForEach(t => 45 | { 46 | if (_config.ParseTypes && !_config.BlacklistTypes.Contains(t.Name)) 47 | frontier.Enqueue(t); 48 | })); 49 | 50 | while (frontier.Count > 0) 51 | { 52 | var t = frontier.Dequeue(); 53 | if (t.Name.StartsWith("<") && t.Namespace.Length == 0 && t.DeclaringType is null) 54 | { 55 | if (t.Name.StartsWith("") || t.Name.StartsWith("")) 56 | { 57 | // Skip Module or PrivateImplementationDetails types 58 | continue; 59 | } 60 | } 61 | 62 | var dllRef = DllTypeRef.From(t); 63 | if (!_types.ContainsKey(dllRef)) 64 | { 65 | var type = new DllTypeData(t, _config); 66 | if (dllRef.DeclaringType != null) 67 | _types[dllRef.DeclaringType].NestedTypes.AddOrThrow(type); 68 | foreach (var nested in t.NestedTypes) 69 | frontier.Enqueue(nested); 70 | _types.Add(dllRef, type); 71 | } 72 | else 73 | Console.Error.WriteLine($"{dllRef} already in _types! Matching item: {_types[dllRef].This}"); 74 | } 75 | 76 | int total = DllTypeRef.Hits + DllTypeRef.Misses; 77 | Console.WriteLine($"{nameof(DllTypeRef)} cache hits: {DllTypeRef.Hits} / {total} = {100.0f * DllTypeRef.Hits / total}"); 78 | // Ignore images for now. 79 | } 80 | 81 | public override string ToString() => $"Types: {Types.Count()}"; 82 | 83 | public ITypeData? Resolve(TypeRef? typeRef) 84 | { 85 | if (typeRef is null) throw new ArgumentNullException(nameof(typeRef)); 86 | return Resolve(typeRef.AsDllTypeRef); 87 | } 88 | 89 | private ITypeData? Resolve(DllTypeRef typeRef) 90 | { 91 | // Generic parameters can never "Resolve" 92 | if (typeRef.IsGenericParameter) return null; 93 | // TODO: Resolve only among our types that we actually plan on serializing 94 | // Basically, check it against our whitelist/blacklist 95 | ITypeData ret; 96 | if (typeRef.IsGenericInstance) 97 | { 98 | // This is a generic instance. We want to convert this instance to a generic type that we have already created in _types 99 | var def = typeRef.This.Resolve(); 100 | var check = DllTypeRef.From(def); 101 | // Try to get our Generic Definition out of _types 102 | if (!_types.TryGetValue(check, out ret)) 103 | // This should never happen. All generic definitions should already be resolved. 104 | throw new InvalidOperationException($"Generic instance: {typeRef} (definition: {check}) cannot map to any type in _types!"); 105 | return ret; 106 | } 107 | 108 | if (!_types.TryGetValue(typeRef, out ret)) 109 | { 110 | var def = typeRef.This.Resolve(); 111 | if (def != null) 112 | { 113 | ret = new DllTypeData(def, _config); 114 | if (!_types.ContainsKey(ret.This)) 115 | Console.Error.WriteLine($"Too late to add {def} to Types!"); 116 | else 117 | { 118 | Console.Error.WriteLine($"{typeRef} already existed in _types?! Matching item: {_types[ret.This].This}"); 119 | ret = _types[ret.This]; 120 | } 121 | } 122 | else 123 | throw new InvalidOperationException($"Non-generic-parameter {typeRef} cannot be resolved!"); 124 | } 125 | return ret; 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DumpHandling/DumpMethod.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Parsers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Il2CppModdingCodegen.Data.DumpHandling 7 | { 8 | internal class DumpMethod : IMethod 9 | { 10 | public List Attributes { get; } = new List(); 11 | public List Specifiers { get; } = new List(); 12 | public long RVA { get; } 13 | public long Offset { get; } 14 | public long VA { get; } 15 | public int Slot { get; } 16 | public TypeRef ReturnType { get; } 17 | public TypeRef DeclaringType { get; } 18 | public TypeRef? ImplementedFrom { get; } 19 | public List BaseMethods { get; } = new List(); 20 | public List ImplementingMethods { get; } = new List(); 21 | public bool HidesBase { get; } 22 | public string Name { get; } 23 | public string Il2CppName { get; } 24 | public List Parameters { get; } = new List(); 25 | public bool Generic { get; } 26 | public IReadOnlyList GenericParameters { get; } 27 | public bool IsSpecialName { get; } 28 | public bool IsVirtual { get; } 29 | 30 | internal DumpMethod(TypeRef declaring, PeekableStreamReader fs) 31 | { 32 | DeclaringType = declaring; 33 | // Read Attributes 34 | var line = fs.PeekLine()?.Trim(); 35 | while (line != null && line.StartsWith("[")) 36 | { 37 | Attributes.Add(new DumpAttribute(fs)); 38 | line = fs.PeekLine()?.Trim(); 39 | } 40 | // Read prefix comment 41 | line = fs.ReadLine()?.Trim() ?? ""; 42 | var split = line.Split(' '); 43 | if (split.Length < 5) 44 | throw new InvalidOperationException($"Line {fs.CurrentLineIndex}: Method cannot be created from: \"{line.Trim()}\""); 45 | 46 | int start = split.Length - 1; 47 | if (split[^2] == "Slot:") 48 | { 49 | Slot = int.Parse(split[^1]); 50 | start = split.Length - 3; 51 | } 52 | if (split[start - 1] == "VA:") 53 | { 54 | if (split[start] == "-1") 55 | VA = -1; 56 | else 57 | VA = Convert.ToInt32(split[start], 16); 58 | start -= 2; 59 | } 60 | if (split[start - 1] == "Offset") 61 | { 62 | if (split[start] == "-1") 63 | Offset = -1; 64 | else 65 | Offset = Convert.ToInt32(split[start], 16); 66 | start -= 2; 67 | } 68 | if (split[start - 1] == "RVA") 69 | if (split[start] == "-1") 70 | RVA = -1; 71 | else 72 | RVA = Convert.ToInt32(split[start], 16); 73 | 74 | // Read parameters 75 | line = fs.ReadLine()?.Trim() ?? ""; 76 | int end = line.LastIndexOf(')'); 77 | int startSubstr = line.LastIndexOf('(', end - 1); 78 | string paramLine = line.Substring(startSubstr + 1, end - startSubstr - 1); 79 | if (paramLine.Length != 0) 80 | { 81 | var spl = paramLine.Split(new string[] { ", " }, StringSplitOptions.None); 82 | for (int i = 0; i < spl.Length; i++) 83 | { 84 | var fullParamString = DumpTypeRef.FromMultiple(spl, i, out int adjust, 1, ", "); 85 | Parameters.Add(new Parameter(fullParamString)); 86 | i = adjust; 87 | } 88 | } 89 | // Read method 90 | var methodSplit = line.Substring(0, startSubstr).Split(' '); 91 | int startIndex = -1; 92 | int nameIdx = methodSplit.Length - 1; 93 | if (!methodSplit[^1].StartsWith(".")) 94 | { 95 | // Not a special name, should have an implementing type 96 | startIndex = methodSplit[^1].LastIndexOf("."); 97 | if (startIndex != -1) 98 | { 99 | var typeStr = DumpTypeRef.FromMultiple(methodSplit, methodSplit.Length - 1, out nameIdx, -1, " "); 100 | var finalDot = typeStr.LastIndexOf('.'); 101 | ImplementedFrom = new DumpTypeRef(typeStr.Substring(0, finalDot)); 102 | Name = typeStr.Substring(finalDot + 1); 103 | Il2CppName = typeStr; 104 | } 105 | else 106 | { 107 | Name = methodSplit[^1]; 108 | Il2CppName = Name; 109 | } 110 | } 111 | else 112 | { 113 | Name = methodSplit[^1].Substring(startIndex + 1); 114 | Il2CppName = Name; 115 | } 116 | ReturnType = new DumpTypeRef(DumpTypeRef.FromMultiple(methodSplit, nameIdx - 1, out nameIdx, -1, " ")); 117 | for (int i = 0; i < nameIdx - 1; i++) 118 | Specifiers.Add(new DumpSpecifier(methodSplit[i])); 119 | 120 | // TODO: mark this and populate GenericParameters iff the method's actual params reference any types that cannot be resolved? 121 | Generic = false; 122 | GenericParameters = new List(); 123 | 124 | HidesBase = Specifiers.Any(s => s.Override); 125 | // TODO: Implement BaseMethod, ImplementingMethods 126 | } 127 | 128 | public override string ToString() 129 | { 130 | var s = ""; 131 | foreach (var atr in Attributes) 132 | s += $"{atr}\n\t"; 133 | s += $"// Offset: 0x{Offset:X}\n\t"; 134 | foreach (var spec in Specifiers) 135 | s += $"{spec} "; 136 | s += $"{ReturnType} {Name}({Parameters.FormatParameters()}) "; 137 | s += "{}"; 138 | return s; 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/Parameter.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Data.DllHandling; 2 | using Il2CppModdingCodegen.Data.DumpHandling; 3 | using Mono.Cecil; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace Il2CppModdingCodegen.Data 9 | { 10 | public class Parameter 11 | { 12 | public TypeRef Type { get; } 13 | public string Name { get; } = ""; 14 | public ParameterModifier Modifier { get; } = ParameterModifier.None; 15 | 16 | internal Parameter(string innard) 17 | { 18 | var spl = innard.Split(' '); 19 | int typeIndex = 1; 20 | if (Enum.TryParse(spl[0], true, out var modifier)) 21 | Modifier = modifier; 22 | else 23 | typeIndex = 0; 24 | 25 | Type = new DumpTypeRef(DumpTypeRef.FromMultiple(spl, typeIndex, out int res, 1, " ")); 26 | if (res + 1 < spl.Length) 27 | Name = spl[res + 1]; 28 | } 29 | 30 | internal Parameter(ParameterDefinition def) 31 | { 32 | Type = DllTypeRef.From(def.ParameterType); 33 | Name = def.Name; 34 | if (def.IsIn) 35 | Modifier = ParameterModifier.In; 36 | else if (def.IsOut) 37 | Modifier = ParameterModifier.Out; 38 | else if (def.ParameterType.IsByReference) 39 | Modifier = ParameterModifier.Ref; 40 | else if (def.CustomAttributes.Any(a => a.AttributeType.FullName == typeof(ParamArrayAttribute).FullName)) 41 | Modifier = ParameterModifier.Params; 42 | // TODO: capture and print default argument values? 43 | } 44 | 45 | public override string ToString() => ToString(true); 46 | 47 | internal string ToString(bool name) 48 | { 49 | string s = ""; 50 | if (Modifier != ParameterModifier.None) 51 | s = $"{Modifier.ToString().ToLower()} "; 52 | s += $"{Type}"; 53 | if (name && Name != null) 54 | s += $" {Name}"; 55 | return s; 56 | } 57 | } 58 | 59 | [Flags] 60 | public enum ParameterFormatFlags 61 | { 62 | Types = 1, 63 | Names = 2, 64 | Normal = Types | Names, 65 | } 66 | 67 | public static class ParameterExtensions 68 | { 69 | internal static string PrintParameter(this (MethodTypeContainer container, ParameterModifier modifier) param, bool header, bool wantWrappers = false) 70 | { 71 | var s = param.container.TypeName(header); 72 | if (param.modifier != ParameterModifier.None && param.modifier != ParameterModifier.Params) 73 | if (!wantWrappers) 74 | s += "&"; 75 | else 76 | s = "ByRef<" + s + ">"; 77 | return s; 78 | } 79 | 80 | internal static string FormatParameters(this List parameters, HashSet? illegalNames = null, 81 | List<(MethodTypeContainer, ParameterModifier)>? resolvedNames = null, ParameterFormatFlags mode = ParameterFormatFlags.Normal, bool header = false, 82 | Func<(MethodTypeContainer, ParameterModifier), string, string>? nameOverride = null, bool wantWrappers = false) 83 | { 84 | var s = ""; 85 | for (int i = 0; i < parameters.Count; i++) 86 | { 87 | if (resolvedNames != null && resolvedNames[i].Item1.Skip) 88 | continue; 89 | string nameStr = ""; 90 | if (mode.HasFlag(ParameterFormatFlags.Names)) 91 | { 92 | nameStr = parameters[i].Name; 93 | if (string.IsNullOrWhiteSpace(nameStr)) 94 | nameStr = $"param_{i}"; 95 | while (illegalNames?.Contains(nameStr) ?? false) 96 | nameStr = "_" + nameStr; 97 | } 98 | nameStr = nameStr.Replace('<', '$').Replace('>', '$'); 99 | if (mode == ParameterFormatFlags.Names) 100 | { 101 | if (resolvedNames != null) 102 | { 103 | var container = resolvedNames[i].Item1; 104 | if (container.ExpandParams) 105 | { 106 | if (!container.HasTemplate) 107 | nameStr = $"::ArrayW<{container.ElementType}>({nameStr})"; 108 | else 109 | { 110 | if (!container.TypeName(true).Contains("...")) 111 | throw new ArgumentException($"resolvedNames[{i}]'s {nameof(MethodTypeContainer)} has ExpandParams " + 112 | "and a Template name that is NOT a ...TArgs style name!"); 113 | nameStr = $"{{{nameStr}...}}"; 114 | } 115 | } 116 | else if (container.UnPointered) 117 | nameStr = "&" + nameStr; 118 | } 119 | // Only names 120 | if (resolvedNames != null && nameOverride != null) 121 | s += nameOverride(resolvedNames[i], nameStr); 122 | else 123 | s += nameStr; 124 | } 125 | else if (mode == ParameterFormatFlags.Types) 126 | { 127 | // Only types 128 | if (resolvedNames != null) 129 | s += $"{resolvedNames[i].PrintParameter(header, wantWrappers)}"; 130 | else 131 | // Includes modifiers 132 | s += $"{parameters[i].ToString(false)}"; 133 | } 134 | else 135 | { 136 | // Types and names 137 | if (resolvedNames != null) 138 | s += $"{resolvedNames[i].PrintParameter(header, wantWrappers)} {nameStr}"; 139 | else 140 | // Includes modifiers 141 | s += $"{parameters[i]}"; 142 | } 143 | if (i != parameters.Count - 1) 144 | s += ", "; 145 | } 146 | return s; 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /EventDescriptor_bkp.hpp: -------------------------------------------------------------------------------- 1 | // Autogenerated from CppHeaderCreator 2 | // Created by Sc2ad 3 | // ========================================================================= 4 | #pragma once 5 | // Begin includes 6 | #include 7 | // Including type: System.ValueType 8 | #include "System/ValueType.hpp" 9 | #include "extern/beatsaber-hook/shared/utils/il2cpp-utils-methods.hpp" 10 | #include "extern/beatsaber-hook/shared/utils/il2cpp-utils-properties.hpp" 11 | #include "extern/beatsaber-hook/shared/utils/il2cpp-utils-fields.hpp" 12 | #include "extern/beatsaber-hook/shared/utils/utils.h" 13 | // Completed includes 14 | // Begin il2cpp-utils forward declares 15 | struct Il2CppObject; 16 | // Completed il2cpp-utils forward declares 17 | // Type namespace: System.Diagnostics.Tracing 18 | namespace System::Diagnostics::Tracing { 19 | // Size: 0x10 20 | #pragma pack(push, 1) 21 | // WARNING Layout: Explicit may not be correctly taken into account! 22 | // Autogenerated type: System.Diagnostics.Tracing.EventDescriptor 23 | // [] Offset: FFFFFFFF 24 | struct EventDescriptor/*, public System::ValueType*/ { 25 | public: 26 | struct __InternalUnionData { 27 | // private System.UInt16 m_id 28 | // Size: 0x2 29 | // Offset: 0x0 30 | uint16_t m_id; 31 | // Field size check 32 | static_assert(sizeof(uint16_t) == 0x2); 33 | // private System.Byte m_version 34 | // Size: 0x1 35 | // Offset: 0x2 36 | uint8_t m_version; 37 | // Field size check 38 | static_assert(sizeof(uint8_t) == 0x1); 39 | // private System.Byte m_channel 40 | // Size: 0x1 41 | // Offset: 0x3 42 | uint8_t m_channel; 43 | // Field size check 44 | static_assert(sizeof(uint8_t) == 0x1); 45 | }; 46 | // Creating union for fields at offset: 0x0 47 | union { 48 | // private System.Int32 m_traceloggingId 49 | // Size: 0x4 50 | // Offset: 0x0 51 | int m_traceloggingId; 52 | // Field size check 53 | static_assert(sizeof(int) == 0x4); 54 | // WARNING: Manual union structure 55 | __InternalUnionData data; 56 | static_assert(sizeof(__InternalUnionData) == 0x4); 57 | }; 58 | // private System.Byte m_level 59 | // Size: 0x1 60 | // Offset: 0x4 61 | uint8_t m_level; 62 | // Field size check 63 | static_assert(sizeof(uint8_t) == 0x1); 64 | // private System.Byte m_opcode 65 | // Size: 0x1 66 | // Offset: 0x5 67 | uint8_t m_opcode; 68 | // Field size check 69 | static_assert(sizeof(uint8_t) == 0x1); 70 | // private System.UInt16 m_task 71 | // Size: 0x2 72 | // Offset: 0x6 73 | uint16_t m_task; 74 | // Field size check 75 | static_assert(sizeof(uint16_t) == 0x2); 76 | // private System.Int64 m_keywords 77 | // Size: 0x8 78 | // Offset: 0x8 79 | int64_t m_keywords; 80 | // Field size check 81 | static_assert(sizeof(int64_t) == 0x8); 82 | // Creating value type constructor for type: EventDescriptor 83 | constexpr EventDescriptor(int m_traceloggingId_ = {}, uint8_t m_level_ = {}, uint8_t m_opcode_ = {}, uint16_t m_task_ = {}, int64_t m_keywords_ = {}) noexcept : m_traceloggingId{m_traceloggingId_}, m_level{m_level_}, m_opcode{m_opcode_}, m_task{m_task_}, m_keywords{m_keywords_} {} 84 | // Creating interface conversion operator: operator System::ValueType 85 | operator System::ValueType() noexcept { 86 | return *reinterpret_cast(this); 87 | } 88 | // public System.Void .ctor(System.Int32 traceloggingId, System.Byte level, System.Byte opcode, System.Int64 keywords) 89 | // Offset: 0x9E4818 90 | template<::il2cpp_utils::CreationType creationType = ::il2cpp_utils::CreationType::Temporary> 91 | EventDescriptor(int traceloggingId, uint8_t level, uint8_t opcode, int64_t keywords) { 92 | static auto ___internal__logger = ::Logger::get().WithContext("System::Diagnostics::Tracing::EventDescriptor::.ctor"); 93 | static auto* ___internal__method = THROW_UNLESS(::il2cpp_utils::FindMethod(*this, ".ctor", std::vector{}, ::il2cpp_utils::ExtractTypes(traceloggingId, level, opcode, keywords))); 94 | ::il2cpp_utils::RunMethodThrow(*this, ___internal__method, traceloggingId, level, opcode, keywords); 95 | } 96 | // public System.Void .ctor(System.Int32 id, System.Byte version, System.Byte channel, System.Byte level, System.Byte opcode, System.Int32 task, System.Int64 keywords) 97 | // Offset: 0x9E4830 98 | template<::il2cpp_utils::CreationType creationType = ::il2cpp_utils::CreationType::Temporary> 99 | EventDescriptor(int id, uint8_t version, uint8_t channel, uint8_t level, uint8_t opcode, int task, int64_t keywords) { 100 | static auto ___internal__logger = ::Logger::get().WithContext("System::Diagnostics::Tracing::EventDescriptor::.ctor"); 101 | static auto* ___internal__method = THROW_UNLESS(::il2cpp_utils::FindMethod(*this, ".ctor", std::vector{}, ::il2cpp_utils::ExtractTypes(id, version, channel, level, opcode, task, keywords))); 102 | ::il2cpp_utils::RunMethodThrow(*this, ___internal__method, id, version, channel, level, opcode, task, keywords); 103 | } 104 | // public System.Int32 get_EventId() 105 | // Offset: 0x9E4838 106 | int get_EventId(); 107 | // public System.Byte get_Version() 108 | // Offset: 0x9E4840 109 | uint8_t get_Version(); 110 | // public System.Byte get_Channel() 111 | // Offset: 0x9E4848 112 | uint8_t get_Channel(); 113 | // public System.Byte get_Level() 114 | // Offset: 0x9E4850 115 | uint8_t get_Level(); 116 | // public System.Byte get_Opcode() 117 | // Offset: 0x9E4858 118 | uint8_t get_Opcode(); 119 | // public System.Int32 get_Task() 120 | // Offset: 0x9E4860 121 | int get_Task(); 122 | // public System.Int64 get_Keywords() 123 | // Offset: 0x9E4868 124 | int64_t get_Keywords(); 125 | // public System.Boolean Equals(System.Diagnostics.Tracing.EventDescriptor other) 126 | // Offset: 0x9E48B0 127 | bool Equals(System::Diagnostics::Tracing::EventDescriptor other); 128 | // public override System.Boolean Equals(System.Object obj) 129 | // Offset: 0x9E4870 130 | // Implemented from: System.ValueType 131 | // Base method: System.Boolean ValueType::Equals(System.Object obj) 132 | bool Equals(::Il2CppObject* obj); 133 | // public override System.Int32 GetHashCode() 134 | // Offset: 0x9E4878 135 | // Implemented from: System.ValueType 136 | // Base method: System.Int32 ValueType::GetHashCode() 137 | int GetHashCode(); 138 | }; // System.Diagnostics.Tracing.EventDescriptor 139 | #pragma pack(pop) 140 | static check_size __System_Diagnostics_Tracing_EventDescriptorSizeCheck; 141 | static_assert(sizeof(EventDescriptor) == 0x10); 142 | } 143 | DEFINE_IL2CPP_ARG_TYPE(System::Diagnostics::Tracing::EventDescriptor, "System.Diagnostics.Tracing", "EventDescriptor"); 144 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Serialization/CppStreamWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.CodeDom.Compiler; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | namespace Il2CppModdingCodegen.Serialization 8 | { 9 | /// 10 | /// A C++ valid syntax writer. 11 | /// Overall todo: 12 | /// - This type should have a subtype that it returns for definition opens and closes 13 | /// - This type should also be harder to write invalid semantic information in (template writes would be ideal) 14 | /// - Should be easier to write members on opened C++ types 15 | /// - Should be easier to keep track of all known C++ members/extract references to them 16 | /// - Should be easier to apply large scale changes without entire type rewrites 17 | /// - Method Serializer should not exist in its current form at all 18 | /// - Parsing can be rewritten to accept dlls specifically 19 | /// - Handling singular members should be far easier-- we should be able to handle each method in isolation 20 | /// - if we see a method that we want to skip, it should not be challenging, likewise, field ops should be easy too 21 | /// Ideally setup for header only approach? includes after the fact, assuming non-generic. 22 | /// Also consider using Cecil to determine type sizes since they should match. 23 | /// 24 | public class CppStreamWriter : IndentedTextWriter 25 | { 26 | private readonly StreamWriter rawWriter; 27 | 28 | internal CppStreamWriter(StreamWriter writer) : base(writer) 29 | { 30 | rawWriter = writer; 31 | } 32 | 33 | internal CppStreamWriter(StreamWriter writer, string tabString) : base(writer, tabString) 34 | { 35 | rawWriter = writer; 36 | } 37 | 38 | /// 39 | /// Write a single line comment 40 | /// 41 | /// 42 | internal void WriteComment(string commentString) => WriteLine("// " + commentString); 43 | 44 | internal void WriteInclude(string include) => WriteLine("#include \"" + include + "\""); 45 | 46 | /// 47 | /// Write a single ;-terminated line (or a declaration) 48 | /// 49 | /// 50 | internal void WriteDeclaration(string declString) => WriteLine(declString + ";"); 51 | 52 | internal void WriteFieldDeclaration(string fieldType, string fieldName) => WriteDeclaration(fieldType + ' ' + fieldName); 53 | 54 | /// 55 | /// Write a single syntax ; terminated line with a comment on the same line (or a declaration) 56 | /// 57 | /// 58 | /// If null or empty, will call 59 | internal void WriteDeclaration(string declString, string commentString) 60 | { 61 | if (string.IsNullOrEmpty(commentString)) 62 | WriteDeclaration(declString); 63 | else 64 | WriteLine(declString + "; // " + commentString); 65 | } 66 | 67 | /// 68 | /// Write a definition and open a body with { 69 | /// 70 | /// 71 | internal void WriteDefinition(string defString) 72 | { 73 | WriteLine(defString + " {"); 74 | Indent++; 75 | } 76 | 77 | /// 78 | /// Close a body with } 79 | /// 80 | internal void CloseDefinition(string suffix = "") 81 | { 82 | Indent--; 83 | WriteLine("}" + suffix); 84 | } 85 | 86 | private static HashSet ExistingFiles { get; set; } = new HashSet(); 87 | 88 | internal static void PopulateExistingFiles(string dir) 89 | { 90 | if (!Directory.Exists(dir)) 91 | { 92 | Directory.CreateDirectory(dir); 93 | return; 94 | } 95 | var options = new EnumerationOptions 96 | { 97 | RecurseSubdirectories = true, 98 | ReturnSpecialDirectories = false 99 | }; 100 | ExistingFiles.UnionWith(Directory.EnumerateFiles(dir, "*", options)); 101 | } 102 | 103 | private static HashSet Written { get; set; } = new HashSet(); 104 | 105 | private static long NumChangedFiles { get; set; } = 0; 106 | 107 | internal void WriteIfDifferent(string filePath, CppTypeContext context) 108 | { 109 | if (!Written.Add(Path.GetFullPath(filePath))) 110 | throw new InvalidOperationException($"Was about to overwrite existing file: {filePath} with context: {context.LocalType.This}"); 111 | 112 | if (WriteIfDifferent(filePath, rawWriter.BaseStream)) 113 | NumChangedFiles++; 114 | } 115 | 116 | internal static bool WriteIfDifferent(string filePath, Stream stream) 117 | { 118 | stream.Position = 0; 119 | bool ret = false; 120 | long positionOfDifference = 0; 121 | if (File.Exists(filePath)) 122 | { 123 | using (var fileRead = File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Read)) 124 | { 125 | int a = 0, b = 0; 126 | while (a != -1 && b != -1) 127 | if ((a = fileRead.ReadByte()) != (b = stream.ReadByte())) 128 | { 129 | ret = true; 130 | if (a != -1) fileRead.Position -= 1; 131 | if (b != -1) stream.Position -= 1; 132 | if (fileRead.Position != stream.Position) 133 | throw new Exception("WriteIfDifferent file position logic is wrong!"); 134 | positionOfDifference = fileRead.Position; 135 | break; 136 | } 137 | } 138 | } else 139 | { 140 | ret = true; 141 | } 142 | if (ret) 143 | { 144 | using var fileWrite = File.OpenWrite(filePath); 145 | fileWrite.Seek(positionOfDifference, SeekOrigin.Begin); 146 | stream.CopyTo(fileWrite); 147 | fileWrite.Flush(); 148 | fileWrite.SetLength(fileWrite.Position); 149 | } 150 | return ret; 151 | } 152 | 153 | internal static void DeleteUnwrittenFiles() 154 | { 155 | Console.WriteLine($"Deleting {ExistingFiles.Except(Written).LongCount()} files!"); 156 | ExistingFiles.Except(Written).AsParallel().ForAll(s => File.Delete(s)); 157 | Console.WriteLine($"Made changes to {NumChangedFiles} / {Written.Count} files = {NumChangedFiles * 100.0f / Written.Count}%"); 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DumpHandling/DumpTypeRef.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Serialization; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Il2CppModdingCodegen.Data.DumpHandling 7 | { 8 | public class DumpTypeRef : TypeRef 9 | { 10 | internal static readonly DumpTypeRef ObjectType = new DumpTypeRef("object"); 11 | 12 | public override string Namespace { get; } = string.Empty; 13 | public override string Name { get; } 14 | 15 | public override bool IsGenericInstance { get; } 16 | public override bool IsGenericTemplate { get; } 17 | public override IReadOnlyList Generics { get; } 18 | 19 | public override TypeRef? OriginalDeclaringType { get; } 20 | public override TypeRef? ElementType { get; } 21 | 22 | public override bool IsPointer() 23 | { 24 | if (Name.EndsWith("*")) 25 | return true; 26 | return base.IsPointer(); 27 | } 28 | 29 | public override bool IsArray() => Name.EndsWith("[]"); 30 | 31 | private DumpTypeRef(DumpTypeRef other, string? nameOverride = null) 32 | { 33 | Namespace = other.Namespace; 34 | Name = nameOverride ?? other.Name; 35 | IsGenericInstance = other.IsGenericInstance; 36 | IsGenericTemplate = other.IsGenericTemplate; 37 | Generics = new List(other.Generics); 38 | OriginalDeclaringType = other.OriginalDeclaringType; 39 | UnNested = other.UnNested; 40 | ElementType = other.ElementType; 41 | } 42 | 43 | private DumpTypeRef(DumpTypeRef other, GenericTypeMap genericTypes) 44 | { 45 | Namespace = other.Namespace; 46 | Name = other.Name; 47 | IsGenericInstance = true; 48 | IsGenericTemplate = false; 49 | var newGenerics = new List(other.Generics.Count); 50 | foreach (var genericParameter in other.Generics) 51 | { 52 | if (genericParameter.IsGeneric) 53 | newGenerics.Add(genericParameter.MakeGenericInstance(genericTypes)); 54 | else if (genericTypes.TryGetValue(genericParameter, out var genericArgument)) 55 | newGenerics.Add(genericArgument); 56 | else 57 | throw new UnresolvedTypeException(genericParameter, other); 58 | } 59 | Generics = newGenerics; 60 | OriginalDeclaringType = other.DeclaringType; 61 | UnNested = other.UnNested; 62 | ElementType = other.ElementType; 63 | } 64 | 65 | public override TypeRef MakePointer() => new DumpTypeRef(this, Name + "*"); 66 | internal override TypeRef MakeGenericInstance(GenericTypeMap genericTypes) => IsGeneric ? new DumpTypeRef(this, genericTypes) : this; 67 | 68 | /// 69 | /// For use with text dumps. Takes a given split array that contains a type at index ind and 70 | /// returns the full type name and index where the end of the type name is while traversing the split array with direction and sep. 71 | /// 72 | /// 73 | /// 74 | /// 75 | /// 76 | /// 77 | /// 78 | internal static string FromMultiple(string[] spl, int ind, out int adjustedIndex, int direction = -1, string sep = " ") 79 | { 80 | adjustedIndex = ind; 81 | if (direction < 0 ? !spl[ind].Contains(">") : !spl[ind].Contains("<")) 82 | { 83 | adjustedIndex = ind; 84 | return spl[ind]; 85 | } 86 | int countToClose = 0; 87 | string s = ""; 88 | for (; direction > 0 ? ind < spl.Length : ind >= 0; ind += direction, adjustedIndex += direction) 89 | { 90 | // Depending which way we are travelling, either decrease or increase countToClose 91 | countToClose -= direction * spl[ind].Count(c => c == '>'); 92 | countToClose += direction * spl[ind].Count(c => c == '<'); 93 | s = direction > 0 ? s + sep + spl[ind] : spl[ind] + sep + s; 94 | if (countToClose == 0) 95 | break; 96 | } 97 | return direction > 0 ? s.Substring(sep.Length) : s.Substring(0, s.Length - sep.Length); 98 | } 99 | 100 | internal DumpTypeRef(string @namespace, string typeName) 101 | { 102 | Namespace = @namespace; 103 | 104 | var GenericTypes = new List(); 105 | if (typeName.EndsWith(">") && !typeName.StartsWith("<")) 106 | { 107 | var ind = typeName.IndexOf("<"); 108 | var types = typeName.Substring(ind + 1, typeName.Length - ind - 2); 109 | var spl = types.Split(new string[] { ", " }, StringSplitOptions.None); 110 | for (int i = 0; i < spl.Length;) 111 | { 112 | string s = spl[i]; 113 | int unclosed = s.Count(c => c == '<'); 114 | unclosed -= s.Count(c => c == '>'); 115 | i++; 116 | while (unclosed > 0) 117 | { 118 | unclosed += spl[i].Count(c => c == '<'); 119 | unclosed -= spl[i].Count(c => c == '>'); 120 | s += ", " + spl[i]; 121 | i++; 122 | } 123 | // TODO: if this DumpTypeRef is the This for a DumpTypeData, mark these IsGenericParameter. "out" is not in dump.cs. 124 | GenericTypes.Add(new DumpTypeRef(s)); 125 | } 126 | } 127 | Generics = GenericTypes; 128 | // TODO: check that this gives correct results 129 | if (string.IsNullOrEmpty(@namespace)) 130 | IsGenericInstance = true; 131 | else 132 | IsGenericTemplate = true; 133 | 134 | var declInd = typeName.LastIndexOf('.'); 135 | if (declInd != -1) 136 | { 137 | // Create a new TypeRef for the declaring type, it should recursively create more declaring types 138 | OriginalDeclaringType = new DumpTypeRef(typeName.Substring(0, declInd)); 139 | // TODO: need to resolve DeclaringType before this will make sense? 140 | Namespace = OriginalDeclaringType.Namespace; 141 | } 142 | Name = typeName.Replace('.', '/'); 143 | if (IsArray()) 144 | { 145 | ElementType = new DumpTypeRef(Name[0..^2]); 146 | // TODO: else set ElementType to `this` as Mono.Cecil does? 147 | } 148 | } 149 | 150 | internal DumpTypeRef(string qualifiedName) : this("", qualifiedName) { } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | 235 | # RIA/Silverlight projects 236 | Generated_Code/ 237 | 238 | # Backup & report files from converting an old project file 239 | # to a newer Visual Studio version. Backup files are not needed, 240 | # because we have git ;-) 241 | _UpgradeReport_Files/ 242 | Backup*/ 243 | UpgradeLog*.XML 244 | UpgradeLog*.htm 245 | ServiceFabricBackup/ 246 | *.rptproj.bak 247 | 248 | # SQL Server files 249 | *.mdf 250 | *.ldf 251 | *.ndf 252 | 253 | # Business Intelligence projects 254 | *.rdl.data 255 | *.bim.layout 256 | *.bim_*.settings 257 | *.rptproj.rsuser 258 | *- Backup*.rdl 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush personal settings 299 | .cr/personal 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | 336 | # Local History for Visual Studio 337 | .localhistory/ 338 | 339 | # BeatPulse healthcheck temp database 340 | healthchecksdb -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/SizeTracker.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Data; 2 | using Il2CppModdingCodegen.Serialization; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace Il2CppModdingCodegen 9 | { 10 | /// 11 | /// Tracks the size of all used value types. 12 | /// 13 | public class SizeTracker 14 | { 15 | private static readonly Dictionary sizeMap = new(); 16 | 17 | public static int GetGenericInstanceSize(ITypeCollection types, TypeRef type, ITypeData td) 18 | { 19 | // If we are a generic instance, we will compute our size right here. 20 | // In order to do so, we need to iterate over every field in the template type (with the correct alignment, presumably 8) 21 | // this is actually VERY IMPORTANT 22 | // For generic types, we may need to actually pad them ourselves so that they get alignment 8 as well 23 | // However, this means we have to actually understand the padding rules, which shouldn't be too bad. 24 | // Of course, padding is practically impossible if we have generic parameters as fields in the middle between other fields, so that can be taken with a grain of salt. 25 | // However, the size of generic instances should still be somewhat feasible to get in isolation, assuming their padding rules don't change based off of the generic type (please don't) 26 | // And count up their sizes, replacing each generic parameter that appears with the arguments provided in this instance. 27 | 28 | // Techincally speaking, we are fucked if a generic type has multiple generic arguments as fields in arbitrary locations since we can't pad them properly. 29 | // If that happens, we should have a compile time failure, but we probably want to handle it more reasonably. 30 | 31 | // 0x21: 0x21 bool 32 | // 0x22: padding[0x2] 33 | // 0x24: int, 0x2 padding 34 | // 0x28: bool 35 | // Padding = offset % sizeof(field) 36 | return -1; 37 | } 38 | 39 | public static int GetSize(ITypeCollection types, TypeRef type) 40 | { 41 | if (type is null) 42 | throw new ArgumentNullException(nameof(type)); 43 | if (type.IsArray() || type.IsPointer()) 44 | return Constants.PointerSize; 45 | if (type.IsGenericParameter) 46 | // Can't compute size of a generic parameter 47 | // TODO: Probably could, if it has constraints, or we just assume it's a reference type/boxed value type 48 | return -1; 49 | var td = type.Resolve(types); 50 | if (td is null) 51 | throw new InvalidOperationException("Cannot get size of something that cannot be resolved!"); 52 | if (td.Info.Refness == Refness.ReferenceType || type.IsPointer()) 53 | return Constants.PointerSize; 54 | if (type.IsGenericInstance) 55 | return GetGenericInstanceSize(types, type, td); 56 | return GetSize(types, td); 57 | } 58 | 59 | private static int GetPrimitiveSize(ITypeData type) 60 | { 61 | // Handle primitive types explicitly 62 | if (type.This.Namespace == "System") 63 | { 64 | switch (type.This.Name) 65 | { 66 | case "Boolean": 67 | case "Byte": 68 | case "SByte": 69 | return 1; 70 | 71 | case "Char": 72 | case "UInt16": 73 | case "Int16": 74 | return 2; 75 | 76 | case "Int32": 77 | case "UInt32": 78 | case "Single": 79 | return 4; 80 | 81 | case "Int64": 82 | case "UInt64": 83 | case "Double": 84 | return 8; 85 | 86 | case "Object": 87 | return 0x10; 88 | 89 | case "ValueType": 90 | return 0; 91 | } 92 | } 93 | return -1; 94 | } 95 | 96 | public static int GetSize(ITypeCollection types, ITypeData type) 97 | { 98 | if (sizeMap.TryGetValue(type, out var res)) 99 | return res; 100 | // Otherwise, we need to compute the size of this type. 101 | if (type.This.IsGeneric) 102 | { 103 | // Generic templates actually DO have a size, however, it needs to be carefully computed and adjusted when calculating the size of an instance. 104 | // Do we pad this type explicitly? Does #pragma pack(push, 8) do the trick? Probably. 105 | sizeMap.Add(type, -1); 106 | return -1; 107 | } 108 | 109 | var primSize = GetPrimitiveSize(type); 110 | if (primSize >= 0) 111 | { 112 | sizeMap.Add(type, primSize); 113 | return primSize; 114 | } 115 | var last = type.InstanceFields.LastOrDefault(); 116 | if (last is null) 117 | { 118 | if (type.Parent is null) 119 | { 120 | sizeMap.Add(type, 0); 121 | // We have 0 size if we have no parent and no fields. 122 | return 0; 123 | } 124 | // Our size is just our parent's size 125 | var pt = type.Parent.Resolve(types); 126 | if (pt is null) 127 | { 128 | sizeMap.Add(type, -1); 129 | // We probably inherit a generic type, or something similar 130 | return -1; 131 | } 132 | int parentSize = GetSize(types, type.Parent.Resolve(types)!); 133 | // Add our size to the map 134 | sizeMap.Add(type, parentSize); 135 | return parentSize; 136 | } 137 | if (type.InstanceFields.Any(f => GetSize(types, f.Type) == -1)) 138 | { 139 | // If we have any invalid fields, we take on a size of -1, even if we are potentially still capable of knowing our size. 140 | sizeMap.Add(type, -1); 141 | return -1; 142 | } 143 | bool acceptZeroOffset = type.Parent is null; 144 | if (type.Parent is not null) 145 | { 146 | // If we have a parent 147 | var resolved = type.Parent.Resolve(types); 148 | if (resolved is null) 149 | { 150 | sizeMap.Add(type, -1); 151 | return -1; 152 | } 153 | var pSize = GetSize(types, resolved); 154 | if (pSize == -1) 155 | { 156 | sizeMap.Add(type, -1); 157 | return -1; 158 | } 159 | acceptZeroOffset = GetSize(types, resolved) == 0; 160 | // If our parent's size is -1, we are also -1. 161 | } 162 | // If the offset is greater than 0, we trust it. 163 | if (last.Offset > 0) 164 | { 165 | int fSize = GetSize(types, last.Type); 166 | if (fSize <= 0) 167 | { 168 | sizeMap.Add(type, -1); 169 | return -1; 170 | } 171 | sizeMap.Add(type, fSize + last.Offset); 172 | return fSize + last.Offset; 173 | } 174 | // If we have no parent, or we have a parent of size 0, and we only have one field, then we trust offset 0 175 | if (acceptZeroOffset && type.InstanceFields.Count == 1 && last.Offset == 0) 176 | { 177 | int fSize = GetSize(types, last.Type); 178 | sizeMap.Add(type, fSize); 179 | return fSize; 180 | } 181 | // We don't know how to handle negative offsets, so we return -1 for those. 182 | return -1; 183 | } 184 | } 185 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Config/SerializationConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace Il2CppModdingCodegen.Config 7 | { 8 | public class SerializationConfig 9 | { 10 | public string? OutputDirectory { get; set; } 11 | public string? OutputHeaderDirectory { get; set; } 12 | public string? OutputSourceDirectory { get; set; } 13 | 14 | /// 15 | /// If all of the C++ code should end up in one source file instead of spanned across multiple files. 16 | /// 17 | public bool OneSourceFile { get; set; } = false; 18 | 19 | /// 20 | /// How often to chunk .cpp files when is true. 21 | /// 22 | public int ChunkFrequency { get; set; } = 500; 23 | 24 | /// 25 | /// Id for the mod to use, also the resultant library name 26 | /// 27 | public string? Id { get; set; } 28 | 29 | /// 30 | /// The (ideally SemVer) version of the library being created 31 | /// 32 | public string? Version { get; set; } 33 | 34 | /// 35 | /// Libil2cpp path, necessary for proper building of Android.mk 36 | /// 37 | public string? Libil2cpp { get; set; } 38 | 39 | /// 40 | /// To create multiple shared/static libraries or a single file 41 | /// 42 | public bool MultipleLibraries { get; set; } = false; 43 | 44 | /// 45 | /// The maximum amount of characters (not including additional characters added by make) for shared libraries 46 | /// 47 | public int SharedLibraryCharacterLimit { get; set; } = 4000; 48 | 49 | /// 50 | /// The maximum amount of characters (not including additional characters added by make) for source files 51 | /// 52 | public int SourceFileCharacterLimit { get; set; } = 4500; 53 | 54 | /// 55 | /// The maximum amount of characters (not including additional characters added by make) for static libraries 56 | /// 57 | public int StaticLibraryCharacterLimit { get; set; } = 5000; 58 | 59 | /// 60 | /// A set of illegal method, field, or type names that must be renamed. 61 | /// The renaming approach is to simply prefix with a _ until it is no longer within this set. 62 | /// 63 | public HashSet? IllegalNames { get; set; } 64 | 65 | /// 66 | /// A set of illegal method names that must be renamed. 67 | /// The renaming approach is to simply suffix with a _ until it is no longer within this set. 68 | /// 69 | public HashSet? IllegalMethodNames { get; set; } 70 | 71 | /// 72 | /// How to output the created methods 73 | /// 74 | public OutputStyle OutputStyle { get; set; } 75 | 76 | public string MacroWrap(string loggerId, string toWrap, bool isReturn) 77 | { 78 | if (toWrap is null) throw new ArgumentNullException(nameof(toWrap)); 79 | string parenWrapped = Regex.IsMatch(toWrap, @"<.*,.*>") ? $"({toWrap})" : toWrap; 80 | switch (OutputStyle) 81 | { 82 | case OutputStyle.CrashUnless: 83 | return $"CRASH_UNLESS({parenWrapped})"; 84 | 85 | case OutputStyle.ThrowUnless: 86 | return $"THROW_UNLESS({parenWrapped})"; 87 | 88 | default: 89 | if (isReturn) return toWrap; 90 | return $"RET_V_UNLESS({loggerId}, {parenWrapped})"; 91 | } 92 | } 93 | 94 | /// 95 | /// How to handle unresolved type exceptions 96 | /// 97 | public UnresolvedTypeExceptionHandlingWrapper? UnresolvedTypeExceptionHandling { get; set; } 98 | 99 | /// 100 | /// How to handle duplicate methods that are serialized 101 | /// 102 | public DuplicateMethodExceptionHandling DuplicateMethodExceptionHandling { get; set; } = DuplicateMethodExceptionHandling.DisplayInFile; 103 | 104 | /// 105 | /// Types blacklisted are explicitly not converted, even if it causes unresolved type exceptions in other types 106 | /// 107 | public List? BlacklistTypes { get; set; } 108 | 109 | /// 110 | /// Methods blacklisted are explicitly not converted 111 | /// 112 | public HashSet BlacklistMethods { get; set; } = new HashSet(); 113 | 114 | /// 115 | /// Methods here are explicitly blacklisted based off of full qualification 116 | /// 117 | public HashSet<(string @namespace, string typeName, string methodName)> QualifiedBlacklistMethods { get; set; } = new HashSet<(string @namespace, string typeName, string methodName)>(); 118 | 119 | /// 120 | /// Types whitelisted are explicitly converted, even if some have unresolved type exceptions 121 | /// 122 | public List? WhitelistTypes { get; set; } 123 | 124 | /// 125 | /// Will attempt to resolve all type exceptions (ignores blacklists and whitelists to look through full context) 126 | /// 127 | public bool EnsureReferences { get; set; } 128 | 129 | /// 130 | /// How to handle generics 131 | /// 132 | public GenericHandling GenericHandling { get; set; } 133 | 134 | /// 135 | /// To display progress using while serializing 136 | /// 137 | public bool PrintSerializationProgress { get; set; } 138 | 139 | /// 140 | /// Frequency to display progress. Only used if is true 141 | /// 142 | public int PrintSerializationProgressFrequency { get; set; } 143 | 144 | public static Dictionary SpecialMethodNames { get; private set; } = new Dictionary(); 145 | 146 | public string SafeMethodName(string name) 147 | { 148 | if (name is null) throw new ArgumentNullException(nameof(name)); 149 | if (name.StartsWith("op_")) 150 | { 151 | if (SpecialMethodNames.ContainsKey(name)) 152 | SpecialMethodNames[name]++; 153 | else 154 | SpecialMethodNames.Add(name, 1); 155 | } 156 | if (!string.IsNullOrEmpty(name)) 157 | while (IllegalNames?.Contains(name) is true || IllegalMethodNames?.Contains(name) is true) 158 | name += "_"; 159 | return name; 160 | } 161 | } 162 | 163 | public class UnresolvedTypeExceptionHandlingWrapper 164 | { 165 | public UnresolvedTypeExceptionHandling TypeHandling { get; set; } 166 | 167 | // TODO: May not work as intended 168 | public UnresolvedTypeExceptionHandling FieldHandling { get; set; } 169 | 170 | // TODO: May not work as intended 171 | public UnresolvedTypeExceptionHandling MethodHandling { get; set; } 172 | } 173 | 174 | public enum DuplicateMethodExceptionHandling 175 | { 176 | Ignore, 177 | DisplayInFile, 178 | Skip, 179 | Elevate 180 | } 181 | 182 | public enum OutputStyle 183 | { 184 | Normal, 185 | CrashUnless, 186 | ThrowUnless 187 | } 188 | 189 | public enum UnresolvedTypeExceptionHandling 190 | { 191 | Ignore, 192 | DisplayInFile, 193 | SkipIssue, 194 | 195 | /// 196 | /// Skips the current, forcing the parent to resolve it. 197 | /// In most cases, this involves forwarding field --> type, or method --> type 198 | /// So this will force TypeHandling to resolve it as if it were a higher level exception 199 | /// 200 | Elevate 201 | } 202 | 203 | public enum GenericHandling 204 | { 205 | Do, 206 | Skip 207 | } 208 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DllHandling/DllMethod.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using Mono.Cecil.Rocks; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace Il2CppModdingCodegen.Data.DllHandling 8 | { 9 | internal class DllMethod : IMethod 10 | { 11 | [System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "aids with debugging")] 12 | private readonly MethodDefinition This; 13 | 14 | public List Attributes { get; } = new List(); 15 | public List Specifiers { get; } = new List(); 16 | public long RVA { get; } 17 | public long Offset { get; } 18 | public long VA { get; } 19 | public int Slot { get; } 20 | public TypeRef ReturnType { get; } 21 | public TypeRef DeclaringType { get; } 22 | public TypeRef? ImplementedFrom { get; } = null; 23 | public List BaseMethods { get; private set; } = new List(); 24 | public List ImplementingMethods { get; } = new List(); 25 | public bool HidesBase { get; } 26 | public string Name { get; private set; } 27 | public string Il2CppName { get; } 28 | public List Parameters { get; } = new List(); 29 | public bool Generic { get; } 30 | public IReadOnlyList GenericParameters { get; } 31 | public bool IsSpecialName { get; } 32 | public bool IsVirtual { get; } 33 | 34 | // Use the specific hash comparer to ensure validity! 35 | private static readonly DllMethodDefinitionHash comparer = new DllMethodDefinitionHash(); 36 | 37 | private static readonly Dictionary cache = new Dictionary(comparer); 38 | 39 | internal static DllMethod From(MethodDefinition def, ref HashSet mappedBaseMethods) 40 | { 41 | // Note that TryGetValue is now significantly slower due to hash collisions and equality checks being expensive. 42 | // Before, it was simply pointers. 43 | if (cache.TryGetValue(def, out var m)) return m; 44 | return new DllMethod(def, ref mappedBaseMethods); 45 | } 46 | 47 | private DllMethod(MethodDefinition m, ref HashSet mappedBaseMethods) 48 | { 49 | cache.Add(m, this); 50 | This = m; 51 | // Il2CppName is the MethodDefinition Name (hopefully we don't need to convert it for il2cpp, but we might) 52 | Il2CppName = m.Name; 53 | // Fuck you parens 54 | Name = m.Name.Replace('(', '_').Replace(')', '_'); 55 | Parameters.AddRange(m.Parameters.Select(p => new Parameter(p))); 56 | Specifiers.AddRange(DllSpecifierHelpers.From(m)); 57 | // This is not necessary: m.GenericParameters.Any(param => !m.DeclaringType.GenericParameters.Contains(param)); 58 | Generic = m.HasGenericParameters; 59 | GenericParameters = m.GenericParameters.Select(g => DllTypeRef.From(g)).ToList(); 60 | 61 | // This may not always be the case, we could have a special name in which case we have to do some sorcery 62 | // Grab the special name, grab the type from the special name 63 | // This only applies to methods that are not new slot 64 | if (!m.IsNewSlot) 65 | { 66 | int idxDot = Name.LastIndexOf('.'); 67 | if (idxDot >= 2 && !Name.StartsWith('<')) 68 | { 69 | // Call a utilities function for converting a special name method to a proper base method 70 | var baseMethod = m.GetSpecialNameBaseMethod(out var iface, idxDot); 71 | if (baseMethod is null) throw new Exception("Failed to find baseMethod for dotted method name!"); 72 | if (iface is null) throw new Exception("Failed to get iface for dotted method name!"); 73 | if (!mappedBaseMethods.Add(baseMethod)) 74 | throw new InvalidOperationException($"Base method: {baseMethod} has already been overriden!"); 75 | // Only one base method for special named methods 76 | BaseMethods.Add(From(baseMethod, ref mappedBaseMethods)); 77 | ImplementedFrom = DllTypeRef.From(iface); 78 | IsSpecialName = true; 79 | } 80 | else 81 | { 82 | var baseMethod = m.GetBaseMethod(); 83 | if (baseMethod == m) 84 | { 85 | var baseMethods = m.GetBaseMethods(); 86 | if (baseMethods.Count > 0) 87 | HidesBase = true; 88 | // We need to check here SPECIFICALLY for a method in our declaring type that shares the same name as us, since we could have the same BaseMethod as it. 89 | // If either ourselves or a method of the same safe name (after . prefixes) exists, we need to ensure that only the one with the dots gets the base method 90 | // It correctly describes. 91 | // Basically, we need to take all our specially named methods on our type that have already been defined and remove them from our current list of baseMethods. 92 | // We should only ever have baseMethods of methods that are of methods that we haven't already used yet. 93 | if (baseMethods.Count > 0) 94 | foreach (var baseM in mappedBaseMethods) 95 | baseMethods.Remove(baseM); 96 | foreach (var bm in baseMethods) 97 | BaseMethods.Add(From(bm, ref mappedBaseMethods)); 98 | } 99 | else 100 | { 101 | if (!mappedBaseMethods.Add(baseMethod)) 102 | throw new InvalidOperationException($"Base method: {baseMethod} has already been overriden!"); 103 | BaseMethods.Add(From(baseMethod, ref mappedBaseMethods)); 104 | } 105 | } 106 | } 107 | if (BaseMethods.Count > 0) 108 | { 109 | // TODO: This may not be true for generic methods. Should ensure validity for IEnumerator methods 110 | // This method is an implemented/overriden method. 111 | // TODO: We need to double check to see if we need multiple ImplementedFroms 112 | ImplementedFrom = BaseMethods.First().DeclaringType; 113 | // Add ourselves to our BaseMethod's ImplementingMethods 114 | foreach (var bm in BaseMethods) 115 | bm.ImplementingMethods.Add(this); 116 | } 117 | 118 | ReturnType = DllTypeRef.From(m.ReturnType); 119 | DeclaringType = DllTypeRef.From(m.DeclaringType); 120 | // This is a very rare condition that we need to handle if it ever happens, but for now just log it 121 | if (m.HasOverrides) 122 | Console.WriteLine($"{m}.HasOverrides!!! Overrides: {string.Join(", ", m.Overrides)}"); 123 | 124 | IsVirtual = m.IsVirtual || m.IsAbstract; 125 | 126 | RVA = -1; 127 | Offset = -1; 128 | VA = -1; 129 | Slot = -1; 130 | if (m.HasCustomAttributes) 131 | foreach (var ca in m.CustomAttributes) 132 | { 133 | if (ca.AttributeType.Name == "AddressAttribute") 134 | { 135 | if (ca.Fields.Count >= 3) 136 | for (int i = 0; i < ca.Fields.Count; i++) 137 | { 138 | var f = ca.Fields[i]; 139 | if (f.Name == "RVA" || f.Name == "Offset" || f.Name == "VA") 140 | { 141 | var val = Convert.ToInt64(f.Argument.Value as string, 16); 142 | if (f.Name == "RVA") RVA = val; 143 | else if (f.Name == "Offset") Offset = val; 144 | else if (f.Name == "VA") VA = val; 145 | } 146 | else if (f.Name == "Slot") 147 | Slot = Convert.ToInt32(f.Argument.Value as string); 148 | } 149 | } 150 | else 151 | { 152 | var atr = new DllAttribute(ca); 153 | if (!string.IsNullOrEmpty(atr.Name)) 154 | Attributes.Add(atr); 155 | } 156 | } 157 | } 158 | 159 | public override string ToString() => $"{ReturnType} {Name}({Parameters.FormatParameters()})"; 160 | } 161 | } -------------------------------------------------------------------------------- /Codegen-CLI/Program.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen; 2 | using Il2CppModdingCodegen.Config; 3 | using Il2CppModdingCodegen.Data; 4 | using Il2CppModdingCodegen.Data.DllHandling; 5 | using Il2CppModdingCodegen.Parsers; 6 | using Il2CppModdingCodegen.Serialization; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Diagnostics; 10 | using System.Globalization; 11 | using System.IO; 12 | using System.Text.Json; 13 | using System.Text.Json.Serialization; 14 | 15 | namespace Codegen_CLI 16 | { 17 | internal class Program 18 | { 19 | private static void Main(string[] args) 20 | { 21 | Console.WriteLine(DateTime.UtcNow.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'")); 22 | Console.WriteLine("Drag and drop your dump.cs file (or a partial of it of the correct format) then press enter..."); 23 | string path = @"C:\Users\Sc2ad\Desktop\Code\Android Modding\BeatSaber\1.16.3\DummyDll-Inspector"; 24 | //string path = @"C:\Users\Sc2ad\Desktop\Code\Android Modding\GorillaTag\DummyDll"; 25 | if (!Directory.Exists(path)) 26 | path = Console.ReadLine().Replace("\"", string.Empty); 27 | bool parseDlls = false; 28 | IParser parser; 29 | if (Directory.Exists(path)) 30 | { 31 | var parseConfig = new DllConfig() { }; 32 | parser = new DllParser(parseConfig); 33 | parseDlls = true; 34 | } 35 | else 36 | { 37 | var parseConfig = new DumpConfig() { }; 38 | parser = new DumpParser(parseConfig); 39 | } 40 | 41 | Console.WriteLine("Parsing..."); 42 | Stopwatch watch = new(); 43 | watch.Start(); 44 | IParsedData parsed; 45 | if (parseDlls) 46 | parsed = parser.Parse(path); 47 | else 48 | { 49 | using var stream = File.OpenRead(path); 50 | parsed = parser.Parse(stream); 51 | } 52 | watch.Stop(); 53 | //Console.WriteLine(parsed); 54 | Console.WriteLine($"Parsing took: {watch.Elapsed}!"); 55 | Console.WriteLine("============================================"); 56 | Console.WriteLine("Type the name of an output style (or don't for Normal) then press enter to serialize:"); 57 | var input = "ThrowUnless"; 58 | //var input = Console.ReadLine(); 59 | // TODO: strip non-alphabetic characters out of input before parsing it 60 | if (Enum.TryParse(input, true, out OutputStyle style)) 61 | Console.WriteLine($"Parsed style '{style}'"); 62 | 63 | var libIl2cpp = @"C:\Program Files\Unity\Hub\Editor\2019.3.15f1\Editor\Data\il2cpp\libil2cpp"; 64 | if (!Directory.Exists(libIl2cpp)) 65 | { 66 | Console.WriteLine("Drag and drop your libil2cpp folder into this window then press enter:"); 67 | libIl2cpp = Console.ReadLine(); 68 | } 69 | 70 | Console.WriteLine("Creating serializer..."); 71 | var config = new SerializationConfig 72 | { 73 | OneSourceFile = true, 74 | ChunkFrequency = 100, 75 | // from https://en.cppreference.com/w/cpp/keyword 76 | IllegalNames = new HashSet { 77 | "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", "auto", 78 | "bitand", "bitor", "bool", "break", "case", "catch", "char", "char8_t", "char16_t", "char32_t", "class", 79 | "compl", "concept", "const", "consteval", "constexpr", "constinit", "const_cast", "continue", "co_await", 80 | "co_return", "co_yield", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", 81 | "explicit", "export", "extern", "false", "float", "for", "friend", "goto", "if", "inline", "int", "long", 82 | "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", 83 | "private", "protected", "public", "reflexpr", "register", "reinterpret_cast", "requires", "return", 84 | "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", 85 | "template", "this", "thread_local", "throw", "true", "try", "typedef", "typeid", "typename", "union", 86 | "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq", "INT_MAX", "INT_MIN", 87 | "Assert", "bzero", "ID", "VERSION", "NULL", "EOF", "MOD_ID" 88 | }, 89 | IllegalMethodNames = new HashSet { 90 | "bzero", "Assert" 91 | }, 92 | QualifiedBlacklistMethods = new HashSet<(string @namespace, string typeName, string methodName)> 93 | { 94 | ("UnityEngine.ResourceManagement.AsyncOperations", "AsyncOperationHandle", "Convert") 95 | }, 96 | OutputDirectory = Path.Combine(Environment.CurrentDirectory, "output"), 97 | OutputHeaderDirectory = "include", 98 | OutputSourceDirectory = "src", 99 | GenericHandling = GenericHandling.Do, 100 | OutputStyle = style, 101 | UnresolvedTypeExceptionHandling = new UnresolvedTypeExceptionHandlingWrapper 102 | { 103 | FieldHandling = UnresolvedTypeExceptionHandling.DisplayInFile, 104 | MethodHandling = UnresolvedTypeExceptionHandling.DisplayInFile, 105 | TypeHandling = UnresolvedTypeExceptionHandling.DisplayInFile 106 | }, 107 | PrintSerializationProgress = true, 108 | PrintSerializationProgressFrequency = 1000, 109 | Id = "codegen", 110 | Version = "0.2.5", 111 | Libil2cpp = libIl2cpp, 112 | }; 113 | 114 | if (config.OneSourceFile) 115 | { 116 | // If we have one source file, yeet our destination src 117 | if (Directory.Exists(Path.Combine(config.OutputDirectory, config.OutputSourceDirectory))) 118 | Directory.Delete(Path.Combine(config.OutputDirectory, config.OutputSourceDirectory), true); 119 | } 120 | Utils.Init(config); 121 | 122 | var serializer = new CppDataSerializer(config, parsed); 123 | Console.WriteLine("Resolving types..."); 124 | try 125 | { 126 | watch.Restart(); 127 | // context unused 128 | serializer.PreSerialize(null, parsed); 129 | watch.Stop(); 130 | Console.WriteLine($"Resolution Complete, took: {watch.Elapsed}!"); 131 | } 132 | catch (Exception e) 133 | { 134 | Console.WriteLine(e); 135 | } 136 | 137 | Console.WriteLine("Performing JSON dump..."); 138 | watch.Restart(); 139 | var jsonOutput = Path.Combine(Environment.CurrentDirectory, "json_output"); 140 | Directory.CreateDirectory(jsonOutput); 141 | var outp = Path.Combine(jsonOutput, "parsed.json"); 142 | if (File.Exists(outp)) 143 | File.Delete(outp); 144 | var conf = new JsonSerializerOptions 145 | { 146 | WriteIndented = true, 147 | Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping 148 | }; 149 | var strc = new SimpleTypeRefConverter(parsed.Types); 150 | conf.Converters.Add(strc); 151 | conf.Converters.Add(new TypeDataConverter(parsed, strc)); 152 | conf.Converters.Add(new MethodConverter()); 153 | conf.Converters.Add(new JsonStringEnumConverter()); 154 | conf.Converters.Add(new FieldConverter()); 155 | conf.Converters.Add(new SpecifierConverter()); 156 | conf.Converters.Add(new PropertyConverter()); 157 | using (var fs = File.OpenWrite(outp)) 158 | { 159 | JsonSerializer.SerializeAsync(fs, parsed as DllData, conf).Wait(); 160 | } 161 | watch.Stop(); 162 | Console.WriteLine($"Json Dump took: {watch.Elapsed}!"); 163 | Console.WriteLine("============================================"); 164 | 165 | Console.WriteLine("Serializing..."); 166 | try 167 | { 168 | watch.Restart(); 169 | serializer.Serialize(null, parsed, true); 170 | watch.Stop(); 171 | Console.WriteLine($"Serialization Complete, took: {watch.Elapsed}!"); 172 | } 173 | catch (Exception e) 174 | { 175 | Console.WriteLine(e); 176 | } 177 | Console.WriteLine(string.Join(", ", SerializationConfig.SpecialMethodNames)); 178 | Console.ReadLine(); 179 | } 180 | } 181 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Data/DumpHandling/DumpTypeData.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Config; 2 | using Il2CppModdingCodegen.Parsers; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics.Contracts; 6 | using System.IO; 7 | using System.Linq; 8 | 9 | namespace Il2CppModdingCodegen.Data.DumpHandling 10 | { 11 | internal class DumpTypeData : ITypeData 12 | { 13 | /// 14 | /// Number of characters the namespace name starts on 15 | /// 16 | private const int NamespaceStartOffset = 13; 17 | 18 | public TypeEnum Type { get; private set; } 19 | public TypeInfo Info { get; private set; } 20 | public TypeRef This { get; private set; } 21 | public TypeRef? Parent { get; private set; } 22 | public HashSet NestedTypes { get; } = new HashSet(); 23 | public List ImplementingInterfaces { get; } = new List(); 24 | public int TypeDefIndex { get; private set; } 25 | public List Attributes { get; } = new List(); 26 | public List Specifiers { get; } = new List(); 27 | public List InstanceFields { get; } = new List(); 28 | public List StaticFields { get; } = new List(); 29 | public List Properties { get; } = new List(); 30 | public List Methods { get; } = new List(); 31 | public ITypeData.LayoutKind Layout { get => throw new InvalidOperationException(); } 32 | 33 | private readonly DumpConfig _config; 34 | 35 | private void ParseAttributes(PeekableStreamReader fs) 36 | { 37 | var line = fs.PeekLine(); 38 | while (line != null && line.StartsWith("[")) 39 | { 40 | if (_config.ParseTypeAttributes) 41 | Attributes.Add(new DumpAttribute(fs)); 42 | else 43 | fs.ReadLine(); 44 | line = fs.PeekLine(); 45 | } 46 | } 47 | 48 | private void ParseTypeName(string @namespace, PeekableStreamReader fs) 49 | { 50 | string line = fs.ReadLine() ?? throw new InvalidDataException(); 51 | var split = line.Split(' '); 52 | TypeDefIndex = int.Parse(split[^1]); 53 | // : at least 4 from end 54 | int start = 4; 55 | bool found = false; 56 | for (int i = split.Length - 1 - start; i > 1; i--) 57 | { 58 | // Count down till we hit the : 59 | if (split[i] == ":") 60 | { 61 | start = i - 1; 62 | found = true; 63 | break; 64 | } 65 | } 66 | if (found) 67 | { 68 | // 1 after is Parent 69 | // We will assume that if the Parent type starts with an I, it is an interface 70 | // TODO: Fix this assumption, perhaps by resolving the types forcibly and ensuring they are interfaces? 71 | var parentCandidate = DumpTypeRef.FromMultiple(split, start + 2, out int tmp, 1, " ").TrimEnd(','); 72 | if (parentCandidate.StartsWith("I")) 73 | ImplementingInterfaces.Add(new DumpTypeRef(parentCandidate)); 74 | else 75 | Parent = new DumpTypeRef(parentCandidate); 76 | // Go from 2 after : to length - 3 77 | for (int i = tmp + 1; i < split.Length - 3; i++) 78 | { 79 | ImplementingInterfaces.Add(new DumpTypeRef(DumpTypeRef.FromMultiple(split, i, out tmp, 1, " ").TrimEnd(','))); 80 | i = tmp; 81 | } 82 | } 83 | else 84 | start = split.Length - start; 85 | 86 | // -4 is name 87 | // -5 is type enum 88 | // all others are specifiers 89 | // This will have DeclaringType set on it 90 | This = new DumpTypeRef(@namespace, DumpTypeRef.FromMultiple(split, start, out int adjusted, -1, " ")); 91 | Type = (TypeEnum)Enum.Parse(typeof(TypeEnum), split[adjusted - 1], true); 92 | for (int i = 0; i < adjusted - 1; i++) 93 | if (_config.ParseTypeSpecifiers) 94 | Specifiers.Add(new DumpSpecifier(split[i])); 95 | 96 | Info = new TypeInfo 97 | { 98 | Refness = Type == TypeEnum.Class || Type == TypeEnum.Interface ? Refness.ReferenceType : Refness.ValueType 99 | }; 100 | 101 | if (Parent is null) 102 | // If the type is a value type, it has no parent. 103 | // If the type is a reference type, it has parent Il2CppObject 104 | if (Info.Refness == Refness.ReferenceType) 105 | Parent = DumpTypeRef.ObjectType; 106 | Contract.Ensures(Info != null); 107 | Contract.Ensures(This != null); 108 | } 109 | 110 | private void ParseFields(PeekableStreamReader fs) 111 | { 112 | var line = fs.PeekLine()?.Trim(); 113 | if (line != "{") 114 | // Nothing in the type 115 | return; 116 | fs.ReadLine(); 117 | 118 | line = fs.PeekLine()?.Trim(); 119 | // Fields should be second line, if it isn't there are no fields. 120 | if (line is null || !line.StartsWith("// Fields")) 121 | // No fields, but other things 122 | return; 123 | // Read past // Fields 124 | fs.ReadLine(); 125 | 126 | while (!string.IsNullOrEmpty(line) && line != "}" && !line.StartsWith("// Properties") && !line.StartsWith("// Methods")) 127 | { 128 | if (_config.ParseTypeFields) 129 | { 130 | var field = new DumpField(This, fs); 131 | if (field.Specifiers.IsStatic()) 132 | StaticFields.Add(field); 133 | else 134 | InstanceFields.Add(field); 135 | } 136 | else 137 | fs.ReadLine(); 138 | line = fs.PeekLine()?.Trim(); 139 | } 140 | } 141 | 142 | private void ParseProperties(PeekableStreamReader fs) 143 | { 144 | string? line = fs.PeekLine()?.Trim(); 145 | if (!string.IsNullOrEmpty(line)) 146 | { 147 | // Spaced after fields 148 | fs.ReadLine(); 149 | line = fs.PeekLine()?.Trim(); 150 | } 151 | if (line is null || !line.StartsWith("// Properties")) 152 | // No properties 153 | return; 154 | // Read past // Properties 155 | fs.ReadLine(); 156 | 157 | while (!string.IsNullOrEmpty(line) && line != "}" && !line.StartsWith("// Methods")) 158 | { 159 | if (_config.ParseTypeProperties) 160 | Properties.Add(new DumpProperty(This, fs)); 161 | else 162 | fs.ReadLine(); 163 | line = fs.PeekLine()?.Trim(); 164 | } 165 | } 166 | 167 | private void ParseMethods(PeekableStreamReader fs) 168 | { 169 | string? line = fs.PeekLine()?.Trim(); 170 | if (string.IsNullOrEmpty(line)) 171 | { 172 | // Spaced after fields or properties 173 | fs.ReadLine(); 174 | line = fs.PeekLine()?.Trim(); 175 | } 176 | if (line is null || !line.StartsWith("// Methods")) 177 | // No methods 178 | return; 179 | // Read past // Methods 180 | fs.ReadLine(); 181 | 182 | while (!string.IsNullOrEmpty(line) && line != "}") 183 | { 184 | if (_config.ParseTypeMethods) 185 | Methods.Add(new DumpMethod(This, fs)); 186 | else 187 | fs.ReadLine(); 188 | line = fs.PeekLine()?.Trim(); 189 | } 190 | 191 | // It's important that Foo.IBar.func() goes after func() (if present) 192 | var methods = new List(Methods); 193 | Methods.Clear(); 194 | Methods.AddRange(methods.Where(m => m.ImplementedFrom is null)); 195 | Methods.AddRange(methods.Where(m => m.ImplementedFrom != null)); 196 | } 197 | 198 | internal DumpTypeData(PeekableStreamReader fs, DumpConfig config) 199 | { 200 | _config = config; 201 | // Extract namespace from line 202 | var @namespace = fs.ReadLine()?.Substring(NamespaceStartOffset).Trim() ?? ""; 203 | ParseAttributes(fs); 204 | 205 | This = null!; // this silences the "uninitialized" warnings 206 | Info = null!; // but these should actually be set in ParseTypeName 207 | ParseTypeName(@namespace, fs); 208 | if (This is null || Info is null) 209 | throw new Exception("ParseTypeName failed to properly initialize This and/or Info!"); 210 | 211 | // Empty type 212 | if (fs.PeekLine() == "{}") 213 | { 214 | fs.ReadLine(); 215 | return; 216 | } 217 | ParseFields(fs); 218 | ParseProperties(fs); 219 | ParseMethods(fs); 220 | // Read closing brace, if it needs to be read 221 | if (fs.PeekLine() == "}") 222 | fs.ReadLine(); 223 | } 224 | } 225 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Serialization/AndroidMkSerializer.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Config; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | namespace Il2CppModdingCodegen.Serialization 8 | { 9 | /// 10 | /// Serializes an Android.mk file 11 | /// 12 | public sealed class AndroidMkSerializer : System.IDisposable 13 | { 14 | internal class Library 15 | { 16 | internal IEnumerable toBuild; 17 | internal bool isSource; 18 | internal string id; 19 | public Library(string _id, bool _isSource, IEnumerable _toBuild) 20 | { 21 | id = _id; 22 | isSource = _isSource; 23 | toBuild = _toBuild; 24 | } 25 | } 26 | 27 | private const string HeaderString = @"# Copyright (C) 2009 The Android Open Source Project 28 | # 29 | # Licensed under the Apache License, Version 2.0 (the License); 30 | # you may not use this file except in compliance with the License. 31 | # You may obtain a copy of the License at 32 | # 33 | # http://www.apache.org/licenses/LICENSE-2.0 34 | # 35 | # Unless required by applicable law or agreed to in writing, software 36 | # distributed under the License is distributed on an AS IS BASIS, 37 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 38 | # See the License for the specific language governing permissions and 39 | # limitations under the License. 40 | # 41 | # 42 | # 43 | # Autogenerated by codegen 44 | LOCAL_PATH := $(call my-dir) 45 | TARGET_ARCH_ABI := $(APP_ABI) 46 | rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2)) 47 | "; 48 | 49 | private string? _filePath; 50 | private StreamWriter? _stream; 51 | private readonly SerializationConfig _config; 52 | 53 | internal AndroidMkSerializer(SerializationConfig config) 54 | { 55 | _config = config; 56 | } 57 | 58 | internal void WriteHeader(string filename) 59 | { 60 | _filePath = filename; 61 | _stream = new StreamWriter(new MemoryStream()); 62 | _stream.WriteLine(HeaderString); 63 | } 64 | 65 | /// 66 | /// Writes a static library, containing either source or library files 67 | /// 68 | /// 69 | internal void WriteStaticLibrary(Library lib) 70 | { 71 | if (_stream is null) throw new InvalidOperationException("Must call WriteHeader first!"); 72 | _stream.WriteLine("# Writing static library: " + lib.id); 73 | _stream.WriteLine("include $(CLEAR_VARS)"); 74 | _stream.WriteLine($"LOCAL_MODULE := {lib.id}"); 75 | var prefix = lib.isSource ? "LOCAL_SRC_FILES" : "LOCAL_STATIC_LIBRARIES"; 76 | foreach (var item in lib.toBuild) 77 | _stream.WriteLine(prefix + " += " + item); 78 | _stream.WriteLine("LOCAL_C_INCLUDES := ./include ./src"); 79 | _stream.WriteLine($"LOCAL_CFLAGS += -DMOD_ID='\"{_config.Id}\"' -DVERSION='\"{_config.Version}\"' -DNEED_UNSAFE_CSHARP"); 80 | _stream.WriteLine($"LOCAL_CFLAGS += -I'{_config.Libil2cpp}' -I'./extern/beatsaber-hook/shared'"); 81 | _stream.WriteLine("include $(BUILD_STATIC_LIBRARY)"); 82 | _stream.WriteLine(""); 83 | } 84 | 85 | internal void WritePrebuiltSharedLibrary(string id, string src, string include) 86 | { 87 | if (_stream is null) throw new InvalidOperationException("Must call WriteHeader first!"); 88 | _stream.WriteLine("# Writing prebuilt shared library: " + id); 89 | _stream.WriteLine("include $(CLEAR_VARS)"); 90 | _stream.WriteLine($"LOCAL_MODULE := {id}"); 91 | _stream.WriteLine("LOCAL_SRC_FILES := " + src); 92 | if (_config.OutputStyle == OutputStyle.ThrowUnless) 93 | _stream.WriteLine("LOCAL_CPP_FEATURES := exceptions"); 94 | _stream.WriteLine("LOCAL_EXPORT_C_INCLUDES := " + include); 95 | _stream.WriteLine("include $(PREBUILT_SHARED_LIBRARY)"); 96 | _stream.WriteLine(""); 97 | } 98 | 99 | internal void WriteSharedLibrary(Library lib) 100 | { 101 | if (_stream is null) throw new InvalidOperationException("Must call WriteHeader first!"); 102 | _stream.WriteLine("# Writing shared library: " + lib.id); 103 | _stream.WriteLine("include $(CLEAR_VARS)"); 104 | _stream.WriteLine($"LOCAL_MODULE := {lib.id}"); 105 | // Write bs-hook SRC. This should be substituted for linking with the bs-hook.so 106 | _stream.WriteLine("LOCAL_SRC_FILES := $(call rwildcard,extern/beatsaber-hook/shared/inline-hook/,*.cpp) $(call rwildcard,extern/beatsaber-hook/shared/utils/,*.cpp) $(call rwildcard,extern/beatsaber-hook/shared/inline-hook/,*.c)"); 107 | _stream.WriteLine("LOCAL_C_INCLUDES := ./include ./src"); 108 | _stream.WriteLine($"LOCAL_CFLAGS += -DMOD_ID='\"{_config.Id}\"' -DVERSION='\"{_config.Version}\"' -DNEED_UNSAFE_CSHARP"); 109 | _stream.WriteLine($"LOCAL_CFLAGS += -I'{_config.Libil2cpp}' -I'./extern/beatsaber-hook/shared'"); 110 | var prefix = lib.isSource ? "LOCAL_SRC_FILES" : "LOCAL_STATIC_LIBRARIES"; 111 | foreach (var item in lib.toBuild) 112 | _stream.WriteLine(prefix + " += " + item); 113 | _stream.WriteLine("LOCAL_LDLIBS := -llog"); 114 | _stream.WriteLine("include $(BUILD_SHARED_LIBRARY)"); 115 | _stream.WriteLine(""); 116 | } 117 | 118 | internal void WriteSingleFile(Library lib) 119 | { 120 | if (_stream is null) throw new InvalidOperationException("Must call WriteHeader first!"); 121 | _stream.WriteLine("# Writing single library: " + lib.id); 122 | _stream.WriteLine("include $(CLEAR_VARS)"); 123 | _stream.WriteLine($"LOCAL_MODULE := {lib.id}"); 124 | _stream.WriteLine("LOCAL_SRC_FILES += $(call rwildcard,./src,*.cpp)"); 125 | _stream.WriteLine("LOCAL_C_INCLUDES := ./include"); 126 | _stream.WriteLine($"LOCAL_CFLAGS += -DMOD_ID='\"{_config.Id}\"' -DVERSION='\"{_config.Version}\"' -DNEED_UNSAFE_CSHARP -DNO_CODEGEN_USE"); 127 | _stream.WriteLine($"LOCAL_CFLAGS += -Wall -Wextra -Werror -Wno-unused-function -isystem'{_config.Libil2cpp}' -isystem'extern'"); 128 | foreach (var l in lib.toBuild) 129 | _stream.WriteLine("LOCAL_SHARED_LIBRARIES += " + l); 130 | _stream.WriteLine("LOCAL_LDLIBS := -llog"); 131 | _stream.WriteLine("include $(BUILD_SHARED_LIBRARY)"); 132 | } 133 | 134 | private static int aggregateIdx = 0; 135 | 136 | internal void AggregateStaticLibraries(IEnumerable libs, int depth = 0) 137 | { 138 | if (_config.Id is null) throw new InvalidOperationException("config.Id should have a value!"); 139 | int innerLibsLength = 0; 140 | int outterLibsLength = 0; 141 | var innerLibs = new List(); 142 | var outterLibs = new List(); 143 | var innerNamesOnly = new List(); 144 | int idx = 0; 145 | // Iterate over each static library, add them to a partial. 146 | foreach (var l in libs) 147 | { 148 | if (innerLibsLength + l.id.Length >= _config.StaticLibraryCharacterLimit) 149 | { 150 | // If the library we are attempting to add would put us over limit, add all the old ones to a shared library 151 | var newLib = new Library(_config.Id + "_partial_" + depth + "_" + idx, false, innerNamesOnly); 152 | WriteStaticLibrary(newLib); 153 | // Reset inners 154 | innerLibsLength = 0; 155 | innerLibs.Clear(); 156 | innerNamesOnly.Clear(); 157 | // Add new lib to outters 158 | outterLibs.Add(newLib); 159 | outterLibsLength += newLib.id.Length; 160 | idx++; 161 | } 162 | innerLibs.Add(l); 163 | innerLibsLength += l.id.Length; 164 | innerNamesOnly.Add(l.id); 165 | } 166 | // If I have made exactly 0 outter libraries, I may as well just write the innerLibraries instead. 167 | if (outterLibsLength == 0) 168 | { 169 | outterLibs = innerLibs; 170 | outterLibsLength = innerLibsLength; 171 | // Actually, this could cause some issues since we could recurse forever... (given that SharedLibraryCharacterLimit and StaticLibraryCharacterLimit are not equal) 172 | // This should be changed to split into 2 if such a case happens. As it stands, it'll recurse forever in such a situation 173 | } 174 | // Upon reaching the end, we should have a list of static libraries that have been created wrapping the provided static libraries. 175 | if (outterLibsLength >= _config.SharedLibraryCharacterLimit) 176 | AggregateStaticLibraries(outterLibs, depth + 1); 177 | else 178 | { 179 | // Otherwise, simply write out the outterLibs directly, and be done! 180 | var overall = new Library(_config.Id + "_complete_" + depth + "_" + aggregateIdx, false, outterLibs.Select(l => l.id)); 181 | if (depth != 0) 182 | { 183 | WriteStaticLibrary(overall); 184 | aggregateIdx++; 185 | } 186 | else 187 | { 188 | // If we have completed depth = 0, that means we have written everything! 189 | overall.id = _config.Id; 190 | WriteSharedLibrary(overall); 191 | } 192 | } 193 | // If we find that this is too long, recurse until we are small enough 194 | } 195 | 196 | public void Close() 197 | { 198 | if (_filePath != null && _stream != null) 199 | { 200 | _stream.Flush(); 201 | CppStreamWriter.WriteIfDifferent(_filePath, _stream.BaseStream); 202 | } 203 | else 204 | Console.Error.WriteLine("Closing AndroidMkSerializer without writing anything!"); 205 | _stream?.Dispose(); 206 | } 207 | public void Dispose() => Close(); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Utils.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Config; 2 | using Il2CppModdingCodegen.Data; 3 | using Mono.Cecil; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics.CodeAnalysis; 7 | using System.Linq; 8 | using System.Text.RegularExpressions; 9 | 10 | namespace Il2CppModdingCodegen 11 | { 12 | public static class Utils 13 | { 14 | private static HashSet? illegalNames; 15 | 16 | public static void Init(SerializationConfig cfg) 17 | { 18 | illegalNames = cfg.IllegalNames; 19 | } 20 | 21 | /// 22 | /// Returns a name that is definitely not within IllegalNames 23 | /// 24 | /// 25 | /// 26 | [return: NotNullIfNotNull("name")] 27 | internal static string? SafeName(string? name) 28 | { 29 | if (name is null) return null; 30 | while (illegalNames?.Contains(name) is true) 31 | name = "_" + name; 32 | return name; 33 | } 34 | 35 | internal static string ReplaceFirst(this string text, string search, string replace) 36 | { 37 | int pos = text.IndexOf(search); 38 | if (pos < 0) 39 | return text; 40 | return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); 41 | } 42 | 43 | internal static string TrimStart(this string str, string prefix) 44 | { 45 | if (str.StartsWith(prefix)) 46 | return str.Substring(prefix.Length); 47 | return str; 48 | } 49 | 50 | internal static IEnumerable GetEnumValues() => Enum.GetValues(typeof(T)).Cast(); 51 | 52 | internal static IEnumerable GetFlagStrings(this T flags) where T : struct, Enum => 53 | // the Convert.ToBoolean filters out None (or whatever has the value 0) 54 | GetEnumValues().Where(f => Convert.ToBoolean(f) && flags.HasFlag(f)).Select(f => f.ToString()); 55 | 56 | internal static string GetFlagsString(this T flags) where T : struct, Enum => string.Join(" ", flags.GetFlagStrings()); 57 | 58 | internal static void AddOrThrow(this ISet set, T item) 59 | { 60 | if (!set.Add(item)) 61 | throw new ArgumentException("The item was already in the set!"); 62 | } 63 | 64 | internal static void RemoveOrThrow(this ISet set, T item) 65 | { 66 | if (!set.Remove(item)) 67 | throw new ArgumentException("The item was not in the set!"); 68 | } 69 | 70 | internal static TValue GetOrAdd(this IDictionary dict, TKey key, Func constructor) 71 | { 72 | if (!dict.TryGetValue(key, out var ret)) 73 | { 74 | ret = constructor(); 75 | dict.Add(key, ret); 76 | } 77 | return ret; 78 | } 79 | 80 | internal static TValue GetOrAdd(this IDictionary dict, TKey key) where TValue : new() 81 | => dict.GetOrAdd(key, () => new TValue()); 82 | 83 | internal static void AddOrUnionWith(this IDictionary> dict, TKey key, SortedSet values) 84 | { 85 | if (dict.TryGetValue(key, out var existingValues)) 86 | existingValues.UnionWith(values); 87 | else 88 | dict.Add(key, values); 89 | } 90 | 91 | private static readonly char[] angleBrackets = new char[] { '<', '>' }; 92 | 93 | internal static string SafeFieldName(this IField field) 94 | { 95 | var name = field.Name; 96 | if (name.EndsWith("k__BackingField")) 97 | name = name.Split(angleBrackets, StringSplitOptions.RemoveEmptyEntries)[0]; 98 | name = string.Join("$", name.Split(angleBrackets)).Trim('_'); 99 | if (char.IsDigit(name[0])) name = "_" + name; 100 | var sstr = name.LastIndexOf('.'); 101 | if (sstr != -1 && sstr < name.Length) 102 | name = name.Substring(sstr + 1); 103 | var tmp = SafeName(name); 104 | return tmp != "base" ? tmp : "_base"; 105 | } 106 | 107 | internal static bool HasSize(this IField field) 108 | { 109 | return field.Attributes.Find(a => a.Name.Equals("IgnoreAttribute")) is null; 110 | } 111 | 112 | // Mostly for unobtrusive break lines 113 | internal static void Noop() { } 114 | 115 | internal static TypeDefinition? ResolvedBaseType(this TypeDefinition self) 116 | { 117 | var base_type = self?.BaseType; 118 | if (base_type is null) return null; 119 | return base_type.Resolve(); 120 | } 121 | 122 | private static Dictionary GetGenerics(this TypeReference self, TypeDefinition templateType) 123 | { 124 | var map = new Dictionary(); 125 | if (!self.IsGenericInstance) 126 | return map; 127 | var instance = (GenericInstanceType)self; 128 | if (instance.GenericArguments.Count != templateType.GenericParameters.Count) 129 | // Mismatch of generic parameters. Presumably, resolved has some inherited generic parameters that it is not listing, although this should not happen. 130 | // Since !0 and !1 will occur in resolved.GenericParameters instead. 131 | throw new InvalidOperationException("instance.GenericArguments is either null or of a mismatching count compared to resolved.GenericParameters!"); 132 | for (int i = 0; i < templateType.GenericParameters.Count; i++) 133 | // Map from resolved generic parameter to self generic parameter 134 | map.Add(templateType.GenericParameters[i].Name, instance.GenericArguments[i]); 135 | return map; 136 | } 137 | 138 | private class QuickComparer : IEqualityComparer 139 | { 140 | public bool Equals(TypeReference r1, TypeReference r2) => r1?.FullName == r2?.FullName && r1?.Module.Name == r2?.Module.Name; 141 | 142 | public int GetHashCode(TypeReference r) => (r?.FullName, r?.Module.Name).GetHashCode(); 143 | }; 144 | 145 | private static readonly QuickComparer quickCompare = new QuickComparer(); 146 | 147 | // Returns all methods with the same name and parameters as `self` in any base type or interface of `type`. 148 | private static HashSet FindIn(this MethodDefinition self, TypeDefinition type, Dictionary genericMapping) 149 | { 150 | HashSet matches = new HashSet(); 151 | if (type == null) return matches; 152 | if (type != self.DeclaringType) 153 | { 154 | var sName = self.Name.Substring(self.Name.LastIndexOf('.') + 1); 155 | foreach (var m in type.Methods) 156 | { 157 | // We don't want to actually check the equivalence of these, we want to check to see if they mean the same thing. 158 | // For example, if we have a T, we want to ensure that the Ts would match 159 | // We need to ensure the name of both self and m are fixed to not have any ., use the last . and ignore generic parameters 160 | if (m.Name.Substring(m.Name.LastIndexOf('.') + 1) != sName) 161 | continue; 162 | var ret = genericMapping.GetValueOrDefault(m.ReturnType.Name, m.ReturnType); 163 | // Only if ret == self.ReturnType, can we have a match 164 | if (!quickCompare.Equals(ret, self.ReturnType)) 165 | continue; 166 | if (m.Parameters.Count != self.Parameters.Count) 167 | continue; 168 | 169 | var mParams = m.Parameters.Select( 170 | p => genericMapping.GetValueOrDefault(p.ParameterType.Name, p.ParameterType)); 171 | if (mParams.SequenceEqual(self.Parameters.Select(p => p.ParameterType), quickCompare)) 172 | matches.Add(m); 173 | } 174 | } 175 | 176 | var bType = type.ResolvedBaseType(); 177 | if (bType != null) 178 | matches.UnionWith(self.FindIn(bType, type.GetGenerics(bType))); 179 | foreach (var @interface in type.Interfaces) 180 | { 181 | var resolved = @interface.InterfaceType.Resolve(); 182 | matches.UnionWith(self.FindIn(resolved, @interface.InterfaceType.GetGenerics(resolved))); 183 | } 184 | return matches; 185 | } 186 | 187 | private static TypeReference? FindInterface(TypeReference? type, string find) 188 | { 189 | if (type is null) return null; 190 | var typeStr = Regex.Replace(type.ToString(), @"`\d+", "").Replace('/', '.'); 191 | if (typeStr == find) 192 | return type; 193 | 194 | var def = type.Resolve(); 195 | if (def is null) return null; 196 | foreach (var iface in def.Interfaces) 197 | { 198 | var ret = FindInterface(iface.InterfaceType, find); 199 | if (ret != null) return ret; 200 | } 201 | return FindInterface(def.BaseType, find); 202 | } 203 | 204 | internal static MethodDefinition? GetSpecialNameBaseMethod(this MethodDefinition self, out TypeReference? iface, int idxDot = -1) 205 | { 206 | iface = null; 207 | if (idxDot == -1) 208 | idxDot = self.Name.LastIndexOf('.'); 209 | if (idxDot < 2) 210 | return null; 211 | var typeStr = self.Name.Substring(0, idxDot); 212 | iface = FindInterface(self.DeclaringType, typeStr); 213 | if (iface is null) 214 | return null; 215 | var tName = self.Name.Substring(idxDot + 1); 216 | return iface.Resolve().Methods.Where(im => im.Name == tName && self.Parameters.Count == im.Parameters.Count).Single(); 217 | } 218 | 219 | /// 220 | /// Returns all methods with the same name, parameters, and return type as in any base type or interface of 221 | /// Returns an empty set if no matching methods are found. Does not include in the search. 222 | /// 223 | /// 224 | /// 225 | internal static HashSet GetBaseMethods(this MethodDefinition self) 226 | { 227 | if (self is null) throw new ArgumentNullException(nameof(self)); 228 | // Whenever we call GetBaseMethods, we should explicitly exclude all base methods that are specifically defined by self.DeclaringType via special names already. 229 | // We would ideally do this by compiling a list of special named methods, and for each of those, explicitly excluding them from our matches. 230 | // However, this means that we need to be able to convert a special name to a base method, which means we need an extension method for it here. 231 | // TODO: This list should be generated once and then cached. 232 | var specialBaseMethods = self.DeclaringType.Methods.Select(m => m.GetSpecialNameBaseMethod(out var iface)).Where(md => md != null); 233 | var matches = self.FindIn(self.DeclaringType, self.DeclaringType.GetGenerics(self.DeclaringType.Resolve())); 234 | foreach (var sbm in specialBaseMethods) 235 | matches.Remove(sbm!); 236 | return matches; 237 | } 238 | } 239 | } -------------------------------------------------------------------------------- /Il2Cpp-Modding-Codegen/Serialization/CppStaticFieldSerializer.cs: -------------------------------------------------------------------------------- 1 | using Il2CppModdingCodegen.Config; 2 | using Il2CppModdingCodegen.Data; 3 | using Il2CppModdingCodegen.Data.DllHandling; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace Il2CppModdingCodegen.Serialization 10 | { 11 | public class CppStaticFieldSerializer : Serializer 12 | { 13 | private string? _declaringFullyQualified; 14 | private string? _thisTypeName; 15 | private string? _declaringLiteral; 16 | private readonly Dictionary _resolvedTypes = new Dictionary(); 17 | private bool _asHeader; 18 | private readonly SerializationConfig _config; 19 | 20 | private struct Constant 21 | { 22 | public string name; 23 | public string type; 24 | public string value; 25 | 26 | public Constant(string name_, string type_, string value_) 27 | { 28 | name = name_; 29 | type = type_; 30 | value = value_; 31 | } 32 | }; 33 | 34 | private readonly Dictionary _constants = new Dictionary(); 35 | 36 | internal CppStaticFieldSerializer(SerializationConfig config) 37 | { 38 | _config = config; 39 | } 40 | 41 | private static string Encode(string value) 42 | { 43 | // This should replace any characters not in the typical ASCII printable range. 44 | static ushort FirstChar(Match match) => match.Value[0]; 45 | // Need to escape \ and " 46 | return Regex.Replace(value.Replace(@"\", @"\\").Replace("\"", "\\\""), @"[^ -~]", match => $"\\u{FirstChar(match):x4}"); 47 | } 48 | 49 | private static TypeRef GetEnumUnderlyingType(ITypeData self) 50 | => self.InstanceFields.FirstOrDefault()?.Type ?? throw new ArgumentException("should be an Enum type!", nameof(self)); 51 | 52 | public override void PreSerialize(CppTypeContext context, IField field) 53 | { 54 | if (context is null) throw new ArgumentNullException(nameof(context)); 55 | if (field is null) throw new ArgumentNullException(nameof(field)); 56 | _declaringFullyQualified = context.QualifiedTypeName.TrimStart(':'); 57 | _declaringLiteral = context.GetCppName(field.DeclaringType, false, false, forceAsType: CppTypeContext.ForceAsType.Literal); 58 | _thisTypeName = context.GetCppName(field.DeclaringType, false, needAs: CppTypeContext.NeedAs.Definition); 59 | var resolvedName = context.GetCppName(field.Type, true); 60 | _resolvedTypes.Add(field, resolvedName); 61 | if (resolvedName != null) 62 | { 63 | // Add static field to forward declares, since it is used by the static _get and _set methods 64 | Resolved(field); 65 | 66 | if (field is DllField dllField) 67 | if (dllField.This.Constant != null) 68 | { 69 | string name = SafeName(field); 70 | string type = ""; 71 | string value = ""; 72 | var resolved = context.ResolveAndStore(field.Type, forceAs: CppTypeContext.ForceAsType.None); 73 | if (resolved?.Type == TypeEnum.Enum || !resolvedName.Any(char.IsUpper)) 74 | { 75 | if (resolved?.Type == TypeEnum.Enum && name == "value") 76 | name = "Value"; 77 | var val = $"{dllField.This.Constant}"; 78 | if (val == "True" || val == "False" || Regex.IsMatch(val, @"-?(?:[\d\.]|E[\+\-])+")) 79 | { 80 | var temp = (resolved?.Type == TypeEnum.Enum) ? context.GetCppName(GetEnumUnderlyingType(resolved), true) : resolvedName; 81 | if (temp is null) throw new Exception($"Failed to get C++ type for {field.Type}"); 82 | type = temp; 83 | if (type.Contains("uint")) val += "u"; 84 | value = val.ToLower(); 85 | if (value == long.MinValue.ToString()) 86 | value = (long.MinValue + 1).ToString() + " - 1"; 87 | if (type == "int8_t" && value == "128") 88 | value = "-128"; 89 | if (type == "float" || type == "double" || type == "long double") 90 | value = value.Replace(',', '.'); 91 | } 92 | else 93 | Console.WriteLine($"{field.DeclaringType}'s {resolvedName} {field.Name} has constant that is not valid C++: {val}"); 94 | } 95 | else if (resolvedName.StartsWith($"::{Constants.StringCppName}")) 96 | { 97 | var str = (string)dllField.This.Constant; 98 | var encodedStr = Encode(str); 99 | type = "char*"; 100 | value = $"\"{encodedStr}\""; 101 | } 102 | else if (resolvedName.StartsWith("::Il2CppChar")) 103 | { 104 | char c = (char)dllField.This.Constant; 105 | var encodedStr = Encode(c.ToString()); 106 | type = resolvedName; 107 | value = $"u'{encodedStr}'"; 108 | } 109 | else 110 | throw new Exception($"Unhandled constant type {resolvedName}!"); 111 | 112 | if (!string.IsNullOrEmpty(type) && !string.IsNullOrEmpty(value)) 113 | _constants.Add(field, new Constant(name, type, value)); 114 | } 115 | else if (dllField.This.HasDefault) 116 | Console.WriteLine($"TODO for {field.DeclaringType}'s {resolvedName} {field.Name}: figure out how to get default values??"); 117 | } 118 | } 119 | 120 | private static string SafeConfigName(string name) => Utils.SafeName(name.Replace('<', '$').Replace('>', '$')); 121 | 122 | private string SafeName(IField field) 123 | { 124 | string name = field.Name; 125 | if (name == _declaringLiteral) 126 | name = "_" + name; 127 | return SafeConfigName(name); 128 | } 129 | 130 | private string GetGetter(string fieldType, IField field, bool namespaceQualified) 131 | { 132 | var retStr = fieldType; 133 | if (_config.OutputStyle == OutputStyle.Normal) 134 | retStr = "std::optional<" + retStr + ">"; 135 | var staticStr = string.Empty; 136 | var ns = string.Empty; 137 | if (namespaceQualified) 138 | ns = _declaringFullyQualified + "::"; 139 | if (_asHeader) 140 | staticStr = "static "; 141 | // Collisions with this name are incredibly unlikely. 142 | return $"{staticStr + retStr} {ns}{SafeConfigName($"_get_{field.Name}")}()"; 143 | } 144 | 145 | private string GetSetter(string fieldType, IField field, bool namespaceQualified) 146 | { 147 | var ns = string.Empty; 148 | var staticStr = string.Empty; 149 | if (namespaceQualified) 150 | ns = _declaringFullyQualified + "::"; 151 | if (_asHeader) 152 | staticStr = "static "; 153 | return $"{staticStr}void {ns}{SafeConfigName($"_set_{field.Name}")}({fieldType} value)"; 154 | } 155 | 156 | public override void Serialize(CppStreamWriter writer, IField field, bool asHeader) 157 | { 158 | if (writer is null) throw new ArgumentNullException(nameof(writer)); 159 | if (field is null) throw new ArgumentNullException(nameof(field)); 160 | _asHeader = asHeader; 161 | if (_resolvedTypes[field] is null) 162 | throw new UnresolvedTypeException(field.DeclaringType, field.Type); 163 | string resolvedType = _resolvedTypes[field]!; 164 | // Write attributes as [] 165 | foreach (var attr in field.Attributes) 166 | { 167 | if (attr.Offset != -1) 168 | writer.WriteComment($"[{attr.Name}] Offset: 0x{attr.Offset:X}"); 169 | } 170 | var fieldCommentString = ""; 171 | foreach (var spec in field.Specifiers) 172 | fieldCommentString += $"{spec} "; 173 | fieldCommentString += $"{field.Type} {field.Name}"; 174 | if (_asHeader && !field.DeclaringType.IsGenericTemplate) 175 | { 176 | if (_constants.TryGetValue(field, out var constant)) 177 | { 178 | writer.WriteComment("static field const value: " + fieldCommentString); 179 | writer.WriteDeclaration($"static constexpr const {constant.type} {constant.name} = {constant.value}"); 180 | } 181 | // Create two method declarations: 182 | // static FIELDTYPE _get_FIELDNAME(); 183 | // static void _set_FIELDNAME(FIELDTYPE value); 184 | writer.WriteComment("Get static field: " + fieldCommentString); 185 | writer.WriteDeclaration(GetGetter(resolvedType, field, !_asHeader)); 186 | writer.WriteComment("Set static field: " + fieldCommentString); 187 | writer.WriteDeclaration(GetSetter(resolvedType, field, !_asHeader)); 188 | } 189 | else 190 | { 191 | var (@namespace, @class) = field.DeclaringType.GetIl2CppName(); 192 | var classArgs = $"\"{@namespace}\", \"{@class}\""; 193 | if (field.DeclaringType.IsGeneric) 194 | classArgs = $"il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class<{_thisTypeName}>::get()"; 195 | 196 | // Write getter 197 | writer.WriteComment("Autogenerated static field getter"); 198 | writer.WriteComment("Get static field: " + fieldCommentString); 199 | writer.WriteDefinition(GetGetter(resolvedType, field, !_asHeader)); 200 | 201 | // TODO: Check invalid name 202 | var loggerId = "___internal__logger"; 203 | 204 | writer.WriteDeclaration($"static auto {loggerId} = ::Logger::get()" + 205 | $".WithContext(\"{field.DeclaringType.GetQualifiedCppName()}::{SafeConfigName($"_get_{field.Name}")}\")"); 206 | 207 | var innard = $"<{resolvedType}>"; 208 | var call = $"il2cpp_utils::GetFieldValue{innard}("; 209 | call += $"{classArgs}, \"{field.Name}\")"; 210 | writer.WriteDeclaration("return " + _config.MacroWrap(loggerId, call, true)); 211 | writer.CloseDefinition(); 212 | 213 | // Write setter 214 | writer.WriteComment("Autogenerated static field setter"); 215 | writer.WriteComment("Set static field: " + fieldCommentString); 216 | writer.WriteDefinition(GetSetter(resolvedType, field, !_asHeader)); 217 | 218 | writer.WriteDeclaration($"static auto {loggerId} = ::Logger::get()" + 219 | $".WithContext(\"{field.DeclaringType.GetQualifiedCppName()}::{SafeConfigName($"_set_{field.Name}")}\")"); 220 | 221 | call = $"il2cpp_utils::SetFieldValue("; 222 | call += $"{classArgs}, \"{field.Name}\", value)"; 223 | writer.WriteDeclaration(_config.MacroWrap(loggerId, call, false)); 224 | writer.CloseDefinition(); 225 | } 226 | writer.Flush(); 227 | Serialized(field); 228 | } 229 | } 230 | } --------------------------------------------------------------------------------