SHA256
Document Harmony Patches
This commit is contained in:
1 parent
8deb64fd4d
commit
d2bd655118
19 files changed
+499
-4
No files matched your search
@@ -3,10 +3,31 @@ using HarmonyLib;
|
||||
|
||||
namespace ADOFAIRenderer.Patch.Engine;
|
||||
|
||||
/// <summary>
|
||||
/// Makes the async input clock offset follow the render clock immediately.
|
||||
/// <para>
|
||||
/// Patched member: <c>AsyncInputUtils.UpdateOffsetTime(long fixDivider = 1)</c>. The async input system maps wall-clock ticks
|
||||
/// (<c>AsyncInputManager.currFrameTick</c> = <c>DateTime.Now.Ticks</c>, 100 ns units) to conductor time with
|
||||
/// <c>AsyncInputManager.offsetTick</c> = currFrameTick - <c>scrConductor.dspTime</c> * 10^7. With <c>fixDivider</c> 1 the
|
||||
/// offset is set directly; otherwise it moves by 1 / fixDivider of the difference per call. <c>scrConductor</c> calls it every
|
||||
/// frame with divider 100 (damped); <c>scrController</c> calls it with divider 1.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Active while <see cref="RenderManager.IsActive"/> (stage >= OnScene). From stage Render the conductor time follows the
|
||||
/// patched <c>AudioSettings.dspTime</c> (offline render clock, a fixed step per frame independent of wall-clock time), so the
|
||||
/// difference changes every frame; the damped update would lag behind it. The prefix always applies the divider-1 update.
|
||||
/// </para>
|
||||
/// <para>Reference: https://learn.microsoft.com/en-us/dotnet/api/system.datetime.ticks</para>
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(AsyncInputUtils), "UpdateOffsetTime")]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static class AsyncInputPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Prefix of <c>AsyncInputUtils.UpdateOffsetTime</c>. While rendering, sets <c>offsetTick</c> to
|
||||
/// currFrameTick - dspTime * 10^7 (10^7 = 100 ns ticks per second) and <c>offsetTickUpdated</c> to true, for any divider.
|
||||
/// </summary>
|
||||
/// <returns>True to run the original method (not rendering); false to skip it (rendering).</returns>
|
||||
[HarmonyPrefix]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static bool Prefix()
|
||||
|
||||
@@ -6,13 +6,40 @@ using UnityEngine;
|
||||
|
||||
namespace ADOFAIRenderer.Patch.Engine;
|
||||
|
||||
/// <summary>
|
||||
/// Unity engine patches that put the game on the offline render clock and the render resolution.
|
||||
/// <para>
|
||||
/// Render clock: while capturing (<see cref="RenderManager.IsCapturingActive"/>, stage >= Render) and
|
||||
/// <c>RenderContext.TimePatch</c> is on (default on; a <c>RenderSession</c> can turn it off), the Unity time getters return
|
||||
/// the value captured at the capture start (<c>RenderManager.timeBase</c>, <c>unscaledTimeBase</c>, <c>dspTimeBase</c>,
|
||||
/// <c>frameCountBase</c>, read in <c>SetupPlay</c>) plus the render time <see cref="TimeSpent"/>. Each rendered frame then
|
||||
/// advances game time by exactly 1 / RenderFPS seconds, independent of how long the frame took to render and encode, and the
|
||||
/// clocks continue from their real values without a jump. The patches only affect managed callers of these getters
|
||||
/// (game and mod scripts).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// All prefixes run on the thread that calls the getter (normally the Unity main thread) and read <c>RenderManager</c> state
|
||||
/// without locks.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class RenderEnvironmentPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Render time of the current frame in seconds: <c>RenderManager.frameCount</c> / <c>RenderFPS</c>.
|
||||
/// frameCount is reset to 0 when capture starts and incremented at the start of every frame (FirstUpdateHook).
|
||||
/// </summary>
|
||||
private static double TimeSpent => RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.RenderFPS;
|
||||
|
||||
/// <summary>
|
||||
/// <c>Time.time</c> (scaled game time in seconds, float): returns timeBase + <see cref="TimeSpent"/> while capturing with
|
||||
/// the time patch on. <c>Time.timeScale</c> is not applied to the render time.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(Time), "time", MethodType.Getter)]
|
||||
public static class TimePatch
|
||||
{
|
||||
/// <summary>Prefix of the <c>Time.time</c> getter.</summary>
|
||||
/// <param name="__result">Replacement value in seconds.</param>
|
||||
/// <returns>True to run the original getter; false when the render clock value is returned.</returns>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public static bool Prefix(ref float __result)
|
||||
{
|
||||
@@ -22,9 +49,13 @@ public static class RenderEnvironmentPatches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary><c>Time.timeAsDouble</c> (double precision of <c>Time.time</c>): timeBase + <see cref="TimeSpent"/>.</summary>
|
||||
[HarmonyPatch(typeof(Time), "timeAsDouble", MethodType.Getter)]
|
||||
public static class TimeAsDoublePatch
|
||||
{
|
||||
/// <summary>Prefix of the getter; returns the render clock value while capturing with the time patch on.</summary>
|
||||
/// <param name="__result">Replacement value in seconds.</param>
|
||||
/// <returns>True to run the original getter; false when the render clock value is returned.</returns>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public static bool Prefix(ref double __result)
|
||||
{
|
||||
@@ -34,9 +65,18 @@ public static class RenderEnvironmentPatches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>Time.unscaledTime</c> (time in seconds independent of <c>Time.timeScale</c>, float): unscaledTimeBase +
|
||||
/// <see cref="TimeSpent"/>. Unscaled time is not advanced by <c>Time.captureDeltaTime</c> (it follows real time), so it is
|
||||
/// replaced as well.
|
||||
/// Reference: https://docs.unity3d.com/ScriptReference/Time-captureDeltaTime.html
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(Time), "unscaledTime", MethodType.Getter)]
|
||||
public static class UnscaledTimePatch
|
||||
{
|
||||
/// <summary>Prefix of the getter; returns the render clock value while capturing with the time patch on.</summary>
|
||||
/// <param name="__result">Replacement value in seconds.</param>
|
||||
/// <returns>True to run the original getter; false when the render clock value is returned.</returns>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public static bool Prefix(ref float __result)
|
||||
{
|
||||
@@ -46,9 +86,15 @@ public static class RenderEnvironmentPatches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>Time.unscaledTimeAsDouble</c> (double precision of <c>Time.unscaledTime</c>): unscaledTimeBase + <see cref="TimeSpent"/>.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(Time), "unscaledTimeAsDouble", MethodType.Getter)]
|
||||
public static class UnscaledTimeAsDoublePatch
|
||||
{
|
||||
/// <summary>Prefix of the getter; returns the render clock value while capturing with the time patch on.</summary>
|
||||
/// <param name="__result">Replacement value in seconds.</param>
|
||||
/// <returns>True to run the original getter; false when the render clock value is returned.</returns>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public static bool Prefix(ref double __result)
|
||||
{
|
||||
@@ -58,9 +104,17 @@ public static class RenderEnvironmentPatches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>AudioSettings.dspTime</c> (audio system clock in seconds, double): dspTimeBase + <see cref="TimeSpent"/>.
|
||||
/// The game schedules the song and computes the song position from this clock (<c>scrConductor</c>), so the level advances
|
||||
/// by one render frame per frame. The offline audio mixer uses the same base (<c>RenderManager.dspTimeBase</c>).
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(AudioSettings), "dspTime", MethodType.Getter)]
|
||||
public static class DspTimePatch
|
||||
{
|
||||
/// <summary>Prefix of the getter; returns the render clock value while capturing with the time patch on.</summary>
|
||||
/// <param name="__result">Replacement value in seconds.</param>
|
||||
/// <returns>True to run the original getter; false when the render clock value is returned.</returns>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public static bool Prefix(ref double __result)
|
||||
{
|
||||
@@ -70,9 +124,19 @@ public static class RenderEnvironmentPatches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>AudioSource.PlayScheduled(double time)</c> (start playback at a dspTime): blocked for every AudioSource while
|
||||
/// <see cref="RenderManager.IsActive"/> (stage >= OnScene), so Unity plays no scheduled sound in real time. During
|
||||
/// capture the song and the sound effects are produced offline by <c>OfflineAudioMixer</c>; sound effects reach it through
|
||||
/// the <c>AudioManager._Play</c> and <c>scrConductor.PlayWithEndTime</c> patches (AudioPatches.cs).
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(AudioSource), "PlayScheduled")]
|
||||
public static class PlayScheduledPatch
|
||||
{
|
||||
/// <summary>Prefix of <c>AudioSource.PlayScheduled</c>.</summary>
|
||||
/// <param name="__instance">The AudioSource (unused).</param>
|
||||
/// <param name="time">Scheduled start in dspTime seconds (unused).</param>
|
||||
/// <returns>True to run the original method (not rendering); false to skip it (rendering).</returns>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static bool Prefix(AudioSource __instance, double time)
|
||||
{
|
||||
@@ -80,9 +144,16 @@ public static class RenderEnvironmentPatches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>Time.frameCount</c> (frames since startup): frameCountBase + <c>RenderManager.frameCount</c> while capturing
|
||||
/// (stage >= Render) with <c>RenderContext.TimePatch</c> on.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(Time), "frameCount", MethodType.Getter)]
|
||||
public static class FrameCountPatch
|
||||
{
|
||||
/// <summary>Prefix of the getter; returns the replacement value under the conditions in the type summary.</summary>
|
||||
/// <param name="__result">Replacement value.</param>
|
||||
/// <returns>True to run the original getter; false when the replacement value is returned.</returns>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public static bool Prefix(ref int __result)
|
||||
{
|
||||
@@ -92,9 +163,13 @@ public static class RenderEnvironmentPatches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary><c>Time.deltaTime</c> (seconds since the previous frame, float): 1 / RenderFPS.</summary>
|
||||
[HarmonyPatch(typeof(Time), "deltaTime", MethodType.Getter)]
|
||||
public static class DeltaTimePatch
|
||||
{
|
||||
/// <summary>Prefix of the getter; returns the render clock value while capturing with the time patch on.</summary>
|
||||
/// <param name="__result">Replacement value in seconds.</param>
|
||||
/// <returns>True to run the original getter; false when the render clock value is returned.</returns>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public static bool Prefix(ref float __result)
|
||||
{
|
||||
@@ -104,9 +179,13 @@ public static class RenderEnvironmentPatches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary><c>Time.unscaledDeltaTime</c> (timeScale-independent frame time in seconds, float): 1 / RenderFPS.</summary>
|
||||
[HarmonyPatch(typeof(Time), "unscaledDeltaTime", MethodType.Getter)]
|
||||
public static class UnscaledDeltaTimePatch
|
||||
{
|
||||
/// <summary>Prefix of the getter; returns the render clock value while capturing with the time patch on.</summary>
|
||||
/// <param name="__result">Replacement value in seconds.</param>
|
||||
/// <returns>True to run the original getter; false when the render clock value is returned.</returns>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public static bool Prefix(ref float __result)
|
||||
{
|
||||
@@ -116,9 +195,18 @@ public static class RenderEnvironmentPatches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>Screen.width</c> (window width in pixels): <c>RenderConfig.RenderWidth</c> while <see cref="RenderManager.IsActive"/>
|
||||
/// (stage >= OnScene) and <c>RenderContext.ScreenPatch</c> is on (default on). Game code that sizes render textures from
|
||||
/// the screen size (for example <c>scrCamera</c> recreates <c>camRT</c> at Screen.width x Screen.height when the size
|
||||
/// differs) then works at the output resolution independent of the window size.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(Screen), "width", MethodType.Getter)]
|
||||
public static class ScreenWidthPatch
|
||||
{
|
||||
/// <summary>Prefix of the getter; returns the replacement value under the conditions in the type summary.</summary>
|
||||
/// <param name="__result">Replacement value.</param>
|
||||
/// <returns>True to run the original getter; false when the replacement value is returned.</returns>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public static bool Prefix(ref int __result)
|
||||
{
|
||||
@@ -128,9 +216,15 @@ public static class RenderEnvironmentPatches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>Screen.height</c> (window height in pixels): <c>RenderConfig.RenderHeight</c>; same conditions as <see cref="ScreenWidthPatch"/>.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(Screen), "height", MethodType.Getter)]
|
||||
public static class ScreenHeightPatch
|
||||
{
|
||||
/// <summary>Prefix of the getter; returns the replacement value under the conditions in the type summary.</summary>
|
||||
/// <param name="__result">Replacement value.</param>
|
||||
/// <returns>True to run the original getter; false when the replacement value is returned.</returns>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public static bool Prefix(ref int __result)
|
||||
{
|
||||
@@ -140,9 +234,22 @@ public static class RenderEnvironmentPatches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>AudioSource.GetSpectrumData(float[] samples, int channel, FFTWindow window)</c> (FFT magnitudes of the source's current
|
||||
/// output): while capturing (stage >= Render) with <c>AdvancedConfig.ProcessAudio</c> on, fills <c>samples</c> with
|
||||
/// <see cref="AudioSpectrumGenerator.GetSpectrumData"/> (spectrum of the song at the render time) for any AudioSource.
|
||||
/// No AudioSource plays during capture, so the original would return silence. The game calls it from
|
||||
/// <c>scrConductor</c>, <c>scrBarMaker</c> and the volume trackers for audio-reactive visuals.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(AudioSource), "GetSpectrumData", typeof(float[]), typeof(int), typeof(FFTWindow))]
|
||||
public static class GetSpectrumDataPatch
|
||||
{
|
||||
/// <summary>Prefix of <c>AudioSource.GetSpectrumData</c>.</summary>
|
||||
/// <param name="__instance">The AudioSource queried (not used; the song spectrum is returned for every source).</param>
|
||||
/// <param name="samples">Output array; its length is the number of spectrum bins.</param>
|
||||
/// <param name="channel">Channel index requested by the caller.</param>
|
||||
/// <param name="window">FFT window type requested by the caller.</param>
|
||||
/// <returns>True to run the original method; false when the generated spectrum was written.</returns>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public static bool Prefix(AudioSource __instance, float[] samples, int channel, FFTWindow window)
|
||||
{
|
||||
@@ -152,9 +259,19 @@ public static class RenderEnvironmentPatches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>AudioManager.FindOrLoadAudioClipExternal(string path, bool mp3Streaming, float length, bool stream)</c> (coroutine
|
||||
/// that loads an external audio file of the level into <c>audioLib</c>): from stage Load with
|
||||
/// <c>AdvancedConfig.ProcessAudio</c> on, forces <c>stream</c> to false. The game passes <c>stream</c> to
|
||||
/// <c>DownloadHandlerAudioClip.streamAudio</c> for ogg/wav/aif/aiff files, so these clips are loaded as non-streaming
|
||||
/// clips; the mp3 path uses <c>mp3Streaming</c> and ignores <c>stream</c>.
|
||||
/// Reference: https://docs.unity3d.com/ScriptReference/Networking.DownloadHandlerAudioClip-streamAudio.html
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(AudioManager), "FindOrLoadAudioClipExternal", typeof(string), typeof(bool), typeof(float), typeof(bool))]
|
||||
public static class FindOrLoadAudioClipExternalPatch
|
||||
{
|
||||
/// <summary>Prefix of <c>AudioManager.FindOrLoadAudioClipExternal</c>; the original always runs.</summary>
|
||||
/// <param name="stream">Streaming flag of the load; set to false under the conditions above.</param>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public static void Prefix(ref bool stream)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user