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