From 964685e44a255826aba085729121f107c5d4668a Mon Sep 17 00:00:00 2001 From: Katy Coe Date: Mon, 20 Jul 2020 06:14:31 +0200 Subject: [PATCH] Extract Unity version range management from UnityHeader to UnityVersionRange --- .../Cpp/UnityHeaders/UnityHeader.cs | 70 ++++++++++++------- .../Cpp/UnityHeaders/UnityVersion.cs | 58 ++++++++++++++- Il2CppInspector.Common/Model/AppModel.cs | 2 +- Il2CppInspector.GUI/MainWindow.xaml.cs | 4 +- 4 files changed, 103 insertions(+), 31 deletions(-) diff --git a/Il2CppInspector.Common/Cpp/UnityHeaders/UnityHeader.cs b/Il2CppInspector.Common/Cpp/UnityHeaders/UnityHeader.cs index 392c1f7..c11faa8 100644 --- a/Il2CppInspector.Common/Cpp/UnityHeaders/UnityHeader.cs +++ b/Il2CppInspector.Common/Cpp/UnityHeaders/UnityHeader.cs @@ -20,34 +20,18 @@ namespace Il2CppInspector.Cpp.UnityHeaders public double MetadataVersion { get; } // Minimum and maximum Unity version numbers corresponding to this header. Both endpoints are inclusive - public UnityVersion MinVersion { get; } - public UnityVersion MaxVersion { get; } + public UnityVersionRange Version { get; } // Filename for the embedded .h resource file containing the header public string HeaderFilename { get; } private UnityHeader(string headerFilename) { HeaderFilename = headerFilename; - var bits = headerFilename.Replace(".h", "").Split("-"); - MetadataVersion = double.Parse(bits[0], NumberFormatInfo.InvariantInfo); - MinVersion = new UnityVersion(bits[1]); - if (bits.Length == 2) - MaxVersion = MinVersion; - else if (bits[2] != "") - MaxVersion = new UnityVersion(bits[2]); + Version = UnityVersionRange.FromFilename(HeaderFilename); + MetadataVersion = double.Parse(headerFilename.Split("-")[0], NumberFormatInfo.InvariantInfo); } - public override string ToString() { - var res = $"{MinVersion}"; - if (MaxVersion == null) - res += "+"; - else if (MaxVersion != MinVersion) - res += $" - {MaxVersion}"; - return res; - } - - // Determine if this header supports the given version of Unity - public bool Contains(UnityVersion version) => version.CompareTo(MinVersion) >= 0 && (MaxVersion == null || version.CompareTo(MaxVersion) <= 0); + public override string ToString() => Version.ToString(); // Return the contents of this header file as a string public string GetHeaderText() { @@ -62,7 +46,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders return result; } - // List all header files embedded into this build of Il2Cpp + // List all header files embedded into this build of Il2CppInspector public static IEnumerable GetAllHeaders() { string prefix = typeof(UnityHeader).Namespace + "."; Assembly assembly = Assembly.GetExecutingAssembly(); @@ -71,26 +55,58 @@ namespace Il2CppInspector.Cpp.UnityHeaders .Select(s => new UnityHeader(s.Substring(prefix.Length))); } + // List all API header files and versions embedded into this build of Il2CppInspector + public static IEnumerable<(string resourceName, UnityVersion minVersion, UnityVersion maxVersion)> GetAPIList() { + string prefix = "Il2CppInspector.Cpp.Il2CppAPIHeaders."; + Assembly assembly = Assembly.GetExecutingAssembly(); + var versions = new List<(string resourceName, UnityVersion minVersion, UnityVersion maxVersion)>(); + + foreach (var headerFilename in assembly.GetManifestResourceNames().Where(s => s.StartsWith(prefix) && s.EndsWith(".h"))) { + var bits = headerFilename.Substring(prefix.Length).Replace(".h", "").Split("-"); + var min = new UnityVersion(bits[0]); + UnityVersion max = min; + if (bits.Length == 2 && bits[1] != "") + max = new UnityVersion(bits[1]); + versions.Add((headerFilename, min, max)); + } + return versions; + } + // Get the header file which supports the given version of Unity public static UnityHeader GetHeaderForVersion(string version) => GetHeaderForVersion(new UnityVersion(version)); - public static UnityHeader GetHeaderForVersion(UnityVersion version) => GetAllHeaders().Where(v => v.Contains(version)).First(); + public static UnityHeader GetHeaderForVersion(UnityVersion version) => GetAllHeaders().First(h => h.Version.Contains(version)); + + public static string GetAPIResourceNameForVersion(UnityVersion version) => + GetAPIList().First(v => version.CompareTo(v.minVersion) >= 0 && (v.maxVersion == null || version.CompareTo(v.maxVersion) <= 0)).resourceName; + + public static string GetAPITextForVersion(UnityVersion version) { + var apiResource = GetAPIResourceNameForVersion(version); + Assembly assembly = Assembly.GetCallingAssembly(); + using Stream stream = assembly.GetManifestResourceStream(apiResource); + if (stream == null) { + throw new FileNotFoundException(apiResource); + } + using StreamReader reader = new StreamReader(stream); + string result = reader.ReadToEnd(); + return result; + } // Guess which header file(s) correspond to the given metadata+binary. // Note that this may match multiple headers due to structural changes between versions // that are not reflected in the metadata version. public static List GuessHeadersForModel(Reflection.TypeModel model) { List result = new List(); - foreach (var v in GetAllHeaders()) { - if (v.MetadataVersion != model.Package.BinaryImage.Version) + foreach (var h in GetAllHeaders()) { + if (h.MetadataVersion != model.Package.BinaryImage.Version) continue; - if (v.MetadataVersion == 21) { + if (h.MetadataVersion == 21) { /* Special version logic for metadata version 21 based on the Il2CppMetadataRegistration.fieldOffsets field */ - var headerFieldOffsetsArePointers = v.MinVersion.CompareTo("5.3.7") >= 0 && v.MinVersion.CompareTo("5.4.0") != 0; + var headerFieldOffsetsArePointers = h.Version.Min.CompareTo("5.3.7") >= 0 && h.Version.Min.CompareTo("5.4.0") != 0; var binaryFieldOffsetsArePointers = model.Package.Binary.FieldOffsets == null; if (headerFieldOffsetsArePointers != binaryFieldOffsetsArePointers) continue; } - result.Add(v); + result.Add(h); } return result; } diff --git a/Il2CppInspector.Common/Cpp/UnityHeaders/UnityVersion.cs b/Il2CppInspector.Common/Cpp/UnityHeaders/UnityVersion.cs index 19ca8f3..42ab697 100644 --- a/Il2CppInspector.Common/Cpp/UnityHeaders/UnityVersion.cs +++ b/Il2CppInspector.Common/Cpp/UnityHeaders/UnityVersion.cs @@ -6,6 +6,8 @@ */ using System; +using System.Globalization; +using System.Linq; using System.Text.RegularExpressions; namespace Il2CppInspector.Cpp.UnityHeaders @@ -110,4 +112,58 @@ namespace Il2CppInspector.Cpp.UnityHeaders return HashCode.Combine(Major, Minor, Update, BuildType, BuildNumber); } } -} + + // A range of Unity versions + public class UnityVersionRange + { + // Minimum and maximum Unity version numbers for this range. Both endpoints are inclusive + public UnityVersion Min { get; } + public UnityVersion Max { get; } + + // Determine if this range contains the specified version + public bool Contains(UnityVersion version) => version.CompareTo(Min) >= 0 && (Max == null || version.CompareTo(Max) <= 0); + + public UnityVersionRange(UnityVersion min, UnityVersion max) { + Min = min; + Max = max; + } + + // Create a version range from a string, in the format "[Il2CppInspector.Cpp..][metadataVersion-]-[max].h" + public static UnityVersionRange FromFilename(string headerFilename) { + var baseNamespace = "Il2CppInspector.Cpp."; + headerFilename = headerFilename.Replace(".h", ""); + + if (headerFilename.StartsWith(baseNamespace)) { + headerFilename = headerFilename.Substring(baseNamespace.Length); + headerFilename = headerFilename.Substring(headerFilename.IndexOf(".") + 1); + } + + var bits = headerFilename.Split("-"); + + // Metadata version supplied + // Note: This relies on the metadata version being either 2 or 4 characters, + // and that the smallest Unity version must be 5 characters or more + if (headerFilename[2] == '-' || headerFilename[4] == '-') + bits = bits.Skip(1).ToArray(); + + var Min = new UnityVersion(bits[0]); + UnityVersion Max = null; + + if (bits.Length == 1) + Max = Min; + if (bits.Length == 2 && bits[1] != "") + Max = new UnityVersion(bits[1]); + + return new UnityVersionRange(Min, Max); + } + + public override string ToString() { + var res = $"{Min}"; + if (Max == null) + res += "+"; + else if (Max != Min) + res += $" - {Max}"; + return res; + } + } +} \ No newline at end of file diff --git a/Il2CppInspector.Common/Model/AppModel.cs b/Il2CppInspector.Common/Model/AppModel.cs index dcb367a..4ffc957 100644 --- a/Il2CppInspector.Common/Model/AppModel.cs +++ b/Il2CppInspector.Common/Model/AppModel.cs @@ -117,7 +117,7 @@ namespace Il2CppInspector.Model // Determine Unity version and get headers UnityHeader = unityVersion != null ? UnityHeader.GetHeaderForVersion(unityVersion) : UnityHeader.GuessHeadersForModel(ILModel)[0]; - UnityVersion = unityVersion ?? UnityHeader.MinVersion; + UnityVersion = unityVersion ?? UnityHeader.Version.Min; // Check for matching metadata and binary versions if (UnityHeader.MetadataVersion != ILModel.Package.BinaryImage.Version) { diff --git a/Il2CppInspector.GUI/MainWindow.xaml.cs b/Il2CppInspector.GUI/MainWindow.xaml.cs index 6c3d5fa..7f319e0 100644 --- a/Il2CppInspector.GUI/MainWindow.xaml.cs +++ b/Il2CppInspector.GUI/MainWindow.xaml.cs @@ -406,7 +406,7 @@ namespace Il2CppInspectorGUI var outFile = scriptSaveFileDialog.FileName; areaBusyIndicator.Visibility = Visibility.Visible; - var selectedVersion = ((UnityHeader)cboUnityVersion.SelectedItem)?.MinVersion; + var selectedVersion = ((UnityHeader) cboUnityVersion.SelectedItem)?.Version.Min; await Task.Run(() => { OnStatusUpdate(this, "Building C++ application model"); model.Build(selectedVersion, CppCompilerType.GCC); @@ -430,7 +430,7 @@ namespace Il2CppInspectorGUI var cppOutPath = cppSaveFolderDialog.SelectedPath; areaBusyIndicator.Visibility = Visibility.Visible; - var selectedCppUnityVersion = ((UnityHeader)cboCppUnityVersion.SelectedItem)?.MinVersion; + var selectedCppUnityVersion = ((UnityHeader) cboCppUnityVersion.SelectedItem)?.Version.Min; var cppCompiler = (CppCompilerType) Enum.Parse(typeof(CppCompilerType), cboCppCompiler.SelectionBoxItem.ToString()); await Task.Run(() => { OnStatusUpdate(this, "Building C++ application model");