using System.Runtime.InteropServices; // Shared "walk a native array of owned-struct entries, convert to managed records, free the // native list" pattern — identical shape for vc_device_list/vc_channel_list/vc_user_list/ // vc_stream_summary_list (all core-allocated, caller-freed; see voicecat.h's doc comments on // each). The matching vc_free_*_list call happens INSIDE each ToManaged here, immediately // after the conversion, so callers never need to remember to free anything themselves. namespace VoiceCat.Interop; internal static class Marshaling { public static List ToManaged(ref VcDeviceListNative native) { var result = new List((int)native.Count); int size = Marshal.SizeOf(); for (nuint i = 0; i < native.Count; i++) { var raw = Marshal.PtrToStructure(native.Items + (int)i * size); result.Add(new DeviceInfo( Marshal.PtrToStringUTF8(raw.Id) ?? string.Empty, Marshal.PtrToStringUTF8(raw.Name) ?? string.Empty, raw.IsDefault != 0)); } NativeMethods.vc_free_device_list(ref native); return result; } public static List ToManaged(ref VcChannelListNative native) { var result = new List((int)native.Count); int size = Marshal.SizeOf(); for (nuint i = 0; i < native.Count; i++) { var raw = Marshal.PtrToStructure(native.Items + (int)i * size); result.Add(new ChannelInfo( raw.Id, raw.ParentId, Marshal.PtrToStringUTF8(raw.Name) ?? string.Empty, Marshal.PtrToStringUTF8(raw.Topic) ?? string.Empty, raw.PasswordProtected != 0, raw.MaxUsers)); } NativeMethods.vc_free_channel_list(ref native); return result; } public static List ToManaged(ref VcUserListNative native) { var result = new List((int)native.Count); int size = Marshal.SizeOf(); for (nuint i = 0; i < native.Count; i++) { var raw = Marshal.PtrToStructure(native.Items + (int)i * size); result.Add(new UserInfo( raw.Id, Marshal.PtrToStringUTF8(raw.Nickname) ?? string.Empty, raw.IsGuest != 0, raw.ChannelId, raw.SelfMicMuted != 0, raw.SelfDeafened != 0, raw.ServerMuted != 0, raw.ServerDeafened != 0)); } NativeMethods.vc_free_user_list(ref native); return result; } public static List ToManaged(ref VcStreamSummaryListNative native) { var result = new List((int)native.Count); int size = Marshal.SizeOf(); for (nuint i = 0; i < native.Count; i++) { var raw = Marshal.PtrToStructure(native.Items + (int)i * size); result.Add(new StreamSummary( raw.StreamId, raw.Kind, Marshal.PtrToStringUTF8(raw.Label) ?? string.Empty)); } NativeMethods.vc_free_stream_summary_list(ref native); return result; } public static RemoteStreamState ToManaged(in VcRemoteStreamStateNative native) => new( native.Gain, native.Muted != 0, native.NoiseReduction != 0); public static AudioConfigInfo ToManaged(in VcAudioConfigNative native) => new( native.Codec, native.Mode != 0, native.SampleRate, native.BitrateBps, native.FrameMs, native.Application, native.Fec != 0, native.ExpectedPacketLoss, native.Dtx != 0, native.Complexity, native.Dred != 0); public static PermissionsInfo ToManaged(in VcPermissionsNative native) => new( native.CanCreateTempChannel != 0, native.CanKick != 0, native.CanBan != 0, native.CanMoveUsers != 0, native.CanAdminAccounts != 0, native.IsAdmin != 0); public static List ToManaged(ref VcAccountListNative native) { var result = new List((int)native.Count); int size = Marshal.SizeOf(); for (nuint i = 0; i < native.Count; i++) { var raw = Marshal.PtrToStructure(native.Items + (int)i * size); result.Add(new AccountInfo( Marshal.PtrToStringUTF8(raw.Username) ?? string.Empty, raw.IsAdmin != 0, raw.CreatedAtUnixMs, raw.LastLoginUnixMs)); } NativeMethods.vc_free_account_list(ref native); return result; } }