-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBundleDownloadMethods.cs
More file actions
187 lines (154 loc) · 7.76 KB
/
BundleDownloadMethods.cs
File metadata and controls
187 lines (154 loc) · 7.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using ReMod.Loader;
namespace ReMod.BundleVerifier
{
internal static unsafe class BundleDownloadMethods
{
private static readonly Dictionary<string, (int VTable, int CreateCached)> ourBundleDownloadOffsets = new()
{
{ "sgZUlX3+LSHKnTiTC+nXNcdtLOTrAB1fNjBLOwDdKzCyndlFLAdL0udR4S1szTC/q5pnFhG3Kdspsj5jvwLY1A==", (0x147F158, 0x33ED30) }, // U2019.4.31 non-dev
};
// TODO: integrate into NativePatchUtils?
// ReSharper disable once CollectionNeverQueried.Local
private static readonly List<Delegate> ourPinnedDelegates = new();
private static AssetBundleDownloadHandlerVTablePrefix ourOriginalVTable;
[DllImport("kernel32.dll")]
static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr CreateCachedDelegate(IntPtr thisPtr, NativePatchUtils.NativeString* url,
NativePatchUtils.NativeString* name, IntPtr hash, int crc);
private static CreateCachedDelegate ourOriginalCreateCached;
internal static bool Init()
{
string unityPlayerHash;
{
using var sha = SHA512.Create();
using var unityPlayerStream = File.OpenRead("UnityPlayer.dll");
unityPlayerHash = Convert.ToBase64String(sha.ComputeHash(unityPlayerStream));
}
if (!ourBundleDownloadOffsets.TryGetValue(unityPlayerHash, out var offsets))
{
ReLogger.Error($"Unknown UnityPlayer hash: {unityPlayerHash}");
ReLogger.Error("Bundle verifier will not work");
return false;
}
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
{
if (!module.FileName.Contains("UnityPlayer")) continue;
var vTableAddress = module.BaseAddress + offsets.VTable;
ourOriginalVTable = *(AssetBundleDownloadHandlerVTablePrefix*)vTableAddress;
var patchedTable = ApplyVTablePatches();
VirtualProtect(vTableAddress, (UIntPtr)Marshal.SizeOf<AssetBundleDownloadHandlerVTablePrefix>(), 0x4, out var oldProtect);
*(AssetBundleDownloadHandlerVTablePrefix*)vTableAddress = patchedTable;
VirtualProtect(vTableAddress, (UIntPtr)Marshal.SizeOf<AssetBundleDownloadHandlerVTablePrefix>(), oldProtect, out oldProtect);
var createCachedTarget = module.BaseAddress + offsets.CreateCached;
NativePatchUtils.NativePatch(createCachedTarget, out ourOriginalCreateCached, CreateCachedPatch);
return true;
}
return false;
}
internal static string ExtractString(NativePatchUtils.NativeString* nativeString)
{
if (nativeString == null) return null;
if (nativeString->Length == 0) return "";
var charsPointer = nativeString->Data;
if (charsPointer == IntPtr.Zero)
charsPointer = (IntPtr)(&nativeString->Capacity);
return new string((sbyte*)charsPointer, 0, (int)nativeString->Length, Encoding.UTF8);
}
private static IntPtr CreateCachedPatch(IntPtr scriptingObjectPtr, NativePatchUtils.NativeString* url,
NativePatchUtils.NativeString* name, IntPtr hash, int crc)
{
var result = ourOriginalCreateCached(scriptingObjectPtr, url, name, hash, crc);
try
{
BundleDlInterceptor.CreateCachedPatchPostfix(result, url);
}
catch (Exception ex)
{
ReLogger.Error($"Exception in CreateCached patch: {ex}");
}
return result;
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int PrepareDelegate(IntPtr thisPtr);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void VoidDelegate(IntPtr thisPtr);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr DtorDelegate(IntPtr thisPtr, long unk);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int ReceiveDelegate(IntPtr thisPtr, IntPtr bytes, int length);
private static IntPtr GetDelegatePointerAndPin<T>(T input) where T : MulticastDelegate
{
ourPinnedDelegates.Add(input);
return Marshal.GetFunctionPointerForDelegate(input);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static AssetBundleDownloadHandlerVTablePrefix ApplyVTablePatches()
{
AssetBundleDownloadHandlerVTablePrefix patchedTable = ourOriginalVTable;
patchedTable.Prepare = BundleDlInterceptor.PreparePatch;
patchedTable.Destructor = BundleDlInterceptor.DestructorPatch;
patchedTable.OnCompleteContent = BundleDlInterceptor.CompletePatch;
patchedTable.OnReceiveData = BundleDlInterceptor.ReceivePatch;
return patchedTable;
}
// returns number of bytes read
internal static int OriginalReceiveBytes(IntPtr assetBundleDownload, IntPtr bytesPtr, int byteCount) => ourOriginalVTable.OnReceiveData(assetBundleDownload, bytesPtr, byteCount);
internal static void OriginalCompleteDownload(IntPtr assetBundleDownload) => ourOriginalVTable.OnCompleteContent(assetBundleDownload);
internal static IntPtr OriginalDestructor(IntPtr assetBundleDownload, long unk) => ourOriginalVTable.Destructor(assetBundleDownload, unk);
internal static int OriginalPrepare(IntPtr assetBundleDownload)
{
return ourOriginalVTable.Prepare(assetBundleDownload);
}
// mono no likey delegate*unmanaged fields
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 8 * 14)]
private struct AssetBundleDownloadHandlerVTablePrefix
{
private IntPtr DestructorValue;
private IntPtr unknown1;
private IntPtr unknown2;
private IntPtr OnReceiveDataValue;
private IntPtr unknown3;
private IntPtr unknown4;
private IntPtr OnCompleteContentValue;
private IntPtr unknown5;
private IntPtr unknown6;
private IntPtr GetMemorySize2Value;
private IntPtr GetMemorySize1Value;
private IntPtr GetProgressValue;
private IntPtr PrepareValue;
private IntPtr OnAbortValue;
public DtorDelegate Destructor
{
get => Marshal.GetDelegateForFunctionPointer<DtorDelegate>(DestructorValue);
set => DestructorValue = GetDelegatePointerAndPin(value);
}
public ReceiveDelegate OnReceiveData
{
get => Marshal.GetDelegateForFunctionPointer<ReceiveDelegate>(OnReceiveDataValue);
set => OnReceiveDataValue = GetDelegatePointerAndPin(value);
}
public VoidDelegate OnCompleteContent
{
get => Marshal.GetDelegateForFunctionPointer<VoidDelegate>(OnCompleteContentValue);
set => OnCompleteContentValue = GetDelegatePointerAndPin(value);
}
public PrepareDelegate Prepare
{
get => Marshal.GetDelegateForFunctionPointer<PrepareDelegate>(PrepareValue);
set => PrepareValue = GetDelegatePointerAndPin(value);
}
}
}
}