Skip to content

Commit

Permalink
Unity 2019 fix WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
limoka committed Jan 16, 2025
1 parent 8784088 commit af8fb61
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;

namespace Il2CppInterop.Runtime.Injection
{
internal class GameManagersAssemblyListFile : IAssemblyListFile
{
private List<string> _assemblies = new List<string>();
private string originalFile;
private string newFile;

public bool IsTargetFile(string originalFilePath)
{
return originalFilePath.Contains("globalgamemanagers");
}

public void Setup(string originalFilePath)
{
if (originalFile != null) return;
originalFile = originalFilePath;
}

public void AddAssembly(string name)
{
_assemblies.Add(name);
}

public string GetOrCreateNewFile()
{
if (newFile != null) return newFile;

newFile = Path.GetTempFileName();
CreateModifiedFile();
return newFile;
}

private void CreateModifiedFile()
{
using var outputStream = File.Open(newFile, FileMode.Create);
using var outputWriter = new BinaryWriter(outputStream, Encoding.ASCII, false);
using var inputStream = File.Open(originalFile, FileMode.Open);
using var inputReader = new BinaryReader(inputStream, Encoding.ASCII, false);

// Assembly list always starts with UnityEngine.dll
var startPos = SeekFirstName(inputStream, inputReader);
if (startPos == -1)
{
throw new Exception("Failed to find start of assembly list in globalgamemanagers file!");
}

inputStream.Position = 0;
startPos -= 8;

for (var i = 0; i < startPos; i++)
{
outputWriter.Write(inputReader.ReadByte());
}

var assemblyCount = inputReader.ReadInt32();
List<string> newAssemblyList = new List<string>(assemblyCount + _assemblies.Count);
List<int> newAssemblyTypes = new List<int>(assemblyCount + _assemblies.Count);
for (var i = 0; i < assemblyCount; i++)
{
newAssemblyList.Add(ReadString(inputReader));
}

assemblyCount = inputReader.ReadInt32();
for (var i = 0; i < assemblyCount; i++)
{
newAssemblyTypes.Add(inputReader.ReadInt32());
}

newAssemblyList.AddRange(_assemblies);
newAssemblyTypes.AddRange(_assemblies.Select(_ => 16));

outputWriter.Write(newAssemblyList.Count);
foreach (var assemblyName in newAssemblyList)
{
WriteString(outputWriter, assemblyName);
}

outputWriter.Write(newAssemblyTypes.Count);
foreach (var assemblyType in newAssemblyTypes)
{
outputWriter.Write(assemblyType);
}

while (inputStream.Position < inputStream.Length)
{
outputWriter.Write(inputReader.ReadByte());
}
}

private static void WriteString(BinaryWriter outputWriter, string @string)
{
outputWriter.Write(@string.Length);
var paddedLenth = (int)(Math.Ceiling(@string.Length / 4f) * 4f);
for (int i = 0; i < paddedLenth; i++)
{
if (i < @string.Length)
outputWriter.Write(@string[i]);
else
outputWriter.Write((byte)0);
}
}

private static string ReadString(BinaryReader inputReader)
{
var length = inputReader.ReadInt32();
var paddedLenth = (int)(Math.Ceiling(length / 4f) * 4f);
StringBuilder sb = new StringBuilder(length);
for (var j = 0; j < paddedLenth; j++)
{
var c = inputReader.ReadChar();
if (j < length)
sb.Append(c);
}

return sb.ToString();
}

private static long SeekFirstName(FileStream inputStream, BinaryReader inputReader)
{
while (inputStream.Position < inputStream.Length)
{
var currentPos = inputStream.Position;
var firstChar = inputReader.ReadChar();
if (firstChar != 'U') continue;

var nextString = new string(inputReader.ReadChars(14));
if (!nextString.Equals("nityEngine.dll")) continue;

return currentPos;
}

return -1;
}
}
}
10 changes: 10 additions & 0 deletions Il2CppInterop.Runtime/Injection/AssemblyList/IAssemblyListFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Il2CppInterop.Runtime.Injection
{
internal interface IAssemblyListFile
{
public bool IsTargetFile(string originalFilePath);
public void Setup(string originalFilePath);
public void AddAssembly(string name);
public string GetOrCreateNewFile();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,35 @@

namespace Il2CppInterop.Runtime.Injection
{
public class AssemblyListFile
internal class JSONAssemblyListFile : IAssemblyListFile
{
private readonly JsonNode node;
private readonly JsonArray names;
private readonly JsonArray types;
private JsonNode node;
private JsonArray names;
private JsonArray types;

private string newFile;

public AssemblyListFile(string originalFilePath)
public void Setup(string originalFilePath)
{
if (node != null) return;

node = JsonNode.Parse(File.ReadAllText(originalFilePath));
names = node["names"].AsArray();
types = node["types"].AsArray();
}

public bool IsTargetFile(string originalFilePath)
{
return originalFilePath.Contains("ScriptingAssemblies.json");
}

public void AddAssembly(string name)
{
names.Add(name);
types.Add(16);
}

public string GetTmpFile()
public string GetOrCreateNewFile()
{
if (!string.IsNullOrEmpty(newFile)) return newFile;

Expand Down
4 changes: 3 additions & 1 deletion Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ internal unsafe class Assembly_Load_Hook : Hook<Assembly_Load_Hook.MethodDelegat
private Il2CppAssembly* Hook(IntPtr name)
{
Il2CppAssembly* assembly = Original(name);
InjectorHelpers.UnpatchIATHooks();
var assemblyName = Marshal.PtrToStringAnsi(name);

Logger.Instance.LogInformation($"Assembly::Load {assemblyName}");
if (assembly == null)
{
var assemblyName = Marshal.PtrToStringAnsi(name);
if (InjectorHelpers.TryGetInjectedImage(assemblyName, out var ptr))
{
var image = UnityVersionHandler.Wrap((Il2CppImage*)ptr);
Expand Down
34 changes: 18 additions & 16 deletions Il2CppInterop.Runtime/Injection/InjectorHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ internal static unsafe class InjectorHelpers
internal static IntPtr Il2CppHandle = NativeLibrary.Load("GameAssembly", typeof(InjectorHelpers).Assembly, null);
internal static IntPtr UnityPlayerHandle = NativeLibrary.Load("UnityPlayer", typeof(InjectorHelpers).Assembly, null);
internal static AssemblyIATHooker UnityPlayerIATHooker;
internal static string NewAssemblyListFile;
internal static IAssemblyListFile AssemblyListFile;

internal static readonly Dictionary<Type, OpCode> StIndOpcodes = new()
{
Expand Down Expand Up @@ -124,8 +124,8 @@ internal static void Setup()
FromNameHook.ApplyHook();
RunFinalizerPatch.ApplyHook();

AssemblyGetLoadedAssemblyHook.ApplyHook();
AppDomainGetAssembliesHook.ApplyHook();
//AssemblyGetLoadedAssemblyHook.ApplyHook();
//AppDomainGetAssembliesHook.ApplyHook();
}

[StructLayout(LayoutKind.Sequential)]
Expand Down Expand Up @@ -160,21 +160,19 @@ public static extern IntPtr CreateFile(
private static int GetFileAttributesExDetour(IntPtr lpFileName, int fInfoLevelId, IntPtr lpFileInformation)
{
var filePath = Marshal.PtrToStringUni(lpFileName);
filePath = filePath.Replace(@"\\?\", "");

if (filePath.Contains("ScriptingAssemblies.json"))
if (AssemblyListFile.IsTargetFile(filePath))
{
filePath = filePath.Replace(@"\\?\", "");

var assemblyList = new AssemblyListFile(filePath);

AssemblyListFile.Setup(filePath);
foreach (var assemblyName in InjectedImages.Keys)
{
assemblyList.AddAssembly(assemblyName);
AssemblyListFile.AddAssembly(assemblyName);
}

NewAssemblyListFile = assemblyList.GetTmpFile();
Logger.Instance.LogInformation($"Forcing unity to read assembly list from {NewAssemblyListFile}");
var newlpFileName = Marshal.StringToHGlobalUni(NewAssemblyListFile);
var newFile = AssemblyListFile.GetOrCreateNewFile();
Logger.Instance.LogInformation($"Forcing unity to read assembly list from {newFile}");
var newlpFileName = Marshal.StringToHGlobalUni(newFile);

var result = GetFileAttributesEx(newlpFileName, fInfoLevelId, lpFileInformation);
Marshal.FreeHGlobal(newlpFileName);
Expand All @@ -197,22 +195,24 @@ private static int ReadFileDetour(IntPtr handle, IntPtr bytes, uint numBytesToRe
if (res != 0)
{
var filePath = sb.ToString();
if (filePath.Contains("ScriptingAssemblies.json"))
if (AssemblyListFile.IsTargetFile(filePath))
{
IntPtr newHandle = CreateFile(NewAssemblyListFile, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
UnpatchIATHooks();
IntPtr newHandle = CreateFile(AssemblyListFile.GetOrCreateNewFile(), FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
return ReadFile(newHandle, bytes, numBytesToRead, numBytesRead, overlapped);
}
}

return ReadFile(handle, bytes, numBytesToRead, numBytesRead, overlapped);
}

private static void UnpatchIATHooks()
internal static void UnpatchIATHooks()
{
if (UnityPlayerIATHooker == null) return;

Logger.Instance.LogInformation("Unpatching UnityPlayer IAT hooks");
UnityPlayerIATHooker.UnpatchIATHook("KERNEL32.dll", "ReadFile");
UnityPlayerIATHooker.UnpatchIATHook("KERNEL32.dll", "GetFileAttributesExW");
UnityPlayerIATHooker = null;
}

// Setup before unity loads assembly list
Expand All @@ -231,6 +231,8 @@ internal static void EarlySetup()
}
}

AssemblyListFile = UnityVersionHandler.GetAssemblyListFile();

UnityPlayerIATHooker = new AssemblyIATHooker(UnityPlayerHandle);
UnityPlayerIATHooker.CreateIATHook("KERNEL32.dll", "ReadFile", thunk =>
{
Expand Down
12 changes: 12 additions & 0 deletions Il2CppInterop.Runtime/Runtime/UnityVersionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Reflection;
using Il2CppInterop.Common;
using Il2CppInterop.Common.Extensions;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.Runtime.VersionSpecific.Assembly;
using Il2CppInterop.Runtime.Runtime.VersionSpecific.AssemblyName;
using Il2CppInterop.Runtime.Runtime.VersionSpecific.Class;
Expand Down Expand Up @@ -79,6 +80,8 @@ static UnityVersionHandler()
public static bool HasShimForGetMethod { get; private set; }
public static bool IsMetadataV29OrHigher { get; private set; }

public static bool HasScriptAssembliesFile { get; private set; }

// Version since which extra_arg is set to invoke_multicast, necessitating constructor calls
public static bool MustUseDelegateConstructor => IsMetadataV29OrHigher;

Expand All @@ -98,6 +101,7 @@ internal static void RecalculateHandlers()

HasGetMethodFromReflection = unityVersion > new Version(2018, 1, 0);
IsMetadataV29OrHigher = unityVersion >= new Version(2021, 2, 0);
HasScriptAssembliesFile = unityVersion >= new Version(2020, 0, 0);

HasShimForGetMethod = unityVersion >= new Version(2020, 3, 41) || IsMetadataV29OrHigher;

Expand Down Expand Up @@ -129,6 +133,14 @@ private static Type[] GetAllTypesSafe()
return typeof(UnityVersionHandler).Assembly.GetTypesSafe();
}

internal static IAssemblyListFile GetAssemblyListFile()
{
if (HasScriptAssembliesFile)
return new JSONAssemblyListFile();

return new GameManagersAssemblyListFile();
}

//Assemblies
public static INativeAssemblyStruct NewAssembly()
{
Expand Down

0 comments on commit af8fb61

Please sign in to comment.