diff --git a/.gitignore b/.gitignore index 6f949c6..3f2979e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /build*/ /.vscode/ /thirdparty/ +.vs/ diff --git a/build.py b/build.py new file mode 100644 index 0000000..e0859c8 --- /dev/null +++ b/build.py @@ -0,0 +1,60 @@ +import os +import subprocess +import sys +import shutil +from contextlib import contextmanager + + +SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) + + +@contextmanager +def cd(new_dir): + """ Temporarily change current directory """ + if new_dir: + old_dir = os.getcwd() + os.chdir(new_dir) + yield + if new_dir: + os.chdir(old_dir) + + +def mkdir_p(path): + """ mkdir -p """ + if not os.path.isdir(path): + os.makedirs(path) + + +def build(build_path, generator, options): + mkdir_p(build_path) + with cd(build_path): + initial_cmake = ['cmake', SCRIPT_PATH] + if generator: + initial_cmake.extend(['-G', generator]) + for key in options: + val = 'ON' if options[key] else 'OFF' + initial_cmake.append('-D%s=%s' %(key, val)) + subprocess.check_call(initial_cmake) + subprocess.check_call(['cmake', '--build', '.', '--config', 'Debug']) + subprocess.check_call(['cmake', '--build', '.', '--config', 'Release']) + + +def main(): + os.chdir(SCRIPT_PATH) + if sys.platform.startswith('win'): + generator = 'Visual Studio 14 2015' + build(os.path.join(SCRIPT_PATH, 'builds', 'win32-static'), generator, {}) + build(os.path.join(SCRIPT_PATH, 'builds', 'win32-dynamic'), generator, {'BUILD_DYNAMIC_LIB': True}) + generator = 'Visual Studio 14 2015 Win64' + build(os.path.join(SCRIPT_PATH, 'builds', 'win64-static'), generator, {}) + build(os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic'), generator, {'BUILD_DYNAMIC_LIB': True}) + # todo: this in some better way + src_dll = os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic', 'src', 'Release', 'discord-rpc.dll') + dst_dll = os.path.join(SCRIPT_PATH, 'examples\\button-clicker\\Assets\\Resources\\discord-rpc.dll') + shutil.copy(src_dll, dst_dll) + dst_dll = os.path.join(SCRIPT_PATH, 'examples\\unrealstatus\\Plugins\\discordrpc\\Binaries\\ThirdParty\\discordrpcLibrary\\Win64\\discord-rpc.dll') + shutil.copy(src_dll, dst_dll) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/examples/button-clicker/.gitignore b/examples/button-clicker/.gitignore index 2f0d3c1..6e47d8f 100644 --- a/examples/button-clicker/.gitignore +++ b/examples/button-clicker/.gitignore @@ -3,3 +3,4 @@ /obj/ *.sln *.csproj +*.userprefs \ No newline at end of file diff --git a/examples/button-clicker/Assets/DiscordController.cs b/examples/button-clicker/Assets/DiscordController.cs index c93ca25..1bc11d4 100644 --- a/examples/button-clicker/Assets/DiscordController.cs +++ b/examples/button-clicker/Assets/DiscordController.cs @@ -5,6 +5,7 @@ using UnityEngine; public class DiscordController : MonoBehaviour { public DiscordRpc.RichPresence presence; public string applicationId; + public string optionalSteamId; public int callbackCalls; public int clickCounter; public UnityEngine.Events.UnityEvent onConnect; @@ -72,7 +73,7 @@ public class DiscordController : MonoBehaviour { handlers.errorCallback += ErrorCallback; handlers.joinCallback += JoinCallback; handlers.spectateCallback += SpectateCallback; - DiscordRpc.Initialize(applicationId, ref handlers, true); + DiscordRpc.Initialize(applicationId, ref handlers, true, optionalSteamId); } void OnDisable() diff --git a/examples/button-clicker/Assets/DiscordRpc.cs b/examples/button-clicker/Assets/DiscordRpc.cs index c91491a..447337f 100644 --- a/examples/button-clicker/Assets/DiscordRpc.cs +++ b/examples/button-clicker/Assets/DiscordRpc.cs @@ -47,7 +47,7 @@ public class DiscordRpc } [DllImport("discord-rpc", EntryPoint = "Discord_Initialize")] - public static extern void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister); + public static extern void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId); [DllImport("discord-rpc", EntryPoint = "Discord_Shutdown")] public static extern void Shutdown(); diff --git a/examples/button-clicker/Assets/Resources/discord-rpc.dll b/examples/button-clicker/Assets/Resources/discord-rpc.dll index 4b3e655..f9eb346 100644 Binary files a/examples/button-clicker/Assets/Resources/discord-rpc.dll and b/examples/button-clicker/Assets/Resources/discord-rpc.dll differ diff --git a/examples/button-clicker/Assets/main.unity b/examples/button-clicker/Assets/main.unity index 080d2d1..b044ae3 100644 --- a/examples/button-clicker/Assets/main.unity +++ b/examples/button-clicker/Assets/main.unity @@ -668,6 +668,7 @@ MonoBehaviour: spectateSecret: instance: 0 applicationId: 345229890980937739 + optionalSteamId: callbackCalls: 0 clickCounter: 0 onConnect: diff --git a/examples/send-presence/send-presence.c b/examples/send-presence/send-presence.c index 65a2b57..a016c29 100644 --- a/examples/send-presence/send-presence.c +++ b/examples/send-presence/send-presence.c @@ -86,7 +86,7 @@ static void discordInit() handlers.errored = handleDiscordError; handlers.joinGame = handleDiscordJoin; handlers.spectateGame = handleDiscordSpectate; - Discord_Initialize(APPLICATION_ID, &handlers, 1); + Discord_Initialize(APPLICATION_ID, &handlers, 1, NULL); } static void gameLoop() diff --git a/examples/unrealstatus/Content/ShowTheUILevel.umap b/examples/unrealstatus/Content/ShowTheUILevel.umap index fc9228f..1b100e4 100644 Binary files a/examples/unrealstatus/Content/ShowTheUILevel.umap and b/examples/unrealstatus/Content/ShowTheUILevel.umap differ diff --git a/examples/unrealstatus/Plugins/discordrpc/Source/discordrpc/Private/DiscordRpcBlueprint.cpp b/examples/unrealstatus/Plugins/discordrpc/Source/discordrpc/Private/DiscordRpcBlueprint.cpp index bfd21e8..deeb926 100644 --- a/examples/unrealstatus/Plugins/discordrpc/Source/discordrpc/Private/DiscordRpcBlueprint.cpp +++ b/examples/unrealstatus/Plugins/discordrpc/Source/discordrpc/Private/DiscordRpcBlueprint.cpp @@ -53,7 +53,9 @@ static void SpectateGameHandler(const char* spectateSecret) } } -void UDiscordRpc::Initialize(const FString& applicationId, bool autoRegister) +void UDiscordRpc::Initialize(const FString& applicationId, + bool autoRegister, + const FString& optionalSteamId) { self = this; IsConnected = false; @@ -68,7 +70,8 @@ void UDiscordRpc::Initialize(const FString& applicationId, bool autoRegister) handlers.spectateGame = SpectateGameHandler; } auto appId = StringCast(*applicationId); - Discord_Initialize((const char*)appId.Get(), &handlers, autoRegister); + auto steamId = StringCast(*optionalSteamId); + Discord_Initialize((const char*)appId.Get(), &handlers, autoRegister, (const char*)steamId.Get()); } void UDiscordRpc::Shutdown() diff --git a/examples/unrealstatus/Plugins/discordrpc/Source/discordrpc/Public/DiscordRpcBlueprint.h b/examples/unrealstatus/Plugins/discordrpc/Source/discordrpc/Public/DiscordRpcBlueprint.h index 4821b66..0ce28bd 100644 --- a/examples/unrealstatus/Plugins/discordrpc/Source/discordrpc/Public/DiscordRpcBlueprint.h +++ b/examples/unrealstatus/Plugins/discordrpc/Source/discordrpc/Public/DiscordRpcBlueprint.h @@ -6,22 +6,19 @@ #include "CoreMinimal.h" #include "DiscordRpcBlueprint.generated.h" +// unreal's header tool hates clang-format +// clang-format off + DECLARE_LOG_CATEGORY_EXTERN(Discord, Log, All); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDiscordConnected); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordDisconnected, - int, - errorCode, - const FString&, - errorMessage); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordErrored, - int, - errorCode, - const FString&, - errorMessage); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordDisconnected, int, errorCode, const FString&, errorMessage); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordErrored, int, errorCode, const FString&, errorMessage); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoin, const FString&, joinSecret); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordSpectate, const FString&, spectateSecret); +// clang-format on + /** * Rich presence data */ @@ -73,7 +70,7 @@ public: UFUNCTION(BlueprintCallable, meta = (DisplayName = "Initialize connection", Keywords = "Discord rpc"), Category = "Discord") - void Initialize(const FString& applicationId, bool autoRegister); + void Initialize(const FString& applicationId, bool autoRegister, const FString& optionalSteamId); UFUNCTION(BlueprintCallable, meta = (DisplayName = "Shut down connection", Keywords = "Discord rpc"), diff --git a/include/discord-rpc.h b/include/discord-rpc.h index 2eba2f7..88c750e 100644 --- a/include/discord-rpc.h +++ b/include/discord-rpc.h @@ -51,7 +51,8 @@ typedef struct DiscordEventHandlers { DISCORD_EXPORT void Discord_Initialize(const char* applicationId, DiscordEventHandlers* handlers, - int autoRegister); + int autoRegister, + const char* optionalSteamId); DISCORD_EXPORT void Discord_Shutdown(); /* checks for incoming messages, dispatches callbacks */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c919cc1..31f95db 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,7 @@ endif (NOT ${ENABLE_IO_THREAD}) set(BASE_RPC_SRC ${PROJECT_SOURCE_DIR}/include/discord-rpc.h discord-rpc.cpp + discord-register.h discord-register.cpp rpc_connection.h rpc_connection.cpp diff --git a/src/discord-register.cpp b/src/discord-register.cpp index bd2fcc3..05a7f6b 100644 --- a/src/discord-register.cpp +++ b/src/discord-register.cpp @@ -12,27 +12,31 @@ #pragma comment(lib, "Psapi.lib") #endif -void Discord_Register(const char* applicationId) -{ #ifdef _WIN32 +void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command) +{ // https://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx // we want to register games so we can run them as discord-:// // Update the HKEY_CURRENT_USER, because it doesn't seem to require special permissions. - wchar_t appId[32]; - MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32); - wchar_t exeFilePath[MAX_PATH]; - GetModuleFileNameExW(GetCurrentProcess(), nullptr, exeFilePath, MAX_PATH); + int exeLen = GetModuleFileNameExW(GetCurrentProcess(), nullptr, exeFilePath, MAX_PATH); + wchar_t openCommand[1024]; + const auto commandBufferLen = sizeof(openCommand) / sizeof(*openCommand); + + if (command && command[0]) { + StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command); + } + else { + lstrcpyW(openCommand, exeFilePath); + } wchar_t protocolName[64]; - StringCbPrintfW(protocolName, sizeof(protocolName), L"discord-%s", appId); + StringCbPrintfW(protocolName, sizeof(protocolName), L"discord-%s", applicationId); wchar_t protocolDescription[128]; StringCbPrintfW( - protocolDescription, sizeof(protocolDescription), L"URL:Run game %s protocol", appId); + protocolDescription, sizeof(protocolDescription), L"URL:Run game %s protocol", applicationId); wchar_t urlProtocol = 0; - wchar_t openCommand[MAX_PATH + 8]; - StringCbPrintfW(openCommand, sizeof(openCommand), L"\"%s\" \"%%1\"", exeFilePath); wchar_t keyName[256]; StringCbPrintfW(keyName, sizeof(keyName), L"Software\\Classes\\%s", protocolName); @@ -58,9 +62,8 @@ void Discord_Register(const char* applicationId) fprintf(stderr, "Error writing description\n"); } - len = lstrlenW(exeFilePath) + 1; - result = - RegSetKeyValueW(key, L"DefaultIcon", nullptr, REG_SZ, exeFilePath, len * sizeof(wchar_t)); + result = RegSetKeyValueW( + key, L"DefaultIcon", nullptr, REG_SZ, exeFilePath, (exeLen + 1) * sizeof(wchar_t)); if (FAILED(result)) { fprintf(stderr, "Error writing icon\n"); } @@ -72,5 +75,63 @@ void Discord_Register(const char* applicationId) fprintf(stderr, "Error writing command\n"); } RegCloseKey(key); +} +#endif + +void Discord_Register(const char* applicationId, const char* command) +{ +#ifdef _WIN32 + + wchar_t appId[32]; + MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32); + + wchar_t openCommand[1024]; + const wchar_t* wcommand = nullptr; + if (command && command[0]) { + const auto commandBufferLen = sizeof(openCommand) / sizeof(*openCommand); + MultiByteToWideChar(CP_UTF8, 0, command, -1, openCommand, commandBufferLen); + wcommand = openCommand; + } + + Discord_RegisterW(appId, wcommand); +#endif +} + +void Discord_RegisterSteamGame(const char* applicationId, const char* steamId) +{ +#ifdef _WIN32 + wchar_t appId[32]; + MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32); + + wchar_t wSteamId[32]; + MultiByteToWideChar(CP_UTF8, 0, steamId, -1, wSteamId, 32); + + HKEY key; + auto status = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam", 0, KEY_READ, &key); + if (status != ERROR_SUCCESS) { + fprintf(stderr, "Error opening Steam key\n"); + return; + } + + wchar_t steamPath[MAX_PATH]; + DWORD pathBytes = sizeof(steamPath); + status = RegQueryValueExW(key, L"SteamExe", nullptr, nullptr, (BYTE*)steamPath, &pathBytes); + RegCloseKey(key); + if (status != ERROR_SUCCESS || pathBytes < 1) { + fprintf(stderr, "Error reading SteamExe key\n"); + return; + } + + DWORD pathChars = pathBytes / sizeof(wchar_t); + for (DWORD i = 0; i < pathChars; ++i) { + if (steamPath[i] == L'/') { + steamPath[i] = L'\\'; + } + } + + wchar_t command[1024]; + StringCbPrintfW(command, sizeof(command), L"\"%s\" steam://run/%s", steamPath, wSteamId); + + Discord_RegisterW(appId, command); #endif } diff --git a/src/discord-register.h b/src/discord-register.h index 1ad943b..8df2105 100644 --- a/src/discord-register.h +++ b/src/discord-register.h @@ -1,3 +1,4 @@ #pragma once -void Discord_Register(const char* applicationId); +void Discord_Register(const char* applicationId, const char* command); +void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); diff --git a/src/discord-rpc.cpp b/src/discord-rpc.cpp index 2ad7442..97a78de 100644 --- a/src/discord-rpc.cpp +++ b/src/discord-rpc.cpp @@ -208,10 +208,16 @@ bool RegisterForEvent(const char* evtName) extern "C" void Discord_Initialize(const char* applicationId, DiscordEventHandlers* handlers, - int autoRegister) + int autoRegister, + const char* optionalSteamId) { if (autoRegister) { - Discord_Register(applicationId); + if (optionalSteamId && optionalSteamId[0]) { + Discord_RegisterSteamGame(applicationId, optionalSteamId); + } + else { + Discord_Register(applicationId, nullptr); + } } Pid = GetProcessId();