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();
}
}
}
}