Skip to content

Commit 165e451

Browse files
committed
feat: add GetLastReplayPath method
1 parent 91ebfa7 commit 165e451

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

src/ObsKit.NET/Native/Interop/ObsSignal.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,67 @@ private static partial byte proc_handler_call_native(
180180
nint calldata);
181181

182182
#endregion
183+
184+
#region Memory (for calldata management)
185+
186+
/// <summary>
187+
/// Allocates zeroed memory using OBS's allocator.
188+
/// </summary>
189+
[LibraryImport(Lib, EntryPoint = "bzalloc")]
190+
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
191+
internal static partial nint bzalloc(nuint size);
192+
193+
/// <summary>
194+
/// Frees memory allocated by OBS's allocator.
195+
/// </summary>
196+
[LibraryImport(Lib, EntryPoint = "bfree")]
197+
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
198+
internal static partial void bfree(nint ptr);
199+
200+
#endregion
201+
202+
#region Calldata Management
203+
204+
/// <summary>
205+
/// Size of the calldata structure (4 fields on x64: pointer + 2 size_t + bool with padding).
206+
/// </summary>
207+
internal static nuint CalldataSize => (nuint)(nint.Size * 4);
208+
209+
/// <summary>
210+
/// Creates and initializes an empty calldata structure.
211+
/// </summary>
212+
internal static nint calldata_create()
213+
{
214+
var cd = bzalloc(CalldataSize);
215+
// bzalloc already zeros the memory, which is equivalent to calldata_init
216+
return cd;
217+
}
218+
219+
/// <summary>
220+
/// Destroys a calldata structure and frees its memory.
221+
/// </summary>
222+
internal static void calldata_destroy(nint calldata)
223+
{
224+
if (calldata == nint.Zero) return;
225+
226+
// Free the internal stack if allocated (first field is the stack pointer)
227+
unsafe
228+
{
229+
var stackPtr = *(nint*)calldata;
230+
// Check if not fixed (4th field, which is a bool - need to check offset)
231+
// For simplicity, we'll just free the stack pointer if it's non-zero
232+
// The 'fixed' flag is at offset: sizeof(nint) + sizeof(nuint) + sizeof(nuint)
233+
var fixedOffset = nint.Size + nint.Size + nint.Size;
234+
var isFixed = *(byte*)(calldata + fixedOffset) != 0;
235+
236+
if (stackPtr != nint.Zero && !isFixed)
237+
{
238+
bfree(stackPtr);
239+
}
240+
}
241+
242+
bfree(calldata);
243+
}
244+
245+
#endregion
183246
}

src/ObsKit.NET/Outputs/ReplayBuffer.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,35 @@ public void Save()
156156
ObsSignal.proc_handler_call(procHandler, "save");
157157
}
158158

159+
/// <summary>
160+
/// Gets the file path of the last saved replay.
161+
/// Returns null if no replay has been saved yet or if currently muxing.
162+
/// </summary>
163+
/// <returns>The file path of the last saved replay, or null if not available.</returns>
164+
public string? GetLastReplayPath()
165+
{
166+
var procHandler = ObsOutput.obs_output_get_proc_handler(Handle);
167+
if (procHandler == 0)
168+
return null;
169+
170+
var calldata = ObsSignal.calldata_create();
171+
try
172+
{
173+
ObsSignal.proc_handler_call(procHandler, "get_last_replay", calldata);
174+
175+
if (ObsSignal.calldata_get_string(calldata, "path", out var pathPtr) && pathPtr != nint.Zero)
176+
{
177+
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(pathPtr);
178+
}
179+
180+
return null;
181+
}
182+
finally
183+
{
184+
ObsSignal.calldata_destroy(calldata);
185+
}
186+
}
187+
159188
/// <summary>Starts the replay buffer.</summary>
160189
public new bool Start()
161190
{

0 commit comments

Comments
 (0)