Skip to content

Commit ca9f2ff

Browse files
committed
Fixed hitmarkers being read incorrectly. Added WIP voice recording.
1 parent 566f8e5 commit ca9f2ff

6 files changed

Lines changed: 168 additions & 64 deletions

File tree

4 KB
Binary file not shown.

ReplayMod.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@
1414

1515
<ItemGroup>
1616
<Reference Include=".ref/*.dll" Private="false" />
17+
<Reference Include="NAudio">
18+
<HintPath>..\..\SteamLibrary\steamapps\common\RUMBLE\UserLibs\NAudio.dll</HintPath>
19+
</Reference>
20+
<Reference Include="NAudio.Core">
21+
<HintPath>..\..\SteamLibrary\steamapps\common\RUMBLE\UserLibs\NAudio.Core.dll</HintPath>
22+
</Reference>
23+
<Reference Include="NAudio.Wasapi">
24+
<HintPath>..\..\SteamLibrary\steamapps\common\RUMBLE\UserLibs\NAudio.Wasapi.dll</HintPath>
25+
</Reference>
26+
<Reference Include="NAudio.WinMM">
27+
<HintPath>..\..\SteamLibrary\steamapps\common\RUMBLE\UserLibs\NAudio.WinMM.dll</HintPath>
28+
</Reference>
1729
<Reference Include="RumbleModdingAPI">
1830
<HintPath>..\..\SteamLibrary\steamapps\common\RUMBLE\Mods\RumbleModdingAPI.dll</HintPath>
1931
</Reference>

src/Core/Patches.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ static void Postfix(PooledMonoBehaviour __result, Vector3 position, Quaternion r
121121
type = EventType.OneShotFX,
122122
fxType = FXOneShotType.Hitmarker,
123123
position = vfx.transform.position,
124-
damage = (int)vfx.GetFloat("Damage")
124+
damage = (byte)vfx.GetFloat("Damage")
125125
};
126126

127127
Main.Recording.Events.Add(evt);

src/Replay/Files/ReplayFiles.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,6 @@ public static void ReloadReplays()
383383
}
384384

385385
RefreshUI();
386-
SelectReplayFromExplorer();
387386
}
388387

389388
public static void RefreshUI()

src/Replay/ReplayVoices.cs

Lines changed: 142 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
using Il2CppPhoton.Voice.PUN;
66
using Il2CppPhoton.Voice.Unity;
77
using Il2CppRUMBLE.Managers;
8+
using Il2CppSystem;
9+
using MelonLoader;
810
using MelonLoader.Utils;
11+
using NAudio.Wave;
912
using ReplayMod.Replay.Serialization;
1013
using UnityEngine;
1114

@@ -16,76 +19,165 @@ namespace ReplayMod.Replay;
1619
// Kept for future ideas
1720
internal static class ReplayVoices
1821
{
19-
// Remote
2022
private static PunVoiceClient voice;
21-
23+
2224
private static readonly Dictionary<(int playerId, int voiceId), VoiceStreamWriter> writers = new();
23-
public static string tempVoiceDir = Path.Combine(MelonEnvironment.UserDataDirectory, "ReplayMod", "TempVoices");
24-
25-
public static List<VoiceTrackInfo> voiceTrackInfos = new();
2625

27-
public static void HookRemote()
26+
public static string TempVoiceDir = Path.Combine(MelonEnvironment.UserDataDirectory, "ReplayMod", "TempVoices");
27+
28+
public static List<VoiceTrackInfo> VoiceTrackInfos = new();
29+
30+
private static bool isRecording;
31+
private static bool subscribed;
32+
33+
public static void StartRecording()
2834
{
35+
isRecording = true;
36+
2937
voice ??= PunVoiceClient.Instance;
30-
31-
voice.RemoteVoiceAdded += (Il2CppSystem.Action<RemoteVoiceLink>)(OnRemoteVoiceAdded);
32-
33-
Directory.CreateDirectory(tempVoiceDir);
38+
39+
if (voice is not null)
40+
{
41+
if (!subscribed)
42+
{
43+
voice.RemoteVoiceAdded += (Action<RemoteVoiceLink>)OnVoiceLinkAdded;
44+
subscribed = true;
45+
}
46+
47+
foreach (var remoteVoiceLink in voice.cachedRemoteVoices)
48+
OnVoiceLinkAdded(remoteVoiceLink);
49+
50+
Directory.CreateDirectory(TempVoiceDir);
51+
}
3452
}
35-
36-
public static void OnRemoteVoiceAdded(RemoteVoiceLink link)
53+
54+
public static void OnVoiceLinkAdded(RemoteVoiceLink link)
3755
{
3856
int playerId = link.PlayerId;
3957
int voiceId = link.VoiceId;
4058

59+
MelonLogger.Msg($"VoiceAdded actor={playerId} voice={voiceId}");
60+
4161
string name = PlayerManager.instance.AllPlayers.ToArray()
42-
.FirstOrDefault(p => p.Data.GeneralData.ActorNo == playerId)?
43-
.Data.GeneralData.PublicUsername
44-
?? $"Unknown";
62+
.FirstOrDefault(p => p.Data.GeneralData.ActorNo == playerId)?
63+
.Data.GeneralData.PublicUsername
64+
?? "Unknown";
4565

4666
name = Utilities.CleanName(name);
4767

48-
string fileName = $"{name}_actor_{playerId}_voice_{voiceId}.ogg";
49-
50-
voiceTrackInfos.Add(new VoiceTrackInfo
68+
var key = (playerId, voiceId);
69+
70+
link.FloatFrameDecoded += (Action<FrameOut<float>>)((FrameOut<float> frame) =>
5171
{
52-
ActorId = playerId,
53-
FileName = fileName,
54-
StartTime = Time.time
55-
});
72+
if (!isRecording)
73+
return;
74+
75+
if (!writers.TryGetValue(key, out var writer))
76+
{
77+
string fileName = $"{name}_actor_{playerId}_voice_{voiceId}_{Time.frameCount}.wav";
78+
string path = Path.Combine(TempVoiceDir, fileName);
5679

57-
string path = Path.Combine(
58-
tempVoiceDir,
59-
fileName
60-
);
80+
writer = new VoiceStreamWriter(
81+
playerId,
82+
link.VoiceInfo.SamplingRate,
83+
link.VoiceInfo.Channels,
84+
path
85+
);
6186

62-
var writer = new VoiceStreamWriter(
63-
playerId,
64-
link.VoiceInfo.SamplingRate,
65-
link.VoiceInfo.Channels,
66-
path
67-
);
87+
writers[key] = writer;
6888

69-
var key = (playerId, voiceId);
70-
writers[key] = writer;
89+
VoiceTrackInfos.Add(new VoiceTrackInfo(
90+
playerId,
91+
fileName,
92+
Time.time
93+
));
7194

72-
link.FloatFrameDecoded += (Il2CppSystem.Action<FrameOut<float>>)((FrameOut<float> frame) =>
73-
{
74-
if (!writers.TryGetValue(key, out var w))
75-
return;
95+
MelonLogger.Msg($"VoiceClipStart actor={playerId} voice={voiceId}");
96+
}
7697

77-
w.Write(frame.Buf);
98+
writer.Write(frame.Buf);
7899

79100
if (frame.EndOfStream)
101+
{
102+
MelonLogger.Msg($"VoiceClipEnd actor={playerId} voice={voiceId}");
80103
StopWriter(key);
104+
}
81105
});
82106

83-
link.RemoteVoiceRemoved += (Il2CppSystem.Action)(() =>
107+
link.RemoteVoiceRemoved += (Action)(() =>
84108
{
109+
MelonLogger.Msg($"VoiceRemoved actor={playerId} voice={voiceId}");
85110
StopWriter(key);
86111
});
87112
}
88113

114+
public static void StopRecording()
115+
{
116+
isRecording = false;
117+
118+
foreach (var key in writers.Keys.ToList())
119+
StopWriter(key);
120+
121+
MergeVoiceClips();
122+
123+
VoiceTrackInfos.Clear();
124+
}
125+
126+
private static void MergeVoiceClips()
127+
{
128+
if (VoiceTrackInfos.Count == 0)
129+
return;
130+
131+
var groups = VoiceTrackInfos.GroupBy(v => v.ActorId);
132+
133+
foreach (var group in groups)
134+
{
135+
var sorted = group.OrderBy(v => v.StartTime).ToList();
136+
137+
string outputPath = Path.Combine(TempVoiceDir, $"actor_{group.Key}_merged.wav");
138+
139+
int sampleRate = 48000;
140+
int channels = 1;
141+
142+
using var output = new WaveFileWriter(
143+
outputPath,
144+
WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channels)
145+
);
146+
147+
float lastTime = sorted[0].StartTime;
148+
149+
foreach (var clip in sorted)
150+
{
151+
string path = Path.Combine(TempVoiceDir, clip.FileName);
152+
153+
if (!File.Exists(path))
154+
continue;
155+
156+
using var reader = new WaveFileReader(path);
157+
158+
float gap = clip.StartTime - lastTime;
159+
160+
if (gap > 0)
161+
{
162+
int silentSamples = (int)(gap * sampleRate);
163+
float[] silence = new float[silentSamples];
164+
output.WriteSamples(silence, 0, silence.Length);
165+
}
166+
167+
var sampleProvider = reader.ToSampleProvider();
168+
float[] buffer = new float[sampleRate];
169+
170+
int read;
171+
while ((read = sampleProvider.Read(buffer, 0, buffer.Length)) > 0)
172+
{
173+
output.WriteSamples(buffer, 0, read);
174+
}
175+
176+
lastTime = clip.StartTime + (float)reader.TotalTime.TotalSeconds;
177+
}
178+
}
179+
}
180+
89181
private static void StopWriter((int playerId, int voiceId) key)
90182
{
91183
if (!writers.Remove(key, out var writer))
@@ -103,8 +195,8 @@ public class VoiceStreamWriter
103195
public readonly string Path;
104196
public bool HasFrames { get; private set; }
105197

106-
private readonly FileStream file;
107-
// private readonly OpusOggWriteStream ogg;
198+
private readonly FileStream fileStream;
199+
private readonly WaveFileWriter wav;
108200

109201
public VoiceStreamWriter(
110202
int actorId,
@@ -116,21 +208,11 @@ string path
116208
ActorId = actorId;
117209
Path = path;
118210

119-
file = File.Create(path);
120-
121-
// Causes system.runtime error
122-
// var encoder = new OpusEncoder(
123-
// sampleRate,
124-
// channels,
125-
// OpusApplication.OPUS_APPLICATION_VOIP
126-
// );
127-
128-
// encoder.Bitrate = 24000;
129-
// encoder.UseVBR = true;
130-
// encoder.UseDTX = true;
131-
// encoder.Complexity = 6;
132-
//
133-
// ogg = new OpusOggWriteStream(encoder, file);
211+
fileStream = File.Create(path);
212+
wav = new WaveFileWriter(
213+
fileStream,
214+
WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channels)
215+
);
134216
}
135217

136218
public void Write(float[] samples)
@@ -139,13 +221,13 @@ public void Write(float[] samples)
139221
return;
140222

141223
HasFrames = true;
142-
// ogg.WriteSamples(samples, 0, samples.Length);
224+
wav.WriteSamples(samples, 0, samples.Length);
143225
}
144226

145227
public void Dispose()
146228
{
147-
// ogg.Finish();
148-
file?.Dispose();
229+
wav.Dispose();
230+
fileStream?.Dispose();
149231
}
150232
}
151233
}

src/Replay/Serialization/ReplaySerializer.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,7 @@ static EventChunk ReadEventChunk(BinaryReader br)
942942
case EventField.rotation: e.rotation = r.ReadQuaternion(); break;
943943
case EventField.masterId: e.masterId = r.ReadString(); break;
944944
case EventField.playerIndex: e.playerIndex = r.ReadInt32(); break;
945-
case EventField.damage: e.damage = r.ReadInt32(); break;
945+
case EventField.damage: e.damage = r.ReadByte(); break;
946946
case EventField.fxType: e.fxType = (FXOneShotType)r.ReadByte(); break;
947947
case EventField.structureId: e.structureId = r.ReadInt32(); break;
948948
}
@@ -1249,6 +1249,17 @@ public enum PlayerShiftstoneVFX : byte
12491249
[Serializable]
12501250
public class VoiceTrackInfo
12511251
{
1252+
public VoiceTrackInfo(
1253+
int ActorId,
1254+
string FileName,
1255+
float StartTime
1256+
)
1257+
{
1258+
this.ActorId = ActorId;
1259+
this.FileName = FileName;
1260+
this.StartTime = StartTime;
1261+
}
1262+
12521263
public int ActorId;
12531264
public string FileName;
12541265
public float StartTime;
@@ -1309,7 +1320,7 @@ public class EventChunk
13091320
public int structureId = -1;
13101321

13111322
// Damage HitMarker
1312-
public int damage;
1323+
public byte damage;
13131324

13141325
// FX
13151326
public FXOneShotType fxType;

0 commit comments

Comments
 (0)