Compare commits
82 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
963aa9f3e5 | ||
|
e4c0c569ec | ||
|
b6d0a9cdbd | ||
|
eff23a770a | ||
|
34ce3ac803 | ||
|
c59fd6df20 | ||
|
4824b20f28 | ||
|
4e53fa0392 | ||
|
d478ed5608 | ||
|
8db649ba5f | ||
|
e6390c8c41 | ||
|
2fec0b6dec | ||
|
dd47c7c66d | ||
|
98855b4d84 | ||
|
ac2d064cb0 | ||
|
d63ed30966 | ||
|
7716eadca3 | ||
|
e32d001809 | ||
|
2cb9813eb6 | ||
|
af380116a0 | ||
|
3d3ae7129d | ||
|
b44defe60a | ||
|
dfad394be0 | ||
|
a3ad6afee2 | ||
|
7c41a8ec19 | ||
|
5df1c5ae6d | ||
|
c05c7148dd | ||
|
ba9fe00c4d | ||
|
cac0362377 | ||
|
7e0480e2ef | ||
|
566076e3d8 | ||
|
aa02012c14 | ||
|
f80bd72d22 | ||
|
acf7d6a054 | ||
|
1129c2ce4f | ||
|
64027b336f | ||
|
2ce9fe068b | ||
|
be8a8e9380 | ||
|
c70acbe7d1 | ||
|
d97e6b48ed | ||
|
087282cd4b | ||
|
7e5d57e6fd | ||
|
f3bd411b99 | ||
|
8e0c7848a6 | ||
|
e7f9396807 | ||
|
ad0b844672 | ||
|
d279c24c6a | ||
|
d9caf72e9a | ||
|
e8091f5137 | ||
|
4055565147 | ||
|
578eb6de7c | ||
|
4e61b9c82c | ||
|
8ec10dc011 | ||
|
f5f2d69a72 | ||
|
453222075b | ||
|
c4201806cf | ||
|
ccf04d21f5 | ||
|
c7b4e6b2fc | ||
|
eee5085e9b | ||
|
94ee4e64d9 | ||
|
bd294d51a8 | ||
|
b85758ec19 | ||
|
ec6af6132d | ||
|
f99a260b07 | ||
|
2c609b1d5f | ||
|
839ba32671 | ||
|
6a59509b7b | ||
|
b0e31a9e25 | ||
|
2d0661c906 | ||
|
265ea814f5 | ||
|
8990824c9c | ||
|
8f9013cea6 | ||
|
085e0e7326 | ||
|
b3102db5c9 | ||
|
5438d6bf22 | ||
|
b9f9b08606 | ||
|
e5bdd61223 | ||
|
1555405d83 | ||
|
051a1eeb70 | ||
|
3852d83d12 | ||
|
d8122e7d69 | ||
|
060182f366 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
||||
/.vscode/
|
||||
/thirdparty/
|
||||
.vs/
|
||||
.DS_Store
|
@ -43,5 +43,5 @@ before_install:
|
||||
script:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -DCLANG_FORMAT_SUFFIX=$CLANG_FORMAT_SUFFIX --config Release ..
|
||||
- cmake -DCLANG_FORMAT_SUFFIX=$CLANG_FORMAT_SUFFIX -DWARNINGS_AS_ERRORS=On --config Release ..
|
||||
- cmake --build . -- -j2
|
||||
|
@ -28,23 +28,23 @@ endif(CLANG_FORMAT_CMD)
|
||||
|
||||
# thirdparty stuff
|
||||
execute_process(
|
||||
COMMAND mkdir ${CMAKE_SOURCE_DIR}/thirdparty
|
||||
COMMAND mkdir ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty
|
||||
ERROR_QUIET
|
||||
)
|
||||
|
||||
find_file(RAPIDJSONTEST NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH)
|
||||
find_file(RAPIDJSONTEST NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH)
|
||||
if (NOT RAPIDJSONTEST)
|
||||
message("no rapidjson, download")
|
||||
set(RJ_TAR_FILE ${CMAKE_SOURCE_DIR}/thirdparty/v1.1.0.tar.gz)
|
||||
set(RJ_TAR_FILE ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/v1.1.0.tar.gz)
|
||||
file(DOWNLOAD https://github.com/miloyip/rapidjson/archive/v1.1.0.tar.gz ${RJ_TAR_FILE})
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E tar xzf ${RJ_TAR_FILE}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/thirdparty
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty
|
||||
)
|
||||
file(REMOVE ${RJ_TAR_FILE})
|
||||
endif(NOT RAPIDJSONTEST)
|
||||
|
||||
find_file(RAPIDJSON NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH)
|
||||
find_file(RAPIDJSON NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH)
|
||||
|
||||
add_library(rapidjson STATIC IMPORTED ${RAPIDJSON})
|
||||
|
||||
|
114
README.md
114
README.md
@ -1,5 +1,11 @@
|
||||
# Discord RPC
|
||||
|
||||
## Deprecation Notice
|
||||
|
||||
This library has been deprecated in favor of Discord's GameSDK. [Learn more here](https://discordapp.com/developers/docs/game-sdk/sdk-starter-guide)
|
||||
|
||||
---
|
||||
|
||||
This is a library for interfacing your game with a locally running Discord desktop client. It's known to work on Windows, macOS, and Linux. You can use the lib directly if you like, or use it as a guide to writing your own if it doesn't suit your game as is. PRs/feedback welcome if you have an improvement everyone might want, or can describe how this doesn't meet your needs.
|
||||
|
||||
Included here are some quick demos that implement the very minimal subset to show current status, and
|
||||
@ -7,7 +13,7 @@ have callbacks for where a more complete game would do more things (joining, spe
|
||||
|
||||
## Documentation
|
||||
|
||||
The most up to date documentation for Rich Presence can always be found in our [developer site](https://discordapp.com/developers/docs/topics/rich-presence)!
|
||||
The most up to date documentation for Rich Presence can always be found on our [developer site](https://discordapp.com/developers/docs/rich-presence/how-to)! If you're interested in rolling your own native implementation of Rich Presence via IPC sockets instead of using our SDK—hey, you've got free time, right?—check out the ["Hard Mode" documentation](https://github.com/discordapp/discord-rpc/blob/master/documentation/hard-mode.md).
|
||||
|
||||
## Basic Usage
|
||||
|
||||
@ -15,13 +21,76 @@ Zeroith, you should be set up to build things because you are a game developer,
|
||||
|
||||
First, head on over to the [Discord developers site](https://discordapp.com/developers/applications/me) and make yourself an app. Keep track of `Client ID` -- you'll need it here to pass to the init function.
|
||||
|
||||
### Unreal Engine 4 Setup
|
||||
|
||||
To use the Rich Presense plugin with Unreal Engine Projects:
|
||||
|
||||
1. Download the latest [release](https://github.com/discordapp/discord-rpc/releases) for each operating system you are targeting and the zipped source code
|
||||
2. In the source code zip, copy the UE plugin—`examples/unrealstatus/Plugins/discordrpc`—to your project's plugin directory
|
||||
3. At `[YOUR_UE_PROJECT]/Plugins/discordrpc/source/ThirdParty/DiscordRpcLibrary/`, create an `Include` folder and copy `discord_rpc.h` and `discord_register.h` to it from the zip
|
||||
4. Follow the steps below for each OS
|
||||
5. Build your UE4 project
|
||||
6. Launch the editor, and enable the Discord plugin.
|
||||
|
||||
#### Windows
|
||||
|
||||
- At `[YOUR_UE_PROJECT]/Plugins/discordrpc/source/ThirdParty/DiscordRpcLibrary/`, create a `Win64` folder
|
||||
- Copy `lib/discord-rpc.lib` and `bin/discord-rpc.dll` from `[RELEASE_ZIP]/win64-dynamic` to the `Win64` folder
|
||||
|
||||
#### Mac
|
||||
|
||||
- At `[YOUR_UE_PROJECT]/Plugins/discordrpc/source/ThirdParty/DiscordRpcLibrary/`, create a `Mac` folder
|
||||
- Copy `libdiscord-rpc.dylib` from `[RELEASE_ZIP]/osx-dynamic/lib` to the `Mac` folder
|
||||
|
||||
#### Linux
|
||||
|
||||
- At `[YOUR_UE_PROJECT]/Plugins/discordrpc/source/ThirdParty/DiscordRpcLibrary/`, create a `Linux` folder
|
||||
- Inside, create another folder `x86_64-unknown-linux-gnu`
|
||||
- Copy `libdiscord-rpc.so` from `[RELEASE_ZIP]/linux-dynamic/lib` to `Linux/x86_64-unknown-linux-gnu`
|
||||
|
||||
### Unity Setup
|
||||
|
||||
If you're a Unity developer looking to integrate Rich Presence into your game, follow this simple guide to get started towards success:
|
||||
|
||||
1. Download the DLLs for any platform that you need from [our releases](https://github.com/discordapp/discord-rpc/releases)
|
||||
2. In your Unity project, create a `Plugins` folder inside your `Assets` folder if you don't already have one
|
||||
3. Copy the file `DiscordRpc.cs` from [here](https://github.com/discordapp/discord-rpc/blob/master/examples/button-clicker/Assets/DiscordRpc.cs) into your `Assets` folder. This is basically your header file for the SDK
|
||||
|
||||
We've got our `Plugins` folder ready, so let's get platform-specific!
|
||||
|
||||
#### Windows
|
||||
|
||||
4. Create `x86` and `x86_64` folders inside `Assets/Plugins/`
|
||||
5. Copy `discord-rpc-win/win64-dynamic/bin/discord-rpc.dll` to `Assets/Plugins/x86_64/`
|
||||
6. Copy `discord-rpc-win/win32-dynamic/bin/discord-rpc.dll` to `Assets/Plugins/x86/`
|
||||
7. Click on both DLLs and make sure they are targetting the correct architectures in the Unity editor properties pane
|
||||
8. Done!
|
||||
|
||||
#### MacOS
|
||||
|
||||
4. Copy `discord-rpc-osx/osx-dynamic/lib/libdiscord-rpc.dylib` to `Assets/Plugins/`
|
||||
5. Rename `libdiscord-rpc.dylib` to `discord-rpc.bundle`
|
||||
6. Done!
|
||||
|
||||
#### Linux
|
||||
|
||||
4. Copy `discord-rpc-linux/linux-dynamic-lib/libdiscord-rpc.so` to `Assets/Plugins/`
|
||||
5. Done!
|
||||
|
||||
You're ready to roll! For code examples on how to interact with the SDK using the `DiscordRpc.cs` header file, check out [our example](https://github.com/discordapp/discord-rpc/blob/master/examples/button-clicker/Assets/DiscordController.cs)
|
||||
|
||||
### From package
|
||||
|
||||
Download a release package for your platform(s) -- they have subdirs with various prebuilt options, select the one you need add `/include` to your compile includes, `/lib` to your linker paths, and link with `discord-rpc`. For the dynamically linked builds, you'll need to ship the associated file along with your game.
|
||||
|
||||
### From repo
|
||||
|
||||
First-eth, you'll want `CMake`. There's a few different ways to install it on your system, and you should refer to [their website](https://cmake.org/install/). Many package managers provide ways of installing CMake as well.
|
||||
|
||||
To make sure it's installed correctly, type `cmake --version` into your flavor of terminal/cmd. If you get a response with a version number, you're good to go!
|
||||
|
||||
There's a [CMake](https://cmake.org/download/) file that should be able to generate the lib for you; Sometimes I use it like this:
|
||||
|
||||
```sh
|
||||
cd <path to discord-rpc>
|
||||
mkdir build
|
||||
@ -29,27 +98,29 @@ There's a [CMake](https://cmake.org/download/) file that should be able to gener
|
||||
cmake .. -DCMAKE_INSTALL_PREFIX=<path to install discord-rpc to>
|
||||
cmake --build . --config Release --target install
|
||||
```
|
||||
|
||||
There is a wrapper build script `build.py` that runs `cmake` with a few different options.
|
||||
|
||||
Usually, I run `build.py` to get things started, then use the generated project files as I work on things.
|
||||
Usually, I run `build.py` to get things started, then use the generated project files as I work on things. It does depend on `click` library, so do a quick `pip install click` to make sure you have it if you want to run `build.py`.
|
||||
|
||||
There are some CMake options you might care about:
|
||||
|
||||
| flag | default | does |
|
||||
|------|---------|------|
|
||||
| `ENABLE_IO_THREAD` | `ON` | When enabled, we start up a thread to do io processing, if disabled you should call `Discord_UpdateConnection` yourself.
|
||||
| `USE_STATIC_CRT` | `OFF` | (Windows) Enable to statically link the CRT, avoiding requiring users install the redistributable package. (The prebuilt binaries enable this option)
|
||||
| [`BUILD_SHARED_LIBS`](https://cmake.org/cmake/help/v3.7/variable/BUILD_SHARED_LIBS.html) | `OFF` | Build library as a DLL
|
||||
| flag | default | does |
|
||||
| ---------------------------------------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ENABLE_IO_THREAD` | `ON` | When enabled, we start up a thread to do io processing, if disabled you should call `Discord_UpdateConnection` yourself. |
|
||||
| `USE_STATIC_CRT` | `OFF` | (Windows) Enable to statically link the CRT, avoiding requiring users install the redistributable package. (The prebuilt binaries enable this option) |
|
||||
| [`BUILD_SHARED_LIBS`](https://cmake.org/cmake/help/v3.7/variable/BUILD_SHARED_LIBS.html) | `OFF` | Build library as a DLL |
|
||||
| `WARNINGS_AS_ERRORS` | `OFF` | When enabled, compiles with `-Werror` (on \*nix platforms). |
|
||||
|
||||
## Continuous Builds
|
||||
|
||||
Why do we have three of these? Three times the fun!
|
||||
|
||||
| CI | badge |
|
||||
|----|-------|
|
||||
| TravisCI | [](https://travis-ci.org/discordapp/discord-rpc)
|
||||
| AppVeyor | [](https://ci.appveyor.com/project/crmarsh/discord-rpc)
|
||||
| Buildkite (internal) | [](https://buildkite.com/discord/discord-rpc)
|
||||
| CI | badge |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| TravisCI | [](https://travis-ci.org/discordapp/discord-rpc) |
|
||||
| AppVeyor | [](https://ci.appveyor.com/project/crmarsh/discord-rpc) |
|
||||
| Buildkite (internal) | [](https://buildkite.com/discord/discord-rpc) |
|
||||
|
||||
## Sample: send-presence
|
||||
|
||||
@ -57,11 +128,11 @@ This is a text adventure "game" that inits/deinits the connection to Discord, an
|
||||
|
||||
## Sample: button-clicker
|
||||
|
||||
This is a sample [Unity](https://unity3d.com/) project that wraps a DLL version of the library, and sends presence updates when you click on a button.
|
||||
This is a sample [Unity](https://unity3d.com/) project that wraps a DLL version of the library, and sends presence updates when you click on a button. Run `python build.py unity` in the root directory to build the correct library files and place them in their respective folders.
|
||||
|
||||
## Sample: unrealstatus
|
||||
|
||||
This is a sample [Unreal](https://www.unrealengine.com) project that wraps the DLL version of the library with an Unreal plugin, exposes a blueprint class for interacting with it, and uses that to make a very simple UI.
|
||||
This is a sample [Unreal](https://www.unrealengine.com) project that wraps the DLL version of the library with an Unreal plugin, exposes a blueprint class for interacting with it, and uses that to make a very simple UI. Run `python build.py unreal` in the root directory to build the correct library files and place them in their respective folders.
|
||||
|
||||
## Wrappers and Implementations
|
||||
|
||||
@ -73,6 +144,15 @@ Below is a table of unofficial, community-developed wrappers for and implementat
|
||||
|
||||
###### Rich Presence Wrappers and Implementations
|
||||
|
||||
| Name | Language |
|
||||
|------|----------|
|
||||
| Be the first! | |
|
||||
| Name | Language |
|
||||
| ------------------------------------------------------------------------- | --------------------------------- |
|
||||
| [Discord RPC C#](https://github.com/Lachee/discord-rpc-csharp) | C# |
|
||||
| [Discord RPC D](https://github.com/voidblaster/discord-rpc-d) | [D](https://dlang.org/) |
|
||||
| [discord-rpc.jar](https://github.com/Vatuu/discord-rpc 'Discord-RPC.jar') | Java |
|
||||
| [java-discord-rpc](https://github.com/MinnDevelopment/java-discord-rpc) | Java |
|
||||
| [Discord-IPC](https://github.com/jagrosh/DiscordIPC) | Java |
|
||||
| [Discord Rich Presence](https://npmjs.org/discord-rich-presence) | JavaScript |
|
||||
| [drpc4k](https://github.com/Bluexin/drpc4k) | [Kotlin](https://kotlinlang.org/) |
|
||||
| [lua-discordRPC](https://github.com/pfirsich/lua-discordRPC) | LuaJIT (FFI) |
|
||||
| [pypresence](https://github.com/qwertyquerty/pypresence) | [Python](https://python.org/) |
|
||||
| [SwordRPC](https://github.com/Azoy/SwordRPC) | [Swift](https://swift.org) |
|
||||
|
132
build.py
132
build.py
@ -30,7 +30,7 @@ INSTALL_ROOT = os.path.join(SCRIPT_PATH, 'builds', 'install')
|
||||
def get_signtool():
|
||||
""" get path to code signing tool """
|
||||
if PLATFORM == 'win':
|
||||
sdk_dir = os.environ['WindowsSdkDir']
|
||||
sdk_dir = 'c:\\Program Files (x86)\\Windows Kits\\10' # os.environ['WindowsSdkDir']
|
||||
return os.path.join(sdk_dir, 'bin', 'x86', 'signtool.exe')
|
||||
elif PLATFORM == 'osx':
|
||||
return '/usr/bin/codesign'
|
||||
@ -62,34 +62,100 @@ def cli(ctx, clean):
|
||||
if ctx.invoked_subcommand is None:
|
||||
ctx.invoke(libs, clean=clean)
|
||||
if IS_BUILD_MACHINE:
|
||||
ctx.invoke(sign)
|
||||
ctx.invoke(sign)
|
||||
ctx.invoke(archive)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def unity():
|
||||
""" todo: build unity project """
|
||||
pass
|
||||
@click.pass_context
|
||||
def unity(ctx):
|
||||
""" build just dynamic libs for use in unity project """
|
||||
ctx.invoke(libs, clean=False, static=False, shared=True, skip_formatter=True, just_release=True)
|
||||
BUILDS = []
|
||||
|
||||
click.echo('--- Copying libs and header into unity example')
|
||||
UNITY_PROJECT_PATH = os.path.join(SCRIPT_PATH, 'examples', 'button-clicker', 'Assets', 'Plugins')
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
LIBRARY_NAME = 'discord-rpc.dll'
|
||||
BUILD_64_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic', 'src', 'Release')
|
||||
UNITY_64_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86_64')
|
||||
BUILDS.append({BUILD_64_BASE_PATH: UNITY_64_DLL_PATH})
|
||||
|
||||
BUILD_32_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win32-dynamic', 'src', 'Release')
|
||||
UNITY_32_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86')
|
||||
BUILDS.append({BUILD_32_BASE_PATH: UNITY_32_DLL_PATH})
|
||||
|
||||
elif sys.platform == 'darwin':
|
||||
LIBRARY_NAME = 'discord-rpc.bundle'
|
||||
BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'osx-dynamic', 'src')
|
||||
UNITY_DLL_PATH = UNITY_PROJECT_PATH
|
||||
os.rename(
|
||||
os.path.join(BUILD_BASE_PATH, 'libdiscord-rpc.dylib'), os.path.join(BUILD_BASE_PATH, 'discord-rpc.bundle'))
|
||||
|
||||
BUILDS.append({BUILD_BASE_PATH: UNITY_DLL_PATH})
|
||||
|
||||
elif sys.platform.startswith('linux'):
|
||||
LIBRARY_NAME = 'discord-rpc.so'
|
||||
BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'linux-dynamic', 'src')
|
||||
UNITY_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86')
|
||||
os.rename(os.path.join(BUILD_BASE_PATH, 'libdiscord-rpc.so'), os.path.join(BUILD_BASE_PATH, 'discord-rpc.so'))
|
||||
|
||||
BUILDS.append({BUILD_BASE_PATH: UNITY_DLL_PATH})
|
||||
|
||||
else:
|
||||
raise Exception('Unsupported platform ' + sys.platform)
|
||||
|
||||
for build in BUILDS:
|
||||
for i in build:
|
||||
mkdir_p(build[i])
|
||||
shutil.copy(os.path.join(i, LIBRARY_NAME), build[i])
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.pass_context
|
||||
def for_unity(ctx):
|
||||
""" build just dynamic libs for use in unity project """
|
||||
ctx.invoke(
|
||||
libs,
|
||||
clean=False,
|
||||
static=False,
|
||||
shared=True,
|
||||
skip_formatter=True,
|
||||
just_release=True
|
||||
)
|
||||
def unreal(ctx):
|
||||
""" build libs and copy them into the unreal project """
|
||||
ctx.invoke(libs, clean=False, static=False, shared=True, skip_formatter=True, just_release=True)
|
||||
BUILDS = []
|
||||
|
||||
click.echo('--- Copying libs and header into unreal example')
|
||||
UNREAL_PROJECT_PATH = os.path.join(SCRIPT_PATH, 'examples', 'unrealstatus', 'Plugins', 'discordrpc')
|
||||
UNREAL_INCLUDE_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Include')
|
||||
mkdir_p(UNREAL_INCLUDE_PATH)
|
||||
shutil.copy(os.path.join(SCRIPT_PATH, 'include', 'discord_rpc.h'), UNREAL_INCLUDE_PATH)
|
||||
|
||||
@cli.command()
|
||||
def unreal():
|
||||
""" todo: build unreal project """
|
||||
pass
|
||||
if sys.platform.startswith('win'):
|
||||
LIBRARY_NAME = 'discord-rpc.lib'
|
||||
BUILD_64_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic', 'src', 'Release')
|
||||
UNREAL_64_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Win64')
|
||||
BUILDS.append({BUILD_64_BASE_PATH: UNREAL_64_DLL_PATH})
|
||||
|
||||
BUILD_32_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win32-dynamic', 'src', 'Release')
|
||||
UNREAL_32_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Win32')
|
||||
BUILDS.append({BUILD_32_BASE_PATH: UNREAL_32_DLL_PATH})
|
||||
|
||||
elif sys.platform == 'darwin':
|
||||
LIBRARY_NAME = 'libdiscord-rpc.dylib'
|
||||
BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'osx-dynamic', 'src')
|
||||
UNREAL_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Mac')
|
||||
|
||||
BUILDS.append({BUILD_BASE_PATH: UNREAL_DLL_PATH})
|
||||
|
||||
elif sys.platform.startswith('linux'):
|
||||
LIBRARY_NAME = 'libdiscord-rpc.so'
|
||||
BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'linux-dynamic', 'src')
|
||||
UNREAL_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Linux')
|
||||
|
||||
BUILDS.append({BUILD_BASE_PATH: UNREAL_DLL_PATH})
|
||||
|
||||
else:
|
||||
raise Exception('Unsupported platform ' + sys.platform)
|
||||
|
||||
for build in BUILDS:
|
||||
for i in build:
|
||||
mkdir_p(build[i])
|
||||
shutil.copy(os.path.join(i, LIBRARY_NAME), build[i])
|
||||
|
||||
|
||||
def build_lib(build_name, generator, options, just_release):
|
||||
@ -99,11 +165,7 @@ def build_lib(build_name, generator, options, just_release):
|
||||
mkdir_p(build_path)
|
||||
mkdir_p(install_path)
|
||||
with cd(build_path):
|
||||
initial_cmake = [
|
||||
'cmake',
|
||||
SCRIPT_PATH,
|
||||
'-DCMAKE_INSTALL_PREFIX=%s' % os.path.join('..', 'install', build_name)
|
||||
]
|
||||
initial_cmake = ['cmake', SCRIPT_PATH, '-DCMAKE_INSTALL_PREFIX=%s' % os.path.join('..', 'install', build_name)]
|
||||
if generator:
|
||||
initial_cmake.extend(['-G', generator])
|
||||
for key in options:
|
||||
@ -145,27 +207,33 @@ def sign():
|
||||
sign_command_base = [
|
||||
tool,
|
||||
'sign',
|
||||
'/n', 'Hammer & Chisel Inc.',
|
||||
'/n',
|
||||
'Discord Inc.',
|
||||
'/a',
|
||||
'/tr', 'http://timestamp.digicert.com/rfc3161',
|
||||
'/tr',
|
||||
'http://timestamp.digicert.com/rfc3161',
|
||||
'/as',
|
||||
'/td', 'sha256',
|
||||
'/fd', 'sha256',
|
||||
'/td',
|
||||
'sha256',
|
||||
'/fd',
|
||||
'sha256',
|
||||
]
|
||||
elif PLATFORM == 'osx':
|
||||
signable_extensions.add('.dylib')
|
||||
sign_command_base = [
|
||||
tool,
|
||||
'--keychain', os.path.expanduser('~/Library/Keychains/login.keychain'),
|
||||
'--keychain',
|
||||
os.path.expanduser('~/Library/Keychains/login.keychain'),
|
||||
'-vvvv',
|
||||
'--deep',
|
||||
'--force',
|
||||
'--sign', 'Developer ID Application: Hammer & Chisel Inc. (53Q6R32WPB)',
|
||||
'--sign',
|
||||
'Developer ID Application: Hammer & Chisel Inc. (53Q6R32WPB)',
|
||||
]
|
||||
else:
|
||||
click.secho('Not signing things on this platform yet', fg='red')
|
||||
return
|
||||
|
||||
|
||||
click.echo('--- Signing')
|
||||
for path, _, filenames in os.walk(INSTALL_ROOT):
|
||||
for fname in filenames:
|
||||
@ -207,6 +275,8 @@ def libs(clean, static, shared, skip_formatter, just_release):
|
||||
|
||||
if IS_BUILD_MACHINE:
|
||||
just_release = True
|
||||
static_options['WARNINGS_AS_ERRORS'] = True
|
||||
dynamic_options['WARNINGS_AS_ERRORS'] = True
|
||||
|
||||
if PLATFORM == 'win':
|
||||
generator32 = 'Visual Studio 14 2015'
|
||||
|
@ -66,7 +66,7 @@ First is the `ACTIVITY_JOIN` event:
|
||||
"data": {
|
||||
"secret": "025ed05c71f639de8bfaa0d679d7c94b2fdce12f"
|
||||
},
|
||||
"evnt": "ACTIVITY_JOIN"
|
||||
"evt": "ACTIVITY_JOIN"
|
||||
}
|
||||
```
|
||||
|
||||
@ -78,7 +78,7 @@ Second is the `ACTIVITY_SPECTATE` event:
|
||||
"data": {
|
||||
"secret": "e7eb30d2ee025ed05c71ea495f770b76454ee4e0"
|
||||
},
|
||||
"evnt": "ACTIVITY_SPECTATE"
|
||||
"evt": "ACTIVITY_SPECTATE"
|
||||
}
|
||||
```
|
||||
|
||||
@ -93,10 +93,9 @@ And third is the `ACTIVITY_JOIN_REQUEST` event:
|
||||
"username": "Mason",
|
||||
"discriminator": "1337",
|
||||
"avatar": "a_bab14f271d565501444b2ca3be944b25"
|
||||
},
|
||||
"secret": "e459ca99273f59909dd16ed97865f3ad"
|
||||
}
|
||||
},
|
||||
"evnt": "ACTIVITY_JOIN_REQUEST"
|
||||
"evt": "ACTIVITY_JOIN_REQUEST"
|
||||
}
|
||||
```
|
||||
|
||||
@ -125,3 +124,41 @@ In order to receive these events, you need to [subscribe](https://discordapp.com
|
||||
"cmd": "SUBSCRIBE"
|
||||
}
|
||||
```
|
||||
|
||||
To unsubscribe from these events, resend with the command `UNSUBSCRIBE`
|
||||
|
||||
## Responding
|
||||
A discord user will request access to the game. If the ACTIVITY_JOIN_REQUEST has been subscribed too, the ACTIVITY_JOIN_REQUEST event will be sent to the host's game. Accept it with following model:
|
||||
```json
|
||||
{
|
||||
"nonce": "5dc0c062-98c6-47a0-8922-15aerg126",
|
||||
"cmd": "SEND_ACTIVITY_JOIN_INVITE",
|
||||
"args":
|
||||
{
|
||||
"user_id": "53908232506183680"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To reject the request, use `CLOSE_ACTIVITY_REQUEST`:
|
||||
```json
|
||||
{
|
||||
"nonce": "5dc0c062-98c6-47a0-8922-dasg256eafg",
|
||||
"cmd": "CLOSE_ACTIVITY_REQUEST",
|
||||
"args":
|
||||
{
|
||||
"user_id": "53908232506183680"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
Here are just some quick notes to help with some common troubleshooting problems.
|
||||
* IPC will echo back every command you send as a response. Use this as a lock-step feature to avoid flooding messages. Can be used to validate messages such as the Presence or Subscribes.
|
||||
* The pipe expects for frames to be written in a single byte array. You cannot do multiple `stream.Write(opcode);` `stream.Write(length);` as it will break the pipe. Instead create a buffer, write the data to the buffer, then send the entire buffer to the stream.
|
||||
* Discord can be on any pipe ranging from `discord-ipc-0` to `discord-ipc-9`. It is a good idea to try and connect to each one and keeping the first one you connect too. For multiple clients (eg Discord and Canary), you might want to add a feature to manually select the pipe so you can more easily debug the application.
|
||||
* All enums are `lower_snake_case`.
|
||||
* The opcode and length in the header are `Little Endian Unsigned Integers (32bits)`. In some languages, you must convert them as they can be architecture specific.
|
||||
* [Discord Rich Presence How-To](https://discordapp.com/developers/docs/rich-presence/how-to) contains a lot of the information this document doesn't. For example, it will tell you about the response payload.
|
||||
* In the documentation, DISCORD_REPLY_IGNORE is just implemented the same as DISCORD_REPLY_NO.
|
||||
* You can test the Join / Spectate feature by enabling them in your profile and whitelisting a test account. Use Canary to run 2 accounts on the same machine.
|
||||
|
@ -7,17 +7,18 @@ public class DiscordJoinEvent : UnityEngine.Events.UnityEvent<string> { }
|
||||
public class DiscordSpectateEvent : UnityEngine.Events.UnityEvent<string> { }
|
||||
|
||||
[System.Serializable]
|
||||
public class DiscordJoinRequestEvent : UnityEngine.Events.UnityEvent<DiscordRpc.JoinRequest> { }
|
||||
public class DiscordJoinRequestEvent : UnityEngine.Events.UnityEvent<DiscordRpc.DiscordUser> { }
|
||||
|
||||
public class DiscordController : MonoBehaviour
|
||||
{
|
||||
public DiscordRpc.RichPresence presence;
|
||||
public DiscordRpc.RichPresence presence = new DiscordRpc.RichPresence();
|
||||
public string applicationId;
|
||||
public string optionalSteamId;
|
||||
public int callbackCalls;
|
||||
public int clickCounter;
|
||||
public DiscordRpc.DiscordUser joinRequest;
|
||||
public UnityEngine.Events.UnityEvent onConnect;
|
||||
public UnityEngine.Events.UnityEvent onDisconnect;
|
||||
public UnityEngine.Events.UnityEvent hasResponded;
|
||||
public DiscordJoinEvent onJoin;
|
||||
public DiscordJoinEvent onSpectate;
|
||||
public DiscordJoinRequestEvent onJoinRequest;
|
||||
@ -30,48 +31,62 @@ public class DiscordController : MonoBehaviour
|
||||
clickCounter++;
|
||||
|
||||
presence.details = string.Format("Button clicked {0} times", clickCounter);
|
||||
presence.joinSecret = "aSecret";
|
||||
presence.partyId = "aPartyId";
|
||||
presence.partySize = 1;
|
||||
presence.partyMax = 3;
|
||||
presence.partyPrivacy = DiscordRpc.PartyPrivacy.Public;
|
||||
|
||||
DiscordRpc.UpdatePresence(ref presence);
|
||||
DiscordRpc.UpdatePresence(presence);
|
||||
}
|
||||
|
||||
public void ReadyCallback()
|
||||
public void RequestRespondYes()
|
||||
{
|
||||
++callbackCalls;
|
||||
Debug.Log("Discord: ready");
|
||||
Debug.Log("Discord: responding yes to Ask to Join request");
|
||||
DiscordRpc.Respond(joinRequest.userId, DiscordRpc.Reply.Yes);
|
||||
hasResponded.Invoke();
|
||||
}
|
||||
|
||||
public void RequestRespondNo()
|
||||
{
|
||||
Debug.Log("Discord: responding no to Ask to Join request");
|
||||
DiscordRpc.Respond(joinRequest.userId, DiscordRpc.Reply.No);
|
||||
hasResponded.Invoke();
|
||||
}
|
||||
|
||||
public void ReadyCallback(ref DiscordRpc.DiscordUser connectedUser)
|
||||
{
|
||||
Debug.Log(string.Format("Discord: connected to {0}#{1}: {2}", connectedUser.username, connectedUser.discriminator, connectedUser.userId));
|
||||
onConnect.Invoke();
|
||||
}
|
||||
|
||||
public void DisconnectedCallback(int errorCode, string message)
|
||||
{
|
||||
++callbackCalls;
|
||||
Debug.Log(string.Format("Discord: disconnect {0}: {1}", errorCode, message));
|
||||
onDisconnect.Invoke();
|
||||
}
|
||||
|
||||
public void ErrorCallback(int errorCode, string message)
|
||||
{
|
||||
++callbackCalls;
|
||||
Debug.Log(string.Format("Discord: error {0}: {1}", errorCode, message));
|
||||
}
|
||||
|
||||
public void JoinCallback(string secret)
|
||||
{
|
||||
++callbackCalls;
|
||||
Debug.Log(string.Format("Discord: join ({0})", secret));
|
||||
onJoin.Invoke(secret);
|
||||
}
|
||||
|
||||
public void SpectateCallback(string secret)
|
||||
{
|
||||
++callbackCalls;
|
||||
Debug.Log(string.Format("Discord: spectate ({0})", secret));
|
||||
onSpectate.Invoke(secret);
|
||||
}
|
||||
|
||||
public void RequestCallback(ref DiscordRpc.JoinRequest request)
|
||||
public void RequestCallback(ref DiscordRpc.DiscordUser request)
|
||||
{
|
||||
++callbackCalls;
|
||||
Debug.Log(string.Format("Discord: join request {0}#{1}: {2}", request.username, request.discriminator, request.userId));
|
||||
joinRequest = request;
|
||||
onJoinRequest.Invoke(request);
|
||||
}
|
||||
|
||||
@ -87,10 +102,8 @@ public class DiscordController : MonoBehaviour
|
||||
void OnEnable()
|
||||
{
|
||||
Debug.Log("Discord: init");
|
||||
callbackCalls = 0;
|
||||
|
||||
handlers = new DiscordRpc.EventHandlers();
|
||||
handlers.readyCallback = ReadyCallback;
|
||||
handlers.readyCallback += ReadyCallback;
|
||||
handlers.disconnectedCallback += DisconnectedCallback;
|
||||
handlers.errorCallback += ErrorCallback;
|
||||
handlers.joinCallback += JoinCallback;
|
||||
|
@ -1,57 +1,70 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using AOT;
|
||||
|
||||
public class DiscordRpc
|
||||
{
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void ReadyCallback();
|
||||
[MonoPInvokeCallback(typeof(OnReadyInfo))]
|
||||
public static void ReadyCallback(ref DiscordUser connectedUser) { Callbacks.readyCallback(ref connectedUser); }
|
||||
public delegate void OnReadyInfo(ref DiscordUser connectedUser);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void DisconnectedCallback(int errorCode, string message);
|
||||
[MonoPInvokeCallback(typeof(OnDisconnectedInfo))]
|
||||
public static void DisconnectedCallback(int errorCode, string message) { Callbacks.disconnectedCallback(errorCode, message); }
|
||||
public delegate void OnDisconnectedInfo(int errorCode, string message);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void ErrorCallback(int errorCode, string message);
|
||||
[MonoPInvokeCallback(typeof(OnErrorInfo))]
|
||||
public static void ErrorCallback(int errorCode, string message) { Callbacks.errorCallback(errorCode, message); }
|
||||
public delegate void OnErrorInfo(int errorCode, string message);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void JoinCallback(string secret);
|
||||
[MonoPInvokeCallback(typeof(OnJoinInfo))]
|
||||
public static void JoinCallback(string secret) { Callbacks.joinCallback(secret); }
|
||||
public delegate void OnJoinInfo(string secret);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void SpectateCallback(string secret);
|
||||
[MonoPInvokeCallback(typeof(OnSpectateInfo))]
|
||||
public static void SpectateCallback(string secret) { Callbacks.spectateCallback(secret); }
|
||||
public delegate void OnSpectateInfo(string secret);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void RequestCallback(ref JoinRequest request);
|
||||
[MonoPInvokeCallback(typeof(OnRequestInfo))]
|
||||
public static void RequestCallback(ref DiscordUser request) { Callbacks.requestCallback(ref request); }
|
||||
public delegate void OnRequestInfo(ref DiscordUser request);
|
||||
|
||||
static EventHandlers Callbacks { get; set; }
|
||||
|
||||
public struct EventHandlers
|
||||
{
|
||||
public ReadyCallback readyCallback;
|
||||
public DisconnectedCallback disconnectedCallback;
|
||||
public ErrorCallback errorCallback;
|
||||
public JoinCallback joinCallback;
|
||||
public SpectateCallback spectateCallback;
|
||||
public RequestCallback requestCallback;
|
||||
public OnReadyInfo readyCallback;
|
||||
public OnDisconnectedInfo disconnectedCallback;
|
||||
public OnErrorInfo errorCallback;
|
||||
public OnJoinInfo joinCallback;
|
||||
public OnSpectateInfo spectateCallback;
|
||||
public OnRequestInfo requestCallback;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct RichPresence
|
||||
[Serializable, StructLayout(LayoutKind.Sequential)]
|
||||
public struct RichPresenceStruct
|
||||
{
|
||||
public string state; /* max 128 bytes */
|
||||
public string details; /* max 128 bytes */
|
||||
public IntPtr state; /* max 128 bytes */
|
||||
public IntPtr 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 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 string matchSecret; /* max 128 bytes */
|
||||
public string joinSecret; /* max 128 bytes */
|
||||
public string spectateSecret; /* max 128 bytes */
|
||||
public int partyPrivacy;
|
||||
public IntPtr matchSecret; /* max 128 bytes */
|
||||
public IntPtr joinSecret; /* max 128 bytes */
|
||||
public IntPtr spectateSecret; /* max 128 bytes */
|
||||
public bool instance;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct JoinRequest
|
||||
[Serializable]
|
||||
public struct DiscordUser
|
||||
{
|
||||
public string userId;
|
||||
public string username;
|
||||
@ -66,8 +79,29 @@ public class DiscordRpc
|
||||
Ignore = 2
|
||||
}
|
||||
|
||||
public enum PartyPrivacy
|
||||
{
|
||||
Private = 0,
|
||||
Public = 1
|
||||
}
|
||||
|
||||
public static void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId)
|
||||
{
|
||||
Callbacks = handlers;
|
||||
|
||||
EventHandlers staticEventHandlers = new EventHandlers();
|
||||
staticEventHandlers.readyCallback += DiscordRpc.ReadyCallback;
|
||||
staticEventHandlers.disconnectedCallback += DiscordRpc.DisconnectedCallback;
|
||||
staticEventHandlers.errorCallback += DiscordRpc.ErrorCallback;
|
||||
staticEventHandlers.joinCallback += DiscordRpc.JoinCallback;
|
||||
staticEventHandlers.spectateCallback += DiscordRpc.SpectateCallback;
|
||||
staticEventHandlers.requestCallback += DiscordRpc.RequestCallback;
|
||||
|
||||
InitializeInternal(applicationId, ref staticEventHandlers, autoRegister, optionalSteamId);
|
||||
}
|
||||
|
||||
[DllImport("discord-rpc", EntryPoint = "Discord_Initialize", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId);
|
||||
static extern void InitializeInternal(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId);
|
||||
|
||||
[DllImport("discord-rpc", EntryPoint = "Discord_Shutdown", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void Shutdown();
|
||||
@ -76,9 +110,122 @@ public class DiscordRpc
|
||||
public static extern void RunCallbacks();
|
||||
|
||||
[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)]
|
||||
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 PartyPrivacy partyPrivacy;
|
||||
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);
|
||||
_presence.details = StrToPtr(details);
|
||||
_presence.startTimestamp = startTimestamp;
|
||||
_presence.endTimestamp = endTimestamp;
|
||||
_presence.largeImageKey = StrToPtr(largeImageKey);
|
||||
_presence.largeImageText = StrToPtr(largeImageText);
|
||||
_presence.smallImageKey = StrToPtr(smallImageKey);
|
||||
_presence.smallImageText = StrToPtr(smallImageText);
|
||||
_presence.partyId = StrToPtr(partyId);
|
||||
_presence.partySize = partySize;
|
||||
_presence.partyMax = partyMax;
|
||||
_presence.partyPrivacy = (int)partyPrivacy;
|
||||
_presence.matchSecret = StrToPtr(matchSecret);
|
||||
_presence.joinSecret = StrToPtr(joinSecret);
|
||||
_presence.spectateSecret = StrToPtr(spectateSecret);
|
||||
_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>
|
||||
/// <returns>Pointer to the UTF-8 representation of <see cref="input"/></returns>
|
||||
private IntPtr StrToPtr(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input)) return IntPtr.Zero;
|
||||
var convbytecnt = Encoding.UTF8.GetByteCount(input);
|
||||
var buffer = Marshal.AllocHGlobal(convbytecnt + 1);
|
||||
for (int i = 0; i < convbytecnt + 1; i++)
|
||||
{
|
||||
Marshal.WriteByte(buffer, i, 0);
|
||||
}
|
||||
_buffers.Add(buffer);
|
||||
Marshal.Copy(Encoding.UTF8.GetBytes(input), 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>
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ public class ScriptBatch
|
||||
proc.StartInfo.EnvironmentVariables["PATH"] = newPath;
|
||||
#endif
|
||||
proc.StartInfo.FileName = "python";
|
||||
proc.StartInfo.Arguments = "build.py for_unity";
|
||||
proc.StartInfo.Arguments = "build.py unity";
|
||||
proc.StartInfo.WorkingDirectory = "../..";
|
||||
proc.Start();
|
||||
proc.WaitForExit();
|
||||
@ -46,8 +46,8 @@ public class ScriptBatch
|
||||
string[] srcDlls = { "../../builds/install/osx-dynamic/lib/libdiscord-rpc.dylib" };
|
||||
#else
|
||||
string[] dstDirs = { "Assets/Plugins", "Assets/Plugins/x86", "Assets/Plugins/x86_64" };
|
||||
string[] dstDlls = { "Assets/Plugins/x86/discord-rpc.so", "Assets/Plugins/x86_64/discord-rpc.so" };
|
||||
string[] srcDlls = { "../../builds/install/linux-dynamic/bin/discord-rpc.dll", "../../builds/install/win64-dynamic/bin/discord-rpc.dll" };
|
||||
string[] dstDlls = { "Assets/Plugins/discord-rpc.so" };
|
||||
string[] srcDlls = { "../../builds/install/linux-dynamic/lib/libdiscord-rpc.so" };
|
||||
#endif
|
||||
|
||||
Debug.Assert(dstDlls.Length == srcDlls.Length);
|
@ -271,6 +271,80 @@ CanvasRenderer:
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 359174702}
|
||||
--- !u!1 &520806049
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
serializedVersion: 5
|
||||
m_Component:
|
||||
- component: {fileID: 520806050}
|
||||
- component: {fileID: 520806052}
|
||||
- component: {fileID: 520806051}
|
||||
m_Layer: 5
|
||||
m_Name: Text
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &520806050
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 520806049}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 806911717}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &520806051
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 520806049}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||
m_FontData:
|
||||
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_FontSize: 14
|
||||
m_FontStyle: 0
|
||||
m_BestFit: 0
|
||||
m_MinSize: 10
|
||||
m_MaxSize: 40
|
||||
m_Alignment: 4
|
||||
m_AlignByGeometry: 0
|
||||
m_RichText: 1
|
||||
m_HorizontalOverflow: 0
|
||||
m_VerticalOverflow: 0
|
||||
m_LineSpacing: 1
|
||||
m_Text: Yes
|
||||
--- !u!222 &520806052
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 520806049}
|
||||
--- !u!1 &657463235
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -345,6 +419,128 @@ RectTransform:
|
||||
m_AnchoredPosition: {x: 16, y: -19.00003}
|
||||
m_SizeDelta: {x: 239.20001, y: 37.799988}
|
||||
m_Pivot: {x: 0, y: 1}
|
||||
--- !u!1 &806911716
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
serializedVersion: 5
|
||||
m_Component:
|
||||
- component: {fileID: 806911717}
|
||||
- component: {fileID: 806911720}
|
||||
- component: {fileID: 806911719}
|
||||
- component: {fileID: 806911718}
|
||||
m_Layer: 5
|
||||
m_Name: ButtonRespondYes
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &806911717
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 806911716}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children:
|
||||
- {fileID: 520806050}
|
||||
m_Father: {fileID: 1766020814}
|
||||
m_RootOrder: 2
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: -129.1, y: -116.3}
|
||||
m_SizeDelta: {x: 160, y: 30}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &806911718
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 806911716}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1392445389, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 0
|
||||
m_TargetGraphic: {fileID: 806911719}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls:
|
||||
- m_Target: {fileID: 1929635629}
|
||||
m_MethodName: RequestRespondYes
|
||||
m_Mode: 1
|
||||
m_Arguments:
|
||||
m_ObjectArgument: {fileID: 0}
|
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||
m_IntArgument: 0
|
||||
m_FloatArgument: 0
|
||||
m_StringArgument:
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
m_TypeName: UnityEngine.UI.Button+ButtonClickedEvent, UnityEngine.UI, Version=1.0.0.0,
|
||||
Culture=neutral, PublicKeyToken=null
|
||||
--- !u!114 &806911719
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 806911716}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -765806418, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
--- !u!222 &806911720
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 806911716}
|
||||
--- !u!1 &1032248338
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -467,6 +663,80 @@ CanvasRenderer:
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 1032248338}
|
||||
--- !u!1 &1238162986
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
serializedVersion: 5
|
||||
m_Component:
|
||||
- component: {fileID: 1238162987}
|
||||
- component: {fileID: 1238162989}
|
||||
- component: {fileID: 1238162988}
|
||||
m_Layer: 5
|
||||
m_Name: JoinRequestInfo
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1238162987
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 1238162986}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1766020814}
|
||||
m_RootOrder: 4
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: -0.0000085831, y: -66.9}
|
||||
m_SizeDelta: {x: 323.38, y: 55.29}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1238162988
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 1238162986}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 0.88965523, b: 0, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||
m_FontData:
|
||||
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_FontSize: 14
|
||||
m_FontStyle: 0
|
||||
m_BestFit: 0
|
||||
m_MinSize: 10
|
||||
m_MaxSize: 40
|
||||
m_Alignment: 1
|
||||
m_AlignByGeometry: 0
|
||||
m_RichText: 1
|
||||
m_HorizontalOverflow: 0
|
||||
m_VerticalOverflow: 0
|
||||
m_LineSpacing: 1
|
||||
m_Text: No requests yet
|
||||
--- !u!222 &1238162989
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 1238162986}
|
||||
--- !u!1 &1470895131
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -616,6 +886,9 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 1032248339}
|
||||
- {fileID: 657463238}
|
||||
- {fileID: 806911717}
|
||||
- {fileID: 1858885002}
|
||||
- {fileID: 1238162987}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
@ -624,6 +897,128 @@ RectTransform:
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0, y: 0}
|
||||
--- !u!1 &1858885001
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
serializedVersion: 5
|
||||
m_Component:
|
||||
- component: {fileID: 1858885002}
|
||||
- component: {fileID: 1858885005}
|
||||
- component: {fileID: 1858885004}
|
||||
- component: {fileID: 1858885003}
|
||||
m_Layer: 5
|
||||
m_Name: ButtonRespondNo
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1858885002
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 1858885001}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children:
|
||||
- {fileID: 1958982062}
|
||||
m_Father: {fileID: 1766020814}
|
||||
m_RootOrder: 3
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 128.7, y: -116.3}
|
||||
m_SizeDelta: {x: 160, y: 30}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1858885003
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 1858885001}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1392445389, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 0
|
||||
m_TargetGraphic: {fileID: 1858885004}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls:
|
||||
- m_Target: {fileID: 1929635629}
|
||||
m_MethodName: RequestRespondNo
|
||||
m_Mode: 1
|
||||
m_Arguments:
|
||||
m_ObjectArgument: {fileID: 0}
|
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||
m_IntArgument: 0
|
||||
m_FloatArgument: 0
|
||||
m_StringArgument:
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
m_TypeName: UnityEngine.UI.Button+ButtonClickedEvent, UnityEngine.UI, Version=1.0.0.0,
|
||||
Culture=neutral, PublicKeyToken=null
|
||||
--- !u!114 &1858885004
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 1858885001}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -765806418, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
--- !u!222 &1858885005
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 1858885001}
|
||||
--- !u!1 &1929635628
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -671,6 +1066,11 @@ MonoBehaviour:
|
||||
optionalSteamId:
|
||||
callbackCalls: 0
|
||||
clickCounter: 0
|
||||
joinRequest:
|
||||
userId:
|
||||
username:
|
||||
discriminator:
|
||||
avatar:
|
||||
onConnect:
|
||||
m_PersistentCalls:
|
||||
m_Calls:
|
||||
@ -703,6 +1103,44 @@ MonoBehaviour:
|
||||
m_CallState: 2
|
||||
m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine, Version=0.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=null
|
||||
hasResponded:
|
||||
m_PersistentCalls:
|
||||
m_Calls:
|
||||
- m_Target: {fileID: 1238162988}
|
||||
m_MethodName: set_text
|
||||
m_Mode: 5
|
||||
m_Arguments:
|
||||
m_ObjectArgument: {fileID: 0}
|
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||
m_IntArgument: 0
|
||||
m_FloatArgument: 0
|
||||
m_StringArgument: No requests yet
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
- m_Target: {fileID: 806911718}
|
||||
m_MethodName: set_interactable
|
||||
m_Mode: 6
|
||||
m_Arguments:
|
||||
m_ObjectArgument: {fileID: 0}
|
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||
m_IntArgument: 0
|
||||
m_FloatArgument: 0
|
||||
m_StringArgument:
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
- m_Target: {fileID: 1858885003}
|
||||
m_MethodName: set_interactable
|
||||
m_Mode: 6
|
||||
m_Arguments:
|
||||
m_ObjectArgument: {fileID: 0}
|
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||
m_IntArgument: 0
|
||||
m_FloatArgument: 0
|
||||
m_StringArgument:
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine, Version=0.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=null
|
||||
onJoin:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
@ -715,7 +1153,40 @@ MonoBehaviour:
|
||||
PublicKeyToken=null
|
||||
onJoinRequest:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Calls:
|
||||
- m_Target: {fileID: 1238162988}
|
||||
m_MethodName: set_text
|
||||
m_Mode: 5
|
||||
m_Arguments:
|
||||
m_ObjectArgument: {fileID: 0}
|
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||
m_IntArgument: 0
|
||||
m_FloatArgument: 0
|
||||
m_StringArgument: Someone asked to join!
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
- m_Target: {fileID: 806911718}
|
||||
m_MethodName: set_interactable
|
||||
m_Mode: 6
|
||||
m_Arguments:
|
||||
m_ObjectArgument: {fileID: 0}
|
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||
m_IntArgument: 0
|
||||
m_FloatArgument: 0
|
||||
m_StringArgument:
|
||||
m_BoolArgument: 1
|
||||
m_CallState: 2
|
||||
- m_Target: {fileID: 1858885003}
|
||||
m_MethodName: set_interactable
|
||||
m_Mode: 6
|
||||
m_Arguments:
|
||||
m_ObjectArgument: {fileID: 0}
|
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||
m_IntArgument: 0
|
||||
m_FloatArgument: 0
|
||||
m_StringArgument:
|
||||
m_BoolArgument: 1
|
||||
m_CallState: 2
|
||||
m_TypeName: DiscordJoinRequestEvent, Assembly-CSharp, Version=0.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=null
|
||||
--- !u!4 &1929635630
|
||||
@ -731,3 +1202,77 @@ Transform:
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 3
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1958982061
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
serializedVersion: 5
|
||||
m_Component:
|
||||
- component: {fileID: 1958982062}
|
||||
- component: {fileID: 1958982064}
|
||||
- component: {fileID: 1958982063}
|
||||
m_Layer: 5
|
||||
m_Name: Text
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1958982062
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 1958982061}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1858885002}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1958982063
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 1958982061}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||
m_FontData:
|
||||
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_FontSize: 14
|
||||
m_FontStyle: 0
|
||||
m_BestFit: 0
|
||||
m_MinSize: 10
|
||||
m_MaxSize: 40
|
||||
m_Alignment: 4
|
||||
m_AlignByGeometry: 0
|
||||
m_RichText: 1
|
||||
m_HorizontalOverflow: 0
|
||||
m_VerticalOverflow: 0
|
||||
m_LineSpacing: 1
|
||||
m_Text: No
|
||||
--- !u!222 &1958982064
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 1958982061}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
This is a simple example in C of using the rich presence API asyncronously.
|
||||
This is a simple example in C of using the rich presence API asynchronously.
|
||||
*/
|
||||
|
||||
#define _CRT_SECURE_NO_WARNINGS /* thanks Microsoft */
|
||||
@ -9,11 +9,12 @@
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "discord-rpc.h"
|
||||
#include "discord_rpc.h"
|
||||
|
||||
static const char* APPLICATION_ID = "345229890980937739";
|
||||
static int FrustrationLevel = 0;
|
||||
static int64_t StartTime;
|
||||
static int SendPresence = 1;
|
||||
|
||||
static int prompt(char* line, size_t size)
|
||||
{
|
||||
@ -32,29 +33,38 @@ static int prompt(char* line, size_t size)
|
||||
|
||||
static void updateDiscordPresence()
|
||||
{
|
||||
char buffer[256];
|
||||
DiscordRichPresence discordPresence;
|
||||
memset(&discordPresence, 0, sizeof(discordPresence));
|
||||
discordPresence.state = "West of House";
|
||||
sprintf(buffer, "Frustration level: %d", FrustrationLevel);
|
||||
discordPresence.details = buffer;
|
||||
discordPresence.startTimestamp = StartTime;
|
||||
discordPresence.endTimestamp = time(0) + 5 * 60;
|
||||
discordPresence.largeImageKey = "canary-large";
|
||||
discordPresence.smallImageKey = "ptb-small";
|
||||
discordPresence.partyId = "party1234";
|
||||
discordPresence.partySize = 1;
|
||||
discordPresence.partyMax = 6;
|
||||
discordPresence.matchSecret = "xyzzy";
|
||||
discordPresence.joinSecret = "join";
|
||||
discordPresence.spectateSecret = "look";
|
||||
discordPresence.instance = 0;
|
||||
Discord_UpdatePresence(&discordPresence);
|
||||
if (SendPresence) {
|
||||
char buffer[256];
|
||||
DiscordRichPresence discordPresence;
|
||||
memset(&discordPresence, 0, sizeof(discordPresence));
|
||||
discordPresence.state = "West of House";
|
||||
sprintf(buffer, "Frustration level: %d", FrustrationLevel);
|
||||
discordPresence.details = buffer;
|
||||
discordPresence.startTimestamp = StartTime;
|
||||
discordPresence.endTimestamp = time(0) + 5 * 60;
|
||||
discordPresence.largeImageKey = "canary-large";
|
||||
discordPresence.smallImageKey = "ptb-small";
|
||||
discordPresence.partyId = "party1234";
|
||||
discordPresence.partySize = 1;
|
||||
discordPresence.partyMax = 6;
|
||||
discordPresence.partyPrivacy = DISCORD_PARTY_PUBLIC;
|
||||
discordPresence.matchSecret = "xyzzy";
|
||||
discordPresence.joinSecret = "join";
|
||||
discordPresence.spectateSecret = "look";
|
||||
discordPresence.instance = 0;
|
||||
Discord_UpdatePresence(&discordPresence);
|
||||
}
|
||||
else {
|
||||
Discord_ClearPresence();
|
||||
}
|
||||
}
|
||||
|
||||
static void handleDiscordReady()
|
||||
static void handleDiscordReady(const DiscordUser* connectedUser)
|
||||
{
|
||||
printf("\nDiscord: ready\n");
|
||||
printf("\nDiscord: connected to user %s#%s - %s\n",
|
||||
connectedUser->username,
|
||||
connectedUser->discriminator,
|
||||
connectedUser->userId);
|
||||
}
|
||||
|
||||
static void handleDiscordDisconnected(int errcode, const char* message)
|
||||
@ -77,13 +87,13 @@ static void handleDiscordSpectate(const char* secret)
|
||||
printf("\nDiscord: spectate (%s)\n", secret);
|
||||
}
|
||||
|
||||
static void handleDiscordJoinRequest(const DiscordJoinRequest* request)
|
||||
static void handleDiscordJoinRequest(const DiscordUser* request)
|
||||
{
|
||||
int response = -1;
|
||||
char yn[4];
|
||||
printf("\nDiscord: join request from %s - %s - %s\n",
|
||||
printf("\nDiscord: join request from %s#%s - %s\n",
|
||||
request->username,
|
||||
request->avatar,
|
||||
request->discriminator,
|
||||
request->userId);
|
||||
do {
|
||||
printf("Accept? (y/n)");
|
||||
@ -143,6 +153,19 @@ static void gameLoop()
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line[0] == 'c') {
|
||||
if (SendPresence) {
|
||||
printf("Clearing presence information.\n");
|
||||
SendPresence = 0;
|
||||
}
|
||||
else {
|
||||
printf("Restoring presence information.\n");
|
||||
SendPresence = 1;
|
||||
}
|
||||
updateDiscordPresence();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line[0] == 'y') {
|
||||
printf("Reinit Discord.\n");
|
||||
discordInit();
|
||||
|
3
examples/unrealstatus/.gitignore
vendored
3
examples/unrealstatus/.gitignore
vendored
@ -73,3 +73,6 @@ Intermediate/
|
||||
|
||||
# Cache files for the editor to use
|
||||
DerivedDataCache/
|
||||
|
||||
# Library headers must be copied automatically by the build script (build.py unreal)
|
||||
Plugins/DiscordRpc/Source/ThirdParty/DiscordRpcLibrary/Include
|
||||
|
Binary file not shown.
@ -15,9 +15,15 @@
|
||||
"Installed": false,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "discordrpc",
|
||||
"Type": "Developer",
|
||||
"LoadingPhase": "Default"
|
||||
"Name": "DiscordRpc",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "PreDefault",
|
||||
"WhitelistPlatforms" :
|
||||
[
|
||||
"Win64",
|
||||
"Linux",
|
||||
"Mac"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
using System.IO;
|
||||
|
||||
public class DiscordRpc : ModuleRules
|
||||
{
|
||||
#if WITH_FORWARDED_MODULE_RULES_CTOR
|
||||
public DiscordRpc(ReadOnlyTargetRules Target) : base(Target)
|
||||
#else
|
||||
public DiscordRpc(TargetInfo Target)
|
||||
#endif
|
||||
{
|
||||
Definitions.Add("DISCORD_DYNAMIC_LIB=1");
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[] {
|
||||
"DiscordRpc/Public"
|
||||
}
|
||||
);
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[] {
|
||||
"DiscordRpc/Private"
|
||||
}
|
||||
);
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"DiscordRpcLibrary"
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"Projects"
|
||||
}
|
||||
);
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
// ... add any modules that your module loads dynamically here ...
|
||||
}
|
||||
);
|
||||
|
||||
string BaseDirectory = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", "Source", "ThirdParty", "DiscordRpcLibrary"));
|
||||
PublicIncludePaths.Add(Path.Combine(BaseDirectory, "Include"));
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "DiscordRpcPrivatePCH.h"
|
||||
#include "IPluginManager.h"
|
||||
#include "ModuleManager.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FDiscordRpcModule"
|
||||
|
||||
void FDiscordRpcModule::StartupModule()
|
||||
{
|
||||
#if !PLATFORM_LINUX
|
||||
#if defined(DISCORD_DYNAMIC_LIB)
|
||||
// Get the base directory of this plugin
|
||||
FString BaseDir = IPluginManager::Get().FindPlugin("DiscordRpc")->GetBaseDir();
|
||||
const FString SDKDir =
|
||||
FPaths::Combine(*BaseDir, TEXT("Source"), TEXT("ThirdParty"), TEXT("DiscordRpcLibrary"));
|
||||
#if PLATFORM_WINDOWS
|
||||
const FString LibName = TEXT("discord-rpc");
|
||||
const FString LibDir = FPaths::Combine(*SDKDir, TEXT("Win64"));
|
||||
if (!LoadDependency(LibDir, LibName, DiscordRpcLibraryHandle)) {
|
||||
FMessageDialog::Open(
|
||||
EAppMsgType::Ok,
|
||||
LOCTEXT(LOCTEXT_NAMESPACE,
|
||||
"Failed to load DiscordRpc plugin. Plug-in will not be functional."));
|
||||
FreeDependency(DiscordRpcLibraryHandle);
|
||||
}
|
||||
#elif PLATFORM_MAC
|
||||
const FString LibName = TEXT("libdiscord-rpc");
|
||||
const FString LibDir = FPaths::Combine(*SDKDir, TEXT("Mac"));
|
||||
if (!LoadDependency(LibDir, LibName, DiscordRpcLibraryHandle)) {
|
||||
FMessageDialog::Open(
|
||||
EAppMsgType::Ok,
|
||||
LOCTEXT(LOCTEXT_NAMESPACE,
|
||||
"Failed to load DiscordRpc plugin. Plug-in will not be functional."));
|
||||
FreeDependency(DiscordRpcLibraryHandle);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void FDiscordRpcModule::ShutdownModule()
|
||||
{
|
||||
// Free the dll handle
|
||||
#if !PLATFORM_LINUX
|
||||
#if defined(DISCORD_DYNAMIC_LIB)
|
||||
FreeDependency(DiscordRpcLibraryHandle);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
bool FDiscordRpcModule::LoadDependency(const FString& Dir, const FString& Name, void*& Handle)
|
||||
{
|
||||
FString Lib = Name + TEXT(".") + FPlatformProcess::GetModuleExtension();
|
||||
FString Path = Dir.IsEmpty() ? *Lib : FPaths::Combine(*Dir, *Lib);
|
||||
|
||||
Handle = FPlatformProcess::GetDllHandle(*Path);
|
||||
|
||||
if (Handle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FDiscordRpcModule::FreeDependency(void*& Handle)
|
||||
{
|
||||
if (Handle != nullptr) {
|
||||
FPlatformProcess::FreeDllHandle(Handle);
|
||||
Handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FDiscordRpcModule, DiscordRpc)
|
@ -1,19 +1,27 @@
|
||||
|
||||
|
||||
#include "DiscordRpcPrivatePCH.h"
|
||||
#include "DiscordRpcBlueprint.h"
|
||||
|
||||
#include "discord-rpc.h"
|
||||
#include "discord_rpc.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(Discord)
|
||||
|
||||
static UDiscordRpc* self = nullptr;
|
||||
|
||||
static void ReadyHandler()
|
||||
static void ReadyHandler(const DiscordUser* connectedUser)
|
||||
{
|
||||
UE_LOG(Discord, Log, TEXT("Discord connected"));
|
||||
FDiscordUserData ud;
|
||||
ud.userId = ANSI_TO_TCHAR(connectedUser->userId);
|
||||
ud.username = ANSI_TO_TCHAR(connectedUser->username);
|
||||
ud.discriminator = ANSI_TO_TCHAR(connectedUser->discriminator);
|
||||
ud.avatar = ANSI_TO_TCHAR(connectedUser->avatar);
|
||||
UE_LOG(Discord,
|
||||
Log,
|
||||
TEXT("Discord connected to %s - %s#%s"),
|
||||
*ud.userId,
|
||||
*ud.username,
|
||||
*ud.discriminator);
|
||||
if (self) {
|
||||
self->IsConnected = true;
|
||||
self->OnConnected.Broadcast();
|
||||
self->OnConnected.Broadcast(ud);
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,16 +62,21 @@ static void SpectateGameHandler(const char* spectateSecret)
|
||||
}
|
||||
}
|
||||
|
||||
static void JoinRequestHandler(const DiscordJoinRequest* request)
|
||||
static void JoinRequestHandler(const DiscordUser* request)
|
||||
{
|
||||
FDiscordJoinRequestData jr;
|
||||
jr.userId = ANSI_TO_TCHAR(request->userId);
|
||||
jr.username = ANSI_TO_TCHAR(request->username);
|
||||
jr.discriminator = ANSI_TO_TCHAR(request->discriminator);
|
||||
jr.avatar = ANSI_TO_TCHAR(request->avatar);
|
||||
UE_LOG(Discord, Log, TEXT("Discord join request from %s#%s"), *jr.username, *jr.discriminator);
|
||||
FDiscordUserData ud;
|
||||
ud.userId = ANSI_TO_TCHAR(request->userId);
|
||||
ud.username = ANSI_TO_TCHAR(request->username);
|
||||
ud.discriminator = ANSI_TO_TCHAR(request->discriminator);
|
||||
ud.avatar = ANSI_TO_TCHAR(request->avatar);
|
||||
UE_LOG(Discord,
|
||||
Log,
|
||||
TEXT("Discord join request from %s - %s#%s"),
|
||||
*ud.userId,
|
||||
*ud.username,
|
||||
*ud.discriminator);
|
||||
if (self) {
|
||||
self->OnJoinRequest.Broadcast(jr);
|
||||
self->OnJoinRequest.Broadcast(ud);
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,12 +149,24 @@ void UDiscordRpc::UpdatePresence()
|
||||
|
||||
auto spectateSecret = StringCast<ANSICHAR>(*RichPresence.spectateSecret);
|
||||
rp.spectateSecret = spectateSecret.Get();
|
||||
|
||||
rp.startTimestamp = RichPresence.startTimestamp;
|
||||
rp.endTimestamp = RichPresence.endTimestamp;
|
||||
rp.partySize = RichPresence.partySize;
|
||||
rp.partyMax = RichPresence.partyMax;
|
||||
rp.partyPrivacy = (int)RichPresence.partyPrivacy;
|
||||
rp.instance = RichPresence.instance;
|
||||
|
||||
Discord_UpdatePresence(&rp);
|
||||
}
|
||||
|
||||
void UDiscordRpc::ClearPresence()
|
||||
{
|
||||
Discord_ClearPresence();
|
||||
}
|
||||
|
||||
void UDiscordRpc::Respond(const FString& userId, int reply)
|
||||
{
|
||||
UE_LOG(Discord, Log, TEXT("Responding %d to join request from %s"), reply, *userId);
|
||||
FTCHARToUTF8 utf8_userid(*userId);
|
||||
Discord_Respond(utf8_userid.Get(), reply);
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
#include "Core.h"
|
||||
#include "DiscordRpc.h"
|
@ -0,0 +1,20 @@
|
||||
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ModuleManager.h"
|
||||
|
||||
class FDiscordRpcModule : public IModuleInterface {
|
||||
public:
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
private:
|
||||
/** Handle to the test dll we will load */
|
||||
void* DiscordRpcLibraryHandle;
|
||||
|
||||
/** StartupModule is covered with defines, these functions are the place to put breakpoints */
|
||||
static bool LoadDependency(const FString& Dir, const FString& Name, void*& Handle);
|
||||
static void FreeDependency(void*& Handle);
|
||||
};
|
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
@ -13,7 +11,7 @@
|
||||
* Ask to join callback data
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FDiscordJoinRequestData {
|
||||
struct FDiscordUserData {
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
@ -26,15 +24,35 @@ struct FDiscordJoinRequestData {
|
||||
FString avatar;
|
||||
};
|
||||
|
||||
/**
|
||||
* Valid response codes for Respond function
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EDiscordJoinResponseCodes : uint8
|
||||
{
|
||||
DISCORD_REPLY_NO UMETA(DisplayName="No"),
|
||||
DISCORD_REPLY_YES UMETA(DisplayName="Yes"),
|
||||
DISCORD_REPLY_IGNORE UMETA(DisplayName="Ignore")
|
||||
};
|
||||
|
||||
/**
|
||||
* Valid party privacy values
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EDiscordPartyPrivacy: uint8
|
||||
{
|
||||
DISCORD_PARTY_PRIVATE UMETA(DisplayName="Private"),
|
||||
DISCORD_PARTY_PUBLIC UMETA(DisplayName="Public")
|
||||
};
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(Discord, Log, All);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDiscordConnected);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordConnected, const FDiscordUserData&, joinRequest);
|
||||
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);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoinRequest, const FDiscordJoinRequestData&, joinRequest);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoinRequest, const FDiscordUserData&, joinRequest);
|
||||
|
||||
// clang-format on
|
||||
|
||||
@ -69,6 +87,8 @@ struct FDiscordRichPresence {
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
int partyMax;
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
EDiscordPartyPrivacy partyPrivacy;
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
FString matchSecret;
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
FString joinSecret;
|
||||
@ -108,6 +128,16 @@ public:
|
||||
Category = "Discord")
|
||||
void UpdatePresence();
|
||||
|
||||
UFUNCTION(BlueprintCallable,
|
||||
meta = (DisplayName = "Clear presence", Keywords = "Discord rpc"),
|
||||
Category = "Discord")
|
||||
void ClearPresence();
|
||||
|
||||
UFUNCTION(BlueprintCallable,
|
||||
meta = (DisplayName = "Respond to join request", Keywords = "Discord rpc"),
|
||||
Category = "Discord")
|
||||
void Respond(const FString& userId, int reply);
|
||||
|
||||
UPROPERTY(BlueprintReadOnly,
|
||||
meta = (DisplayName = "Is Discord connected", Keywords = "Discord rpc"),
|
||||
Category = "Discord")
|
@ -0,0 +1,59 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
using System.IO;
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class DiscordRpcLibrary : ModuleRules
|
||||
{
|
||||
#if WITH_FORWARDED_MODULE_RULES_CTOR
|
||||
public DiscordRpcLibrary(ReadOnlyTargetRules Target) : base(Target)
|
||||
#else
|
||||
public DiscordRpcLibrary(TargetInfo Target)
|
||||
#endif
|
||||
{
|
||||
Type = ModuleType.External;
|
||||
Definitions.Add("DISCORD_DYNAMIC_LIB=1");
|
||||
|
||||
string BaseDirectory = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", "ThirdParty", "DiscordRpcLibrary"));
|
||||
|
||||
if (Target.Platform == UnrealTargetPlatform.Win64)
|
||||
{
|
||||
string lib = Path.Combine(BaseDirectory, "Win64");
|
||||
|
||||
// Include headers
|
||||
PublicIncludePaths.Add(Path.Combine(BaseDirectory, "Include"));
|
||||
|
||||
// Add the import library
|
||||
PublicLibraryPaths.Add(lib);
|
||||
PublicAdditionalLibraries.Add(Path.Combine(lib, "discord-rpc.lib"));
|
||||
|
||||
// Dynamic
|
||||
RuntimeDependencies.Add(new RuntimeDependency(Path.Combine(lib, "discord-rpc.dll")));
|
||||
PublicDelayLoadDLLs.Add("discord-rpc.dll");
|
||||
}
|
||||
else if (Target.Platform == UnrealTargetPlatform.Linux)
|
||||
{
|
||||
string lib = Path.Combine(BaseDirectory, "Linux", "x86_64-unknown-linux-gnu");
|
||||
|
||||
// Include headers
|
||||
PublicIncludePaths.Add(Path.Combine(BaseDirectory, "Include"));
|
||||
|
||||
// Add the import library
|
||||
PublicLibraryPaths.Add(lib);
|
||||
PublicAdditionalLibraries.Add(Path.Combine(lib, "libdiscord-rpc.so"));
|
||||
RuntimeDependencies.Add(new RuntimeDependency(Path.Combine(lib, "libdiscord-rpc.so")));
|
||||
}
|
||||
else if (Target.Platform == UnrealTargetPlatform.Mac)
|
||||
{
|
||||
string lib = Path.Combine(BaseDirectory, "Mac");
|
||||
|
||||
// Include headers
|
||||
PublicIncludePaths.Add(Path.Combine(BaseDirectory, "Include"));
|
||||
|
||||
// Add the import library
|
||||
PublicLibraryPaths.Add(lib);
|
||||
PublicAdditionalLibraries.Add(Path.Combine(lib, "libdiscord-rpc.dylib"));
|
||||
RuntimeDependencies.Add(new RuntimeDependency(Path.Combine(lib, "libdiscord-rpc.dylib")));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
using System.IO;
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class discordrpcLibrary : ModuleRules
|
||||
{
|
||||
public discordrpcLibrary(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
Type = ModuleType.External;
|
||||
|
||||
if (Target.Platform == UnrealTargetPlatform.Win64)
|
||||
{
|
||||
// Add the import library
|
||||
PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "Include"));
|
||||
PublicLibraryPaths.Add(Path.Combine(ModuleDirectory, "x64", "Release"));
|
||||
PublicAdditionalLibraries.Add("discord-rpc.lib");
|
||||
|
||||
// Delay-load the DLL, so we can load it from the right place first
|
||||
PublicDelayLoadDLLs.Add("discord-rpc.dll");
|
||||
}
|
||||
else if (Target.Platform == UnrealTargetPlatform.Mac)
|
||||
{
|
||||
PublicDelayLoadDLLs.Add(Path.Combine(ModuleDirectory, "Mac", "Release", "libdiscord-rpc.dylib"));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "discordrpc.h"
|
||||
#include "Core.h"
|
||||
#include "IPluginManager.h"
|
||||
#include "ModuleManager.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FdiscordrpcModule"
|
||||
|
||||
void FdiscordrpcModule::StartupModule()
|
||||
{
|
||||
// This code will execute after your module is loaded into memory; the exact timing is specified
|
||||
// in the .uplugin file per-module
|
||||
|
||||
// Get the base directory of this plugin
|
||||
FString BaseDir = IPluginManager::Get().FindPlugin("discordrpc")->GetBaseDir();
|
||||
|
||||
// Add on the relative location of the third party dll and load it
|
||||
FString LibraryPath;
|
||||
#if PLATFORM_WINDOWS
|
||||
LibraryPath = FPaths::Combine(
|
||||
*BaseDir, TEXT("Binaries/ThirdParty/discordrpcLibrary/Win64/discord-rpc.dll"));
|
||||
#elif PLATFORM_MAC
|
||||
LibraryPath = FPaths::Combine(
|
||||
*BaseDir, TEXT("Source/ThirdParty/discordrpcLibrary/Mac/Release/libdiscord-rpc.dylib"));
|
||||
#endif // PLATFORM_WINDOWS
|
||||
|
||||
DiscordLibraryHandle =
|
||||
!LibraryPath.IsEmpty() ? FPlatformProcess::GetDllHandle(*LibraryPath) : nullptr;
|
||||
|
||||
if (!DiscordLibraryHandle) {
|
||||
FMessageDialog::Open(
|
||||
EAppMsgType::Ok, LOCTEXT("ThirdPartyLibraryError", "Failed to load discord-rpc library"));
|
||||
}
|
||||
}
|
||||
|
||||
void FdiscordrpcModule::ShutdownModule()
|
||||
{
|
||||
// This function may be called during shutdown to clean up your module. For modules that
|
||||
// support dynamic reloading,
|
||||
// we call this function before unloading the module.
|
||||
|
||||
// Free the dll handle
|
||||
FPlatformProcess::FreeDllHandle(DiscordLibraryHandle);
|
||||
DiscordLibraryHandle = nullptr;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FdiscordrpcModule, discordrpc)
|
@ -1,16 +0,0 @@
|
||||
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ModuleManager.h"
|
||||
|
||||
class FdiscordrpcModule : public IModuleInterface {
|
||||
public:
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
private:
|
||||
/** Handle to the test dll we will load */
|
||||
void* DiscordLibraryHandle;
|
||||
};
|
@ -1,65 +0,0 @@
|
||||
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class discordrpc : ModuleRules
|
||||
{
|
||||
public discordrpc(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
Definitions.Add("DISCORD_DYNAMIC_LIB=1");
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[] {
|
||||
"discordrpc/Public"
|
||||
// ... add public include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[] {
|
||||
"discordrpc/Private",
|
||||
"../../../../../include"
|
||||
// ... add other private include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
PublicLibraryPaths.AddRange(
|
||||
new string[] {
|
||||
System.IO.Path.Combine(ModuleDirectory, "../../Binaries/ThirdParty/discordrpcLibrary/", Target.Platform.ToString()),
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"Core",
|
||||
"discordrpcLibrary",
|
||||
"Projects"
|
||||
// ... add other public dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
// ... add private dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
// ... add any modules that your module loads dynamically here ...
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
26
include/discord_register.h
Normal file
26
include/discord_register.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(DISCORD_DYNAMIC_LIB)
|
||||
#if defined(_WIN32)
|
||||
#if defined(DISCORD_BUILDING_SDK)
|
||||
#define DISCORD_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define DISCORD_EXPORT __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#define DISCORD_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
#else
|
||||
#define DISCORD_EXPORT
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command);
|
||||
DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -35,31 +35,34 @@ typedef struct DiscordRichPresence {
|
||||
const char* partyId; /* max 128 bytes */
|
||||
int partySize;
|
||||
int partyMax;
|
||||
int partyPrivacy;
|
||||
const char* matchSecret; /* max 128 bytes */
|
||||
const char* joinSecret; /* max 128 bytes */
|
||||
const char* spectateSecret; /* max 128 bytes */
|
||||
int8_t instance;
|
||||
} DiscordRichPresence;
|
||||
|
||||
typedef struct DiscordJoinRequest {
|
||||
typedef struct DiscordUser {
|
||||
const char* userId;
|
||||
const char* username;
|
||||
const char* discriminator;
|
||||
const char* avatar;
|
||||
} DiscordJoinRequest;
|
||||
} DiscordUser;
|
||||
|
||||
typedef struct DiscordEventHandlers {
|
||||
void (*ready)();
|
||||
void (*ready)(const DiscordUser* request);
|
||||
void (*disconnected)(int errorCode, const char* message);
|
||||
void (*errored)(int errorCode, const char* message);
|
||||
void (*joinGame)(const char* joinSecret);
|
||||
void (*spectateGame)(const char* spectateSecret);
|
||||
void (*joinRequest)(const DiscordJoinRequest* request);
|
||||
void (*joinRequest)(const DiscordUser* request);
|
||||
} DiscordEventHandlers;
|
||||
|
||||
#define DISCORD_REPLY_NO 0
|
||||
#define DISCORD_REPLY_YES 1
|
||||
#define DISCORD_REPLY_IGNORE 2
|
||||
#define DISCORD_PARTY_PRIVATE 0
|
||||
#define DISCORD_PARTY_PUBLIC 1
|
||||
|
||||
DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
|
||||
DiscordEventHandlers* handlers,
|
||||
@ -76,9 +79,12 @@ DISCORD_EXPORT void Discord_UpdateConnection(void);
|
||||
#endif
|
||||
|
||||
DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence);
|
||||
DISCORD_EXPORT void Discord_ClearPresence(void);
|
||||
|
||||
DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply);
|
||||
|
||||
DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
@ -2,13 +2,14 @@ include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||
|
||||
option(ENABLE_IO_THREAD "Start up a separate I/O thread, otherwise I'd need to call an update function" ON)
|
||||
option(USE_STATIC_CRT "Use /MT[d] for dynamic library" OFF)
|
||||
option(WARNINGS_AS_ERRORS "When enabled, compiles with `-Werror` (on *nix platforms)." OFF)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
|
||||
set(BASE_RPC_SRC
|
||||
${PROJECT_SOURCE_DIR}/include/discord-rpc.h
|
||||
discord-rpc.cpp
|
||||
discord_register.h
|
||||
${PROJECT_SOURCE_DIR}/include/discord_rpc.h
|
||||
discord_rpc.cpp
|
||||
${PROJECT_SOURCE_DIR}/include/discord_register.h
|
||||
rpc_connection.h
|
||||
rpc_connection.cpp
|
||||
serialization.h
|
||||
@ -29,16 +30,18 @@ if(WIN32)
|
||||
set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_win.cpp discord_register_win.cpp)
|
||||
add_library(discord-rpc ${BASE_RPC_SRC})
|
||||
if (MSVC)
|
||||
set(CRT_FLAGS)
|
||||
if(USE_STATIC_CRT)
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CRT_FLAGS /MTd)
|
||||
else()
|
||||
set(CRT_FLAGS /MT)
|
||||
endif()
|
||||
foreach(CompilerFlag
|
||||
CMAKE_CXX_FLAGS
|
||||
CMAKE_CXX_FLAGS_DEBUG
|
||||
CMAKE_CXX_FLAGS_RELEASE
|
||||
CMAKE_C_FLAGS
|
||||
CMAKE_C_FLAGS_DEBUG
|
||||
CMAKE_C_FLAGS_RELEASE)
|
||||
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
|
||||
endforeach()
|
||||
endif(USE_STATIC_CRT)
|
||||
target_compile_options(discord-rpc PRIVATE /EHsc
|
||||
${CRT_FLAGS}
|
||||
/Wall
|
||||
/wd4100 # unreferenced formal parameter
|
||||
/wd4514 # unreferenced inline
|
||||
@ -53,7 +56,7 @@ if(WIN32)
|
||||
/wd5027 # move assignment operator was implicitly defined as deleted
|
||||
)
|
||||
endif(MSVC)
|
||||
target_link_libraries(discord-rpc PRIVATE psapi)
|
||||
target_link_libraries(discord-rpc PRIVATE psapi advapi32)
|
||||
endif(WIN32)
|
||||
|
||||
if(UNIX)
|
||||
@ -69,12 +72,23 @@ if(UNIX)
|
||||
|
||||
add_library(discord-rpc ${BASE_RPC_SRC})
|
||||
target_link_libraries(discord-rpc PUBLIC pthread)
|
||||
|
||||
if (APPLE)
|
||||
target_link_libraries(discord-rpc PRIVATE "-framework AppKit, -mmacosx-version-min=10.10")
|
||||
endif (APPLE)
|
||||
|
||||
target_compile_options(discord-rpc PRIVATE
|
||||
-g
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wpedantic
|
||||
-Werror
|
||||
)
|
||||
|
||||
if (${WARNINGS_AS_ERRORS})
|
||||
target_compile_options(discord-rpc PRIVATE -Werror)
|
||||
endif (${WARNINGS_AS_ERRORS})
|
||||
|
||||
target_compile_options(discord-rpc PRIVATE
|
||||
-Wno-unknown-pragmas # pragma push thing doesn't work on clang
|
||||
-Wno-old-style-cast # it's fine
|
||||
-Wno-c++98-compat # that was almost 2 decades ago
|
||||
@ -127,6 +141,7 @@ install(
|
||||
|
||||
install(
|
||||
FILES
|
||||
"../include/discord-rpc.h"
|
||||
"../include/discord_rpc.h"
|
||||
"../include/discord_register.h"
|
||||
DESTINATION "include"
|
||||
)
|
||||
|
@ -118,5 +118,8 @@ bool BaseConnection::Read(void* data, size_t length)
|
||||
}
|
||||
Close();
|
||||
}
|
||||
else if (res == 0) {
|
||||
Close();
|
||||
}
|
||||
return res == (int)length;
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void Discord_Register(const char* applicationId, const char* command);
|
||||
void Discord_RegisterSteamGame(const char* applicationId, const char* steamId);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -1,4 +1,5 @@
|
||||
#include "discord-rpc.h"
|
||||
#include "discord_rpc.h"
|
||||
#include "discord_register.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#include <errno.h>
|
||||
@ -8,7 +9,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
bool Mkdir(const char* path)
|
||||
static bool Mkdir(const char* path)
|
||||
{
|
||||
int result = mkdir(path, 0755);
|
||||
if (result == 0) {
|
||||
@ -21,7 +22,7 @@ bool Mkdir(const char* path)
|
||||
}
|
||||
|
||||
// we want to register games so we can run them from Discord client as discord-<appid>://
|
||||
extern "C" void Discord_Register(const char* applicationId, const char* command)
|
||||
extern "C" DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command)
|
||||
{
|
||||
// Add a desktop file and update some mime handlers so that xdg-open does the right thing.
|
||||
|
||||
@ -32,13 +33,15 @@ extern "C" void Discord_Register(const char* applicationId, const char* command)
|
||||
|
||||
char exePath[1024];
|
||||
if (!command || !command[0]) {
|
||||
if (readlink("/proc/self/exe", exePath, sizeof(exePath)) <= 0) {
|
||||
ssize_t size = readlink("/proc/self/exe", exePath, sizeof(exePath));
|
||||
if (size <= 0 || size >= (ssize_t)sizeof(exePath)) {
|
||||
return;
|
||||
}
|
||||
exePath[size] = '\0';
|
||||
command = exePath;
|
||||
}
|
||||
|
||||
const char* destopFileFormat = "[Desktop Entry]\n"
|
||||
const char* desktopFileFormat = "[Desktop Entry]\n"
|
||||
"Name=Game %s\n"
|
||||
"Exec=%s %%u\n" // note: it really wants that %u in there
|
||||
"Type=Application\n"
|
||||
@ -47,7 +50,7 @@ extern "C" void Discord_Register(const char* applicationId, const char* command)
|
||||
"MimeType=x-scheme-handler/discord-%s;\n";
|
||||
char desktopFile[2048];
|
||||
int fileLen = snprintf(
|
||||
desktopFile, sizeof(desktopFile), destopFileFormat, applicationId, command, applicationId);
|
||||
desktopFile, sizeof(desktopFile), desktopFileFormat, applicationId, command, applicationId);
|
||||
if (fileLen <= 0) {
|
||||
return;
|
||||
}
|
||||
@ -90,7 +93,8 @@ extern "C" void Discord_Register(const char* applicationId, const char* command)
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
|
||||
extern "C" DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId,
|
||||
const char* steamId)
|
||||
{
|
||||
char command[256];
|
||||
sprintf(command, "xdg-open steam://rungameid/%s", steamId);
|
||||
|
@ -5,45 +5,28 @@
|
||||
|
||||
#include "discord_register.h"
|
||||
|
||||
static bool Mkdir(const char* path)
|
||||
{
|
||||
int result = mkdir(path, 0755);
|
||||
if (result == 0) {
|
||||
return true;
|
||||
}
|
||||
if (errno == EEXIST) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void RegisterCommand(const char* applicationId, const char* command)
|
||||
{
|
||||
// There does not appear to be a way to register arbitrary commands on OSX, so instead we'll save the command
|
||||
// to a file in the Discord config path, and when it is needed, Discord can try to load the file there, open
|
||||
// the command therein (will pass to js's window.open, so requires a url-like thing)
|
||||
|
||||
const char* home = getenv("HOME");
|
||||
// Note: will not work for sandboxed apps
|
||||
NSString *home = NSHomeDirectory();
|
||||
if (!home) {
|
||||
return;
|
||||
}
|
||||
|
||||
char path[2048];
|
||||
sprintf(path, "%s/Library/Application Support/discord", home);
|
||||
Mkdir(path);
|
||||
strcat(path, "/games");
|
||||
Mkdir(path);
|
||||
strcat(path, "/");
|
||||
strcat(path, applicationId);
|
||||
strcat(path, ".json");
|
||||
NSString *path = [[[[[[home stringByAppendingPathComponent:@"Library"]
|
||||
stringByAppendingPathComponent:@"Application Support"]
|
||||
stringByAppendingPathComponent:@"discord"]
|
||||
stringByAppendingPathComponent:@"games"]
|
||||
stringByAppendingPathComponent:[NSString stringWithUTF8String:applicationId]]
|
||||
stringByAppendingPathExtension:@"json"];
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
|
||||
FILE* f = fopen(path, "w");
|
||||
if (f) {
|
||||
char jsonBuffer[2048];
|
||||
int len = snprintf(jsonBuffer, sizeof(jsonBuffer), "{\"command\": \"%s\"}", command);
|
||||
fwrite(jsonBuffer, (size_t)len, 1, f);
|
||||
fclose(f);
|
||||
}
|
||||
NSString *jsonBuffer = [NSString stringWithFormat:@"{\"command\": \"%s\"}", command];
|
||||
[jsonBuffer writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:nil];
|
||||
}
|
||||
|
||||
static void RegisterURL(const char* applicationId)
|
||||
@ -83,15 +66,15 @@ void Discord_Register(const char* applicationId, const char* command)
|
||||
}
|
||||
else {
|
||||
// raii lite
|
||||
void* pool = [[NSAutoreleasePool alloc] init];
|
||||
RegisterURL(applicationId);
|
||||
[(id)pool drain];
|
||||
@autoreleasepool {
|
||||
RegisterURL(applicationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
|
||||
{
|
||||
char command[256];
|
||||
sprintf(command, "steam://rungameid/%s", steamId);
|
||||
snprintf(command, 256, "steam://rungameid/%s", steamId);
|
||||
Discord_Register(applicationId, command);
|
||||
}
|
||||
|
@ -1,17 +1,5 @@
|
||||
/*
|
||||
* MinGW defaults to WINNT 5.1 (aka XP), however some of functions used here
|
||||
* require WINNT >= 6.0 APIs, which are only visible when WINVER and
|
||||
* _WIN32_WINNT defines are set properly before including any system headers.
|
||||
* Such API is e.g. RegSetKeyValueW.
|
||||
*/
|
||||
#ifdef __MINGW32__
|
||||
// 0x0600 == vista
|
||||
#define WINVER 0x0600
|
||||
#define _WIN32_WINNT 0x0600
|
||||
#endif // __MINGW32__
|
||||
|
||||
#include "discord-rpc.h"
|
||||
#include <stdio.h>
|
||||
#include "discord_rpc.h"
|
||||
#include "discord_register.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMCX
|
||||
@ -19,9 +7,67 @@
|
||||
#define NOIME
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#include <strsafe.h>
|
||||
#include <cstdio>
|
||||
|
||||
void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command)
|
||||
/**
|
||||
* Updated fixes for MinGW and WinXP
|
||||
* This block is written the way it does not involve changing the rest of the code
|
||||
* Checked to be compiling
|
||||
* 1) strsafe.h belongs to Windows SDK and cannot be added to MinGW
|
||||
* #include guarded, functions redirected to <string.h> substitutes
|
||||
* 2) RegSetKeyValueW and LSTATUS are not declared in <winreg.h>
|
||||
* The entire function is rewritten
|
||||
*/
|
||||
#ifdef __MINGW32__
|
||||
#include <wchar.h>
|
||||
/// strsafe.h fixes
|
||||
static HRESULT StringCbPrintfW(LPWSTR pszDest, size_t cbDest, LPCWSTR pszFormat, ...)
|
||||
{
|
||||
HRESULT ret;
|
||||
va_list va;
|
||||
va_start(va, pszFormat);
|
||||
cbDest /= 2; // Size is divided by 2 to convert from bytes to wide characters - causes segfault
|
||||
// othervise
|
||||
ret = vsnwprintf(pszDest, cbDest, pszFormat, va);
|
||||
pszDest[cbDest - 1] = 0; // Terminate the string in case a buffer overflow; -1 will be returned
|
||||
va_end(va);
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
#include <cwchar>
|
||||
#include <strsafe.h>
|
||||
#endif // __MINGW32__
|
||||
|
||||
/// winreg.h fixes
|
||||
#ifndef LSTATUS
|
||||
#define LSTATUS LONG
|
||||
#endif
|
||||
#ifdef RegSetKeyValueW
|
||||
#undefine RegSetKeyValueW
|
||||
#endif
|
||||
#define RegSetKeyValueW regset
|
||||
static LSTATUS regset(HKEY hkey,
|
||||
LPCWSTR subkey,
|
||||
LPCWSTR name,
|
||||
DWORD type,
|
||||
const void* data,
|
||||
DWORD len)
|
||||
{
|
||||
HKEY htkey = hkey, hsubkey = nullptr;
|
||||
LSTATUS ret;
|
||||
if (subkey && subkey[0]) {
|
||||
if ((ret = RegCreateKeyExW(hkey, subkey, 0, 0, 0, KEY_ALL_ACCESS, 0, &hsubkey, 0)) !=
|
||||
ERROR_SUCCESS)
|
||||
return ret;
|
||||
htkey = hsubkey;
|
||||
}
|
||||
ret = RegSetValueExW(htkey, name, 0, type, (const BYTE*)data, len);
|
||||
if (hsubkey && hsubkey != hkey)
|
||||
RegCloseKey(hsubkey);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static 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-<appid>://
|
||||
@ -35,7 +81,8 @@ void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command)
|
||||
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command);
|
||||
}
|
||||
else {
|
||||
StringCbCopyW(openCommand, sizeof(openCommand), exeFilePath);
|
||||
// StringCbCopyW(openCommand, sizeof(openCommand), exeFilePath);
|
||||
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", exeFilePath);
|
||||
}
|
||||
|
||||
wchar_t protocolName[64];
|
||||
@ -84,7 +131,7 @@ void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command)
|
||||
RegCloseKey(key);
|
||||
}
|
||||
|
||||
extern "C" void Discord_Register(const char* applicationId, const char* command)
|
||||
extern "C" DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command)
|
||||
{
|
||||
wchar_t appId[32];
|
||||
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
|
||||
@ -100,7 +147,8 @@ extern "C" void Discord_Register(const char* applicationId, const char* command)
|
||||
Discord_RegisterW(appId, wcommand);
|
||||
}
|
||||
|
||||
extern "C" void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
|
||||
extern "C" DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId,
|
||||
const char* steamId)
|
||||
{
|
||||
wchar_t appId[32];
|
||||
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include "discord-rpc.h"
|
||||
#include "discord_rpc.h"
|
||||
|
||||
#include "backoff.h"
|
||||
#include "discord_register.h"
|
||||
@ -32,7 +32,7 @@ struct QueuedMessage {
|
||||
}
|
||||
};
|
||||
|
||||
struct JoinRequest {
|
||||
struct User {
|
||||
// snowflake (64bit int), turned into a ascii decimal string, at most 20 chars +1 null
|
||||
// terminator = 21
|
||||
char userId[32];
|
||||
@ -47,12 +47,14 @@ struct JoinRequest {
|
||||
};
|
||||
|
||||
static RpcConnection* Connection{nullptr};
|
||||
static DiscordEventHandlers QueuedHandlers{};
|
||||
static DiscordEventHandlers Handlers{};
|
||||
static std::atomic_bool WasJustConnected{false};
|
||||
static std::atomic_bool WasJustDisconnected{false};
|
||||
static std::atomic_bool GotErrorMessage{false};
|
||||
static std::atomic_bool WasJoinGame{false};
|
||||
static std::atomic_bool WasSpectateGame{false};
|
||||
static std::atomic_bool UpdatePresence{false};
|
||||
static char JoinGameSecret[256];
|
||||
static char SpectateGameSecret[256];
|
||||
static int LastErrorCode{0};
|
||||
@ -60,9 +62,11 @@ static char LastErrorMessage[256];
|
||||
static int LastDisconnectErrorCode{0};
|
||||
static char LastDisconnectErrorMessage[256];
|
||||
static std::mutex PresenceMutex;
|
||||
static std::mutex HandlerMutex;
|
||||
static QueuedMessage QueuedPresence{};
|
||||
static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue;
|
||||
static MsgQueue<JoinRequest, JoinQueueSize> JoinAskQueue;
|
||||
static MsgQueue<User, JoinQueueSize> JoinAskQueue;
|
||||
static User connectedUser;
|
||||
|
||||
// We want to auto connect, and retry on failure, but not as fast as possible. This does expoential
|
||||
// backoff from 0.5 seconds to 1 minute
|
||||
@ -86,10 +90,11 @@ public:
|
||||
keepRunning.store(true);
|
||||
ioThread = std::thread([&]() {
|
||||
const std::chrono::duration<int64_t, std::milli> maxWait{500LL};
|
||||
Discord_UpdateConnection();
|
||||
while (keepRunning.load()) {
|
||||
Discord_UpdateConnection();
|
||||
std::unique_lock<std::mutex> lock(waitForIOMutex);
|
||||
waitForIOActivity.wait_for(lock, maxWait);
|
||||
Discord_UpdateConnection();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -115,7 +120,7 @@ public:
|
||||
void Notify() {}
|
||||
};
|
||||
#endif // DISCORD_DISABLE_IO_THREAD
|
||||
static IoThreadHolder IoThread;
|
||||
static IoThreadHolder* IoThread{nullptr};
|
||||
|
||||
static void UpdateReconnectTime()
|
||||
{
|
||||
@ -210,17 +215,17 @@ static void Discord_UpdateConnection(void)
|
||||
}
|
||||
|
||||
// writes
|
||||
if (QueuedPresence.length) {
|
||||
if (UpdatePresence.exchange(false) && QueuedPresence.length) {
|
||||
QueuedMessage local;
|
||||
PresenceMutex.lock();
|
||||
local.Copy(QueuedPresence);
|
||||
QueuedPresence.length = 0;
|
||||
PresenceMutex.unlock();
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(PresenceMutex);
|
||||
local.Copy(QueuedPresence);
|
||||
}
|
||||
if (!Connection->Write(local.buffer, local.length)) {
|
||||
// if we fail to send, requeue
|
||||
PresenceMutex.lock();
|
||||
std::lock_guard<std::mutex> guard(PresenceMutex);
|
||||
QueuedPresence.Copy(local);
|
||||
PresenceMutex.unlock();
|
||||
UpdatePresence.exchange(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,7 +239,9 @@ static void Discord_UpdateConnection(void)
|
||||
|
||||
static void SignalIOActivity()
|
||||
{
|
||||
IoThread.Notify();
|
||||
if (IoThread != nullptr) {
|
||||
IoThread->Notify();
|
||||
}
|
||||
}
|
||||
|
||||
static bool RegisterForEvent(const char* evtName)
|
||||
@ -250,11 +257,29 @@ static bool RegisterForEvent(const char* evtName)
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool DeregisterForEvent(const char* evtName)
|
||||
{
|
||||
auto qmessage = SendQueue.GetNextAddMessage();
|
||||
if (qmessage) {
|
||||
qmessage->length =
|
||||
JsonWriteUnsubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
|
||||
SendQueue.CommitAdd();
|
||||
SignalIOActivity();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
|
||||
DiscordEventHandlers* handlers,
|
||||
int autoRegister,
|
||||
const char* optionalSteamId)
|
||||
{
|
||||
IoThread = new (std::nothrow) IoThreadHolder();
|
||||
if (IoThread == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (autoRegister) {
|
||||
if (optionalSteamId && optionalSteamId[0]) {
|
||||
Discord_RegisterSteamGame(applicationId, optionalSteamId);
|
||||
@ -266,10 +291,16 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
|
||||
|
||||
Pid = GetProcessId();
|
||||
|
||||
if (handlers) {
|
||||
Handlers = *handlers;
|
||||
}
|
||||
else {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||
|
||||
if (handlers) {
|
||||
QueuedHandlers = *handlers;
|
||||
}
|
||||
else {
|
||||
QueuedHandlers = {};
|
||||
}
|
||||
|
||||
Handlers = {};
|
||||
}
|
||||
|
||||
@ -278,21 +309,33 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
|
||||
}
|
||||
|
||||
Connection = RpcConnection::Create(applicationId);
|
||||
Connection->onConnect = []() {
|
||||
Connection->onConnect = [](JsonDocument& readyMessage) {
|
||||
Discord_UpdateHandlers(&QueuedHandlers);
|
||||
if (QueuedPresence.length > 0) {
|
||||
UpdatePresence.exchange(true);
|
||||
SignalIOActivity();
|
||||
}
|
||||
auto data = GetObjMember(&readyMessage, "data");
|
||||
auto user = GetObjMember(data, "user");
|
||||
auto userId = GetStrMember(user, "id");
|
||||
auto username = GetStrMember(user, "username");
|
||||
auto avatar = GetStrMember(user, "avatar");
|
||||
if (userId && username) {
|
||||
StringCopy(connectedUser.userId, userId);
|
||||
StringCopy(connectedUser.username, username);
|
||||
auto discriminator = GetStrMember(user, "discriminator");
|
||||
if (discriminator) {
|
||||
StringCopy(connectedUser.discriminator, discriminator);
|
||||
}
|
||||
if (avatar) {
|
||||
StringCopy(connectedUser.avatar, avatar);
|
||||
}
|
||||
else {
|
||||
connectedUser.avatar[0] = 0;
|
||||
}
|
||||
}
|
||||
WasJustConnected.exchange(true);
|
||||
ReconnectTimeMs.reset();
|
||||
|
||||
if (Handlers.joinGame) {
|
||||
RegisterForEvent("ACTIVITY_JOIN");
|
||||
}
|
||||
|
||||
if (Handlers.spectateGame) {
|
||||
RegisterForEvent("ACTIVITY_SPECTATE");
|
||||
}
|
||||
|
||||
if (Handlers.joinRequest) {
|
||||
RegisterForEvent("ACTIVITY_JOIN_REQUEST");
|
||||
}
|
||||
};
|
||||
Connection->onDisconnect = [](int err, const char* message) {
|
||||
LastDisconnectErrorCode = err;
|
||||
@ -301,10 +344,10 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
|
||||
UpdateReconnectTime();
|
||||
};
|
||||
|
||||
IoThread.Start();
|
||||
IoThread->Start();
|
||||
}
|
||||
|
||||
extern "C" DISCORD_EXPORT void Discord_Shutdown()
|
||||
extern "C" DISCORD_EXPORT void Discord_Shutdown(void)
|
||||
{
|
||||
if (!Connection) {
|
||||
return;
|
||||
@ -312,19 +355,33 @@ extern "C" DISCORD_EXPORT void Discord_Shutdown()
|
||||
Connection->onConnect = nullptr;
|
||||
Connection->onDisconnect = nullptr;
|
||||
Handlers = {};
|
||||
IoThread.Stop();
|
||||
QueuedPresence.length = 0;
|
||||
UpdatePresence.exchange(false);
|
||||
if (IoThread != nullptr) {
|
||||
IoThread->Stop();
|
||||
delete IoThread;
|
||||
IoThread = nullptr;
|
||||
}
|
||||
|
||||
RpcConnection::Destroy(Connection);
|
||||
}
|
||||
|
||||
extern "C" DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence)
|
||||
{
|
||||
PresenceMutex.lock();
|
||||
QueuedPresence.length = JsonWriteRichPresenceObj(
|
||||
QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
|
||||
PresenceMutex.unlock();
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(PresenceMutex);
|
||||
QueuedPresence.length = JsonWriteRichPresenceObj(
|
||||
QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
|
||||
UpdatePresence.exchange(true);
|
||||
}
|
||||
SignalIOActivity();
|
||||
}
|
||||
|
||||
extern "C" DISCORD_EXPORT void Discord_ClearPresence(void)
|
||||
{
|
||||
Discord_UpdatePresence(nullptr);
|
||||
}
|
||||
|
||||
extern "C" DISCORD_EXPORT void Discord_Respond(const char* userId, /* DISCORD_REPLY_ */ int reply)
|
||||
{
|
||||
// if we are not connected, let's not batch up stale messages for later
|
||||
@ -340,7 +397,7 @@ extern "C" DISCORD_EXPORT void Discord_Respond(const char* userId, /* DISCORD_RE
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" DISCORD_EXPORT void Discord_RunCallbacks()
|
||||
extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void)
|
||||
{
|
||||
// Note on some weirdness: internally we might connect, get other signals, disconnect any number
|
||||
// of times inbetween calls here. Externally, we want the sequence to seem sane, so any other
|
||||
@ -355,25 +412,42 @@ extern "C" DISCORD_EXPORT void Discord_RunCallbacks()
|
||||
|
||||
if (isConnected) {
|
||||
// if we are connected, disconnect cb first
|
||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||
if (wasDisconnected && Handlers.disconnected) {
|
||||
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (WasJustConnected.exchange(false) && Handlers.ready) {
|
||||
Handlers.ready();
|
||||
if (WasJustConnected.exchange(false)) {
|
||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||
if (Handlers.ready) {
|
||||
DiscordUser du{connectedUser.userId,
|
||||
connectedUser.username,
|
||||
connectedUser.discriminator,
|
||||
connectedUser.avatar};
|
||||
Handlers.ready(&du);
|
||||
}
|
||||
}
|
||||
|
||||
if (GotErrorMessage.exchange(false) && Handlers.errored) {
|
||||
Handlers.errored(LastErrorCode, LastErrorMessage);
|
||||
if (GotErrorMessage.exchange(false)) {
|
||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||
if (Handlers.errored) {
|
||||
Handlers.errored(LastErrorCode, LastErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (WasJoinGame.exchange(false) && Handlers.joinGame) {
|
||||
Handlers.joinGame(JoinGameSecret);
|
||||
if (WasJoinGame.exchange(false)) {
|
||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||
if (Handlers.joinGame) {
|
||||
Handlers.joinGame(JoinGameSecret);
|
||||
}
|
||||
}
|
||||
|
||||
if (WasSpectateGame.exchange(false) && Handlers.spectateGame) {
|
||||
Handlers.spectateGame(SpectateGameSecret);
|
||||
if (WasSpectateGame.exchange(false)) {
|
||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||
if (Handlers.spectateGame) {
|
||||
Handlers.spectateGame(SpectateGameSecret);
|
||||
}
|
||||
}
|
||||
|
||||
// Right now this batches up any requests and sends them all in a burst; I could imagine a world
|
||||
@ -383,17 +457,48 @@ extern "C" DISCORD_EXPORT void Discord_RunCallbacks()
|
||||
// not it should be trivial for the implementer to make a queue themselves.
|
||||
while (JoinAskQueue.HavePendingSends()) {
|
||||
auto req = JoinAskQueue.GetNextSendMessage();
|
||||
if (Handlers.joinRequest) {
|
||||
DiscordJoinRequest djr{req->userId, req->username, req->discriminator, req->avatar};
|
||||
Handlers.joinRequest(&djr);
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||
if (Handlers.joinRequest) {
|
||||
DiscordUser du{req->userId, req->username, req->discriminator, req->avatar};
|
||||
Handlers.joinRequest(&du);
|
||||
}
|
||||
}
|
||||
JoinAskQueue.CommitSend();
|
||||
}
|
||||
|
||||
if (!isConnected) {
|
||||
// if we are not connected, disconnect message last
|
||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||
if (wasDisconnected && Handlers.disconnected) {
|
||||
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* newHandlers)
|
||||
{
|
||||
if (newHandlers) {
|
||||
#define HANDLE_EVENT_REGISTRATION(handler_name, event) \
|
||||
if (!Handlers.handler_name && newHandlers->handler_name) { \
|
||||
RegisterForEvent(event); \
|
||||
} \
|
||||
else if (Handlers.handler_name && !newHandlers->handler_name) { \
|
||||
DeregisterForEvent(event); \
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||
HANDLE_EVENT_REGISTRATION(joinGame, "ACTIVITY_JOIN")
|
||||
HANDLE_EVENT_REGISTRATION(spectateGame, "ACTIVITY_SPECTATE")
|
||||
HANDLE_EVENT_REGISTRATION(joinRequest, "ACTIVITY_JOIN_REQUEST")
|
||||
|
||||
#undef HANDLE_EVENT_REGISTRATION
|
||||
|
||||
Handlers = *newHandlers;
|
||||
}
|
||||
else {
|
||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||
Handlers = {};
|
||||
}
|
||||
return;
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
#include <windows.h>
|
||||
|
||||
// outsmart GCC's missing-declarations warning
|
||||
BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID);
|
||||
BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID)
|
||||
{
|
||||
return TRUE;
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
template <typename ElementType, size_t QueueSize>
|
||||
class MsgQueue {
|
||||
ElementType queue_[QueueSize]{};
|
||||
ElementType queue_[QueueSize];
|
||||
std::atomic_uint nextAdd_{0};
|
||||
std::atomic_uint nextSend_{0};
|
||||
std::atomic_uint pendingSends_{0};
|
||||
|
@ -26,12 +26,8 @@ void RpcConnection::Open()
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == State::Disconnected) {
|
||||
if (connection->Open()) {
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
if (state == State::Disconnected && !connection->Open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == State::SentHandshake) {
|
||||
@ -42,7 +38,7 @@ void RpcConnection::Open()
|
||||
if (cmd && evt && !strcmp(cmd, "DISPATCH") && !strcmp(evt, "READY")) {
|
||||
state = State::Connected;
|
||||
if (onConnect) {
|
||||
onConnect();
|
||||
onConnect(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ struct RpcConnection {
|
||||
|
||||
BaseConnection* connection{nullptr};
|
||||
State state{State::Disconnected};
|
||||
void (*onConnect)(){nullptr};
|
||||
void (*onConnect)(JsonDocument& message){nullptr};
|
||||
void (*onDisconnect)(int errorCode, const char* message){nullptr};
|
||||
char appId[64]{};
|
||||
int lastErrorCode{0};
|
||||
|
@ -1,6 +1,6 @@
|
||||
#include "serialization.h"
|
||||
#include "connection.h"
|
||||
#include "discord-rpc.h"
|
||||
#include "discord_rpc.h"
|
||||
|
||||
template <typename T>
|
||||
void NumberToString(char* dest, T number)
|
||||
@ -102,7 +102,7 @@ size_t JsonWriteRichPresenceObj(char* dest,
|
||||
WriteKey(writer, "pid");
|
||||
writer.Int(pid);
|
||||
|
||||
{
|
||||
if (presence != nullptr) {
|
||||
WriteObject activity(writer, "activity");
|
||||
|
||||
WriteOptionalString(writer, "state", presence->state);
|
||||
@ -134,15 +134,18 @@ size_t JsonWriteRichPresenceObj(char* dest,
|
||||
}
|
||||
|
||||
if ((presence->partyId && presence->partyId[0]) || presence->partySize ||
|
||||
presence->partyMax) {
|
||||
presence->partyMax || presence->partyPrivacy) {
|
||||
WriteObject party(writer, "party");
|
||||
WriteOptionalString(writer, "id", presence->partyId);
|
||||
if (presence->partySize) {
|
||||
if (presence->partySize && presence->partyMax) {
|
||||
WriteArray size(writer, "size");
|
||||
writer.Int(presence->partySize);
|
||||
if (0 < presence->partyMax) {
|
||||
writer.Int(presence->partyMax);
|
||||
}
|
||||
writer.Int(presence->partyMax);
|
||||
}
|
||||
|
||||
if (presence->partyPrivacy) {
|
||||
WriteKey(writer, "privacy");
|
||||
writer.Int(presence->partyPrivacy);
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,6 +201,25 @@ size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const cha
|
||||
return writer.Size();
|
||||
}
|
||||
|
||||
size_t JsonWriteUnsubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName)
|
||||
{
|
||||
JsonWriter writer(dest, maxLen);
|
||||
|
||||
{
|
||||
WriteObject obj(writer);
|
||||
|
||||
JsonWriteNonce(writer, nonce);
|
||||
|
||||
WriteKey(writer, "cmd");
|
||||
writer.String("UNSUBSCRIBE");
|
||||
|
||||
WriteKey(writer, "evt");
|
||||
writer.String(evtName);
|
||||
}
|
||||
|
||||
return writer.Size();
|
||||
}
|
||||
|
||||
size_t JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce)
|
||||
{
|
||||
JsonWriter writer(dest, maxLen);
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef __MINGW32__
|
||||
#pragma warning(push)
|
||||
|
||||
#pragma warning(disable : 4061) // enum is not explicitly handled by a case label
|
||||
@ -9,12 +10,15 @@
|
||||
#pragma warning(disable : 4464) // relative include path contains
|
||||
#pragma warning(disable : 4668) // is not defined as a preprocessor macro
|
||||
#pragma warning(disable : 6313) // Incorrect operator
|
||||
#endif // __MINGW32__
|
||||
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
#include "rapidjson/writer.h"
|
||||
|
||||
#ifndef __MINGW32__
|
||||
#pragma warning(pop)
|
||||
#endif // __MINGW32__
|
||||
|
||||
// if only there was a standard library function for this
|
||||
template <size_t Len>
|
||||
@ -43,6 +47,8 @@ size_t JsonWriteRichPresenceObj(char* dest,
|
||||
const DiscordRichPresence* presence);
|
||||
size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName);
|
||||
|
||||
size_t JsonWriteUnsubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName);
|
||||
|
||||
size_t JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce);
|
||||
|
||||
// I want to use as few allocations as I can get away with, and to do that with RapidJson, you need
|
||||
|
Loading…
x
Reference in New Issue
Block a user