Decompiled source of RumbleReplay v1.0.2

Mods/RumbleReplayMod.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppRUMBLE.Players;
using Il2CppSystem.Collections.Generic;
using Il2CppSystem.Text;
using MelonLoader;
using MelonLoader.Preferences;
using Newtonsoft.Json;
using RumbleModdingAPI;
using RumbleReplay;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(RumbleReplayModClass), "RumbleReplayMod", "1.0.2", "blank", null)]
[assembly: MelonGame("Buckethead Entertainment", "RUMBLE")]
[assembly: MelonAdditionalDependencies(new string[] { "RumbleModdingAPI" })]
[assembly: MelonColor(255, 255, 170, 238)]
[assembly: AssemblyTitle("RumbleReplayMod")]
[assembly: AssemblyDescription("Generates Replay Files for use in blender or other supported programs")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RumbleReplayMod")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("7eebd5a9-aa24-4565-9bca-aad698946213")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace RumbleReplay;

public sealed class RumbleReplayModClass : MelonMod
{
	public sealed class ReplayHeader
	{
		public readonly string Version = "1.0.2";

		public string EnemyName = "Unknown";

		public string LocalName = "Unknown";

		public string MapName = "Unknown";

		public readonly string Date = DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss");
	}

	private readonly GameObject[] _poolObjects = (GameObject[])(object)new GameObject[8];

	private readonly int[][] _cullers = new int[8][];

	private readonly List<byte> _writebuffer = new List<byte>();

	private MelonPreferences_Category _rumbleReplayPreferences;

	private MelonPreferences_Entry<int> _basicPlayerUpdateInterval;

	private MelonPreferences_Entry<int> _basicStructureUpdateInterval;

	private MelonPreferences_Entry<bool> _enabled;

	public bool Recording;

	public short FrameCounter;

	private string _currentScene;

	private FileStream _replayFile;

	private BinaryWriter _replayWriter;

	private static List<byte> SerializeTransform(Transform transform, bool includeRotation = true, bool includePosition = true, bool includeScale = false)
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		List<byte> list = new List<byte>();
		if (includePosition)
		{
			list.AddRange(BitConverter.GetBytes(transform.position.x));
			list.AddRange(BitConverter.GetBytes(transform.position.y));
			list.AddRange(BitConverter.GetBytes(transform.position.z));
		}
		if (includeRotation)
		{
			list.AddRange(BitConverter.GetBytes(transform.rotation.w));
			list.AddRange(BitConverter.GetBytes(transform.rotation.y));
			list.AddRange(BitConverter.GetBytes(transform.rotation.x));
			list.AddRange(BitConverter.GetBytes(transform.rotation.z));
		}
		if (includeScale)
		{
			list.AddRange(BitConverter.GetBytes(transform.localScale.x));
			list.AddRange(BitConverter.GetBytes(transform.localScale.y));
			list.AddRange(BitConverter.GetBytes(transform.localScale.z));
		}
		return list;
	}

	public void NewReplay(string scene, string localPlayerName = "", string remotePlayerName = "")
	{
		if (_enabled.Value)
		{
			string text = JsonConvert.SerializeObject((object)new ReplayHeader
			{
				EnemyName = remotePlayerName,
				LocalName = localPlayerName,
				MapName = scene
			});
			if (_replayFile != null)
			{
				StopReplay();
			}
			((MelonBase)this).LoggerInstance.Msg("Recording Started");
			if (!Directory.Exists("UserData/Replays"))
			{
				Directory.CreateDirectory("UserData/Replays");
			}
			_replayFile = File.Create("UserData/Replays/" + localPlayerName + "-Vs-" + remotePlayerName + " On " + scene + "-" + Path.GetRandomFileName() + ".rr");
			_replayWriter = new BinaryWriter(_replayFile);
			byte[] buffer = new byte[2] { 82, 82 };
			_replayWriter.Write(buffer);
			_replayWriter.Write((byte)text.Length);
			_replayWriter.Write(Il2CppArrayBase<byte>.op_Implicit((Il2CppArrayBase<byte>)(object)Encoding.UTF8.GetBytes(text)));
			Recording = true;
		}
	}

	public void StopReplay()
	{
		if (_replayFile == null)
		{
			((MelonBase)this).LoggerInstance.Warning("StopReplay with null replay file");
			return;
		}
		_replayWriter.Write(_writebuffer.ToArray());
		_writebuffer.Clear();
		Recording = false;
		FrameCounter = 0;
		((MelonBase)this).LoggerInstance.Msg("Recording Stopped");
		_replayFile.Close();
		_replayFile = null;
		_replayWriter = null;
	}

	public override void OnLateInitializeMelon()
	{
		Calls.onMapInitialized += MapReady;
	}

	public override void OnInitializeMelon()
	{
		_rumbleReplayPreferences = MelonPreferences.CreateCategory("RumbleReplaySettings");
		_rumbleReplayPreferences.SetFilePath("UserData/RumbleReplay.cfg");
		_basicPlayerUpdateInterval = _rumbleReplayPreferences.CreateEntry<int>("BasicPlayerUpdate_Interval", 4, (string)null, "The interval we create updates for the players Hands and Head (will deprecate when better solution arises)", false, false, (ValueValidator)null, (string)null);
		_basicStructureUpdateInterval = _rumbleReplayPreferences.CreateEntry<int>("BasicStructureUpdate_Interval", 1, (string)null, "the interval structure positions and rotations are updated (Leave at 1 for 1 update every physics frame, 2 for one update every 2 and so on)", false, false, (ValueValidator)null, (string)null);
		_enabled = _rumbleReplayPreferences.CreateEntry<bool>("RecordingEnabled", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
		_rumbleReplayPreferences.SaveToFile(true);
		((MelonBase)this).LoggerInstance.Msg($"BasicPlayerUpdate_Interval={_basicPlayerUpdateInterval.Value}");
		((MelonBase)this).LoggerInstance.Msg($"BasicStructureUpdate_Interval={_basicStructureUpdateInterval.Value}");
		((MelonBase)this).LoggerInstance.Msg($"Enabled {_enabled.Value}");
	}

	public override void OnSceneWasLoaded(int _, string sceneName)
	{
		if (Recording)
		{
			StopReplay();
		}
		_currentScene = sceneName;
	}

	private void MapReady()
	{
		//IL_014a: Unknown result type (might be due to invalid IL or missing references)
		//IL_014f: Unknown result type (might be due to invalid IL or missing references)
		if (!(_currentScene != "Loader") || !(_currentScene != "Park") || !(_currentScene != "Gym"))
		{
			return;
		}
		((MelonBase)this).LoggerInstance.Msg("Loaded scene: " + _currentScene);
		_poolObjects[0] = Structures.GetPoolBall();
		_poolObjects[1] = Structures.GetPoolBoulderBall();
		_poolObjects[2] = Structures.GetPoolCube();
		_poolObjects[3] = Structures.GetPoolDisc();
		_poolObjects[4] = Structures.GetPoolLargeRock();
		_poolObjects[5] = Structures.GetPoolPillar();
		_poolObjects[6] = Structures.GetPoolWall();
		_poolObjects[7] = Structures.GetPoolSmallRock();
		for (ushort num = 0; num < _poolObjects.Length; num++)
		{
			GameObject val = _poolObjects[num];
			_cullers[num] = new int[val.transform.GetChildCount()];
			((MelonBase)this).LoggerInstance.Msg((object)_cullers[num].Length);
			((MelonBase)this).LoggerInstance.Msg(((Object)val.transform.GetChild(0)).name);
			for (ushort num2 = 0; num2 < val.transform.GetChildCount(); num2++)
			{
				GameObject gameObject = ((Component)val.transform.GetChild((int)num2)).gameObject;
				int[] obj = _cullers[num];
				ushort num3 = num2;
				Vector3 position = gameObject.transform.position;
				obj[num3] = ((object)(Vector3)(ref position)).GetHashCode();
			}
		}
		Player localPlayer = Managers.GetPlayerManager().LocalPlayer;
		string text = ((localPlayer != null) ? localPlayer.Data.GeneralData.PublicUsername : null) ?? "Unknown";
		Player? obj2 = Players.GetEnemyPlayers().FirstOrDefault();
		string text2 = ((obj2 != null) ? obj2.Data.GeneralData.PublicUsername : null) ?? "Unknown";
		((MelonBase)this).LoggerInstance.Msg(text);
		((MelonBase)this).LoggerInstance.Msg(text2);
		NewReplay(_currentScene, Regex.Replace(text, "[^a-zA-Z0-9_ ]", ""), Regex.Replace(text2, "[^a-zA-Z0-9_ ]", ""));
	}

	public override void OnApplicationQuit()
	{
		if (Recording)
		{
			StopReplay();
		}
	}

	public override void OnFixedUpdate()
	{
		//IL_017c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0181: Unknown result type (might be due to invalid IL or missing references)
		//IL_0073: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ee: 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_00ce: Unknown result type (might be due to invalid IL or missing references)
		if (!Recording)
		{
			return;
		}
		List<byte> list = new List<byte>();
		if (FrameCounter % _basicPlayerUpdateInterval.Value == 0)
		{
			int num = 0;
			Enumerator<Player> enumerator = Managers.GetPlayerManager().AllPlayers.GetEnumerator();
			while (enumerator.MoveNext())
			{
				Player current = enumerator.Current;
				Transform transform = (Transform)(((object)((current != null) ? ((Component)((Component)current.Controller).transform.GetChild((num > 0) ? 6 : 5).GetChild(4)).transform : null)) ?? ((object)new Transform()));
				Transform transform2 = (Transform)(((object)((current != null) ? ((Component)((Component)current.Controller).transform.GetChild(1).GetChild(1)).transform : null)) ?? ((object)new Transform()));
				Transform transform3 = (Transform)(((object)((current != null) ? ((Component)((Component)current.Controller).transform.GetChild(1).GetChild(2)).transform : null)) ?? ((object)new Transform()));
				list.Add((byte)num);
				list.AddRange(SerializeTransform(transform));
				list.AddRange(SerializeTransform(transform2));
				list.AddRange(SerializeTransform(transform3));
				num++;
			}
		}
		List<byte> list2 = new List<byte>();
		if (FrameCounter % _basicStructureUpdateInterval.Value == 0)
		{
			for (ushort num2 = 0; num2 < _poolObjects.Length; num2++)
			{
				GameObject val = _poolObjects[num2];
				for (ushort num3 = 0; num3 < _cullers[num2].Length; num3++)
				{
					GameObject gameObject = ((Component)val.transform.GetChild((int)num3)).gameObject;
					int num4 = _cullers[num2][num3];
					Vector3 position = gameObject.transform.position;
					if (num4 != ((object)(Vector3)(ref position)).GetHashCode())
					{
						int[] obj = _cullers[num2];
						ushort num5 = num3;
						position = gameObject.transform.position;
						obj[num5] = ((object)(Vector3)(ref position)).GetHashCode();
						list2.Add((byte)num2);
						list2.Add((byte)num3);
						Transform transform4 = gameObject.transform;
						if (!gameObject.active)
						{
							transform4.position = new Vector3(0f, -300f, 0f);
						}
						list2.AddRange(SerializeTransform(transform4));
					}
				}
			}
		}
		if (list2.Count != 0)
		{
			_writebuffer.AddRange(BitConverter.GetBytes((short)list2.Count));
			_writebuffer.AddRange(BitConverter.GetBytes(FrameCounter));
			_writebuffer.Add(0);
			_writebuffer.AddRange(list2);
		}
		if (list.Count != 0)
		{
			_writebuffer.AddRange(BitConverter.GetBytes((short)list.Count));
			_writebuffer.AddRange(BitConverter.GetBytes(FrameCounter));
			_writebuffer.Add(1);
			_writebuffer.AddRange(list);
		}
		FrameCounter++;
		if (_writebuffer.Count >= 1000)
		{
			_replayWriter.Write(_writebuffer.ToArray());
			_writebuffer.Clear();
		}
	}
}