Decompiled source of RumbleReplay v1.1.1

Mods/RumbleReplayMod.dll

Decompiled 2 months 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), "RumbleReplay", "1.1.0", "blank", null)]
[assembly: MelonGame("Buckethead Entertainment", "RUMBLE")]
[assembly: MelonAdditionalDependencies(new string[] { "RumbleModdingAPI" })]
[assembly: MelonColor(255, 255, 170, 238)]
[assembly: AssemblyTitle("RumbleReplay")]
[assembly: AssemblyDescription("Generates Replay Files for use in blender or other supported programs")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RumbleReplay")]
[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 ReplayPlayerData
	{
		public string Name = "Unknown";

		public int Battlepoints;

		public string PlayfabID = "Unknown";

		public string Cosmetics = PlayerVisualData.DefaultFemale.ToPlayfabDataString();
	}

	public sealed class ReplayHeader
	{
		public readonly string Version = "1.1.0";

		public ReplayPlayerData LocalPlayer = new ReplayPlayerData();

		public ReplayPlayerData RemotePlayer = new ReplayPlayerData();

		public string Scene = "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> _playerUpdateInterval;

	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, ReplayPlayerData localPlayer, ReplayPlayerData remotePlayer)
	{
		if (_enabled.Value)
		{
			string text = JsonConvert.SerializeObject((object)new ReplayHeader
			{
				LocalPlayer = localPlayer,
				RemotePlayer = remotePlayer,
				Scene = 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/" + localPlayer.Name + "-Vs-" + remotePlayer.Name + " On " + scene + "-" + Path.GetRandomFileName() + ".rr");
			_replayWriter = new BinaryWriter(_replayFile);
			byte[] buffer = new byte[2] { 82, 82 };
			_replayWriter.Write(buffer);
			_replayWriter.Write((short)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");
		_playerUpdateInterval = _rumbleReplayPreferences.CreateEntry<int>("PlayerUpdate_Interval", 4, (string)null, "The interval we create updates for the players Hands and Head", 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($"PlayerUpdate_Interval={_playerUpdateInterval.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();
			}
		}
		ReplayPlayerData replayPlayerData = new ReplayPlayerData();
		Player localPlayer = Managers.GetPlayerManager().LocalPlayer;
		replayPlayerData.Name = Regex.Replace(((localPlayer != null) ? localPlayer.Data.GeneralData.PublicUsername : null) ?? "Unknown", "<.*?>|[^a-zA-Z0-9_ ]", "");
		Player localPlayer2 = Managers.GetPlayerManager().LocalPlayer;
		replayPlayerData.Battlepoints = ((localPlayer2 != null) ? localPlayer2.Data.GeneralData.BattlePoints : 0);
		Player localPlayer3 = Managers.GetPlayerManager().LocalPlayer;
		replayPlayerData.PlayfabID = ((localPlayer3 != null) ? localPlayer3.Data.GeneralData.PlayFabMasterId : null) ?? "Unknown";
		Player localPlayer4 = Managers.GetPlayerManager().LocalPlayer;
		replayPlayerData.Cosmetics = ((localPlayer4 != null) ? localPlayer4.Data.visualData.ToPlayfabDataString() : null) ?? PlayerVisualData.DefaultFemale.ToPlayfabDataString();
		ReplayPlayerData replayPlayerData2 = replayPlayerData;
		ReplayPlayerData replayPlayerData3 = new ReplayPlayerData();
		Player? obj2 = Players.GetEnemyPlayers().FirstOrDefault();
		replayPlayerData3.Name = Regex.Replace(((obj2 != null) ? obj2.Data.GeneralData.PublicUsername : null) ?? "Unknown", "<.*?>|[^a-zA-Z0-9_ ]", "");
		Player? obj3 = Players.GetEnemyPlayers().FirstOrDefault();
		replayPlayerData3.Battlepoints = ((obj3 != null) ? obj3.Data.GeneralData.BattlePoints : 0);
		Player? obj4 = Players.GetEnemyPlayers().FirstOrDefault();
		replayPlayerData3.PlayfabID = ((obj4 != null) ? obj4.Data.GeneralData.PlayFabMasterId : null) ?? "Unknown";
		Player? obj5 = Players.GetEnemyPlayers().FirstOrDefault();
		replayPlayerData3.Cosmetics = ((obj5 != null) ? obj5.Data.visualData.ToPlayfabDataString() : null) ?? PlayerVisualData.DefaultFemale.ToPlayfabDataString();
		ReplayPlayerData replayPlayerData4 = replayPlayerData3;
		((MelonBase)this).LoggerInstance.Msg(replayPlayerData2.Name);
		((MelonBase)this).LoggerInstance.Msg(replayPlayerData4.Name);
		NewReplay(_currentScene, replayPlayerData2, replayPlayerData4);
	}

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

	private static List<byte> _createPlayerUpdate()
	{
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0132: Unknown result type (might be due to invalid IL or missing references)
		//IL_017d: Unknown result type (might be due to invalid IL or missing references)
		int num = 0;
		List<byte> list = new List<byte>();
		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(0).GetChild(1).GetChild(0)
				.GetChild(4)).transform : null)) ?? ((object)new Transform()));
			Transform transform3 = (Transform)(((object)((current != null) ? ((Component)((Component)current.Controller).transform.GetChild(1).GetChild(1)).transform : null)) ?? ((object)new Transform()));
			Transform transform4 = (Transform)(((object)((current != null) ? ((Component)((Component)current.Controller).transform.GetChild(1).GetChild(2)).transform : null)) ?? ((object)new Transform()));
			Transform transform5 = (Transform)(((object)((current != null) ? ((Component)((Component)current.Controller).transform.GetChild(0).GetChild(1).GetChild(0)
				.GetChild(2)
				.GetChild(0)
				.GetChild(0)
				.GetChild(0)).transform : null)) ?? ((object)new Transform()));
			Transform transform6 = (Transform)(((object)((current != null) ? ((Component)((Component)current.Controller).transform.GetChild(0).GetChild(1).GetChild(0)
				.GetChild(3)
				.GetChild(0)
				.GetChild(0)
				.GetChild(0)).transform : null)) ?? ((object)new Transform()));
			list.Add((byte)num);
			list.AddRange(SerializeTransform(transform));
			list.AddRange(SerializeTransform(transform2));
			list.AddRange(SerializeTransform(transform3));
			list.AddRange(SerializeTransform(transform4));
			list.AddRange(SerializeTransform(transform5));
			list.AddRange(SerializeTransform(transform6));
			num++;
		}
		return list;
	}

	private List<byte> _createBasicStructureUpdate()
	{
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00af: Unknown result type (might be due to invalid IL or missing references)
		List<byte> list = new List<byte>();
		for (ushort num = 0; num < _poolObjects.Length; num++)
		{
			GameObject val = _poolObjects[num];
			for (ushort num2 = 0; num2 < _cullers[num].Length; num2++)
			{
				GameObject gameObject = ((Component)val.transform.GetChild((int)num2)).gameObject;
				int num3 = _cullers[num][num2];
				Vector3 position = gameObject.transform.position;
				if (num3 != ((object)(Vector3)(ref position)).GetHashCode())
				{
					int[] obj = _cullers[num];
					ushort num4 = num2;
					position = gameObject.transform.position;
					obj[num4] = ((object)(Vector3)(ref position)).GetHashCode();
					list.Add((byte)num);
					list.Add((byte)num2);
					Transform transform = gameObject.transform;
					if (!gameObject.active)
					{
						transform.position = new Vector3(0f, -300f, 0f);
					}
					list.AddRange(SerializeTransform(transform));
				}
			}
		}
		return list;
	}

	public override void OnFixedUpdate()
	{
		if (Recording)
		{
			List<byte> list = new List<byte>();
			if (FrameCounter % _playerUpdateInterval.Value == 0)
			{
				list.AddRange(_createPlayerUpdate());
			}
			List<byte> list2 = new List<byte>();
			if (FrameCounter % _basicStructureUpdateInterval.Value == 0)
			{
				list2.AddRange(_createBasicStructureUpdate());
			}
			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(2);
				_writebuffer.AddRange(list);
			}
			FrameCounter++;
			if (_writebuffer.Count >= 1000)
			{
				_replayWriter.Write(_writebuffer.ToArray());
				_writebuffer.Clear();
			}
		}
	}
}