Files
Cpp2IL/LibCpp2IL/LibCpp2IlMain.cs
Sam Byass 17bcb005f7 Core+Lib: Stop using Il2CppTypeReflectionData in most places, it's not a good data type.
Also fixes silent coercion of SZArray types to Array[1] in various places in analysis
2026-07-25 16:21:39 +01:00

251 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using AssetRipper.Primitives;
using LibCpp2IL.BinaryStructures;
using LibCpp2IL.Logging;
using LibCpp2IL.Metadata;
using LibCpp2IL.Reflection;
namespace LibCpp2IL;
public static class LibCpp2IlMain
{
public delegate byte[]? MetadataFixupFunc(byte[] originalBytes, UnityVersion unityVersion);
private static readonly Regex UnityVersionRegex = new Regex(@"^[0-9]+\.[0-9]+\.[0-9]+[abcfxp][0-9]+$", RegexOptions.Compiled);
public class LibCpp2IlSettings
{
public bool AllowManualMetadataAndCodeRegInput;
public bool DisableMethodPointerMapping;
public bool DisableGlobalResolving;
public MetadataFixupFunc? MetadataFixupFunc; //If set, this will be called if metadata fails to parse, to allow you to attempt to fix it.
}
public static readonly LibCpp2IlSettings Settings = new();
/// <summary>
/// Initialize the metadata and binary from a pair of byte arrays, returning a context.
/// </summary>
public static LibCpp2IlContext InitializeAsContext(byte[] binaryBytes, byte[] metadataBytes, UnityVersion unityVersion)
=> LibCpp2IlContextBuilder.Build(binaryBytes, metadataBytes, unityVersion);
/// <summary>
/// Initialize the metadata and binary from file paths, returning a context.
/// </summary>
public static LibCpp2IlContext LoadFromFileAsContext(string pePath, string metadataPath, UnityVersion unityVersion)
=> LibCpp2IlContextBuilder.BuildFromFiles(pePath, metadataPath, unityVersion);
/// <summary>
/// Attempts to determine the Unity version from the given binary path and game data path
/// </summary>
/// <param name="unityPlayerPath">The path to the unity player executable - either the executable itself or [lib]unityplayer[.dll]</param>
/// <param name="gameDataPath">The path to the GameName_Data folder, from which assets files can be read.</param>
/// <returns>A valid unity version if one can be read, else 0.0.0a0</returns>
public static UnityVersion DetermineUnityVersion(string? unityPlayerPath, string? gameDataPath)
{
//We prefer pulling from assets because it gives a full version number (i.e. including what is usually an f1 at the end)
//But we can fall back to the unity player if we have to (and are on windows)
if (!string.IsNullOrEmpty(gameDataPath))
{
LibLogger.VerboseNewline($"DetermineUnityVersion: Have game data path {gameDataPath}, trying to pull version from globalgamemanagers or data.unity3d");
//Globalgamemanagers
var globalgamemanagersPath = Path.Combine(gameDataPath, "globalgamemanagers");
if (File.Exists(globalgamemanagersPath))
{
LibLogger.VerboseNewline($"DetermineUnityVersion: globalgamemanagers exists, pulling version from it");
var ggmBytes = File.ReadAllBytes(globalgamemanagersPath);
return GetVersionFromGlobalGameManagers(ggmBytes);
}
//Data.unity3d
var dataPath = Path.Combine(gameDataPath, "data.unity3d");
if (File.Exists(dataPath))
{
LibLogger.VerboseNewline($"DetermineUnityVersion: data.unity3d exists, pulling version from it");
using var dataStream = File.OpenRead(dataPath);
return GetVersionFromDataUnity3D(dataStream);
}
LibLogger.VerboseNewline($"DetermineUnityVersion: No globalgamemanagers or data.unity3d found in game data path.");
}
if (Environment.OSVersion.Platform == PlatformID.Win32NT && !string.IsNullOrEmpty(unityPlayerPath))
{
LibLogger.VerboseNewline($"DetermineUnityVersion: Running on windows so have FileVersionInfo, trying to pull version from unity player {unityPlayerPath}");
var unityVer = FileVersionInfo.GetVersionInfo(unityPlayerPath);
if (unityVer.FileMajorPart > 0)
return new UnityVersion((ushort)unityVer.FileMajorPart, (ushort)unityVer.FileMinorPart, (ushort)unityVer.FileBuildPart);
LibLogger.VerboseNewline($"DetermineUnityVersion: FileVersionInfo gave useless result.");
}
LibLogger.VerboseNewline($"DetermineUnityVersion: All methods to determine unity version failed!");
return default;
}
/// <summary>
/// Attempts to determine the Unity version from the given globalgamemanagers file
/// </summary>
/// <param name="ggmBytes">The bytes making up the globalgamemanagers asset file</param>
/// <returns>A valid unity version if one can be read, else 0.0.0a0</returns>
public static UnityVersion GetVersionFromGlobalGameManagers(byte[] ggmBytes)
{
var verString = new StringBuilder();
var idx = 0x14;
while (ggmBytes[idx] != 0)
{
verString.Append(Convert.ToChar(ggmBytes[idx]));
idx++;
}
string unityVer = verString.ToString();
if (!UnityVersionRegex.IsMatch(unityVer))
{
idx = 0x30;
verString = new StringBuilder();
while (ggmBytes[idx] != 0)
{
verString.Append(Convert.ToChar(ggmBytes[idx]));
idx++;
}
unityVer = verString.ToString().Trim();
}
return UnityVersion.Parse(unityVer);
}
/// <summary>
/// Attempts to determine the Unity version from the given data.unity3d file
/// </summary>
/// <param name="fileStream">A stream referencing the data.unity3d file. A stream is used instead of a byte array because these files can be very large. Only the first 30-or-so bytes are used.</param>
/// <returns>A valid unity version if one can be read, else 0.0.0a0</returns>
public static UnityVersion GetVersionFromDataUnity3D(Stream fileStream)
{
//data.unity3d is a bundle file and it's used on later unity versions.
//These files are usually really large and we only want the first couple bytes, so it's done via a stream.
//e.g.: Secret Neighbour
//Fake unity version at 0xC, real one at 0x12
var verString = new StringBuilder();
if (fileStream.CanSeek)
fileStream.Seek(0x12, SeekOrigin.Begin);
else
{
if (fileStream.Read(new byte[0x12], 0, 0x12) != 0x12)
throw new("Failed to seek to 0x12 in data.unity3d");
}
while (true)
{
var read = fileStream.ReadByte();
if (read == 0)
{
//I'm using a while true..break for this, shoot me.
break;
}
verString.Append(Convert.ToChar(read));
}
var unityVer = verString.ToString().Trim();
return UnityVersion.Parse(unityVer);
}
#region Legacy static API — kept for backwards compatibility during migration
// These fields exist solely to allow external consumers to keep working during the transition to context-based APIs.
// New code should use LibCpp2IlContext directly.
private static LibCpp2IlContext? _defaultContext;
[Obsolete("Use LibCpp2IlContext instead.")]
public static LibCpp2IlContext DefaultContext
{
get => _defaultContext ?? throw new InvalidOperationException("LibCpp2IL is not Initialized");
set => _defaultContext = value;
}
[Obsolete("Use LibCpp2IlContext.Binary instead.")]
public static Il2CppBinary Binary => DefaultContext.Binary;
[Obsolete("Use LibCpp2IlContext.Metadata instead.")]
public static Il2CppMetadata TheMetadata => DefaultContext.Metadata;
[Obsolete("Use context.Il2CppTypeHasNumMods5Bits instead.")]
public static bool Il2CppTypeHasNumMods5Bits => DefaultContext.Il2CppTypeHasNumMods5Bits;
[Obsolete("Use LibCpp2IlContext.MetadataVersion instead.")]
public static float MetadataVersion => DefaultContext.Metadata.MetadataVersion;
[Obsolete("Use LibCpp2IlContext.MethodsByPtr instead.")]
public static Dictionary<ulong, List<Il2CppMethodDefinition>> MethodsByPtr => DefaultContext.MethodsByPtr;
[Obsolete("Use LibCpp2IlContextBuilder directly.")]
public static bool Initialize(byte[] binaryBytes, byte[] metadataBytes, UnityVersion unityVersion)
{
_defaultContext = LibCpp2IlContextBuilder.Build(binaryBytes, metadataBytes, unityVersion);
return true;
}
[Obsolete("Use LibCpp2IlContextBuilder directly.")]
public static bool LoadFromFile(string pePath, string metadataPath, UnityVersion unityVersion)
{
_defaultContext = LibCpp2IlContextBuilder.BuildFromFiles(pePath, metadataPath, unityVersion);
return true;
}
[Obsolete("Use context.GetManagedMethodImplementationsAtAddress instead.")]
public static List<Il2CppMethodDefinition>? GetManagedMethodImplementationsAtAddress(ulong addr) => DefaultContext.GetManagedMethodImplementationsAtAddress(addr);
[Obsolete("Use context.GetAnyGlobalByAddress instead.")]
public static MetadataUsage? GetAnyGlobalByAddress(ulong address) => DefaultContext.GetAnyGlobalByAddress(address);
[Obsolete("Use context.GetLiteralGlobalByAddress instead.")]
public static MetadataUsage? GetLiteralGlobalByAddress(ulong address) => DefaultContext.GetLiteralGlobalByAddress(address);
[Obsolete("Use context.GetLiteralByAddress instead.")]
public static string? GetLiteralByAddress(ulong address) => DefaultContext.GetLiteralByAddress(address);
[Obsolete("Use context.GetRawTypeGlobalByAddress instead.")]
public static MetadataUsage? GetRawTypeGlobalByAddress(ulong address) => DefaultContext.GetRawTypeGlobalByAddress(address);
[Obsolete("Use context.GetTypeGlobalByAddress instead.")]
public static Il2CppType? GetTypeGlobalByAddress(ulong address) => DefaultContext.GetTypeGlobalByAddress(address);
[Obsolete("Use context.GetRawFieldGlobalByAddress instead.")]
public static MetadataUsage? GetRawFieldGlobalByAddress(ulong address) => DefaultContext.GetRawFieldGlobalByAddress(address);
[Obsolete("Use context.GetFieldGlobalByAddress instead.")]
public static Il2CppFieldDefinition? GetFieldGlobalByAddress(ulong address) => DefaultContext.GetFieldGlobalByAddress(address);
[Obsolete("Use context.GetMethodGlobalByAddress instead.")]
public static MetadataUsage? GetMethodGlobalByAddress(ulong address) => DefaultContext.GetMethodGlobalByAddress(address);
[Obsolete("Use context.GetMethodDefinitionByGlobalAddress instead.")]
public static Il2CppMethodDefinition? GetMethodDefinitionByGlobalAddress(ulong address) => DefaultContext.GetMethodDefinitionByGlobalAddress(address);
[Obsolete("Use LibCpp2IlContext instead.")]
public static void Reset()
{
_defaultContext = null;
}
#endregion
}