Skip to content

Instantly share code, notes, and snippets.

@mukaschultze
Last active January 18, 2020 07:01
Show Gist options
  • Save mukaschultze/748ca5e4e017a8c249ffb4d49f795205 to your computer and use it in GitHub Desktop.
Save mukaschultze/748ca5e4e017a8c249ffb4d49f795205 to your computer and use it in GitHub Desktop.
AssetStoreToolsCallbacks (self contained)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using UnityEditor;
public class AssetStoreToolsCallbacks {
public static Action<object, string> afterAssetsUploaded = (sender, err) => { };
public static Action<object, string> afterAssetsBundleUploaded = (sender, err) => { };
public static Action<object, string> beforePackageExport = (sender, packagePath) => { };
public static Action<object, string> afterPackageExport = (sender, packagePath) => { };
public static Action<object> beforeUpload = (sender) => { };
public static Action<object> afterUpload = (sender) => { };
public static Action<object> beforeBundleUpload = (sender) => { };
public static Action<object> afterBundleUpload = (sender) => { };
public static Action<object, double, double> onUploading = (sender, pctUp, pctDown) => { };
public static Action<object> uploadSucessfull = (sender) => { };
public static Action<object> uploadFailed = (sender) => { };
// [InitializeOnLoadMethod]
// private static void AddLogs() {
// afterAssetsUploaded += (sender, err) => UnityEngine.Debug.LogFormat("afterAssetsUploaded: {0}", err);
// afterAssetsBundleUploaded += (sender, err) => UnityEngine.Debug.LogFormat("afterAssetsBundleUploaded: {0}", err);
// beforePackageExport += (sender, packagePath) => UnityEngine.Debug.LogFormat("beforePackageExport: {0}", packagePath);
// afterPackageExport += (sender, packagePath) => UnityEngine.Debug.LogFormat("afterPackageExport: {0}", packagePath);
// beforeUpload += (sender) => UnityEngine.Debug.Log("beforeUpload");
// afterUpload += (sender) => UnityEngine.Debug.Log("afterUpload");
// beforeBundleUpload += (sender) => UnityEngine.Debug.Log("beforeBundleUpload");
// afterBundleUpload += (sender) => UnityEngine.Debug.Log("afterBundleUpload");
// onUploading += (sender, pctUp, pctDown) => UnityEngine.Debug.LogFormat("onUploading {0:00.00} {1:00.00}", pctUp, pctDown);
// uploadSucessfull += (sender) => UnityEngine.Debug.Log("uploadSucessfull");
// uploadFailed += (sender) => UnityEngine.Debug.Log("uploadFailed");
// }
public const BindingFlags FULL_BINDING = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
private static Assembly[] cachedAssemblies;
private static readonly Dictionary<string, Patcher> patchers = new Dictionary<string, Patcher>();
public static Type FindClass(string name) {
var result = FindTypeInAssembly(name, typeof(Editor).Assembly);
if (result != null)
return result;
if (cachedAssemblies == null)
cachedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
for (var i = 0; i < cachedAssemblies.Length; i++) {
result = FindTypeInAssembly(name, cachedAssemblies[i]);
if (result != null)
return result;
}
return result;
}
private static Type FindTypeInAssembly(string name, Assembly assembly) {
return assembly == null ?
null :
assembly.GetType(name, false, true);
}
public static MethodInfo FindMethod(Type type, string methodName) {
return type.GetMethod(methodName, FULL_BINDING);
}
static AssetStoreToolsCallbacks() {
var AssetStorePackageController = FindClass("AssetStorePackageController");
var methods = new [] {
"OnAssetsUploaded",
"OnUploadAssetBundlesFinished",
"Export",
"Upload",
"UploadAssetBundles",
"OnAssetsUploading",
"OnUploadSuccessfull",
"OnSubmitionFail",
};
foreach (var method in methods) {
patchers[method] = new Patcher(
FindMethod(AssetStorePackageController, method),
FindMethod(typeof(AssetStoreToolsCallbacks), method)
);
patchers[method].SwapMethods();
}
}
private static Patcher GetCurrentPatcher() {
var frame = new StackFrame(1, true);
var methodName = frame.GetMethod().Name;
return patchers[methodName];
}
private void OnAssetsUploaded(string errorMessage) {
GetCurrentPatcher().InvokeOriginal(this, errorMessage);
afterAssetsUploaded(this, errorMessage);
}
private void OnUploadAssetBundlesFinished(string errorMessage) {
GetCurrentPatcher().InvokeOriginal(this, errorMessage);
afterAssetsBundleUploaded(this, errorMessage);
}
private void Export(string packagePath) {
beforePackageExport(this, packagePath);
GetCurrentPatcher().InvokeOriginal(this, packagePath);
afterPackageExport(this, packagePath);
}
private void Upload() {
beforeUpload(this);
GetCurrentPatcher().InvokeOriginal(this, null);
afterUpload(this);
}
private void UploadAssetBundles() {
beforeBundleUpload(this);
GetCurrentPatcher().InvokeOriginal(this, null);
afterBundleUpload(this);
}
private void OnAssetsUploading(double pctUp, double pctDown) {
onUploading(this, pctUp, pctDown);
GetCurrentPatcher().InvokeOriginal(this, pctUp, pctDown);
}
private void OnUploadSuccessfull() {
GetCurrentPatcher().InvokeOriginal(this, null);
uploadSucessfull(this);
}
private void OnSubmitionFail() {
GetCurrentPatcher().InvokeOriginal(this, null);
uploadFailed(this);
}
}
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
// error CS0702: A constraint cannot be special class `System.Delegate'
// Unity 2018.3.11f1 - .NET 3.5 equivalent
// public class Patcher<T> : Patcher where T : Delegate {
// public Patcher(T method, T replacement) : base(method.GetMethodInfo(), replacement.GetMethodInfo()) { }
// public Patcher(MethodBase method, T replacement) : base(method, replacement.GetMethodInfo()) { }
// }
public unsafe class Patcher {
private bool swapped;
private MethodBase method;
private MethodInfo replacement;
private byte[] backup = new byte[25];
private IntPtr pBody;
private IntPtr pBorrowed;
public Patcher(MethodBase method, MethodInfo replacement) {
this.method = method;
this.replacement = replacement;
}
public Patcher(MethodBase method, Delegate replacement) {
this.method = method;
this.replacement = replacement.GetMethodInfo();
}
public bool IsPatched() {
// var cursor = (byte * )pBody.ToPointer();
// var isOriginal = backup.All(b => * (cursor++) == b);
return swapped;
}
public void Revert() {
if (!swapped) {
throw new Exception("Methods is not patched");
}
swapped = false;
unsafe {
var cursor = (byte * )pBody.ToPointer();
for (var i = 0; i < backup.Length; i++) {
*(cursor++) = backup[i];
}
}
}
public void InvokeOriginal(object obj, params object[] parameters) {
try {
if (IsPatched())
Revert();
method.Invoke(obj, parameters);
} finally {
if (!IsPatched())
SwapMethods();
}
}
public void SwapMethods() {
if (swapped) {
throw new Exception("Methods already patched");
}
swapped = true;
RuntimeHelpers.PrepareMethod(method.MethodHandle);
RuntimeHelpers.PrepareMethod(replacement.MethodHandle);
pBody = method.MethodHandle.GetFunctionPointer();
pBorrowed = replacement.MethodHandle.GetFunctionPointer();
unsafe {
var ptr = (byte * )pBody.ToPointer();
var ptr2 = (byte * )pBorrowed.ToPointer();
var ptrDiff = ptr2 - ptr - 5;
// Backup orignal opcodes so we can revert it later
for (var i = 0; i < backup.Length; i++) {
backup[i] = * (ptr + i);
}
if (ptrDiff < (long)0xFFFFFFFF && ptrDiff > (long) - 0xFFFFFFFF) {
// 32-bit relative jump, available on both 32 and 64 bit arch.
// Debug.Trace($"diff is {ptrDiff} doing relative jmp");
// Debug.Trace("patching on {0:X}, target: {1:X}", (ulong)ptr, (ulong)ptr2);
* ptr = 0xE9; // JMP
*((uint * )(ptr + 1)) = (uint)ptrDiff;
} else {
// Debug.Trace($"diff is {ptrDiff} doing push+ret trampoline");
// Debug.Trace("patching on {0:X}, target: {1:X}", (ulong)ptr, (ulong)ptr2);
if (sizeof(IntPtr) == 8) {
// For 64bit arch and likely 64bit pointers, do:
// PUSH bits 0 - 32 of addr
// MOV [RSP+4] bits 32 - 64 of addr
// RET
var cursor = ptr;
*(cursor++) = 0x68; // PUSH
*((uint * )cursor) = (uint)ptr2;
cursor += 4;
*(cursor++) = 0xC7; // MOV [RSP+4]
*(cursor++) = 0x44;
*(cursor++) = 0x24;
*(cursor++) = 0x04;
*((uint * )cursor) = (uint)((ulong)ptr2 >> 32);
cursor += 4;
*(cursor++) = 0xC3; // RET
} else {
// For 32bit arch and 32bit pointers, do: PUSH addr, RET.
* ptr = 0x68;
*((uint * )(ptr + 1)) = (uint)ptr2;
*(ptr + 5) = 0xC3;
}
}
// Logger.Debug("Patched 0x{0:X} to 0x{1:X}.", (ulong)ptr, (ulong)ptr2);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment