Skip to content

Instantly share code, notes, and snippets.

@Emzi0767
Last active April 5, 2017 11:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Emzi0767/9c40663c13bbb09b1bf7d8e7c436d544 to your computer and use it in GitHub Desktop.
Save Emzi0767/9c40663c13bbb09b1bf7d8e7c436d544 to your computer and use it in GitHub Desktop.
Unmanaged glue for Discord.NET
// dnet_glue.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
#include "dnet_glue.h"
SENDCALLBACK send_message;
void __cdecl set_send_delegate(SENDCALLBACK clb)
{
send_message = clb;
}
void __cdecl execute_snippet(uint64_t guild, uint64_t channel, uint64_t message, uint64_t author)
{
EMBED* embed = new EMBED();
embed->title = "Unmanaged embed test";
embed->description = "This embed was fully constructed inside unmanaged code.";
embed->footer = new EMBED_FOOTER();
embed->footer->text = "Unmanaged message";
embed->footer->timestamp = time(NULL);
embed->colour = 0x007FFF;
embed->author = new EMBED_AUTHOR();
embed->author->avatar_url = "https://images.discordapp.net/avatars/181875147148361728/c37a8343ab1c74f51011ea0bea4a1ba0.png?size=1024";
embed->author->name = "libdnet_glue";
embed->field_count = 1;
embed->fields = new EMBED_FIELD();
embed->fields->is_inline = false;
embed->fields->name = "A field, even!";
embed->fields->value = "This thing can do wonders!";
send_message(channel, "Check this shit out!", embed);
delete embed->fields;
embed->fields = NULL;
delete embed->author;
embed->author = NULL;
delete embed->footer;
embed->footer = NULL;
delete embed;
embed = NULL;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <stdint.h>
#include <ctime>
#ifdef DNET_GLUE_EXPORTS
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif
extern "C"
{
typedef struct
{
char* name;
char* value;
bool is_inline;
} EMBED_FIELD;
typedef struct
{
char* text;
long timestamp;
char* icon_url;
} EMBED_FOOTER;
typedef struct
{
char* name;
char* url;
char* avatar_url;
} EMBED_AUTHOR;
typedef struct
{
char* title;
char* description;
char* url;
char* image_url;
char* thumbnail_url;
uint32_t colour;
EMBED_AUTHOR* author;
EMBED_FOOTER* footer;
int field_count;
EMBED_FIELD* fields;
} EMBED;
typedef API void(__cdecl *SENDCALLBACK)(uint64_t, char*, EMBED*);
API void __cdecl set_send_delegate(SENDCALLBACK clb);
API void __cdecl execute_snippet(uint64_t guild, uint64_t channel, uint64_t message, uint64_t author);
}
using System;
using System.Runtime.InteropServices;
using Discord;
using Discord.WebSocket;
namespace Emzi0767.GluedEval
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SendMessageDelegate(ulong channel, string msg, IntPtr embed);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SetSendDelegate(SendMessageDelegate dlg);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ExecuteSnippet(ulong guild, ulong channel, ulong message, ulong author);
public static class NativeGlue
{
private static IntPtr Dll { get; set; }
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LoadLibrary(string dll);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hdll, string proc);
[DllImport("kernel32.dll")]
public static extern bool FreeLibrary(IntPtr hdll);
private static SetSendDelegate SetSendDelegate { get; set; }
public static ExecuteSnippet ExecuteSnippet { get; set; }
public static bool IsLoaded { get { return Dll != IntPtr.Zero; } }
public static void LoadDll(string dll)
{
if (IsLoaded)
throw new InvalidOperationException("Glue was already loaded");
Dll = LoadLibrary(dll);
if (Dll == IntPtr.Zero)
throw new DllNotFoundException(string.Concat("Could not load the specified DLL (", Marshal.GetLastWin32Error() , ", HRESULT 0x", Marshal.GetHRForLastWin32Error().ToString("X8") , ")"));
var ssd = GetProcAddress(Dll, "set_send_delegate");
var es = GetProcAddress(Dll, "execute_snippet");
if (ssd == IntPtr.Zero || es == IntPtr.Zero)
throw new BadImageFormatException("Invalid DLL specified");
SetSendDelegate = Marshal.GetDelegateForFunctionPointer<SetSendDelegate>(ssd);
ExecuteSnippet = Marshal.GetDelegateForFunctionPointer<ExecuteSnippet>(es);
}
public static void SetupDll(DiscordSocketClient client)
{
var smd = new SendMessageDelegate((c, m, e) =>
{
var chn = client.GetChannel(c) as SocketTextChannel;
if (chn == null)
return;
if (string.IsNullOrWhiteSpace(m))
return;
var embed = (Embed)null;
if (e != IntPtr.Zero)
{
// prepare embed
var es = Marshal.PtrToStructure<UnmanagedEmbed>(e);
var eb = new EmbedBuilder
{
Title = es.Title,
Description = es.Description,
Color = new Color(es.Colour),
ImageUrl = es.ImageUrl,
ThumbnailUrl = es.ThumbnailUrl,
Url = es.Url
};
var efp = es.Footer;
var eap = es.Author;
if (efp != IntPtr.Zero)
{
var ef = Marshal.PtrToStructure<UnmanagedEmbedFooter>(efp);
eb.Footer = new EmbedFooterBuilder
{
IconUrl = ef.IconUrl,
Text = ef.Text
};
eb.Timestamp = DateTimeOffset.FromUnixTimeSeconds(ef.Timestamp);
}
if (eap != IntPtr.Zero)
{
var ea = Marshal.PtrToStructure<UnmanagedEmbedAuthor>(eap);
eb.Author = new EmbedAuthorBuilder
{
IconUrl = ea.AvatarUrl,
Name = ea.Name,
Url = ea.Url
};
}
var efsp = es.Fields;
if (efsp != IntPtr.Zero && es.FieldCount > 0 && es.FieldCount < 25)
{
var size = Marshal.SizeOf<UnmanagedEmbedField>();
for (var i = 0; i < es.FieldCount; i++)
{
var ef = Marshal.PtrToStructure<UnmanagedEmbedField>(efsp);
eb.AddField(new EmbedFieldBuilder
{
IsInline = ef.Inline,
Name = ef.Name,
Value = ef.Value
});
efsp += size;
}
}
embed = eb.Build();
}
chn.SendMessageAsync(m, false, embed).GetAwaiter().GetResult();
});
SetSendDelegate(smd);
}
public static void UnloadDll()
{
if (Dll == IntPtr.Zero)
throw new InvalidOperationException("Dll is not loaded");
SetSendDelegate = null;
ExecuteSnippet = null;
FreeLibrary(Dll);
Dll = IntPtr.Zero;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct UnmanagedEmbed
{
public string Title { get; set; }
public string Description { get; set; }
public string Url { get; set; }
public string ImageUrl { get; set; }
public string ThumbnailUrl { get; set; }
public uint Colour { get; set; }
public IntPtr Author { get; set; } // UnmanagedEmbedAuthor
public IntPtr Footer { get; set; } // UnmanagedEmbedFooter
public int FieldCount { get; set; }
public IntPtr Fields { get; set; } // UnmanagedEmbedField[]
}
[StructLayout(LayoutKind.Sequential)]
public struct UnmanagedEmbedAuthor
{
public string Name { get; set; }
public string Url { get; set; }
public string AvatarUrl { get; set; }
}
[StructLayout(LayoutKind.Sequential)]
public struct UnmanagedEmbedFooter
{
public string Text { get; set; }
public long Timestamp { get; set; }
public string IconUrl { get; set; }
}
[StructLayout(LayoutKind.Sequential)]
public struct UnmanagedEmbedField
{
public string Name { get; set; }
public string Value { get; set; }
public bool Inline { get; set; }
}
}
using System;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
namespace Emzi0767.GluedEval
{
internal static class Program
{
private static CommandService Commands { get; set; }
internal static void Main(string[] args)
{
MainAsync(args).GetAwaiter().GetResult();
}
internal static async Task MainAsync(string[] args)
{
var discord = new DiscordSocketClient();
discord.MessageReceived += Discord_MessageReceived;
var commands = new CommandService();
Commands = commands;
await commands.AddModuleAsync<GlueCommandModule>();
await discord.LoginAsync(TokenType.Bot, "nope");
await discord.StartAsync();
await Task.Delay(-1);
}
private static async Task Discord_MessageReceived(SocketMessage arg)
{
var msg = arg as SocketUserMessage;
if (msg == null)
return;
var chn = msg.Channel as SocketGuildChannel;
if (chn == null)
return;
var gld = chn.Guild as SocketGuild;
if (gld == null)
return;
var apos = -1;
if (!msg.HasStringPrefix("glue:", ref apos))
return;
var ctx = new SocketCommandContext(msg.Discord, msg);
await Commands.ExecuteAsync(ctx, apos);
}
}
public class GlueCommandModule : ModuleBase<SocketCommandContext>
{
[Command("load_glue")]
public async Task LoadGlue()
{
await this.Context.Channel.TriggerTypingAsync();
var e = (Exception)null;
try
{
NativeGlue.LoadDll("dnet_glue_x64.dll");
}
catch (Exception ex) { e = ex; }
try
{
if (!NativeGlue.IsLoaded)
NativeGlue.LoadDll("dnet_glue_x86.dll");
}
catch (Exception ex) { e = e ?? ex; }
if (!NativeGlue.IsLoaded)
{
await this.ReplyAsync(string.Concat("Could not load glue (`", e.Message , "`)."));
return;
}
NativeGlue.SetupDll(this.Context.Client);
await this.ReplyAsync("Glue DLL loaded.");
}
[Command("unload_glue")]
public async Task UnloadGlue()
{
await this.Context.Channel.TriggerTypingAsync();
NativeGlue.UnloadDll();
await this.ReplyAsync("Glue DLL unloaded.");
}
[Command("glue")]
public async Task ExecuteGlue()
{
await this.Context.Channel.TriggerTypingAsync();
try
{
NativeGlue.ExecuteSnippet(this.Context.Guild.Id, this.Context.Channel.Id, this.Context.Message.Id, this.Context.User.Id);
}
catch (Exception ex)
{
await this.ReplyAsync(string.Concat("Failed to call glue (`", ex.Message , "`)"));
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment