Last active
April 5, 2017 11:23
-
-
Save Emzi0767/9c40663c13bbb09b1bf7d8e7c436d544 to your computer and use it in GitHub Desktop.
Unmanaged glue for Discord.NET
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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