Decompiled source of RallyXAutoSplitter v1.0.0

Mods\RallyXAutoSplitter.dll

Decompiled 11 hours ago
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using HarmonyLib;
using Il2CppRUMBLE.Environment.Minigames;
using MelonLoader;
using Microsoft.CodeAnalysis;
using RallyXAutoSplitter;
using RallyXAutoSplitter.Adapters;
using RallyXAutoSplitter.Services;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Mod), "RallyXAutoSplitter", "1.0.0", "ACutiePi", null)]
[assembly: MelonGame("Buckethead Entertainment", "RUMBLE")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("RallyXAutoSplitter")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+74ae05adf6839148e184688c55b0f807d2a09f1e")]
[assembly: AssemblyProduct("RallyXAutoSplitter")]
[assembly: AssemblyTitle("RallyXAutoSplitter")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace RallyXAutoSplitter
{
	public class Mod : MelonMod
	{
		public static Instance Logger { get; private set; }

		public static AutoSplitterService Service { get; private set; }

		public static LiveSplitService LiveSplitService { get; private set; }

		public override void OnInitializeMelon()
		{
			Logger = ((MelonBase)this).LoggerInstance;
			MelonLoggerAdapter logger = new MelonLoggerAdapter(((MelonBase)this).LoggerInstance);
			TcpClientFactory tcpFactory = new TcpClientFactory();
			LiveSplitService = new LiveSplitService(logger, tcpFactory);
			Service = new AutoSplitterService(LiveSplitService, logger);
			Logger.Msg($"Connected to LiveSplit Server: {LiveSplitService.EnsureConnected()}");
			((MelonBase)this).HarmonyInstance.PatchAll();
		}
	}
}
namespace RallyXAutoSplitter.Services
{
	public sealed class AutoSplitterService
	{
		private readonly ILogger _logger;

		private readonly ILiveSplitService _livesplit;

		private bool _runActive;

		private short _expectedTrack;

		public AutoSplitterService(ILiveSplitService livesplit, ILogger logger)
		{
			_logger = logger;
			_livesplit = livesplit;
		}

		public void RaceStarted(RockRace race)
		{
			RockRaceAdapter race2 = new RockRaceAdapter(race);
			RaceStarted((IRaceData)race2);
		}

		public void RaceStarted(IRaceData race)
		{
			short selectedTrackIndex = race.SelectedTrackIndex;
			if (race.SelectedLapCount != 3)
			{
				return;
			}
			switch (selectedTrackIndex)
			{
			case 0:
				if (_expectedTrack == 0)
				{
					_runActive = true;
					_expectedTrack = 1;
					_livesplit.ResetRun();
					_livesplit.StartTimer();
					_logger.Msg("RaceStarted: Track 0 - Timer started");
				}
				break;
			case 1:
			case 2:
			case 3:
			case 4:
				if (_runActive)
				{
					if (selectedTrackIndex != _expectedTrack)
					{
						_logger.Warning($"Ignoring unexpected track {selectedTrackIndex}, expected {_expectedTrack}");
					}
					else
					{
						_expectedTrack++;
						_livesplit.Split();
						_logger.Msg($"RaceStarted: Track {selectedTrackIndex} - Split");
					}
				}
				break;
			}
		}

		public void RaceEnded(RockRace race, short winningPlayer)
		{
			RockRaceAdapter race2 = new RockRaceAdapter(race);
			RaceEnded((IRaceData)race2, winningPlayer);
		}

		public void RaceEnded(IRaceData race, short winningPlayer)
		{
			if (winningPlayer == -1)
			{
				_livesplit.ResetRun();
				ClearRunState();
				_logger.Msg("RaceEnded: Detected reset.");
			}
			else if (_runActive && race.SelectedTrackIndex == 4 && race.SelectedLapCount == 3)
			{
				if (_expectedTrack == 5)
				{
					_livesplit.Split();
					_logger.Msg("RaceEnded: Track 4 completed - Final split.");
				}
				else
				{
					_logger.Warning($"Track 4 completed but expected track state was {_expectedTrack}");
				}
				ClearRunState();
			}
		}

		private void ClearRunState()
		{
			_runActive = false;
			_expectedTrack = 0;
			_logger.Msg("State cleared.");
		}
	}
	public interface ILiveSplitService
	{
		void StartTimer();

		void Split();

		void ResetRun();
	}
	public interface ITcpClientFactory
	{
		ITcpClientWrapper CreateClient();
	}
	public interface ITcpClientWrapper : IDisposable
	{
		bool Connected { get; }

		void Connect(string host, int port);

		Stream GetStream();
	}
	public class LiveSplitService : ILiveSplitService, IDisposable
	{
		private readonly ILogger _logger;

		private readonly ITcpClientFactory _tcpFactory;

		private ITcpClientWrapper? _client;

		private StreamWriter? _writer;

		private bool _hasWarnedConnectionFailure;

		public LiveSplitService(ILogger logger, ITcpClientFactory tcpFactory)
		{
			_logger = logger;
			_tcpFactory = tcpFactory;
		}

		public void StartTimer()
		{
			_logger.Msg("[AUTOSPLITTER] START TIMER");
			Send("starttimer");
		}

		public void Split()
		{
			_logger.Msg("[AUTOSPLITTER] SPLIT");
			Send("split");
		}

		public void ResetRun()
		{
			_logger.Msg("[AUTOSPLITTER] RESET");
			Send("reset");
		}

		private void Send(string command)
		{
			if (!EnsureConnected())
			{
				return;
			}
			try
			{
				_logger.Msg("-> LiveSplit: " + command);
				_writer.WriteLine(command);
			}
			catch (Exception ex)
			{
				_logger.Warning("Failed sending '" + command + "': " + ex.Message);
			}
		}

		public bool EnsureConnected()
		{
			try
			{
				ITcpClientWrapper? client = _client;
				if (client != null && client.Connected)
				{
					return true;
				}
				_client?.Dispose();
				_client = _tcpFactory.CreateClient();
				_client.Connect("127.0.0.1", 16834);
				_writer = new StreamWriter(_client.GetStream())
				{
					AutoFlush = true
				};
				if (_hasWarnedConnectionFailure)
				{
					_logger.Msg("Connected to LiveSplit.");
				}
				_hasWarnedConnectionFailure = false;
				return true;
			}
			catch
			{
				if (!_hasWarnedConnectionFailure)
				{
					_logger.Warning("Could not connect to LiveSplit. Is the TCP server running?");
					_hasWarnedConnectionFailure = true;
				}
				return false;
			}
		}

		public void Dispose()
		{
			GC.SuppressFinalize(this);
			_writer?.Dispose();
			_client?.Dispose();
		}
	}
}
namespace RallyXAutoSplitter.Patches
{
	[HarmonyPatch(typeof(ParkMinigame), "StartMinigame")]
	public static class RockRaceStartPatch
	{
		internal static void Postfix(RockRace __instance)
		{
			Mod.Service.RaceStarted(__instance);
		}
	}
	[HarmonyPatch(typeof(ParkMinigame), "OnMiniGameEnded")]
	public static class OnMiniGameEndedPatch
	{
		public static void Postfix(RockRace __instance, short winningPlayer)
		{
			Mod.Service.RaceEnded(__instance, winningPlayer);
		}
	}
}
namespace RallyXAutoSplitter.Adapters
{
	public interface ILogger
	{
		void Msg(string message);

		void Warning(string message);

		void Error(string message);
	}
	public class MelonLoggerAdapter : ILogger
	{
		private readonly Instance _logger;

		public MelonLoggerAdapter(Instance logger)
		{
			_logger = logger;
		}

		public void Msg(string message)
		{
			_logger.Msg(message);
		}

		public void Warning(string message)
		{
			_logger.Warning(message);
		}

		public void Error(string message)
		{
			_logger.Error(message);
		}
	}
	public interface IRaceData
	{
		short SelectedTrackIndex { get; }

		short SelectedLapCount { get; }
	}
	public class RockRaceAdapter : IRaceData
	{
		private readonly RockRace _race;

		public short SelectedTrackIndex => _race.selectedTrackIndex;

		public short SelectedLapCount => _race.selectedLapCount;

		public RockRaceAdapter(RockRace race)
		{
			_race = race;
		}
	}
	public class TcpClientFactory : ITcpClientFactory
	{
		public ITcpClientWrapper CreateClient()
		{
			return new TcpClientWrapper(new TcpClient());
		}
	}
	public class TcpClientWrapper : ITcpClientWrapper, IDisposable
	{
		private readonly TcpClient _client;

		public bool Connected => _client.Connected;

		public TcpClientWrapper(TcpClient client)
		{
			_client = client;
		}

		public void Connect(string host, int port)
		{
			_client.Connect(host, port);
		}

		public Stream GetStream()
		{
			return _client.GetStream();
		}

		public void Dispose()
		{
			_client?.Dispose();
		}
	}
}