Update C# wrapper with visual C# compatible version (#126)

- Custom serializer to fix utf-8 strings in C#
This commit is contained in:
sll552 2018-02-22 20:47:18 +01:00 committed by Mason Sciotti
parent 578eb6de7c
commit 4055565147
2 changed files with 148 additions and 18 deletions

View file

@ -11,7 +11,7 @@ public class DiscordJoinRequestEvent : UnityEngine.Events.UnityEvent<DiscordRpc.
public class DiscordController : MonoBehaviour public class DiscordController : MonoBehaviour
{ {
public DiscordRpc.RichPresence presence; public DiscordRpc.RichPresence presence = new DiscordRpc.RichPresence();
public string applicationId; public string applicationId;
public string optionalSteamId; public string optionalSteamId;
public int callbackCalls; public int callbackCalls;
@ -33,7 +33,7 @@ public class DiscordController : MonoBehaviour
presence.details = string.Format("Button clicked {0} times", clickCounter); presence.details = string.Format("Button clicked {0} times", clickCounter);
DiscordRpc.UpdatePresence(ref presence); DiscordRpc.UpdatePresence(presence);
} }
public void RequestRespondYes() public void RequestRespondYes()

View file

@ -1,4 +1,7 @@
using System.Runtime.InteropServices; using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
public class DiscordRpc public class DiscordRpc
{ {
@ -30,27 +33,27 @@ public class DiscordRpc
public RequestCallback requestCallback; public RequestCallback requestCallback;
} }
[System.Serializable] [Serializable, StructLayout(LayoutKind.Sequential)]
public struct RichPresence public struct RichPresenceStruct
{ {
public string state; /* max 128 bytes */ public IntPtr state; /* max 128 bytes */
public string details; /* max 128 bytes */ public IntPtr details; /* max 128 bytes */
public long startTimestamp; public long startTimestamp;
public long endTimestamp; public long endTimestamp;
public string largeImageKey; /* max 32 bytes */ public IntPtr largeImageKey; /* max 32 bytes */
public string largeImageText; /* max 128 bytes */ public IntPtr largeImageText; /* max 128 bytes */
public string smallImageKey; /* max 32 bytes */ public IntPtr smallImageKey; /* max 32 bytes */
public string smallImageText; /* max 128 bytes */ public IntPtr smallImageText; /* max 128 bytes */
public string partyId; /* max 128 bytes */ public IntPtr partyId; /* max 128 bytes */
public int partySize; public int partySize;
public int partyMax; public int partyMax;
public string matchSecret; /* max 128 bytes */ public IntPtr matchSecret; /* max 128 bytes */
public string joinSecret; /* max 128 bytes */ public IntPtr joinSecret; /* max 128 bytes */
public string spectateSecret; /* max 128 bytes */ public IntPtr spectateSecret; /* max 128 bytes */
public bool instance; public bool instance;
} }
[System.Serializable] [Serializable]
public struct JoinRequest public struct JoinRequest
{ {
public string userId; public string userId;
@ -76,12 +79,139 @@ public class DiscordRpc
public static extern void RunCallbacks(); public static extern void RunCallbacks();
[DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence", CallingConvention = CallingConvention.Cdecl)] [DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence", CallingConvention = CallingConvention.Cdecl)]
public static extern void UpdatePresence(ref RichPresence presence); private static extern void UpdatePresenceNative(ref RichPresenceStruct presence);
[DllImport("discord-rpc", EntryPoint = "Discord_ClearPresence", CallingConvention = CallingConvention.Cdecl)] [DllImport("discord-rpc", EntryPoint = "Discord_ClearPresence", CallingConvention = CallingConvention.Cdecl)]
public static extern void ClearPresence(); public static extern void ClearPresence();
[DllImport("discord-rpc", EntryPoint = "Discord_Respond", CallingConvention = CallingConvention.Cdecl)] [DllImport("discord-rpc", EntryPoint = "Discord_Respond", CallingConvention = CallingConvention.Cdecl)]
public static extern void Respond(string userId, Reply reply); public static extern void Respond(string userId, Reply reply);
public static void UpdatePresence(RichPresence presence)
{
var presencestruct = presence.GetStruct();
UpdatePresenceNative(ref presencestruct);
presence.FreeMem();
} }
public class RichPresence
{
private RichPresenceStruct _presence;
private readonly List<IntPtr> _buffers = new List<IntPtr>(10);
public string state; /* max 128 bytes */
public string details; /* max 128 bytes */
public long startTimestamp;
public long endTimestamp;
public string largeImageKey; /* max 32 bytes */
public string largeImageText; /* max 128 bytes */
public string smallImageKey; /* max 32 bytes */
public string smallImageText; /* max 128 bytes */
public string partyId; /* max 128 bytes */
public int partySize;
public int partyMax;
public string matchSecret; /* max 128 bytes */
public string joinSecret; /* max 128 bytes */
public string spectateSecret; /* max 128 bytes */
public bool instance;
/// <summary>
/// Get the <see cref="RichPresenceStruct"/> reprensentation of this instance
/// </summary>
/// <returns><see cref="RichPresenceStruct"/> reprensentation of this instance</returns>
internal RichPresenceStruct GetStruct()
{
if (_buffers.Count > 0)
{
FreeMem();
}
_presence.state = StrToPtr(state, 128);
_presence.details = StrToPtr(details, 128);
_presence.startTimestamp = startTimestamp;
_presence.endTimestamp = endTimestamp;
_presence.largeImageKey = StrToPtr(largeImageKey, 32);
_presence.largeImageText = StrToPtr(largeImageText, 128);
_presence.smallImageKey = StrToPtr(smallImageKey, 32);
_presence.smallImageText = StrToPtr(smallImageText, 128);
_presence.partyId = StrToPtr(partyId, 128);
_presence.partySize = partySize;
_presence.partyMax = partyMax;
_presence.matchSecret = StrToPtr(matchSecret, 128);
_presence.joinSecret = StrToPtr(joinSecret, 128);
_presence.spectateSecret = StrToPtr(spectateSecret, 128);
_presence.instance = instance;
return _presence;
}
/// <summary>
/// Returns a pointer to a representation of the given string with a size of maxbytes
/// </summary>
/// <param name="input">String to convert</param>
/// <param name="maxbytes">Max number of bytes to use</param>
/// <returns>Pointer to the UTF-8 representation of <see cref="input"/></returns>
private IntPtr StrToPtr(string input, int maxbytes)
{
if (string.IsNullOrEmpty(input)) return IntPtr.Zero;
var convstr = StrClampBytes(input, maxbytes);
var convbytecnt = Encoding.UTF8.GetByteCount(convstr);
var buffer = Marshal.AllocHGlobal(convbytecnt);
_buffers.Add(buffer);
Marshal.Copy(Encoding.UTF8.GetBytes(convstr), 0, buffer, convbytecnt);
return buffer;
}
/// <summary>
/// Convert string to UTF-8 and add null termination
/// </summary>
/// <param name="toconv">string to convert</param>
/// <returns>UTF-8 representation of <see cref="toconv"/> with added null termination</returns>
private static string StrToUtf8NullTerm(string toconv)
{
var str = toconv.Trim();
var bytes = Encoding.Default.GetBytes(str);
if (bytes.Length > 0 && bytes[bytes.Length - 1] != 0)
{
str += "\0\0";
}
return Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(str));
}
/// <summary>
/// Clamp the string to the given byte length preserving null termination
/// </summary>
/// <param name="toclamp">string to clamp</param>
/// <param name="maxbytes">max bytes the resulting string should have (including null termination)</param>
/// <returns>null terminated string with a byte length less or equal to <see cref="maxbytes"/></returns>
private static string StrClampBytes(string toclamp, int maxbytes)
{
var str = StrToUtf8NullTerm(toclamp);
var strbytes = Encoding.UTF8.GetBytes(str);
if (strbytes.Length <= maxbytes)
{
return str;
}
var newstrbytes = new byte[] { };
Array.Copy(strbytes, 0, newstrbytes, 0, maxbytes - 1);
newstrbytes[newstrbytes.Length - 1] = 0;
newstrbytes[newstrbytes.Length - 2] = 0;
return Encoding.UTF8.GetString(newstrbytes);
}
/// <summary>
/// Free the allocated memory for conversion to <see cref="RichPresenceStruct"/>
/// </summary>
internal void FreeMem()
{
for (var i = _buffers.Count - 1; i >= 0; i--)
{
Marshal.FreeHGlobal(_buffers[i]);
_buffers.RemoveAt(i);
}
}
}
}