Skip to content
This repository was archived by the owner on Sep 30, 2019. It is now read-only.

Commit

Permalink
Inital solution commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
xyonico committed May 13, 2018
1 parent 6a15921 commit 3d0a4a4
Show file tree
Hide file tree
Showing 6 changed files with 343 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,4 @@ ASALocalRun/

# MFractors (Xamarin productivity tool) working folder
.mfractor/
BeatSaberDiscordPresence/libs/
16 changes: 16 additions & 0 deletions BeatSaberDiscordPresence.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BeatSaberDiscordPresence", "BeatSaberDiscordPresence\BeatSaberDiscordPresence.csproj", "{0A3B23AC-9395-49EF-BD55-57BAC10947AE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0A3B23AC-9395-49EF-BD55-57BAC10947AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0A3B23AC-9395-49EF-BD55-57BAC10947AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A3B23AC-9395-49EF-BD55-57BAC10947AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A3B23AC-9395-49EF-BD55-57BAC10947AE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
68 changes: 68 additions & 0 deletions BeatSaberDiscordPresence/BeatSaberDiscordPresence.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{0A3B23AC-9395-49EF-BD55-57BAC10947AE}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BeatSaberDiscordPresence</RootNamespace>
<AssemblyName>BeatSaberDiscordPresence</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>D:\SteamLibrary\steamapps\common\Beat Saber\Beat Saber_Data\Managed\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="discord-rpc">
<HintPath>libs\discord-rpc.dll</HintPath>
</Reference>
<Reference Include="IllusionPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>D:\SteamLibrary\steamapps\common\Beat Saber\Beat Saber_Data\Managed\IllusionPlugin.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>D:\SteamLibrary\steamapps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>D:\SteamLibrary\steamapps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="DiscordRpc.cs" />
<Compile Include="Plugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
223 changes: 223 additions & 0 deletions BeatSaberDiscordPresence/DiscordRpc.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace BeatSaberDiscordPresence
{
public class DiscordRpc
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ReadyCallback(ref DiscordUser connectedUser);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DisconnectedCallback(int errorCode, string message);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ErrorCallback(int errorCode, string message);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void JoinCallback(string secret);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SpectateCallback(string secret);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void RequestCallback(ref DiscordUser request);

public struct EventHandlers
{
public ReadyCallback readyCallback;
public DisconnectedCallback disconnectedCallback;
public ErrorCallback errorCallback;
public JoinCallback joinCallback;
public SpectateCallback spectateCallback;
public RequestCallback requestCallback;
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct RichPresenceStruct
{
public IntPtr state; /* max 128 bytes */
public IntPtr details; /* max 128 bytes */
public long startTimestamp;
public long endTimestamp;
public IntPtr largeImageKey; /* max 32 bytes */
public IntPtr largeImageText; /* max 128 bytes */
public IntPtr smallImageKey; /* max 32 bytes */
public IntPtr smallImageText; /* max 128 bytes */
public IntPtr partyId; /* max 128 bytes */
public int partySize;
public int partyMax;
public IntPtr matchSecret; /* max 128 bytes */
public IntPtr joinSecret; /* max 128 bytes */
public IntPtr spectateSecret; /* max 128 bytes */
public bool instance;
}

[Serializable]
public struct DiscordUser
{
public string userId;
public string username;
public string discriminator;
public string avatar;
}

public enum Reply
{
No = 0,
Yes = 1,
Ignore = 2
}

[DllImport("discord-rpc", EntryPoint = "Discord_Initialize", CallingConvention = CallingConvention.Cdecl)]
public static extern void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId);

[DllImport("discord-rpc", EntryPoint = "Discord_Shutdown", CallingConvention = CallingConvention.Cdecl)]
public static extern void Shutdown();

[DllImport("discord-rpc", EntryPoint = "Discord_RunCallbacks", CallingConvention = CallingConvention.Cdecl)]
public static extern void RunCallbacks();

[DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence", CallingConvention = CallingConvention.Cdecl)]
private static extern void UpdatePresenceNative(ref RichPresenceStruct presence);

[DllImport("discord-rpc", EntryPoint = "Discord_ClearPresence", CallingConvention = CallingConvention.Cdecl)]
public static extern void ClearPresence();

[DllImport("discord-rpc", EntryPoint = "Discord_Respond", CallingConvention = CallingConvention.Cdecl)]
public static extern void Respond(string userId, Reply reply);

[DllImport("discord-rpc", EntryPoint = "Discord_UpdateHandlers", CallingConvention = CallingConvention.Cdecl)]
public static extern void UpdateHandlers(ref EventHandlers handlers);

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);
}
}
}
}
}
Binary file added BeatSaberDiscordPresence/Plugin.cs
Binary file not shown.
35 changes: 35 additions & 0 deletions BeatSaberDiscordPresence/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("BeatSaberDiscordPresence")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("BeatSaberDiscordPresence")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("0A3B23AC-9395-49EF-BD55-57BAC10947AE")]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

0 comments on commit 3d0a4a4

Please sign in to comment.