diff --git a/Raylib-cs/LoggingUtils.cs b/Raylib-cs/LoggingUtils.cs
new file mode 100644
index 0000000..d672079
--- /dev/null
+++ b/Raylib-cs/LoggingUtils.cs
@@ -0,0 +1,166 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Raylib_cs
+{
+ internal readonly struct Native
+ {
+ internal const string Msvcrt = "msvcrt";
+ internal const string Libc = "libc";
+ internal const string LibSystem = "libSystem";
+
+ [DllImport(LibSystem, EntryPoint = "vasprintf", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int vasprintf_apple(ref IntPtr buffer, IntPtr format, IntPtr args);
+
+ [DllImport(Libc, EntryPoint = "vsprintf", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int vsprintf_linux(IntPtr buffer, IntPtr format, IntPtr args);
+
+ [DllImport(Msvcrt, EntryPoint = "vsprintf", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int vsprintf_windows(IntPtr buffer, IntPtr format, IntPtr args);
+
+ [DllImport(Libc, EntryPoint = "vsnprintf", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int vsnprintf_linux(IntPtr buffer, UIntPtr size, IntPtr format, IntPtr args);
+
+ [DllImport(Msvcrt, EntryPoint = "vsnprintf", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int vsnprintf_windows(IntPtr buffer, UIntPtr size, IntPtr format, IntPtr args);
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 4)]
+ struct VaListLinuxX64
+ {
+ uint gp_offset;
+ uint fp_offset;
+ IntPtr overflow_arg_area;
+ IntPtr reg_save_area;
+ }
+
+ ///
+ /// Logging workaround for formatting strings from native code
+ ///
+ public static class LoggingUtils
+ {
+ static LoggingUtils()
+ {
+ Raylib.SetTraceLogCallback(LogConsole);
+ }
+
+ public static void LogConsole(TraceLogLevel msgType, IntPtr text, IntPtr args)
+ {
+ var message = LoggingUtils.GetLogMessage(text, args);
+ Console.WriteLine(message);
+ }
+
+ public static string GetLogMessage(IntPtr format, IntPtr args)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ return AppleLogCallback(format, args);
+ }
+
+ // Special marshalling is needed on Linux desktop 64 bits.
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && IntPtr.Size == 8)
+ {
+ return LinuxX64LogCallback(format, args);
+ }
+
+ var byteLength = vsnprintf(IntPtr.Zero, UIntPtr.Zero, format, args) + 1;
+ if (byteLength <= 1)
+ {
+ return string.Empty;
+ }
+
+ var buffer = Marshal.AllocHGlobal(byteLength);
+ vsprintf(buffer, format, args);
+
+ string result = Marshal.PtrToStringUTF8(buffer);
+ Marshal.FreeHGlobal(buffer);
+
+ return result;
+ }
+
+ static string AppleLogCallback(IntPtr format, IntPtr args)
+ {
+ IntPtr buffer = IntPtr.Zero;
+ try
+ {
+ var count = Native.vasprintf_apple(ref buffer, format, args);
+ if (count == -1)
+ {
+ return string.Empty;
+ }
+ return Marshal.PtrToStringUTF8(buffer) ?? string.Empty;
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(buffer);
+ }
+ }
+
+ static string LinuxX64LogCallback(IntPtr format, IntPtr args)
+ {
+ // The args pointer cannot be reused between two calls. We need to make a copy of the underlying structure.
+ var listStructure = Marshal.PtrToStructure(args);
+ IntPtr listPointer = IntPtr.Zero;
+ int byteLength = 0;
+ string result = "";
+
+ // Get length of args
+ listPointer = Marshal.AllocHGlobal(Marshal.SizeOf(listStructure));
+ Marshal.StructureToPtr(listStructure, listPointer, false);
+ byteLength = Native.vsnprintf_linux(IntPtr.Zero, UIntPtr.Zero, format, listPointer) + 1;
+
+ // Allocate buffer for result
+ // listPointer = Marshal.AllocHGlobal(Marshal.SizeOf(listStructure));
+ Marshal.StructureToPtr(listStructure, listPointer, false);
+
+ IntPtr utf8Buffer = IntPtr.Zero;
+ utf8Buffer = Marshal.AllocHGlobal(byteLength);
+
+ // Print result into buffer
+ Native.vsprintf_linux(utf8Buffer, format, listPointer);
+ result = Marshal.PtrToStringUTF8(utf8Buffer);
+
+ Marshal.FreeHGlobal(listPointer);
+ Marshal.FreeHGlobal(utf8Buffer);
+
+ return result;
+ }
+
+ // https://github.com/dotnet/runtime/issues/51052
+ static int vsnprintf(IntPtr buffer, UIntPtr size, IntPtr format, IntPtr args)
+ {
+ var os = Environment.OSVersion;
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return Native.vsnprintf_windows(buffer, size, format, args);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ return Native.vsnprintf_linux(buffer, size, format, args);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("ANDROID")))
+ {
+ return Native.vsprintf_linux(buffer, format, args);
+ }
+ return -1;
+ }
+
+ // https://github.com/dotnet/runtime/issues/51052
+ static int vsprintf(IntPtr buffer, IntPtr format, IntPtr args)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return Native.vsprintf_windows(buffer, format, args);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ return Native.vsprintf_linux(buffer, format, args);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("ANDROID")))
+ {
+ return Native.vsprintf_linux(buffer, format, args);
+ }
+ return -1;
+ }
+ }
+}
diff --git a/Raylib-cs/Raylib.cs b/Raylib-cs/Raylib.cs
index 846a592..8687ec4 100644
--- a/Raylib-cs/Raylib.cs
+++ b/Raylib-cs/Raylib.cs
@@ -26,7 +26,7 @@ namespace Raylib_cs
/// WARNING: This callback is intended for advance users
///
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- public delegate void TraceLogCallback(TraceLogLevel logLevel, string text, IntPtr args);
+ public delegate void TraceLogCallback(TraceLogLevel logLevel, IntPtr text, IntPtr args);
///
/// FileIO: Load binary data
@@ -58,6 +58,8 @@ namespace Raylib_cs
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate CBool SaveFileTextCallback(string fileName, string text);
+ private static TraceLogCallback traceLogCallback;
+
///
/// Returns color with alpha applied, alpha goes from 0.0f to 1.0f
/// NOTE: Added for compatability with previous versions
@@ -497,8 +499,15 @@ namespace Raylib_cs
// WARNING: Callbacks setup is intended for advance users
/// Set custom trace log
- [DllImport(nativeLibName, CallingConvention = CallingConvention.Cdecl)]
- public static extern void SetTraceLogCallback(TraceLogCallback callback);
+ [DllImport(nativeLibName, EntryPoint = "SetTraceLogCallback", CallingConvention = CallingConvention.Cdecl)]
+ private static extern void SetTraceLogCallbackInternal(TraceLogCallback callback);
+
+ /// Set custom trace log
+ public static void SetTraceLogCallback(TraceLogCallback callback)
+ {
+ SetTraceLogCallbackInternal(callback);
+ traceLogCallback = callback;
+ }
/// Set custom file binary data loader
[DllImport(nativeLibName, CallingConvention = CallingConvention.Cdecl)]