Skip to content

Instantly share code, notes, and snippets.

@Slaynash
Created July 3, 2020 12:14
Show Gist options
  • Save Slaynash/b75bbc7de6110a575ee472233ba039f7 to your computer and use it in GitHub Desktop.
Save Slaynash/b75bbc7de6110a575ee472233ba039f7 to your computer and use it in GitHub Desktop.
Custom Unity render loop, drawing a magenta quad at 0,0 of 400x400px (hardcoded offsets for Unity 2018.4.23f1)
private delegate void PerformMainLoop(/* HWND__* */ IntPtr param_1, uint param_2, uint param_3, ulong param_4); // These parameters may be inaccurate
private delegate GfxDevice GetGfxDevice();
private delegate IntPtr GetIVRDevice();
private delegate void SetupPixelCorrectCoordinates(bool _0);
private static IntPtr GetFunctionPointerFromMethod(string methodName) =>
typeof(CustomUnityRender).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static).MethodHandle.GetFunctionPointer();
internal static void Init()
{
unityPlayerModule = Externs.GetModuleHandle("UnityPlayer.dll");
string unityVersion = MelonLoader.Main.UnityVersion;
IntPtr performMainLoop =
unityVersion.StartsWith("2017")
? CppUtils.Sigscan(unityPlayerModule, "48 83 ec 28 80 ?? ?? ?? ?? ?? 00 0f 85 ?? ?? ?? ?? e8 ?? ?? ?? ?? 80 b8 4d 01 00 00 00 74 ?? 80")
: unityVersion.StartsWith("2018")
? CppUtils.Sigscan(unityPlayerModule, "48 83 ec 28 80 ?? ?? ?? ?? ?? 00 0f 85 ?? ?? ?? ?? 48 89 5c 24 30 e8 ?? ?? ?? ?? 80 b8 4d 01 00 00 00 74 ?? e8")
: CppUtils.Sigscan(unityPlayerModule, "48 83 ec 28 80 ?? ?? ?? ?? ?? 00 0f 85 ?? ?? ?? ?? 48 89 5c 24 30 e8 ?? ?? ?? ?? 80 b8 55 01 00 00 00 74 ?? e8");
CreateDetourHook("PerformMainLoopDetour", ref originalPerformMainLoop, performMainLoop);
IntPtr getIVRDevicePtr = CppUtils.ResolvePtrOffset(drawSplashScreenPtr.Add(0xd), drawSplashScreenPtr.Add(0x11));
getIVRDevice = (GetIVRDevice)Marshal.GetDelegateForFunctionPointer(getIVRDevicePtr, typeof(GetIVRDevice));
IntPtr drawsplashscreen_postIvrCheckPtr = drawSplashScreenPtr.Add(0x16).Add(drawSplashScreenPtr.Add(0x15).GetByte());
IntPtr getGfxDevicePtr = CppUtils.ResolvePtrOffset(drawsplashscreen_postIvrCheckPtr.Add(0x16), drawsplashscreen_postIvrCheckPtr.Add(0x1a));
getGfxDevice = (GetGfxDevice)Marshal.GetDelegateForFunctionPointer(getGfxDevicePtr, typeof(GetGfxDevice));
setupPixelCorrectCoordinates = (SetupPixelCorrectCoordinates)Marshal.GetDelegateForFunctionPointer(unityPlayerModule.Add(0x69f940), typeof(SetupPixelCorrectCoordinates));
}
// These parameters may be inaccurate
private static unsafe void PerformMainLoopDetour(/* HWND__* */ IntPtr window, uint param_2, uint param_3, ulong param_4)
{
//MelonModLogger.Log("PerformMainLoop called");
if (inPerformMainLoop) // Invalid call
return;
inPerformMainLoop = true;
// So uh yeah, we do some test here, and uhhh, we may break everything but whatever. Let`s do this!
Externs.NativeMessage lpMsg;
Externs.PeekMessage(out lpMsg, IntPtr.Zero, 0, 0, 0);
IntPtr eventHandle = Externs.CreateEvent(IntPtr.Zero, false, false, null);
Externs.timeBeginPeriod(1);
while (lpMsg.msg != 0x12) // WM_QUIT
{
if (!Externs.PeekMessage(out lpMsg, IntPtr.Zero, 0, 0, 1)) // If there is no pending message
{
//updateTimer();
GfxDevice gfxDevice = getGfxDevice();
bool currentlyRendering = gfxDevice.isCurrentlyRendering;
if (!currentlyRendering)
gfxDevice.BeginFrame();
setupPixelCorrectCoordinates(false); // Set MVP matrices
Texture texture = new Texture(unityPlayerModule.Add(0x15acf60).GetValue()); // whiteTexture
DrawQuad(0, new UnityEngine.Rect(0, 0, 400, 400), texture, UnityEngine.Color.magenta, new UnityEngine.Rect(0, 0, 1, 1));
if (!currentlyRendering)
gfxDevice.EndFrame();
gfxDevice.SetInvertProjectionMatrix(false);
ScreenManager screenManager = ScreenManager.GetScreenManagerPtr();
IntPtr shaderChannelMask = default;
screenManager.SetBlitMaterial((IntPtr)(&shaderChannelMask));
gfxDevice.PresentFrame(shaderChannelMask);
Thread.Sleep(16); // ~60fps
}
else
{
Externs.TranslateMessage(ref lpMsg);
Externs.DispatchMessage(ref lpMsg);
}
}
MelonModLogger.Log("QUIT");
inPerformMainLoop = false;
return;
}
private static unsafe void DrawQuad(float depth, UnityEngine.Rect rect, Texture texture, UnityEngine.Color color, UnityEngine.Rect textureRect)
{
//The splash material is the same as the UI material, so I guess we can use it for everything
Material splashUnityLogoMaterial = new Material(unityPlayerModule.Add(0x15acf38).GetValue());
FastPropertyName _MainTexFPName = new FastPropertyName(unityPlayerModule.Add(0x15184e0));
if (_MainTexFPName.ptr.GetUInt() == 0xffffffff)
_MainTexFPName.Init("_MainTex");
SharedPassContext g_SharedPassContext = new SharedPassContext(unityPlayerModule.Add(0x15b87d8));
splashUnityLogoMaterial.SetTexture(_MainTexFPName, texture);
IntPtr shaderChannelMask = default;
splashUnityLogoMaterial.SetPassSlow((IntPtr)(&shaderChannelMask), 0, g_SharedPassContext, 0, 0);
GfxDevice gfxDevice = getGfxDevice();
gfxDevice.ImmediateBegin(2, shaderChannelMask);
gfxDevice.ImmediateColor(color.r * 0.5f, color.g * 0.5f, color.b * 0.5f, color.a * 0.5f);
float halfWidth = rect.width * 0.5f;
float halfHeight = rect.height * 0.5f;
float centerX = halfWidth + rect.x;
float centerY = halfHeight + rect.y;
float minX = centerX - halfWidth;
float minY = centerY - halfHeight;
float maxX = centerX + halfWidth;
float maxY = centerY + halfHeight;
gfxDevice.ImmediateTexCoordAll(textureRect.x, textureRect.y, 0);
gfxDevice.ImmediateVertex(minX, minY, depth);
gfxDevice.ImmediateTexCoordAll(textureRect.x, textureRect.y + textureRect.height, 0);
gfxDevice.ImmediateVertex(minX, maxY, depth);
gfxDevice.ImmediateTexCoordAll(textureRect.x + textureRect.width, textureRect.y + textureRect.height, 0);
gfxDevice.ImmediateVertex(maxX, maxY, depth);
gfxDevice.ImmediateTexCoordAll(textureRect.x + textureRect.width, textureRect.y, 0);
gfxDevice.ImmediateVertex(maxX, minY, depth);
gfxDevice.ImmediateEnd();
}
internal struct FastPropertyName
{
private delegate void Void_CharPtr_Function(IntPtr _this, string _1);
public IntPtr ptr;
public FastPropertyName(IntPtr ptr)
{
this.ptr = ptr;
}
internal void Init(string name)
{
((Void_CharPtr_Function)Marshal.GetDelegateForFunctionPointer(CustomUnityRender.unityPlayerModule.Add(0x8628f0), typeof(Void_CharPtr_Function)))
(ptr, name);
}
}
internal struct GfxDevice
{
private delegate void Void_Function(IntPtr _this);
private delegate void Void_Bool_Function(IntPtr _this, bool _0);
private delegate void Void_GfxPrimitiveType_VertexInputMasks_Function(IntPtr _this, int primitiveType, IntPtr vertexInputMasks);
private delegate void Void_Float_Float_Float_Function(IntPtr _this, float _1, float _2, float _3);
private delegate void Void_Float_Float_Float_Float_Function(IntPtr _this, float _1, float _2, float _3, float _4);
private delegate void PresentFrame_delegate(IntPtr _this, IntPtr shaderChannelMask);
public IntPtr ptr;
public bool isCurrentlyRendering => ptr.Add(0x21d4).GetBool();
internal IntPtr GetPointerFromVTable(int offset) =>
ptr.GetValue().Add(offset).GetValue(); // this->vtable(@0)->(@offset)
internal void SetInvertProjectionMatrix(bool _0) =>
((Void_Bool_Function)Marshal.GetDelegateForFunctionPointer(GetPointerFromVTable(0x40), typeof(Void_Bool_Function)))
(ptr, _0);
internal void BeginFrame() =>
((Void_Function)Marshal.GetDelegateForFunctionPointer(GetPointerFromVTable(0x508), typeof(Void_Function)))(ptr);
internal void EndFrame() =>
((Void_Function)Marshal.GetDelegateForFunctionPointer(GetPointerFromVTable(0x510), typeof(Void_Function)))(ptr);
internal void PresentFrame(IntPtr shaderChannelMask) =>
((PresentFrame_delegate)Marshal.GetDelegateForFunctionPointer(GetPointerFromVTable(0x520), typeof(PresentFrame_delegate)))
(ptr, shaderChannelMask);
internal void ImmediateVertex(float x, float y, float z) =>
((Void_Float_Float_Float_Function)Marshal.GetDelegateForFunctionPointer(GetPointerFromVTable(0x578), typeof(Void_Float_Float_Float_Function)))
(ptr, x, y, z);
internal void ImmediateColor(float a, float r, float g, float b) =>
((Void_Float_Float_Float_Float_Function)Marshal.GetDelegateForFunctionPointer(GetPointerFromVTable(0x588), typeof(Void_Float_Float_Float_Float_Function)))
(ptr, a, r, g, b);
internal void ImmediateTexCoordAll(float u, float v, float w) =>
((Void_Float_Float_Float_Function)Marshal.GetDelegateForFunctionPointer(GetPointerFromVTable(0x590), typeof(Void_Float_Float_Float_Function)))
(ptr, u, v, w);
internal void ImmediateBegin(int primitiveType, IntPtr vertexInputMasks) =>
((Void_GfxPrimitiveType_VertexInputMasks_Function)Marshal.GetDelegateForFunctionPointer(GetPointerFromVTable(0x5a0), typeof(Void_GfxPrimitiveType_VertexInputMasks_Function)))
(ptr, primitiveType, vertexInputMasks);
internal void ImmediateEnd() =>
((Void_Function)Marshal.GetDelegateForFunctionPointer(GetPointerFromVTable(0x5a8), typeof(Void_Function)))(ptr);
}
internal struct Material
{
private struct SetSlowPass_stack
{
public int _0;
public byte _1;
}
private delegate void SetTexture_delegate(IntPtr _this, uint _1, Texture _2);
private delegate int SetPassSlow_delegate(IntPtr _this, IntPtr sharedPassContext, int _3, SharedPassContext _4, SetSlowPass_stack stack);
public IntPtr ptr;
public Material(IntPtr ptr)
{
this.ptr = ptr;
}
public void SetTexture(FastPropertyName property, Texture texture)
{
((SetTexture_delegate)Marshal.GetDelegateForFunctionPointer(CustomUnityRender.unityPlayerModule.Add(0x859710), typeof(SetTexture_delegate)))
(ptr, property.ptr.GetUInt(), texture);
}
internal int SetPassSlow(IntPtr _1, int _2, SharedPassContext sharedPassContext, int _4, byte _5)
{
return ((SetPassSlow_delegate)Marshal.GetDelegateForFunctionPointer(CustomUnityRender.unityPlayerModule.Add(0x8592b0), typeof(SetPassSlow_delegate)))
(ptr, _1, _2, sharedPassContext, new SetSlowPass_stack { _0 = _4, _1 = _5 });
}
}
internal struct ScreenManager
{
private delegate ScreenManager GetScreenManagerPtr_delegate();
private delegate IntPtr SetBlitMaterial_delegate(ScreenManager _this, IntPtr sharedMaterialData);
public IntPtr ptr;
public static ScreenManager GetScreenManagerPtr() =>
((GetScreenManagerPtr_delegate) Marshal.GetDelegateForFunctionPointer(CustomUnityRender.unityPlayerModule.Add(0x726d30), typeof(GetScreenManagerPtr_delegate)))();
public IntPtr SetBlitMaterial(IntPtr sharedMaterialData) =>
((SetBlitMaterial_delegate)Marshal.GetDelegateForFunctionPointer(CustomUnityRender.unityPlayerModule.Add(0x728a50), typeof(SetBlitMaterial_delegate)))
(this, sharedMaterialData);
}
internal struct SharedPassContext
{
public IntPtr ptr;
internal SharedPassContext(IntPtr ptr)
{
this.ptr = ptr;
}
}
internal struct Texture
{
public IntPtr ptr;
internal Texture(IntPtr ptr)
{
this.ptr = ptr;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment