using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using LLBML.Messages;
using LLBML.Players;
using LLBML.Settings;
using LLBML.Utils;
using Microsoft.CodeAnalysis;
using Multiplayer;
using SyncFix.FrameRecorder;
using SyncFix.Utils;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("ca.gov.mechasoulindustries.llb.my.cute.syncfix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+7c7594535044a6becc0877c0ef102699d7f49cef")]
[assembly: AssemblyProduct("Sync Fix")]
[assembly: AssemblyTitle("ca.gov.mechasoulindustries.llb.my.cute.syncfix")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace SyncFix
{
public class FrameAccumulator
{
public readonly int playerIndex;
public readonly float updateRate;
public readonly float threshold;
public float currentValue = -1f;
public float accumulator;
public float upperBound;
public float lowerBound;
public Func<int, int, float> upperBoundFunc;
public Func<int, int, float> lowerBoundFunc;
public FrameAccumulator(int playerIndex, float updateRate, float threshold)
{
this.playerIndex = playerIndex;
this.updateRate = updateRate;
this.threshold = threshold;
}
public void Reset()
{
currentValue = -1f;
accumulator = 0f;
}
private void UpdateValue(float newValue)
{
if (currentValue == -1f)
{
currentValue = newValue;
}
else
{
currentValue = Mathf.Lerp(currentValue, newValue, updateRate);
}
}
public void FrameUpdate(int frame, float frameValue)
{
upperBound = upperBoundFunc(frame, playerIndex);
lowerBound = lowerBoundFunc(frame, playerIndex);
_ = currentValue;
UpdateValue(frameValue);
if (currentValue > upperBound)
{
float num = currentValue - upperBound;
accumulator += num;
}
else if (currentValue < lowerBound)
{
float num2 = currentValue - lowerBound;
accumulator = Math.Max(accumulator + num2, 0f);
}
}
public bool ThresholdReached()
{
return accumulator >= threshold;
}
public bool ThresholdVeryReached()
{
return accumulator >= threshold * 10f;
}
}
public interface ITimeSyncComponent
{
void FrameUpdate();
float GetSleepInterval();
void OnSleep(float frames);
void Reset();
bool ShouldEmergencySleep();
}
[BepInPlugin("ca.gov.mechasoulindustries.llb.my.cute.syncfix", "Sync Fix", "1.0.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInProcess("LLBlaze.exe")]
public class Plugin : BaseUnityPlugin
{
internal static ManualLogSource Logger;
private Harmony _harmony;
public static Plugin Instance { get; private set; }
private void Awake()
{
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
//IL_0037: Expected O, but got Unknown
Logger = ((BaseUnityPlugin)this).Logger;
Instance = this;
SyncFixConfig.LoadConfig(((BaseUnityPlugin)this).Config);
PathUtils.Init(((BaseUnityPlugin)this).Info);
_harmony = new Harmony("ca.gov.mechasoulindustries.llb.my.cute.syncfix");
_harmony.PatchAll();
Logger.LogInfo((object)"Plugin ca.gov.mechasoulindustries.llb.my.cute.syncfix is loaded!");
}
private void Start()
{
StateManager.RegisterLobbyMessages();
SyncFixManager.RegisterGameMessages();
ModDependenciesUtils.RegisterToModMenu(((BaseUnityPlugin)this).Info, new List<string> { "Fixes host advantage" });
}
private void OnDestroy()
{
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
}
}
public class RollbackStats
{
private static int numRollbacks = 0;
private static float total = 0f;
private static float recentSize = -1f;
private static int numSleeps = 0;
public static int NumRollbacks => numRollbacks;
public static float Average => total / (float)NumRollbacks;
public static float RecentSize => recentSize;
public static int NumSleeps => numSleeps;
public static void Reset()
{
numRollbacks = 0;
total = 0f;
recentSize = -1f;
numSleeps = 0;
}
public static void AddRollback(int size)
{
numRollbacks++;
total += size;
if (recentSize == -1f)
{
recentSize = size;
}
else
{
recentSize = Mathf.Lerp(recentSize, (float)size, 0.1f);
}
}
public static void AddSleep(float size)
{
numSleeps++;
}
public static string GetStats()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("rollbacks: ");
stringBuilder.Append(NumRollbacks);
stringBuilder.AppendLine();
stringBuilder.Append("average: ");
stringBuilder.Append(Average);
stringBuilder.AppendLine();
stringBuilder.Append("recent: ");
stringBuilder.Append(RecentSize);
stringBuilder.AppendLine();
stringBuilder.Append("sleeps: ");
stringBuilder.Append(NumSleeps);
stringBuilder.AppendLine();
return stringBuilder.ToString();
}
}
public class StateManager
{
public enum SyncFixMode
{
SOLO,
GROUP
}
public enum LobbyPeerModStatus
{
UNKNOWN,
CONFIRMED
}
public static SyncFixMode CurrentMode = SyncFixMode.SOLO;
private static readonly LobbyPeerModStatus[] peerModStatus = new LobbyPeerModStatus[4];
public static bool HostHasSyncFix = false;
public static void RegisterLobbyMessages()
{
MessageApi.RegisterCustomMessage(((BaseUnityPlugin)Plugin.Instance).Info, (ushort)5040, SyncFixMessages.LOBBY_MOD_CHECK.ToString(), (Action<Message>)ReceiveModCheck);
MessageApi.RegisterCustomMessage(((BaseUnityPlugin)Plugin.Instance).Info, (ushort)5041, SyncFixMessages.LOBBY_MOD_REPLY.ToString(), (Action<Message>)ReceiveModReply);
MessageApi.RegisterCustomMessage(((BaseUnityPlugin)Plugin.Instance).Info, (ushort)5042, SyncFixMessages.GAME_USE_GROUP.ToString(), (Action<Message>)ReceiveGroupMessage);
}
public static void PeerJoined(int playerIndex)
{
if (ShouldManageState())
{
if (IsLocalPeer(playerIndex))
{
SetPeerModStatus(playerIndex, LobbyPeerModStatus.CONFIRMED);
}
else
{
SendModCheck(playerIndex);
}
}
}
public static void PeerLeft(int playerIndex)
{
if (ShouldManageState())
{
SetPeerModStatus(playerIndex, LobbyPeerModStatus.UNKNOWN);
}
}
public static void SendModCheck(int playerIndex)
{
//IL_001b: Unknown result type (might be due to invalid IL or missing references)
if (ShouldManageState())
{
P2P.SendToPlayerNr(playerIndex, new Message((Msg)5040, ((Peer)P2P.localPeer).playerNr, -1, (object)null, -1));
Plugin.Logger.LogInfo((object)$"sent mod check to player {playerIndex}");
}
}
public static void ReceiveModCheck(Message message)
{
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Unknown result type (might be due to invalid IL or missing references)
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
if (SyncFixConfig.Instance.Enabled)
{
if (message.playerNr == 0)
{
HostHasSyncFix = true;
}
P2P.SendToPlayerNr(message.playerNr, new Message((Msg)5041, ((Peer)P2P.localPeer).playerNr, -1, (object)null, -1));
Plugin.Logger.LogInfo((object)$"received mod check from player {message.playerNr}, sent reply");
}
}
public static void ReceiveModReply(Message message)
{
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
if (ShouldManageState())
{
SetPeerModStatus(message.playerNr, LobbyPeerModStatus.CONFIRMED);
Plugin.Logger.LogInfo((object)$"received mod reply from player {message.playerNr}, set CONFIRMED");
}
}
public static bool AllPeersConfirmed()
{
bool flag = !peerModStatus.Where((LobbyPeerModStatus status, int index) => Player.GetPlayer(index).IsInMatch && status == LobbyPeerModStatus.UNKNOWN).Any();
Plugin.Logger.LogInfo((object)$"all peers confirmed: {flag}");
return flag;
}
public static void SendAllGroupMessage()
{
if (ShouldManageState())
{
ForAllRemotePeersInMatch(delegate(int i)
{
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
P2P.SendToPlayerNr(i, new Message((Msg)5042, ((Peer)P2P.localPeer).playerNr, -1, (object)null, -1));
});
CurrentMode = SyncFixMode.GROUP;
Plugin.Logger.LogInfo((object)"sent all peers group message");
}
}
public static void ReceiveGroupMessage(Message message)
{
if (SyncFixConfig.Instance.Enabled)
{
CurrentMode = SyncFixMode.GROUP;
Plugin.Logger.LogInfo((object)"received group message");
}
}
public static bool IsUsingGroup()
{
return CurrentMode == SyncFixMode.GROUP;
}
private static void SetPeerModStatus(int playerIndex, LobbyPeerModStatus status)
{
if (ShouldManageState())
{
peerModStatus[playerIndex] = status;
}
}
public static void ResetState()
{
ResetPeerModStatus();
ResetMode();
}
public static void ResetPeerModStatus()
{
if (SyncFixConfig.Instance.Enabled)
{
for (int i = 0; i < peerModStatus.Length; i++)
{
SetPeerModStatus(i, (i == ((Peer)P2P.localPeer).playerNr) ? LobbyPeerModStatus.CONFIRMED : LobbyPeerModStatus.UNKNOWN);
}
if (P2P.isHost)
{
HostHasSyncFix = true;
}
else
{
HostHasSyncFix = false;
}
Plugin.Logger.LogInfo((object)("reset status: " + string.Join(", ", peerModStatus.Select((LobbyPeerModStatus status) => status.ToString()).ToArray())));
}
}
public static void ResetMode()
{
CurrentMode = SyncFixMode.SOLO;
}
private static bool IsLocalPeer(int playerIndex)
{
return playerIndex == ((Peer)P2P.localPeer).playerNr;
}
private static bool ShouldManageState()
{
if (P2P.isHost)
{
return SyncFixConfig.Instance.Enabled;
}
return false;
}
private static void ForAllRemotePeersInMatch(Action<int> action)
{
Player.ForAllInMatch((Action<Player>)delegate(Player player)
{
if (!IsLocalPeer(player.nr))
{
action(player.nr);
}
});
}
}
public class SyncFixConfig
{
private static SyncFixConfig instance;
private readonly ConfigEntry<bool> enabled;
private readonly ConfigEntry<bool> showDebugInfo;
private readonly ConfigEntry<KeyCode> debugInfoKey;
private readonly ConfigEntry<bool> recordDebugInfo;
public static SyncFixConfig Instance
{
get
{
return instance;
}
private set
{
instance = value;
}
}
public bool Enabled
{
get
{
return enabled.Value;
}
set
{
enabled.Value = value;
}
}
public bool ShowDebugInfo
{
get
{
return showDebugInfo.Value;
}
set
{
showDebugInfo.Value = value;
}
}
public KeyCode DebugInfoKey
{
get
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
return debugInfoKey.Value;
}
set
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
debugInfoKey.Value = value;
}
}
public bool RecordDebugInfo
{
get
{
return recordDebugInfo.Value;
}
set
{
recordDebugInfo.Value = value;
}
}
private SyncFixConfig(ConfigFile configFile)
{
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Expected O, but got Unknown
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Expected O, but got Unknown
//IL_0064: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Expected O, but got Unknown
enabled = configFile.Bind<bool>(new ConfigDefinition("Sync Fix", "Enable host advantage fix"), true, (ConfigDescription)null);
showDebugInfo = configFile.Bind<bool>(new ConfigDefinition("Sync Fix", "Show debug info ingame"), false, (ConfigDescription)null);
debugInfoKey = configFile.Bind<KeyCode>("Sync Fix", "Toggle debug info key", (KeyCode)0, (ConfigDescription)null);
recordDebugInfo = configFile.Bind<bool>(new ConfigDefinition("Sync Fix", "Save debug info to disk at match end"), false, (ConfigDescription)null);
}
internal static void LoadConfig(ConfigFile configFile)
{
if (Instance != null)
{
throw new InvalidOperationException("config already loaded");
}
configFile.SaveOnConfigSet = true;
Instance = new SyncFixConfig(configFile);
}
}
public class SyncFixManager
{
[CompilerGenerated]
private sealed class <CSendSelfTimeAlignAfterDelay>d__32 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public float initialDelay;
public float time;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <CSendSelfTimeAlignAfterDelay>d__32(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Expected O, but got Unknown
//IL_007e: Unknown result type (might be due to invalid IL or missing references)
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
Plugin.Logger.LogInfo((object)$"delaying self-timesync by {initialDelay}");
<>2__current = (object)new WaitForSeconds(initialDelay);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
P2P.SendToPlayerNr(((Peer)P2P.localPeer).playerNr, new Message((Msg)189, Sync.matchNr, Mathf.RoundToInt(time * 1000f), (object)null, -1));
return false;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private static readonly SyncFixManager instance;
public static readonly int GROUP_SLEEP_CHECK_INTERVAL;
public static readonly int GROUP_ADVANTAGE_UPDATE_INTERVAL;
public TimeSync[] timeSync = new TimeSync[4]
{
new TimeSync(0),
new TimeSync(1),
new TimeSync(2),
new TimeSync(3)
};
private int nextAdvantageUpdate = int.MaxValue;
private int nextRecommendedSleep = int.MaxValue;
private int lastSleep = -1;
public static SyncFixManager Instance => instance;
public int NextAdvantageUpdate => nextAdvantageUpdate;
public int NextRecommendedSleep => nextRecommendedSleep;
public int LastSleep => lastSleep;
static SyncFixManager()
{
instance = new SyncFixManager();
GROUP_SLEEP_CHECK_INTERVAL = 120;
GROUP_ADVANTAGE_UPDATE_INTERVAL = 60;
}
public static void RegisterGameMessages()
{
MessageApi.RegisterCustomMessage(((BaseUnityPlugin)Plugin.Instance).Info, (ushort)5043, SyncFixMessages.GAME_LOCAL_ADVANTAGE.ToString(), (Action<Message>)Instance.ReceiveRemoteAdvantage);
}
public void Reset()
{
if (SyncFixConfig.Instance.Enabled)
{
for (int i = 0; i < Sync.nPlayers; i++)
{
timeSync[i].Reset();
}
nextAdvantageUpdate = int.MaxValue;
nextRecommendedSleep = int.MaxValue;
lastSleep = -1;
}
}
public void Start()
{
if (SyncFixConfig.Instance.Enabled)
{
UpdateNextRecommendedSleep();
UpdateNextAdvantageTime();
}
}
public void MidMatchReset()
{
if (SyncFixConfig.Instance.Enabled)
{
for (int i = 0; i < Sync.nPlayers; i++)
{
timeSync[i].ResetActiveComponent();
}
UpdateNextAdvantageTime();
UpdateNextRecommendedSleep();
lastSleep = -1;
}
}
public void UpdateNextRecommendedSleep()
{
nextRecommendedSleep = Sync.curFrame + GROUP_SLEEP_CHECK_INTERVAL;
}
public void UpdateLastSleep()
{
lastSleep = Sync.curFrame;
}
public void OnSleep(float sleepDuration)
{
UpdateNextRecommendedSleep();
UpdateLastSleep();
if (StateManager.IsUsingGroup())
{
ForAllValidOthers(delegate(int i)
{
float frames = sleepDuration * (float)World.FPS;
timeSync[i].OnSleep(frames);
SendLocalAdvantageToPlayer(i, notifySleep: true);
});
}
UpdateNextAdvantageTime();
}
public void UpdateNextAdvantageTime()
{
nextAdvantageUpdate = Sync.curFrame + GROUP_ADVANTAGE_UPDATE_INTERVAL;
}
public float GetRecommendedSleepInterval(int playerIndex)
{
if (!SyncFixConfig.Instance.Enabled)
{
throw new InvalidOperationException("asked for sleep interval when sync fix disabled?");
}
return timeSync[playerIndex].GetSleepInterval();
}
public float GetCurrentLocalAdvantage(int playerIndex)
{
if (!SyncFixConfig.Instance.Enabled)
{
throw new InvalidOperationException("asked for local advantage when sync fix disabled?");
}
return timeSync[playerIndex].GetCurrentLocalAdvantage();
}
public void SendLocalAdvantageToPlayer(int i, bool notifySleep = false)
{
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
if (SyncFixConfig.Instance.Enabled)
{
byte[] bytes = BitConverter.GetBytes(GetCurrentLocalAdvantage(i));
Message val = default(Message);
((Message)(ref val))..ctor((Msg)5043, ((Peer)P2P.localPeer).playerNr, notifySleep ? 1 : 0, (object)bytes, bytes.Length);
P2P.SendToPlayerNr(i, val);
}
}
public void ReceiveRemoteAdvantage(Message message)
{
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
if (SyncFixConfig.Instance.Enabled)
{
float remoteAdvantage = BitConverter.ToSingle((byte[])message.ob, 0);
UpdateRemoteAdvantage(message.playerNr, remoteAdvantage);
}
}
public void UpdateRemoteAdvantage(int playerIndex, float remoteAdvantage)
{
timeSync[playerIndex].UpdateRemoteFrameAdvantage(remoteAdvantage);
}
public void GroupAlignTimes()
{
if (Sync.isAwaiting)
{
return;
}
ForAllValidOthers(delegate(int i)
{
timeSync[i].FrameUpdate();
});
bool flag = Sync.curFrame > NextRecommendedSleep;
for (int j = 0; j < Sync.nPlayers; j++)
{
if (Sync.IsValidOther(j))
{
flag = flag || timeSync[j].ShouldEmergencySleep();
if (flag)
{
break;
}
}
}
if (!flag || Sync.isAwaiting)
{
return;
}
float num = 0f;
for (int k = 0; k < Sync.nPlayers; k++)
{
if (Sync.othersInfo[k] != null)
{
num = Math.Max(num, GetRecommendedSleepInterval(k));
}
}
if (num > 0f)
{
Plugin.Logger.LogInfo((object)$"waiting for {num}s");
P2P.Wait(num);
}
}
public void SoloHostAlignTimes()
{
//IL_00de: Unknown result type (might be due to invalid IL or missing references)
if (Sync.isAwaiting)
{
return;
}
float num = float.MaxValue;
for (int i = 0; i < Sync.nPlayers; i++)
{
timeSync[i].FrameUpdate();
if (timeSync[i].GetCurrentFrameEstimate() < num)
{
num = timeSync[i].GetCurrentFrameEstimate();
}
}
for (int j = 0; j < Sync.nPlayers; j++)
{
timeSync[j].UpdateRunAheadEstimate(num);
if (!timeSync[j].CanSleep())
{
continue;
}
float sleepInterval = timeSync[j].GetSleepInterval();
if (sleepInterval > 0f)
{
Plugin.Logger.LogInfo((object)$"sleeping p{j + 1} for {sleepInterval}");
timeSync[j].OnSleep(sleepInterval * (float)World.FPS);
if (j == 0)
{
SendSelfTimeAlignAfterDelay(sleepInterval);
}
else
{
P2P.SendToPlayerNr(j, new Message((Msg)189, Sync.matchNr, Mathf.RoundToInt(sleepInterval * 1000f), (object)null, -1));
}
}
}
}
private static void SendSelfTimeAlignAfterDelay(float time)
{
float num = (from player in Player.EPlayers()
where player.LAADACKBGLL() && Sync.IsValidOther(player.CJFLMDNNMIE)
select player).Average((ALDOKEMAOMB player) => player.KLEEADMGHNE.ping);
num *= 0.49f;
((MonoBehaviour)P2P.instance).StartCoroutine(CSendSelfTimeAlignAfterDelay(num, time));
}
private static IEnumerator CSendSelfTimeAlignAfterDelay(float initialDelay, float time)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <CSendSelfTimeAlignAfterDelay>d__32(0)
{
initialDelay = initialDelay,
time = time
};
}
public static void ForAllValidOthers(Action<int> action)
{
for (int i = 0; i < Sync.nPlayers; i++)
{
if (Sync.IsValidOther(i))
{
action(i);
}
}
}
}
public enum SyncFixMessages
{
LOBBY_MOD_CHECK = 5040,
LOBBY_MOD_REPLY,
GAME_USE_GROUP,
GAME_LOCAL_ADVANTAGE
}
public class TimeSync
{
protected readonly int playerIndex;
private readonly TimeSyncGroupComponent groupComponent;
private readonly TimeSyncSoloHostComponent soloComponent;
private ITimeSyncComponent activeComponent;
public TimeSync(int playerIndex)
{
this.playerIndex = playerIndex;
groupComponent = new TimeSyncGroupComponent(playerIndex);
soloComponent = new TimeSyncSoloHostComponent(playerIndex);
SetActiveComponent();
}
public void Reset()
{
SetActiveComponent();
activeComponent.Reset();
}
public void ResetActiveComponent()
{
activeComponent.Reset();
}
public void FrameUpdate()
{
activeComponent.FrameUpdate();
}
public float GetSleepInterval()
{
return activeComponent.GetSleepInterval();
}
public void OnSleep(float frames)
{
activeComponent.OnSleep(frames);
}
public void UpdateRemoteFrameAdvantage(float remoteAdvantage)
{
groupComponent.UpdateRemoteFrameAdvantage(remoteAdvantage);
}
public float GetCurrentLocalAdvantage()
{
return groupComponent.CurrentLocalAdvantage;
}
public void UpdateRunAheadEstimate(float minimumFrame)
{
soloComponent.UpdateRunAheadEstimate(minimumFrame);
}
public float GetCurrentFrameEstimate()
{
return soloComponent.CurrentFrameEstimate;
}
public bool CanSleep()
{
if (Sync.curFrame <= soloComponent.NextRecommendedSleep)
{
return soloComponent.ShouldEmergencySleep();
}
return true;
}
public bool ShouldEmergencySleep()
{
return activeComponent.ShouldEmergencySleep();
}
public void SetActiveComponent()
{
if (StateManager.IsUsingGroup())
{
activeComponent = groupComponent;
}
else
{
activeComponent = soloComponent;
}
}
}
public abstract class TimeSyncComponentBase : ITimeSyncComponent
{
protected static readonly float RECENT_SLEEP_WINDOW_HOST = 1800f;
protected static readonly float RECENT_SLEEP_WINDOW_CLIENT = 1800f;
protected static readonly float ALIGN_TIMES_FACTOR = 0.8f;
protected readonly int playerIndex;
protected readonly Queue<int> recentSleeps = new Queue<int>();
public TimeSyncComponentBase(int playerIndex)
{
this.playerIndex = playerIndex;
}
public abstract float GetSleepInterval();
public abstract bool ShouldEmergencySleep();
protected abstract float GetRecentSleepBaseFactor();
protected abstract float GetRecentSleepWindow();
public virtual void Reset()
{
recentSleeps.Clear();
}
public virtual void FrameUpdate()
{
while (recentSleeps.Count > 0 && (float)Sync.curFrame > (float)recentSleeps.Peek() + GetRecentSleepWindow())
{
recentSleeps.Dequeue();
}
}
public virtual void OnSleep(float frames)
{
recentSleeps.Enqueue(Sync.curFrame);
}
protected float GetRecentSleepFactor()
{
float num = 0f;
foreach (int recentSleep in recentSleeps)
{
num += (GetRecentSleepWindow() - (float)(Sync.curFrame - recentSleep)) / GetRecentSleepWindow() * GetRecentSleepBaseFactor();
}
return num;
}
protected static float LinearClamped(float x, float xStart, float xEnd, float yMin, float yMax)
{
return Mathf.Clamp((yMax - yMin) / (xEnd - xStart) * (x - xStart) + yMin, yMin, yMax);
}
}
public class TimeSyncGroupComponent : TimeSyncComponentBase
{
private static readonly float MAX_SLEEP_DURATION = 0.5f;
private static readonly float MIN_SLEEP_DURATION = World.DELTA_TIME;
private static readonly float LOCAL_ADVANTAGE_UPDATE_RATE = 0.1f;
private static readonly float RECENT_SLEEP_BASE_FACTOR = 0.25f;
private float currentLocalAdvantage;
private float currentRemoteAdvantage;
private float lastRemoteAdvantage;
private readonly FrameAccumulator accumulator;
public float CurrentLocalAdvantage => currentLocalAdvantage;
public float CurrentRemoteAdvantage => currentRemoteAdvantage;
public TimeSyncGroupComponent(int playerIndex)
: base(playerIndex)
{
accumulator = new FrameAccumulator(base.playerIndex, 0.25f, 1.5f);
accumulator.upperBoundFunc = (int frame, int i) => TimeSyncComponentBase.LinearClamped(NetUtils.MaxPing, 0.03f, 0.3f, 0.5f, 0.7f) * (1f + GetRecentSleepFactor());
accumulator.lowerBoundFunc = (int frame, int i) => TimeSyncComponentBase.LinearClamped(NetUtils.MaxPing, 0.03f, 0.3f, 0.1f, 0.4f);
}
public override void Reset()
{
currentLocalAdvantage = 0f;
currentRemoteAdvantage = 0f;
lastRemoteAdvantage = 0f;
accumulator.Reset();
base.Reset();
}
public override void FrameUpdate()
{
int curFrame = Sync.curFrame;
float num = Sync.statusInput.otherReceived[playerIndex];
if (!(num < 0f))
{
float travelTimeEstimate = NetUtils.GetTravelTimeEstimate(Sync.othersInfo[playerIndex].peer.ping);
float num2 = num + travelTimeEstimate - (float)curFrame;
currentLocalAdvantage = Mathf.Lerp(CurrentLocalAdvantage, num2, LOCAL_ADVANTAGE_UPDATE_RATE);
currentRemoteAdvantage = Mathf.Lerp(CurrentRemoteAdvantage, lastRemoteAdvantage, 0.2f);
accumulator.FrameUpdate(Sync.curFrame, (CurrentRemoteAdvantage - CurrentLocalAdvantage) / 2f);
base.FrameUpdate();
}
}
public override float GetSleepInterval()
{
if (!accumulator.ThresholdReached())
{
return 0f;
}
return Mathf.Clamp(accumulator.currentValue * World.DELTA_TIME * TimeSyncComponentBase.ALIGN_TIMES_FACTOR, MIN_SLEEP_DURATION, MAX_SLEEP_DURATION);
}
public override bool ShouldEmergencySleep()
{
return accumulator.ThresholdVeryReached();
}
public override void OnSleep(float frames)
{
currentLocalAdvantage += frames;
currentRemoteAdvantage -= frames;
lastRemoteAdvantage -= frames;
accumulator.Reset();
base.OnSleep(frames);
}
public void UpdateRemoteFrameAdvantage(float remoteAdvantage)
{
lastRemoteAdvantage = remoteAdvantage;
}
protected override float GetRecentSleepBaseFactor()
{
return RECENT_SLEEP_BASE_FACTOR;
}
protected override float GetRecentSleepWindow()
{
return TimeSyncComponentBase.RECENT_SLEEP_WINDOW_CLIENT;
}
}
public class TimeSyncSoloHostComponent : TimeSyncComponentBase
{
private static readonly float MAX_SLEEP_DURATION = 0.5f;
private static readonly float MIN_SLEEP_DURATION = World.DELTA_TIME;
private static readonly int ESTIMATE_SLEEP_CHECK_INTERVAL = 120;
private static readonly float RUN_AHEAD_UPDATE_RATE = 0.1f;
private static readonly float RUN_AHEAD_ACCUMULATOR_THRESHOLD = 1.5f;
private static readonly float RECENT_SLEEP_BASE_FACTOR = 0.3f;
private int nextRecommendedSleep = int.MaxValue;
private float currentFrameEstimate = -1f;
private int noRunAheadUpdatesUntil = -1;
private readonly FrameAccumulator accumulator;
public int NextRecommendedSleep => nextRecommendedSleep;
public float CurrentFrameEstimate => currentFrameEstimate;
public float RunAheadEstimate => accumulator.currentValue;
public TimeSyncSoloHostComponent(int playerIndex)
: base(playerIndex)
{
accumulator = new FrameAccumulator(base.playerIndex, RUN_AHEAD_UPDATE_RATE, RUN_AHEAD_ACCUMULATOR_THRESHOLD);
accumulator.upperBoundFunc = (int frame, int i) => TimeSyncComponentBase.LinearClamped(NetUtils.MaxPing, 0.03f, 0.3f, 0.55f, 1.08f) * (1f + GetRecentSleepFactor());
accumulator.lowerBoundFunc = (int frame, int i) => TimeSyncComponentBase.LinearClamped(NetUtils.MaxPing, 0.03f, 0.3f, 0.25f, 0.5f);
}
public override void Reset()
{
nextRecommendedSleep = 60;
currentFrameEstimate = -1f;
noRunAheadUpdatesUntil = -1;
accumulator.Reset();
base.Reset();
}
public override void FrameUpdate()
{
currentFrameEstimate = EstimateCurrentFrame();
base.FrameUpdate();
}
public float EstimateCurrentFrame()
{
if (playerIndex == 0)
{
return Sync.curFrame;
}
float travelTimeEstimate = NetUtils.GetTravelTimeEstimate(Player.GetPlayer(playerIndex).peer.ping);
return (float)Sync.statusInput.otherReceived[playerIndex] + travelTimeEstimate;
}
public void UpdateRunAheadEstimate(float minimumFrame)
{
if (Sync.curFrame >= noRunAheadUpdatesUntil)
{
if (Sync.curFrame == noRunAheadUpdatesUntil)
{
noRunAheadUpdatesUntil = -1;
}
float frameValue = CurrentFrameEstimate - minimumFrame;
accumulator.FrameUpdate(Sync.curFrame, frameValue);
}
}
public override bool ShouldEmergencySleep()
{
return accumulator.ThresholdVeryReached();
}
public override float GetSleepInterval()
{
if (!accumulator.ThresholdReached())
{
return 0f;
}
return Mathf.Clamp(RunAheadEstimate * World.DELTA_TIME * TimeSyncComponentBase.ALIGN_TIMES_FACTOR, MIN_SLEEP_DURATION, MAX_SLEEP_DURATION);
}
public override void OnSleep(float frames)
{
noRunAheadUpdatesUntil = (int)((double)Sync.curFrame + Math.Ceiling(frames + NetUtils.MaxPing * (float)World.FPS) + 2.0);
nextRecommendedSleep = Sync.curFrame + ESTIMATE_SLEEP_CHECK_INTERVAL;
accumulator.Reset();
base.OnSleep(frames);
}
protected override float GetRecentSleepBaseFactor()
{
return RECENT_SLEEP_BASE_FACTOR;
}
protected override float GetRecentSleepWindow()
{
if (playerIndex != 0)
{
return TimeSyncComponentBase.RECENT_SLEEP_WINDOW_CLIENT;
}
return TimeSyncComponentBase.RECENT_SLEEP_WINDOW_HOST;
}
}
public static class MyPluginInfo
{
public const string PLUGIN_GUID = "ca.gov.mechasoulindustries.llb.my.cute.syncfix";
public const string PLUGIN_NAME = "Sync Fix";
public const string PLUGIN_VERSION = "1.0.0";
}
}
namespace SyncFix.Utils
{
public class InstructionBuilder
{
private List<CodeInstruction> instructions = new List<CodeInstruction>();
private Queue<OpCode> previousOpCodes = new Queue<OpCode>();
private bool expectingOpCode = true;
public InstructionBuilder OpCode(OpCode opcode)
{
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Expected O, but got Unknown
if (!expectingOpCode)
{
throw new InvalidOperationException($"tried to add opcode {opcode.Name} when expecting operand (previous instruction: {instructions.LastOrDefault()})");
}
previousOpCodes.Enqueue(opcode);
expectingOpCode = false;
if (opcode.OperandType == OperandType.InlineNone)
{
instructions.Add(new CodeInstruction(previousOpCodes.Dequeue(), (object)null));
expectingOpCode = true;
}
return this;
}
public InstructionBuilder Operand(object operand)
{
//IL_008a: Unknown result type (might be due to invalid IL or missing references)
//IL_0094: Expected O, but got Unknown
if (expectingOpCode)
{
if (operand == null)
{
CodeInstruction? obj = instructions.LastOrDefault();
if (obj != null && obj.opcode.OperandType == OperandType.InlineNone)
{
return this;
}
}
throw new InvalidOperationException($"tried to add operand {operand} when expecting opcode (previous instruction: {instructions.LastOrDefault()}, opcode: {previousOpCodes.LastOrDefault()}");
}
if (previousOpCodes.Count == 0)
{
throw new InvalidOperationException($"tried to add operand {operand} without previously adding an opcode (also this shouldnt happen?)");
}
instructions.Add(new CodeInstruction(previousOpCodes.Dequeue(), operand));
expectingOpCode = true;
return this;
}
public CodeInstruction[] Build()
{
return instructions.ToArray();
}
public CodeMatch[] BuildAsMatch()
{
return ((IEnumerable<CodeInstruction>)instructions).Select((Func<CodeInstruction, CodeMatch>)((CodeInstruction instruction) => new CodeMatch(instruction, (string)null))).ToArray();
}
}
public class NetUtils
{
private static float maxPing = 0f;
private static int maxPingPlayer = -1;
public static float MaxPing => maxPing;
public static float GetTravelTimeEstimate(float ping)
{
return Mathf.Pow(ping, 2f) * -17.3f + ping * 36.2f + 0.23f;
}
public static void UpdateMaxPing(Peer peer)
{
if (peer.playerNr == maxPingPlayer)
{
if (peer.ping > maxPing)
{
maxPing = peer.ping;
}
else
{
maxPing = GetMaxPing(out maxPingPlayer);
}
}
else if (peer.ping > maxPing)
{
maxPing = peer.ping;
maxPingPlayer = peer.playerNr;
}
}
public static float GetMaxPing(out int maxPlayerIndex)
{
float num = -1f;
int num2 = -1;
foreach (ALDOKEMAOMB item in Player.EPlayers())
{
if (item.LAADACKBGLL() && (!Sync.isActive || Sync.IsValidOther(item.CJFLMDNNMIE)) && item.KLEEADMGHNE.ping > num)
{
num = item.KLEEADMGHNE.ping;
num2 = item.CJFLMDNNMIE;
}
}
maxPlayerIndex = num2;
return num;
}
public static void ResetMaxPing()
{
maxPing = 0f;
maxPingPlayer = -1;
}
}
internal class PathUtils
{
public static DirectoryInfo ModdingFolder { get; private set; }
public static string ModdingFolderName { get; private set; }
public static void Init(PluginInfo info)
{
ModdingFolder = ModdingFolder.GetModSubFolder(info);
ModdingFolderName = ModdingFolder.FullName;
}
public static string GetFilepath(string resourceName)
{
return Utility.CombinePaths(new string[2] { ModdingFolderName, resourceName });
}
public static string GetCurrentGameDebugPath()
{
return Utility.CombinePaths(new string[3]
{
ModdingFolderName,
GetCurrentUserId(),
GetCurrentGameString()
});
}
private static string GetCurrentUserId()
{
KIIIINKJKNI gIGAKBJGFDI = CGLLJHHAJAK.GIGAKBJGFDI;
return ((gIGAKBJGFDI != null) ? gIGAKBJGFDI.ECEAOMHNGOL() : null) ?? "no_id";
}
private static string GetCurrentGameString()
{
if (!Sync.isActive)
{
return "";
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(Sync.matchNr);
stringBuilder.Append("_");
for (int i = 0; i < Sync.nPlayers; i++)
{
if (Sync.IsValidOther(i))
{
stringBuilder.Append(Player.GetPlayer(i).peer.peerId);
stringBuilder.Append("_");
}
}
stringBuilder.Append($"P{((Peer)P2P.localPeer).playerNr}");
stringBuilder.Append("_");
stringBuilder.Append(StateManager.IsUsingGroup() ? "GROUP" : "SOLO");
return stringBuilder.ToString();
}
}
}
namespace SyncFix.Patches
{
[HarmonyPatch]
public class Debug_Patches
{
[HarmonyPatch(typeof(Sync), "StopNow")]
[HarmonyPrefix]
public static bool RedirectStopNow()
{
if (SyncFixConfig.Instance.RecordDebugInfo)
{
FrameRecorders.SaveAll();
}
return true;
}
[HarmonyPatch(typeof(World), "Init1")]
[HarmonyPostfix]
public static void Init1Postfix(World __instance)
{
if (GameSettings.IsOnline)
{
RollbackStats.Reset();
((Component)__instance).gameObject.AddComponent<DebugInfo>().parent = __instance;
}
}
[HarmonyPatch(typeof(World), "Update")]
[HarmonyPrefix]
public static bool RedirectUpdate(World __instance)
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
if (Input.GetKeyDown(SyncFixConfig.Instance.DebugInfoKey))
{
SyncFixConfig.Instance.ShowDebugInfo = !SyncFixConfig.Instance.ShowDebugInfo;
}
return true;
}
[HarmonyPatch(typeof(Sync), "Rollback")]
[HarmonyPrefix]
public static bool RedirectRollback(int frame)
{
int num = Sync.curFrame - frame;
if (SyncFixConfig.Instance.RecordDebugInfo)
{
FrameRecorders.Record("rollbacks", Sync.curFrame, num);
}
RollbackStats.AddRollback(num);
return true;
}
[HarmonyPatch(typeof(P2P), "Wait")]
[HarmonyPrefix]
public static bool RedirectWait(float wait)
{
if (SyncFixConfig.Instance.RecordDebugInfo)
{
FrameRecorders.Record("wait", Sync.curFrame, wait);
}
RollbackStats.AddSleep(wait);
return true;
}
}
public class DebugInfo : MonoBehaviour
{
public World parent;
public GUIStyle guiStyle;
private void Awake()
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Expected O, but got Unknown
//IL_002e: Unknown result type (might be due to invalid IL or missing references)
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
guiStyle = new GUIStyle();
guiStyle.fontSize = 12;
guiStyle.normal.textColor = Color32.op_Implicit(new Color32((byte)100, (byte)100, (byte)100, byte.MaxValue));
}
private void OnGUI()
{
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
if (SyncFixConfig.Instance.ShowDebugInfo)
{
GUI.Label(new Rect(20f, 20f, 100f, 100f), RollbackStats.GetStats(), guiStyle);
}
}
}
[HarmonyPatch]
public class LobbyState_Patches
{
[CompilerGenerated]
private sealed class <DJLJONJDDDOPostfix>d__3 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public IEnumerator __result;
public HDLIJDBFGKN __instance;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <DJLJONJDDDOPostfix>d__3(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
break;
case 1:
<>1__state = -1;
break;
}
if (__result.MoveNext())
{
<>2__current = __result.Current;
<>1__state = 1;
return true;
}
if (__instance.FBJIDODJNFN)
{
StateManager.ResetState();
}
else
{
StateManager.ResetMode();
}
NetUtils.ResetMaxPing();
return false;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[HarmonyPatch(typeof(LocalHost), "OnOtherLeft")]
[HarmonyPostfix]
public static void OnOtherLeftPostfix(LocalHost __instance, Peer otherPeer)
{
StateManager.PeerLeft(otherPeer.playerNr);
}
[HarmonyPatch(typeof(LocalHost), "OnOtherJoined")]
[HarmonyPostfix]
public static void OnOtherJoinedPostfix(LocalHost __instance, string otherPeerId, string otherPeerName, int otherPlayerNr)
{
StateManager.PeerJoined(otherPlayerNr);
}
[HarmonyPatch(typeof(HDLIJDBFGKN), "OAACLLGMFLH", new Type[] { })]
[HarmonyPostfix]
public static void OAACLLGMFLHPostfix(HDLIJDBFGKN __instance)
{
if (StateManager.AllPeersConfirmed())
{
StateManager.SendAllGroupMessage();
}
}
[HarmonyPatch(typeof(HDLIJDBFGKN), "DJLJONJDDDO")]
[HarmonyPostfix]
public static IEnumerator DJLJONJDDDOPostfix(IEnumerator __result, HDLIJDBFGKN __instance)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <DJLJONJDDDOPostfix>d__3(0)
{
__result = __result,
__instance = __instance
};
}
[HarmonyPatch(typeof(Peer), "ResetPing")]
[HarmonyPostfix]
public static void ResetPingPostfix(Peer __instance)
{
__instance.pingsPrev[0] = -1f;
__instance.ping = 0f;
}
}
[HarmonyPatch]
public class TimeSyncGroup_Patches
{
[HarmonyPatch(typeof(Sync), "AlignTimes")]
[HarmonyPrefix]
public static bool RedirectAlignTimes()
{
if (!SyncFixConfig.Instance.Enabled)
{
return true;
}
if (Sync.doAwait)
{
SyncFixManager.Instance.MidMatchReset();
}
if (Sync.isAwaiting)
{
return false;
}
if (StateManager.IsUsingGroup())
{
SyncFixManager.Instance.GroupAlignTimes();
return false;
}
if (P2P.isHost)
{
if (Sync.curFrame < 60)
{
return false;
}
SyncFixManager.Instance.SoloHostAlignTimes();
return false;
}
return true;
}
[HarmonyPatch(typeof(Sync), "Init")]
[HarmonyPostfix]
public static void InitPostfix()
{
SyncFixManager.Instance.Reset();
}
[HarmonyPatch(typeof(LocalPeer), "SendToPlayerNr")]
[HarmonyPrefix]
public static bool RedirectSendToPlayerNr(LocalPeer __instance, int receiverPlayerNr, ref Message message)
{
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
//IL_0019: Invalid comparison between Unknown and I4
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_0058: Unknown result type (might be due to invalid IL or missing references)
//IL_0069: Unknown result type (might be due to invalid IL or missing references)
//IL_0075: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
if (!SyncFixConfig.Instance.Enabled)
{
return true;
}
if ((int)message.msg == 190)
{
JKMAAHELEMF val = (JKMAAHELEMF)message.ob;
float num = (float)val.CGJJEHPPOAN * 0.5f;
if (val.GCPKPHMKLBN == 126)
{
num += 30000f * World.DELTA_TIME;
}
val.CGJJEHPPOAN = (int)num;
message = new Message(message.msg, message.playerNr, message.index, (object)val, message.obSize);
}
return true;
}
[HarmonyPatch(typeof(OGONAGCFDPK), "IMEGGOOAADG")]
[HarmonyTranspiler]
public static IEnumerable<CodeInstruction> IMEGGOOAADGTranspiler(IEnumerable<CodeInstruction> instructions)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0008: Expected O, but got Unknown
//IL_0032: Unknown result type (might be due to invalid IL or missing references)
//IL_0038: Expected O, but got Unknown
CodeMatcher val = new CodeMatcher(instructions, (ILGenerator)null);
val.MatchStartForward((CodeMatch[])(object)new CodeMatch[1]
{
new CodeMatch((OpCode?)OpCodes.Call, (object)AccessTools.Method(typeof(Sync), "Start", (Type[])null, (Type[])null), (string)null)
});
val.Advance(1).Insert((CodeInstruction[])(object)new CodeInstruction[1] { Transpilers.EmitDelegate<Action>((Action)delegate
{
if (SyncFixConfig.Instance.Enabled)
{
SyncFixManager.Instance.Start();
}
}) });
return val.InstructionEnumeration();
}
[HarmonyPatch(typeof(P2P), "Update")]
[HarmonyPostfix]
public static void UpdatePostfix(P2P __instance)
{
if (SyncFixConfig.Instance.Enabled && P2P.isPinging && Sync.isActive && !Sync.doAwait && !Sync.isAwaiting && Sync.curFrame > SyncFixManager.Instance.NextAdvantageUpdate && Sync.curFrame > SyncFixManager.Instance.LastSleep + 1 && StateManager.IsUsingGroup())
{
SyncFixManager.ForAllValidOthers(delegate(int i)
{
SyncFixManager.Instance.SendLocalAdvantageToPlayer(i);
});
SyncFixManager.Instance.UpdateNextAdvantageTime();
}
}
[HarmonyPatch(typeof(P2P), "Wait")]
[HarmonyTranspiler]
public static IEnumerable<CodeInstruction> WaitTranspiler(IEnumerable<CodeInstruction> instructions)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0008: Expected O, but got Unknown
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0024: Expected O, but got Unknown
CodeMatcher val = new CodeMatcher(instructions, (ILGenerator)null);
val.End();
val.Insert((CodeInstruction[])(object)new CodeInstruction[2]
{
new CodeInstruction(OpCodes.Ldarg_0, (object)null),
Transpilers.EmitDelegate<Action<float>>((Action<float>)delegate(float f)
{
if (SyncFixConfig.Instance.Enabled)
{
SyncFixManager.Instance.OnSleep(f);
}
})
});
return val.InstructionEnumeration();
}
[HarmonyPatch(typeof(Sync), "UpdateAwait")]
[HarmonyTranspiler]
public static IEnumerable<CodeInstruction> UpdateAwaitTranspiler(IEnumerable<CodeInstruction> instructions)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
CodeMatcher val = new CodeMatcher(instructions, (ILGenerator)null);
val.MatchStartForward(new InstructionBuilder().OpCode(OpCodes.Ldsfld).Operand(AccessTools.Field(typeof(Sync), "statusInput")).OpCode(OpCodes.Ldfld)
.Operand(AccessTools.Field(typeof(FrameStatus), "handledByAll"))
.OpCode(OpCodes.Call)
.Operand(AccessTools.PropertyGetter(typeof(Sync), "curFrame"))
.OpCode(OpCodes.Ldc_I4_S)
.Operand((sbyte)30)
.OpCode(OpCodes.Sub)
.BuildAsMatch());
val.Advance(2);
val.RemoveInstructions(3);
val.Insert(new InstructionBuilder().OpCode(OpCodes.Ldsfld).Operand(AccessTools.Field(typeof(Sync), "awaitFrame")).OpCode(OpCodes.Ldc_I4_S)
.Operand((sbyte)120)
.OpCode(OpCodes.Add)
.Build());
return val.InstructionEnumeration();
}
}
[HarmonyPatch]
public class TimeSyncSolo_Patches
{
[HarmonyPatch(typeof(Sync), "AlignTimes")]
[HarmonyPostfix]
public static void AlignTimesPostfix(Sync __instance)
{
if (SyncFixConfig.Instance.Enabled && !P2P.isHost && !StateManager.HostHasSyncFix && Sync.curFrame % 60 == 0)
{
SelfVanillaAlignTimes();
}
}
private static void SelfVanillaAlignTimes()
{
//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
float fixedDeltaTime = Time.fixedDeltaTime;
float num = (float)Sync.curFrame * fixedDeltaTime;
float num2 = num;
for (int i = 0; i < Sync.nPlayers; i++)
{
OtherInfo val = Sync.othersInfo[i];
if (val != null)
{
float num3 = (float)Sync.statusInput.otherReceived[i] * fixedDeltaTime;
num3 += val.peer.ping * 0.5f;
if (num3 < num2)
{
num2 = num3;
}
}
}
float num4 = num - num2;
num4 *= 0.75f;
if (num4 >= 0.02f)
{
num4 = Mathf.Min(num4, 0.5f);
P2P.SendToPlayerNr(((Peer)P2P.localPeer).playerNr, new Message((Msg)189, Sync.matchNr, Mathf.RoundToInt(num4 * 1000f), (object)null, -1));
}
}
[HarmonyPatch(typeof(LocalPeer), "OnReceiveMessage")]
[HarmonyPrefix]
public static bool RedirectOnReceiveMessage(LocalPeer __instance, Envelope envelope)
{
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Invalid comparison between Unknown and I4
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
if (!SyncFixConfig.Instance.Enabled)
{
return true;
}
if ((int)envelope.message.msg == 189 && envelope.sender != ((Peer)P2P.localPeer).peerId && !StateManager.HostHasSyncFix)
{
return false;
}
return true;
}
[HarmonyPatch(typeof(Peer), "ResolvePing", new Type[] { typeof(int) })]
[HarmonyPostfix]
public static void ResolvePingPostfix(Peer __instance)
{
NetUtils.UpdateMaxPing(__instance);
}
}
}
namespace SyncFix.FrameRecorder
{
internal class FrameRecord<T> : IComparable<FrameRecord<T>>
{
public int frame;
public T value;
public FrameRecord(int frame, T value)
{
this.frame = frame;
this.value = value;
}
public int CompareTo(FrameRecord<T> other)
{
return frame.CompareTo(other.frame);
}
public override string ToString()
{
return $"{frame},{value}";
}
}
internal class FrameRecorder<T> : IFrameRecorder
{
public readonly string name;
public List<FrameRecord<T>> records;
private Func<FrameRecord<T>, string> toStringFunction = (FrameRecord<T> record) => record.ToString();
public Func<FrameRecord<T>, string> ToStringFunc
{
get
{
return toStringFunction;
}
set
{
toStringFunction = value;
}
}
public FrameRecorder(string name)
{
this.name = name;
records = new List<FrameRecord<T>>();
}
public void Record<U>(int frame, U value)
{
if (!(value is T))
{
throw new InvalidCastException($"tried to record a {typeof(U)} in a FrameRecorder<{typeof(T)}>");
}
object obj = value;
T value2 = (T)((obj is T) ? obj : null);
records.Add(new FrameRecord<T>(frame, value2));
}
public void SaveToFile()
{
if (records.Count == 0)
{
return;
}
records.Sort();
StringBuilder stringBuilder = new StringBuilder();
foreach (FrameRecord<T> record in records)
{
stringBuilder.Append(ToStringFunc(record));
stringBuilder.AppendLine();
}
string path = Utility.CombinePaths(new string[2]
{
PathUtils.GetCurrentGameDebugPath(),
name + ".csv"
});
Directory.CreateDirectory(Directory.GetParent(path).FullName);
File.AppendAllText(path, stringBuilder.ToString());
}
public void Clear()
{
records.Clear();
}
}
internal class FrameRecorders
{
private static readonly Dictionary<string, IFrameRecorder> _frameRecorders;
static FrameRecorders()
{
_frameRecorders = new Dictionary<string, IFrameRecorder>();
}
public static IFrameRecorder GetFrameRecorder<T>(string name)
{
if (_frameRecorders.TryGetValue(name, out var value))
{
return value;
}
IFrameRecorder frameRecorder = new FrameRecorder<T>(name);
_frameRecorders.Add(name, frameRecorder);
return frameRecorder;
}
public static void Record<T>(string name, int frame, T value)
{
GetFrameRecorder<T>(name).Record(frame, value);
}
public static void SaveAll()
{
foreach (IFrameRecorder value in _frameRecorders.Values)
{
value.SaveToFile();
value.Clear();
}
}
public static void ClearAll()
{
foreach (IFrameRecorder value in _frameRecorders.Values)
{
value.Clear();
}
}
}
internal interface IFrameRecorder
{
void Record<T>(int frame, T value);
void SaveToFile();
void Clear();
}
}
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
internal sealed class IgnoresAccessChecksToAttribute : Attribute
{
public IgnoresAccessChecksToAttribute(string assemblyName)
{
}
}
}