Compare commits

..

143 Commits

Author SHA1 Message Date
Mason Sciotti
963aa9f3e5
[Maintenance PR for Legacy Gamedevs] Add party privacy enum (#306)
* Add party privacy enum

* Add party privacy enum

* Possible unreal working

* Cast to int
2020-09-21 14:51:48 -07:00
msciotti
e4c0c569ec
Clarify deprecation 2019-11-27 11:26:13 -08:00
IceNinjaman
b6d0a9cdbd wchar.h instead cwchar when compiler is MinGW to prevent weird behavior with vsnwprintf (#277) 2019-11-27 11:24:32 -08:00
msciotti
eff23a770a
add deprecation note to readme 2019-07-10 14:39:38 -07:00
Kenny McCormick
34ce3ac803 fix "destopFileFormat" typo (#283)
resolution for #282
2019-04-30 11:39:14 -07:00
msciotti
c59fd6df20
Revert "Choose pipe number on initialize (#250)"
This reverts commit 4824b20f28b1ebffb2a57881684ef87f76659a6c.
2019-01-24 13:23:35 -08:00
Mason Sciotti
4824b20f28
Choose pipe number on initialize (#250)
* Choose pipe number on initialize

* Get pipe from base connection instance

* UE4 support

* Warnings as errors yelling

* Fix windows connection

* Oops all variables

* maybe this fixes it

* This one actually works!!!!

* Fix double function declaration
2019-01-14 00:16:22 -08:00
Mason Sciotti
4e53fa0392
Fix code signing for macOS 10.10 (#260)
* Fix code signing for macOS 10.10
- Fixes #259

* Maybe this works
2018-12-19 08:40:51 -08:00
Joshua Harrington
d478ed5608 Moved UE setup to be with unity setup (#254) 2018-12-14 16:04:31 -08:00
Jan Bubenik
8db649ba5f Static EventHandler for IL2CPP support (#258)
* Added staticEventHandler which forward the invoke to the actual eventHandler set though Initialize

* Code Formatting - Replaced abs with 4 white spaces
2018-12-14 16:00:47 -08:00
Mason Sciotti
e6390c8c41
Unity il2cpp support (#249)
* Initial il2cpp support attempts

* Fix crashes

* Different variable name

* Fix indenting

* Change back unneeded stuff
- callbackCalls didnt seem to do anything
2018-11-27 09:19:14 -08:00
Mason Sciotti
2fec0b6dec
Wrong name for macOS DLL file for Unity 2018-11-26 09:03:44 -08:00
Florian Spieß
dd47c7c66d Improve handling of disconnects and reconnects (#228)
* Check response 0 on disconnect

From recv(): The return value will be 0 when the peer has performed an orderly shutdown

* Add persistent presence and handlers

* Use buffer instead of raw struct

* Clear presence data on shutdown

* Remove CurrentPresence and add boolean instead

This removes the need for having 2 big buffers in favor of using a small boolean
2018-11-16 10:41:49 -08:00
Michał Janiszewski
98855b4d84 Fix #210: WARNINGS_AS_ERRORS doesn't work (#211)
#210 was created under assumption `build.py` is actually used, but upon
inspecting `.travis.yml` it turns out it is not.
2018-11-06 14:55:10 -08:00
Michał Janiszewski
ac2d064cb0 Flatten the condition to get rid of empty branch (#247) 2018-11-06 14:48:59 -08:00
Oliver Boudet
d63ed30966 Fix typo in readme. (#245)
* fix typo in readme

* revert accidental quote change
2018-10-23 15:13:36 -07:00
Elias Batek
7716eadca3 Update D binding link (#234)
DerelictDiscordRPC has been abandoned
and superseded by Discord RPC D.
2018-09-17 17:26:28 -07:00
msciotti
e32d001809
readme nits 2018-08-17 05:19:17 -07:00
msciotti
2cb9813eb6
Unity specific DLL setup 2018-08-17 05:17:57 -07:00
Mason Sciotti
af380116a0
Check C# strings against UTF8 bytes instead of clamping (#221) 2018-08-16 11:23:28 -07:00
Michał Janiszewski
3d3ae7129d Fix mismatched signs in comparison after b44defe (#209)
```
../src/discord_register_linux.cpp: In function ‘void Discord_Register(const char*, const char*)’:
../src/discord_register_linux.cpp:37:31: warning: comparison of integer expressions of different signedness: ‘ssize_t’ {aka ‘long int’} and ‘long unsigned int’ [-Wsign-compare]
         if (size <= 0 || size >= sizeof(exePath)) {
                          ~~~~~^~~~~~~~~~~~~~~~~~
```
2018-07-30 12:50:49 -07:00
Sander in 't Veld
b44defe60a [Linux] Null-terminate output from readlink in Discord_Register. (#208)
* Explicitly null-terminate the output from readlink in discord_register_linux.cpp.

* The return value of readlink is a signed size_t.
2018-07-27 10:04:04 -07:00
gamingexpx12
dfad394be0 Added enum for response codes (#207)
Makes it easier to respond to a join request via Blueprints, without having to look it up in the docs.
Optional and fully backwards compatible.
2018-07-25 13:23:34 -07:00
Lachee
a3ad6afee2 Added Discord RPC C# implementation to list. (#205)
Added my library to the list which is a implementation written entirely in C#
2018-07-09 12:04:59 -07:00
Sleepy Flower Girl
7c41a8ec19 Fixed issue with Discord RPC not updating presence during shutdown (#189) 2018-06-07 16:10:40 -07:00
msciotti
5df1c5ae6d
copy the whole folder UE4 2018-05-30 09:40:39 -07:00
Joshua Harrington
c05c7148dd Updated README with UE plugin instructions (#183)
* Updated README with UE plugin instructions

Hopefully this will save some time when others want to implement this into their own UE Projects.

* Update README.md

* cleanup UE4 plugin help
2018-05-29 13:51:39 -07:00
Ted John
ba9fe00c4d Dynamically create IoThread... (#179)
So that it doesn't get deleted before Discord is destroyed.
2018-05-16 13:21:16 -07:00
Ben Morse
cac0362377 don't rely on unset env vars 2018-05-14 10:05:21 -07:00
Michał Janiszewski
7e0480e2ef Apply formatting (#178) 2018-05-14 09:25:17 -07:00
Ben Morse
566076e3d8
add WARNINGS_AS_ERRORS cmake option (#176) 2018-05-10 17:46:11 -07:00
Mason Sciotti
aa02012c14 alphabetize libs 2018-05-04 15:13:16 -07:00
Isaac
f80bd72d22 Include pypresence library (#167) 2018-05-04 15:12:24 -07:00
Joel Schumacher
acf7d6a054 Add link to lua-discordRPC (LuaJIT bindings) (#171) 2018-05-03 15:33:49 -07:00
Elias Batek
1129c2ce4f Add link to DerelictDiscordRPC (D binding) (#169)
* Add link to DerelictDiscordRPC (D binding)

* Fix alphabetic order of community-wrappers
2018-04-23 11:20:47 -07:00
Mason Sciotti
64027b336f
Adding user object to READY event (#159)
* Pass the READY event data down in onConnect

* Changes made for UE4 and Unity wrappers

* Changing object name from joinRequest to DiscordUser
2018-04-16 10:25:44 -07:00
Codecat
2ce9fe068b Syntax change to avoid gcc 4.8 segfaulting (#162) 2018-04-04 10:00:24 -07:00
Mason Sciotti
be8a8e9380 ACTUALLY register the handlers on init 2018-03-29 14:33:46 -07:00
Mason Sciotti
c70acbe7d1 Fix Unity buildhelper for linux
- Fixes #157
2018-03-26 10:56:05 -07:00
Mason Sciotti
d97e6b48ed Note to install cmake
- Fixes #149
2018-03-26 10:37:03 -07:00
Mason Sciotti
087282cd4b
Dynamic Event Handler Registration (#135)
- Discord_RegisterHandlers() exported
- C# wrapper updated
- Dynamically sub/unsub to events
- Better mutex locking, for safety!
2018-03-23 10:25:28 -07:00
Kodie Goodwin
7e5d57e6fd Update cert to use new name (#158) 2018-03-23 10:18:46 -07:00
Mason Sciotti
f3bd411b99
Update README.md 2018-03-19 10:29:11 -07:00
Lachee
8e0c7848a6 Added more hard mode documentation (#148)
* ACTIVITY_JOIN_REQUEST does not have a secret

There is no secret passed to a Join Request

* Added how to respond

* Update hard-mode.md

* Added some tips

Added some helpful tips. Plan to add more as I go along.
2018-03-19 10:27:29 -07:00
Felix Yan
e7f9396807 Fix a typo in send-presence.c (#144) 2018-03-13 16:58:14 -07:00
Cynthia
ad0b844672 Changed CMAKE_SOURCE_DIR to CMAKE_CURRENT_SOURCE_DIR (#143)
Helps with submodule implementations
2018-03-13 13:13:38 -07:00
Michał Janiszewski
d279c24c6a Add advapi32 to linked libraries (#140)
Required by `RegCreateKeyExW` and others.
2018-03-13 13:00:47 -07:00
Mason Sciotti
d9caf72e9a Add missing timestamps in UE4 example
Fixes #137
2018-03-06 09:51:31 -08:00
Mason Sciotti
e8091f5137 Changing kebab case filenames in source to snake case for consistency 2018-02-27 13:33:00 -08:00
sll552
4055565147 Update C# wrapper with visual C# compatible version (#126)
- Custom serializer to fix utf-8 strings in C#
2018-02-22 11:47:18 -08:00
Michał Janiszewski
578eb6de7c Provide fake DllMain declaration to fix missing-declarations warning (#130) 2018-02-15 14:36:31 -08:00
Michał Janiszewski
4e61b9c82c Fix mingw compilation with -Werror=missing-declarations (#128) 2018-02-14 13:33:02 -08:00
Michał Janiszewski
8ec10dc011 Fix compilation with -Werror=missing-declarations (#127) 2018-02-14 11:42:29 -08:00
Mason Sciotti
f5f2d69a72
Update Unreal Example to include Ask to Join (#125) 2018-02-12 13:47:38 -08:00
Mason Sciotti
453222075b
partyMax is mandatory if partySize is included (#122) 2018-02-12 13:44:49 -08:00
Mason Sciotti
c4201806cf
Update build.py to properly build and copy libraries for Unity and Unreal (#120) 2018-02-12 13:40:41 -08:00
Mason Sciotti
ccf04d21f5
Moving buildhelper to editor folder (#118) 2018-02-02 16:02:11 -08:00
Dmitry
c7b4e6b2fc Fix segfaults in Discord_RegisterW in MinGW builds (#105)
* Add MinGW and WinXP support, remove Win SDK dep when using MinGW

* Remove Win SDK dependency when compiled with MinGW

* Remap the Win SDK-depended functions to string.h substitutes

* Remap missing WinAPI call RegSetKeyValueW to a substitute function

* Remove warnings by pragma when using MinGW

* Fix segfaults in Discord_RegisterW in MinGW builds
2018-02-02 09:40:33 -08:00
Nicolas Adamoglou
eee5085e9b Exported Discord_Register and Discord_RegisterSteam (#109)
* Update CMakeLists.txt

* Update and rename src/discord_register.h to include/discord_register.h

* Update CMakeLists.txt

* Update discord_register_win.cpp

* Update discord_register_linux.cpp
2018-02-01 16:42:17 -08:00
Joe
94ee4e64d9 [Unreal] Fix for Shipping Builds (#112)
* Changes to allow plugin to work in Shipping Builds

* Add files via upload

SourceTree is case-insensitive on Windows, fixin

* Remove duplicated directory

* Platform whitelist, mac support, compilation fix.

* Finalization steps

* Mac fix

* Tabs/Spaces consistency.

* Updated build.py
https://github.com/discordapp/discord-rpc/issues/89

* .gitignore (headers)

* Renamed folder to lowercase

* Removed duplicates

* Revert to lowercase
2018-01-22 10:47:33 -08:00
Florian Spieß
bd294d51a8 Renamed Java-DiscordRPC to new repository name (#113) 2018-01-21 11:24:33 -08:00
Ben Morse
b85758ec19 fix decls 2018-01-09 10:41:16 -08:00
Ben Morse
ec6af6132d fix build.py for unreal =) 2018-01-09 10:41:00 -08:00
Ben Morse
f99a260b07 'build.py unreal' to copy libs/headers into unreal example project 2018-01-09 10:35:37 -08:00
Glenn Smith
2c609b1d5f Fix buffer overflows in RegisterCommand on mac (#99) 2018-01-05 16:59:44 -08:00
Ben Morse
839ba32671 use unambiguous C declaration style 2018-01-05 16:56:08 -08:00
Ben Morse
6a59509b7b
add Discord_ClearPresence() (#104)
send-presence example updated - start a line with 'c' to toggle
whether presence information is sent or not.

Added ClearPresence to .cs bindings

Added ClearPresence to UE4 blueprint class.
2018-01-05 15:17:23 -08:00
Dmitry
b0e31a9e25 MinGW and WinXP support (issue #102) (#103)
* Add MinGW and WinXP support, remove Win SDK dep when using MinGW

Was meant to add MinGW support only, WinXP support was made by accident.
Changes:
* Remove Win SDK dependency when compiled with MinGW
* Remap the Win SDK-depended functions to string.h substitutes
* Remap missing WinAPI call RegSetKeyValueW to a substitute function

* Remove warnings by pragma when using MinGW
2018-01-05 10:56:55 -08:00
Ben Morse
2d0661c906 fix debug build with static crt
CMAKE_BUILD_TYPE is not set at configuration time, so we can't test
against it.  So, we string-replace /MD with /MT in the c[xx] flags for
the different targets.  CF:

https://stackoverflow.com/questions/14172856/cmake-compile-with-mt-instead-of-md
2018-01-05 10:42:44 -08:00
Nicolas Adamoglou
265ea814f5 Add Java Wrapper (#75) 2018-01-03 13:12:29 -08:00
Florian Zwoch
8990824c9c make ready() to explicitly take no arguments -> ready(void) (#100) 2017-12-26 22:21:32 -08:00
Gus Caplan
8f9013cea6 Revert "Clarify js rp wrapper" (#96)
This reverts commit 5438d6bf2252e00d1f45b511df095f7f98e192c8.
2017-12-18 09:46:33 -08:00
Mason Sciotti
085e0e7326 Click dependency in build.py 2017-12-14 13:58:03 -08:00
Fades
b3102db5c9 Fix broken docs URL (#95)
Signed-off-by: Fades <me@fades.me>
2017-12-12 10:30:53 -08:00
Mason Sciotti
5438d6bf22 Clarify js rp wrapper 2017-12-11 17:22:08 -08:00
Gus Caplan
b9f9b08606 add discord rich presence js lib (#90)
* add discord rich presence js lib

* alphabeticalizeify
2017-12-08 16:05:46 -08:00
John Grosh
e5bdd61223 Added Java Implementation to list(#76) 2017-12-08 13:46:40 -08:00
Alejandro
1555405d83 Add SwordRPC to implementations (#82)
This also reorganizes the list to have languages in alphabetical order
2017-12-06 09:05:45 -08:00
Mason Sciotti
051a1eeb70 Added ATJ for unity example 2017-12-04 15:16:59 -08:00
Bluexin
3852d83d12 Adding drpc4k to wrappers/implementations list (#72)
* Adding drpc4k to wrappers/implementations list

* Git you're dumb and I hate you
2017-12-03 10:26:49 -08:00
Florian Spieß
d8122e7d69 Add Java Binding to Community Wrappers (#73) 2017-12-03 10:20:15 -08:00
Alejandro
060182f366 Update "hard mode" to change evnt to evt (#80) 2017-12-03 10:11:34 -08:00
Chris Marsh
19ed45ac25 Also only build release in for_unity build 2017-12-01 14:41:20 -08:00
Chris Marsh
be7fda7c50 Update build script for a more targeted set of stuff to build for unity 2017-12-01 14:41:20 -08:00
Chris Marsh
50ea4e61c6 Fix unity build for osx 2017-12-01 14:41:20 -08:00
Chris Marsh
82439911c6 wip update unity example 2017-12-01 14:41:20 -08:00
Chris Marsh
0d24fabdf4 Fix param type 2017-12-01 14:41:20 -08:00
Chris Marsh
79eea99d19 Update Unreal example (register more callbacks, still mostly debug prints) 2017-12-01 14:41:20 -08:00
Chris Marsh
c053b72f58 Update Unity example (also, remove dll, need to do that a better way) 2017-12-01 14:41:20 -08:00
Chris Marsh
1a278528a2 Add discriminator to join request 2017-12-01 14:41:20 -08:00
Chris Marsh
c1a8899d02 Build script cleanup 2017-12-01 14:41:20 -08:00
Chris Marsh
b01d0a8af3 Move the signing stuff out of cmake 2017-11-29 14:00:37 -08:00
Mason Sciotti
8cae35ea46 Added community wrapper/implementation table 2017-11-29 11:24:39 -08:00
Chris Marsh
8307a1ad83 Update readme 2017-11-29 10:36:14 -08:00
Chris Marsh
8af28e46be Disable code formatting on CI builds. 2017-11-29 09:50:30 -08:00
Michał Janiszewski
2af5adca3d Use simpler version of GetModuleFileNameW
As per
https://msdn.microsoft.com/en-us/library/windows/desktop/ms683198(v=vs.85).aspx,
the advised use for getting the file name of a module from `<current
process>` is to use the simplified version. Additionally, this clears
the confusion introduced in Win7's PSAPI which moved
`GetModuleFileNameExW` into the kernel, changing its signature to
`K32GetModuleFileNameExW`, effectively breaking pre-Win7 uses unless
actively mitigated with `#define PSAPI_VERSION 1`
2017-11-29 08:08:47 -08:00
Chris Marsh
7300d1caa8 wip build script, playing with buildkite logging 2017-11-28 10:58:10 -08:00
Chris Marsh
7c3e28870e wip more build script, add signing 2017-11-28 10:27:40 -08:00
Chris Marsh
9130707086 Wrap std::thread in a holder that joins it on destruction. 2017-11-28 09:16:00 -08:00
Chris Marsh
49b23040c6 wip updating build script 2017-11-22 16:12:37 -08:00
Hunter
522c304b32 Change appveyor artifacts 2017-11-22 08:51:37 -08:00
Kia
5d5bc82e26 Moved generated.h include to last position as it is required by UE4. 2017-11-19 17:26:33 -08:00
Michał Janiszewski
49b7703334 Skip formatting on Travis 2017-11-17 10:53:59 -08:00
Michał Janiszewski
706847dd47 Add Xcode 2017-11-17 10:53:59 -08:00
Michał Janiszewski
2c566b208b Add clang 5 2017-11-17 10:53:59 -08:00
Michał Janiszewski
95be02d4ce Add Travis CI support 2017-11-17 10:53:59 -08:00
Chris Marsh
c834a2e6d9 Thanks, paranoia 2017-11-17 09:15:24 -08:00
Chris Marsh
6a963456ed Thanks, Danny. 2017-11-17 09:13:22 -08:00
Michał Janiszewski
c5d3481c47 Add GNUInstallDirs include to top-level CMakeLists 2017-11-16 09:03:17 -08:00
Michał Janiszewski
2b248a6cef Don't use hardcoded paths for installation targets 2017-11-16 09:03:17 -08:00
Ted John
c9cf6b3f41 Move /MT directive to MSVC only 2017-11-16 09:03:17 -08:00
Ted John
704c56d13f Add option for building with /MT 2017-11-16 09:03:17 -08:00
Ted John
5085d23dd1 Improve cmakelists for vcpkg building
- Remove forced /MT directive.
- Allow building for debug.
- Add option to prevent building of example apps.
2017-11-16 09:03:17 -08:00
Michał Janiszewski
1675d5d2dc Provide link to info about BUILD_SHARED_LIBS 2017-11-15 11:35:55 -08:00
Hunter
bf6e0fddd5 Update appveyor.yml 2017-11-15 09:31:21 -08:00
judge2020
18f6d878e0 Fix appveyor build 2017-11-15 09:31:21 -08:00
Hunter
364606f7e9 Create Appveyor config for automated CI testing 2017-11-15 09:31:21 -08:00
Michał Janiszewski
b206dd44f0 Use -fPIC option when building shared libs 2017-11-15 09:22:07 -08:00
Michał Janiszewski
d121bbe709 Use cmake-standard flag BUILD_SHARED_LIBS
This removes custom `BUILD_DYNAMIC_LIB` option and replaces it with a
standard BUILD_SHARED_LIBS:
https://cmake.org/cmake/help/v3.7/variable/BUILD_SHARED_LIBS.html

Although not mentioned in the documentation there, this flag is
implicitly available.
2017-11-15 09:22:07 -08:00
Michał Janiszewski
cfd6470946 Add a comment explaining WINVER macros for MinGW 2017-11-15 08:20:48 -08:00
Michał Janiszewski
f22f299330 Fix mingw builds 2017-11-15 08:20:48 -08:00
Mason Sciotti
767b15184f
What's build.py for 2017-11-14 20:40:32 -08:00
Chris Marsh
6e744d228f Update to clang-format 6, specify all the options, turn off sort includes. 2017-11-13 10:50:37 -08:00
Michał Janiszewski
a1ab6c96f2 Fix unused result 2017-11-13 10:38:18 -08:00
Max Kamps
0ea7ddbd5a Fix CMake issue when ENABLE_IO_THREAD=OFF (#28)
This was probably just a typo.
2017-11-13 08:01:58 -08:00
Chris Marsh
2f54e62c23 fix include order 2017-11-10 13:14:00 -08:00
Ivan Molodetskikh
54abef2624 Fix unused parameter warning triggering 2017-11-10 09:59:51 -08:00
Ivan Molodetskikh
c5d70514ac Use steam://rungameid/ 2017-11-10 09:59:51 -08:00
Ivan Molodetskikh
57316cbaee clang-format I suppose 2017-11-10 09:59:51 -08:00
Kia
9d4d145e7f Add entry for include folder where you have to place discord-rpc.h 2017-11-10 08:13:01 -08:00
Chris Marsh
7dc663a170 Static link crt 2017-11-09 14:24:30 -08:00
Gus Caplan
f872b4e49c fix event names and add ACTIVITY_JOIN_REQUEST (#10)
* fix event names and add ACTIVITY_JOIN_REQUEST

* Update hard-mode.md

* fix typo
2017-11-09 13:32:17 -08:00
ArsenArsen
ca5d70a5f9 Add more -Wflags 2017-11-09 13:08:05 -08:00
ArsenArsen
ee9c504d1c Change -Weverything to -Wall for more compilers 2017-11-09 13:08:05 -08:00
Mason Sciotti
127eadcb89
Added VS2015 C++ redist dependency info 2017-11-07 09:59:04 -08:00
Chris Marsh
a7808a20ed Fix some sizes on join request strings. 2017-11-03 13:40:30 -07:00
Chris Marsh
3bdb88d918
Unity ajt fix 2017-11-02 11:59:45 -07:00
Chris Marsh
aa79c70bf9 Adding MIT license. 2017-11-01 15:25:14 -07:00
Chris Marsh
a089aab53e Update unity. 2017-10-17 13:34:03 -07:00
Mason Sciotti
dafd85c39f Rename avatarUrl -> avatar for API consistency 2017-10-17 13:34:03 -07:00
SK83RJOSH
b1d6a7c0fc Normalize white-space 2017-10-17 09:37:56 -07:00
SK83RJOSH
e4b3ef63b7 Added request to join functionality 2017-10-17 09:37:56 -07:00
Chris Marsh
86ca320cb9 These should have been extern C. 2017-10-13 10:02:12 -07:00
57 changed files with 2434 additions and 616 deletions

View File

@ -1,52 +1,71 @@
--- ---
Language: Cpp
AccessModifierOffset: -4 AccessModifierOffset: -4
AlignAfterOpenBracket: true AlignAfterOpenBracket: true
AlignEscapedNewlinesLeft: true AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: false AlignOperands: false
AlignTrailingComments: true AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: false AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false AllowShortLoopsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline AlwaysBreakAfterReturnType: None
AlwaysBreakAfterDefinitionReturnType: false
AlwaysBreakTemplateDeclarations: true
AlwaysBreakBeforeMultilineStrings: false AlwaysBreakBeforeMultilineStrings: false
BreakBeforeBraces: Stroustrup AlwaysBreakTemplateDeclarations: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: true
BinPackParameters: false
BinPackArguments: false BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Stroustrup
BreakBeforeInheritanceComma: true
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma
BreakStringLiterals: true
ColumnLimit: 100 ColumnLimit: 100
CommentPragmas: '' CommentPragmas: ''
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 2 ConstructorInitializerIndentWidth: 2
ContinuationIndentWidth: 2 ContinuationIndentWidth: 2
Cpp11BracedListStyle: true Cpp11BracedListStyle: true
DerivePointerAlignment: false DerivePointerAlignment: false
DisableFormat: false DisableFormat: false
ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true
ForEachMacros: [ foreach ] ForEachMacros: []
IndentCaseLabels: false
IncludeCategories:
- Regex: '^("|<)stdafx\.h(pp)?("|>)'
Priority: -1
- Regex: '^<(W|w)indows.h>'
Priority: 1
- Regex: '^<'
Priority: 2
- Regex: '.*'
Priority: 3
IncludeIsMainRegex: '(_test|_win|_linux|_mac|_ios|_osx|_null)?$'
IndentCaseLabels: false IndentCaseLabels: false
IndentWidth: 4 IndentWidth: 4
IndentWrappedFunctionNames: false IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1 MaxEmptyLinesToKeep: 1
NamespaceIndentation: None NamespaceIndentation: None
ObjCBlockIndentWidth: 4 PenaltyBreakAssignment: 0
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PenaltyBreakBeforeFirstCallParameter: 1 PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300 PenaltyBreakComment: 300
PenaltyBreakString: 1000
PenaltyBreakFirstLessLess: 120 PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000 PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 9999999 PenaltyReturnTypeOnItsOwnLine: 9999999
PointerAlignment: Left PointerAlignment: Left
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: true
SpaceAfterCStyleCast: false SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false SpaceInEmptyParentheses: false
@ -59,4 +78,15 @@ SpacesInSquareBrackets: false
Standard: Cpp11 Standard: Cpp11
TabWidth: 4 TabWidth: 4
UseTab: Never UseTab: Never
---
Language: Cpp
---
Language: ObjC
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: false
---
Language: Java
BasedOnStyle: Google
BreakAfterJavaFieldAnnotations: true
... ...

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
/.vscode/ /.vscode/
/thirdparty/ /thirdparty/
.vs/ .vs/
.DS_Store

47
.travis.yml Normal file
View File

@ -0,0 +1,47 @@
language: cpp
env:
global:
- CLANG_FORMAT_SUFFIX="-dummy" # don't use formatting on Travis, this is
# needed not to use default 3.5 version
# which is too old.
matrix:
include:
- os: linux
env: MATRIX_EVAL="CC=gcc-5 && CXX=g++-5"
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-5
- os: linux
env: MATRIX_EVAL="CC=clang-4.0 && CXX=clang++-4.0"
addons:
apt:
sources:
- llvm-toolchain-trusty-4.0
packages:
- clang-4.0
- os: linux
env: MATRIX_EVAL="CC=clang-5.0 && CXX=clang++-5.0"
addons:
apt:
sources:
- llvm-toolchain-trusty-5.0
packages:
- clang-5.0
- os: osx
osx_image: xcode9
# prevent Travis from overwriting our CXX variables
before_install:
- eval "${MATRIX_EVAL}"
- echo $CXX
script:
- mkdir build
- cd build
- cmake -DCLANG_FORMAT_SUFFIX=$CLANG_FORMAT_SUFFIX -DWARNINGS_AS_ERRORS=On --config Release ..
- cmake --build . -- -j2

View File

@ -1,6 +1,10 @@
cmake_minimum_required (VERSION 3.7.0) cmake_minimum_required (VERSION 3.2.0)
project (DiscordRPC) project (DiscordRPC)
include(GNUInstallDirs)
option(BUILD_EXAMPLES "Build example apps" ON)
# format # format
file(GLOB_RECURSE ALL_SOURCE_FILES file(GLOB_RECURSE ALL_SOURCE_FILES
examples/*.cpp examples/*.h examples/*.c examples/*.cpp examples/*.h examples/*.c
@ -8,47 +12,45 @@ file(GLOB_RECURSE ALL_SOURCE_FILES
src/*.cpp src/*.h src/*.c src/*.cpp src/*.h src/*.c
) )
find_program(CLANG_FORMAT_CMD clang-format) # Set CLANG_FORMAT_SUFFIX if you are using custom clang-format, e.g. clang-format-5.0
find_program(CLANG_FORMAT_CMD clang-format${CLANG_FORMAT_SUFFIX})
if (CLANG_FORMAT_CMD) if (CLANG_FORMAT_CMD)
add_custom_target( add_custom_target(
clangformat clangformat
COMMAND clang-format COMMAND ${CLANG_FORMAT_CMD}
-i -style=file -fallback-style=none -i -style=file -fallback-style=none
${ALL_SOURCE_FILES} ${ALL_SOURCE_FILES}
DEPENDS DEPENDS
${ALL_SOURCE_FILES} ${ALL_SOURCE_FILES}
) )
else(CLANG_FORMAT_CMD)
add_custom_target(
clangformat
COMMENT "no clang format"
)
endif(CLANG_FORMAT_CMD) endif(CLANG_FORMAT_CMD)
# thirdparty stuff # thirdparty stuff
execute_process( execute_process(
COMMAND mkdir ${CMAKE_SOURCE_DIR}/thirdparty COMMAND mkdir ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty
ERROR_QUIET ERROR_QUIET
) )
find_file(RAPIDJSONTEST NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_SOURCE_DIR}/thirdparty) find_file(RAPIDJSONTEST NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH)
if (NOT RAPIDJSONTEST) if (NOT RAPIDJSONTEST)
message("no rapidjson, download") 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}) file(DOWNLOAD https://github.com/miloyip/rapidjson/archive/v1.1.0.tar.gz ${RJ_TAR_FILE})
execute_process( execute_process(
COMMAND ${CMAKE_COMMAND} -E tar xzf ${RJ_TAR_FILE} 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}) file(REMOVE ${RJ_TAR_FILE})
endif(NOT RAPIDJSONTEST) endif(NOT RAPIDJSONTEST)
find_file(RAPIDJSON NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_SOURCE_DIR}/thirdparty) 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}) add_library(rapidjson STATIC IMPORTED ${RAPIDJSON})
# add subdirs # add subdirs
add_subdirectory(src) add_subdirectory(src)
if (BUILD_EXAMPLES)
add_subdirectory(examples/send-presence) add_subdirectory(examples/send-presence)
endif(BUILD_EXAMPLES)

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright 2017 Discord, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

149
README.md
View File

@ -1,33 +1,96 @@
# Discord RPC # Discord RPC
This is a lib and quick demos that implement the very minimal subset to show current status, and ## Deprecation Notice
have callbacks for where a more complete game would do more things. 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 This library has been deprecated in favor of Discord's GameSDK. [Learn more here](https://discordapp.com/developers/docs/game-sdk/sdk-starter-guide)
doesn't meet your needs.
---
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
have callbacks for where a more complete game would do more things (joining, spectating, etc).
## Documentation ## 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).
## Usage ## Basic Usage
Zeroith, you should be set up to build things because you are a game developer, right? Zeroith, you should be set up to build things because you are a game developer, right?
First, head on over to the [Discord developers site](https://discordapp.com/developers/applications/me) 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.
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 ### From package
Download a release package, extract it, add `/include` to your compile includes, `/lib` to your 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.
linker paths, and link with `discord-rpc`.
### From repo ### From repo
There's a [CMake](https://cmake.org/download/) file that should be able to generate the lib for 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.
you; I use it like this:
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 ```sh
cd <path to discord-rpc> cd <path to discord-rpc>
mkdir build mkdir build
@ -35,25 +98,61 @@ you; I use it like this:
cmake .. -DCMAKE_INSTALL_PREFIX=<path to install discord-rpc to> cmake .. -DCMAKE_INSTALL_PREFIX=<path to install discord-rpc to>
cmake --build . --config Release --target install cmake --build . --config Release --target install
``` ```
Sometimes I use the generated project files. There are a couple of CMake options you might care about:
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. 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 | | 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. | `ENABLE_IO_THREAD` | `ON` | When enabled, we start up a thread to do io processing, if disabled you should call `Discord_UpdateConnection` yourself. |
| `BUILD_DYNAMIC_LIB` | `OFF` | Build library as a DLL | `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 | [![Build status](https://travis-ci.org/discordapp/discord-rpc.svg?branch=master)](https://travis-ci.org/discordapp/discord-rpc) |
| AppVeyor | [![Build status](https://ci.appveyor.com/api/projects/status/qvkoc0w1c4f4b8tj?svg=true)](https://ci.appveyor.com/project/crmarsh/discord-rpc) |
| Buildkite (internal) | [![Build status](https://badge.buildkite.com/e103d79d247f6776605a15246352a04b8fd83d69211b836111.svg)](https://buildkite.com/discord/discord-rpc) |
## Sample: send-presence ## Sample: send-presence
This is a text adventure "game" that inits/deinits the connection to Discord, and sends a presence This is a text adventure "game" that inits/deinits the connection to Discord, and sends a presence update on each command.
update on each command.
## Sample: button-clicker ## Sample: button-clicker
This is a sample [Unity](https://unity3d.com/) project that wraps a DLL version of the library, and 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.
sends presence updates when you click on a button.
## Sample: unrealstatus ## Sample: unrealstatus
This is a sample [Unreal](https://www.unrealengine.com) project that wraps the DLL version of the 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.
library with an Unreal plugin, exposes a blueprint class for interacting with it, and uses that to
make a very simple UI. ## Wrappers and Implementations
Below is a table of unofficial, community-developed wrappers for and implementations of Rich Presence in various languages. If you would like to have yours added, please make a pull request adding your repository to the table. The repository should include:
- The code
- A brief ReadMe of how to use it
- A working example
###### Rich Presence Wrappers and Implementations
| 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) |

17
appveyor.yml Normal file
View File

@ -0,0 +1,17 @@
version: '{build}'
install:
- python -m pip install click
build_script:
- mkdir examples\unrealstatus\Plugins\discordrpc\Binaries\ThirdParty\discordrpcLibrary\Win64
- python build.py
artifacts:
- path: builds\install\win32-dynamic
name: win32-dynamic
- path: builds\install\win32-static
name: win32-static
- path: builds\install\win64-dynamic
name: win64-dynamic
- path: builds\install\win64-static
name: win64-static

274
build.py
View File

@ -1,15 +1,39 @@
#!/usr/bin/env python #!/usr/bin/env python
import click
import os import os
import subprocess import subprocess
import sys import sys
import shutil import shutil
import zipfile import zipfile
from contextlib import contextmanager from contextlib import contextmanager
import click
def get_platform():
""" a name for the platform """
if sys.platform.startswith('win'):
return 'win'
elif sys.platform == 'darwin':
return 'osx'
elif sys.platform.startswith('linux'):
return 'linux'
raise Exception('Unsupported platform ' + sys.platform)
SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
# we use Buildkite which sets this env variable by default
IS_BUILD_MACHINE = os.environ.get('CI', '') == 'true'
PLATFORM = get_platform()
INSTALL_ROOT = os.path.join(SCRIPT_PATH, 'builds', 'install')
def get_signtool():
""" get path to code signing tool """
if PLATFORM == 'win':
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'
@contextmanager @contextmanager
@ -26,12 +50,118 @@ def cd(new_dir):
def mkdir_p(path): def mkdir_p(path):
""" mkdir -p """ """ mkdir -p """
if not os.path.isdir(path): if not os.path.isdir(path):
click.secho('Making ' + path, fg='yellow')
os.makedirs(path) os.makedirs(path)
def build_lib(build_name, generator, options): @click.group(invoke_without_command=True)
@click.pass_context
@click.option('--clean', is_flag=True)
def cli(ctx, clean):
""" click wrapper for command line stuff """
if ctx.invoked_subcommand is None:
ctx.invoke(libs, clean=clean)
if IS_BUILD_MACHINE:
ctx.invoke(sign)
ctx.invoke(archive)
@cli.command()
@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 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)
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):
""" Create a dir under builds, run build and install in it """
build_path = os.path.join(SCRIPT_PATH, 'builds', build_name) build_path = os.path.join(SCRIPT_PATH, 'builds', build_name)
install_path = os.path.join(SCRIPT_PATH, 'builds', 'install', build_name) install_path = os.path.join(INSTALL_ROOT, build_name)
mkdir_p(build_path) mkdir_p(build_path)
mkdir_p(install_path) mkdir_p(install_path)
with cd(build_path): with cd(build_path):
@ -39,56 +169,136 @@ def build_lib(build_name, generator, options):
if generator: if generator:
initial_cmake.extend(['-G', generator]) initial_cmake.extend(['-G', generator])
for key in options: for key in options:
val = 'ON' if options[key] else 'OFF' val = options[key]
if type(val) is bool:
val = 'ON' if val else 'OFF'
initial_cmake.append('-D%s=%s' % (key, val)) initial_cmake.append('-D%s=%s' % (key, val))
click.echo('--- Building ' + build_name)
subprocess.check_call(initial_cmake) subprocess.check_call(initial_cmake)
if not just_release:
subprocess.check_call(['cmake', '--build', '.', '--config', 'Debug']) subprocess.check_call(['cmake', '--build', '.', '--config', 'Debug'])
subprocess.check_call(['cmake', '--build', '.', '--config', 'Release', '--target', 'install']) subprocess.check_call(['cmake', '--build', '.', '--config', 'Release', '--target', 'install'])
def create_archive(): @cli.command()
archive_file_path = os.path.join(SCRIPT_PATH, 'builds', 'discord-rpc-%s.zip' % sys.platform) def archive():
""" create zip of install dir """
click.echo('--- Archiving')
archive_file_path = os.path.join(SCRIPT_PATH, 'builds', 'discord-rpc-%s.zip' % get_platform())
archive_file = zipfile.ZipFile(archive_file_path, 'w', zipfile.ZIP_DEFLATED) archive_file = zipfile.ZipFile(archive_file_path, 'w', zipfile.ZIP_DEFLATED)
archive_src_base_path = os.path.join(SCRIPT_PATH, 'builds', 'install') archive_src_base_path = INSTALL_ROOT
archive_dst_base_path = 'discord-rpc' archive_dst_base_path = 'discord-rpc'
with cd(archive_src_base_path): with cd(archive_src_base_path):
for path, subdirs, filenames in os.walk('.'): for path, _, filenames in os.walk('.'):
for fname in filenames: for fname in filenames:
fpath = os.path.join(path, fname) fpath = os.path.join(path, fname)
archive_file.write(fpath, os.path.normpath(os.path.join(archive_dst_base_path, fpath))) dst_path = os.path.normpath(os.path.join(archive_dst_base_path, fpath))
click.echo('Adding ' + dst_path)
archive_file.write(fpath, dst_path)
@click.command() @cli.command()
def sign():
""" Do code signing within install directory using our cert """
tool = get_signtool()
signable_extensions = set()
if PLATFORM == 'win':
signable_extensions.add('.dll')
sign_command_base = [
tool,
'sign',
'/n',
'Discord Inc.',
'/a',
'/tr',
'http://timestamp.digicert.com/rfc3161',
'/as',
'/td',
'sha256',
'/fd',
'sha256',
]
elif PLATFORM == 'osx':
signable_extensions.add('.dylib')
sign_command_base = [
tool,
'--keychain',
os.path.expanduser('~/Library/Keychains/login.keychain'),
'-vvvv',
'--deep',
'--force',
'--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:
ext = os.path.splitext(fname)[1]
if ext not in signable_extensions:
continue
fpath = os.path.join(path, fname)
click.echo('Sign ' + fpath)
sign_command = sign_command_base + [fpath]
subprocess.check_call(sign_command)
@cli.command()
@click.option('--clean', is_flag=True) @click.option('--clean', is_flag=True)
def main(clean): @click.option('--static', is_flag=True)
os.chdir(SCRIPT_PATH) @click.option('--shared', is_flag=True)
@click.option('--skip_formatter', is_flag=True)
@click.option('--just_release', is_flag=True)
def libs(clean, static, shared, skip_formatter, just_release):
""" Do all the builds for this platform """
if clean: if clean:
shutil.rmtree('builds', ignore_errors=True) shutil.rmtree('builds', ignore_errors=True)
mkdir_p('builds') mkdir_p('builds')
if sys.platform.startswith('win'): if not (static or shared):
static = True
shared = True
static_options = {}
dynamic_options = {
'BUILD_SHARED_LIBS': True,
'USE_STATIC_CRT': True,
}
if skip_formatter or IS_BUILD_MACHINE:
static_options['CLANG_FORMAT_SUFFIX'] = 'none'
dynamic_options['CLANG_FORMAT_SUFFIX'] = 'none'
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' generator32 = 'Visual Studio 14 2015'
generator64 = 'Visual Studio 14 2015 Win64' generator64 = 'Visual Studio 14 2015 Win64'
if static:
build_lib('win32-static', generator32, {}) build_lib('win32-static', generator32, static_options, just_release)
build_lib('win32-dynamic', generator32, {'BUILD_DYNAMIC_LIB': True}) build_lib('win64-static', generator64, static_options, just_release)
build_lib('win64-static', generator64, {}) if shared:
build_lib('win64-dynamic', generator64, {'BUILD_DYNAMIC_LIB': True}) build_lib('win32-dynamic', generator32, dynamic_options, just_release)
build_lib('win64-dynamic', generator64, dynamic_options, just_release)
# todo: this in some better way elif PLATFORM == 'osx':
src_dll = os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic', 'src', 'Release', 'discord-rpc.dll') if static:
dst_dll = os.path.join(SCRIPT_PATH, 'examples', 'button-clicker', 'Assets', 'Resources', 'discord-rpc.dll') build_lib('osx-static', None, static_options, just_release)
shutil.copy(src_dll, dst_dll) if shared:
dst_dll = os.path.join(SCRIPT_PATH, 'examples', 'unrealstatus', 'Plugins', 'discordrpc', 'Binaries', 'ThirdParty', 'discordrpcLibrary', 'Win64', 'discord-rpc.dll') build_lib('osx-dynamic', None, dynamic_options, just_release)
shutil.copy(src_dll, dst_dll) elif PLATFORM == 'linux':
elif sys.platform == 'darwin': if static:
build_lib('osx-static', None, {}) build_lib('linux-static', None, static_options, just_release)
build_lib('osx-dynamic', None, {'BUILD_DYNAMIC_LIB': True}) if shared:
build_lib('linux-dynamic', None, dynamic_options, just_release)
create_archive()
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main()) os.chdir(SCRIPT_PATH)
sys.exit(cli())

View File

@ -56,7 +56,9 @@ Below is a full example of a `SET_ACTIVITY` command. Field restrictions like siz
## New RPC Events ## New RPC Events
The two new RPC events for Rich Presence power the ability to join and spectate your friends' games. First is the `GAME_JOIN` event: The three new RPC events for Rich Presence power the ability to join and spectate your friends' games.
First is the `ACTIVITY_JOIN` event:
```json ```json
{ {
@ -64,11 +66,11 @@ The two new RPC events for Rich Presence power the ability to join and spectate
"data": { "data": {
"secret": "025ed05c71f639de8bfaa0d679d7c94b2fdce12f" "secret": "025ed05c71f639de8bfaa0d679d7c94b2fdce12f"
}, },
"evnt": "GAME_JOIN" "evt": "ACTIVITY_JOIN"
} }
``` ```
And second is the `GAME_SPECTATE` event: Second is the `ACTIVITY_SPECTATE` event:
```json ```json
{ {
@ -76,7 +78,24 @@ And second is the `GAME_SPECTATE` event:
"data": { "data": {
"secret": "e7eb30d2ee025ed05c71ea495f770b76454ee4e0" "secret": "e7eb30d2ee025ed05c71ea495f770b76454ee4e0"
}, },
"evnt": "GAME_SPECTATE" "evt": "ACTIVITY_SPECTATE"
}
```
And third is the `ACTIVITY_JOIN_REQUEST` event:
```json
{
"cmd": "DISPATCH",
"data": {
"user": {
"id": "53908232506183680",
"username": "Mason",
"discriminator": "1337",
"avatar": "a_bab14f271d565501444b2ca3be944b25"
}
},
"evt": "ACTIVITY_JOIN_REQUEST"
} }
``` ```
@ -85,7 +104,7 @@ In order to receive these events, you need to [subscribe](https://discordapp.com
```json ```json
{ {
"nonce": "be9a6de3-31d0-4767-a8e9-4818c5690015", "nonce": "be9a6de3-31d0-4767-a8e9-4818c5690015",
"evt": "GAME_JOIN", "evt": "ACTIVITY_JOIN",
"cmd": "SUBSCRIBE" "cmd": "SUBSCRIBE"
} }
``` ```
@ -93,7 +112,53 @@ In order to receive these events, you need to [subscribe](https://discordapp.com
```json ```json
{ {
"nonce": "ae9qdde3-31d0-8989-a8e9-dnakwy174he", "nonce": "ae9qdde3-31d0-8989-a8e9-dnakwy174he",
"evt": "GAME_SPECTATE", "evt": "ACTIVITY_SPECTATE",
"cmd": "SUBSCRIBE" "cmd": "SUBSCRIBE"
} }
``` ```
```json
{
"nonce": "5dc0c062-98c6-47a0-8922-bbb52e9d6afa",
"evt": "ACTIVITY_JOIN_REQUEST",
"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.

View File

@ -1,6 +1,8 @@
/Library/ /Library/
/Temp/ /Temp/
/obj/ /obj/
/Assets/Plugins/
/Assets/Plugins.meta
*.sln *.sln
*.csproj *.csproj
*.userprefs *.userprefs

View File

@ -1,13 +1,27 @@
using UnityEngine; using UnityEngine;
public class DiscordController : MonoBehaviour { [System.Serializable]
public DiscordRpc.RichPresence presence; public class DiscordJoinEvent : UnityEngine.Events.UnityEvent<string> { }
[System.Serializable]
public class DiscordSpectateEvent : UnityEngine.Events.UnityEvent<string> { }
[System.Serializable]
public class DiscordJoinRequestEvent : UnityEngine.Events.UnityEvent<DiscordRpc.DiscordUser> { }
public class DiscordController : MonoBehaviour
{
public DiscordRpc.RichPresence presence = new DiscordRpc.RichPresence();
public string applicationId; public string applicationId;
public string optionalSteamId; public string optionalSteamId;
public int callbackCalls;
public int clickCounter; public int clickCounter;
public DiscordRpc.DiscordUser joinRequest;
public UnityEngine.Events.UnityEvent onConnect; public UnityEngine.Events.UnityEvent onConnect;
public UnityEngine.Events.UnityEvent onDisconnect; public UnityEngine.Events.UnityEvent onDisconnect;
public UnityEngine.Events.UnityEvent hasResponded;
public DiscordJoinEvent onJoin;
public DiscordJoinEvent onSpectate;
public DiscordJoinRequestEvent onJoinRequest;
DiscordRpc.EventHandlers handlers; DiscordRpc.EventHandlers handlers;
@ -17,60 +31,84 @@ public class DiscordController : MonoBehaviour {
clickCounter++; clickCounter++;
presence.details = string.Format("Button clicked {0} times", 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: responding yes to Ask to Join request");
Debug.Log("Discord: ready"); 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(); onConnect.Invoke();
} }
public void DisconnectedCallback(int errorCode, string message) public void DisconnectedCallback(int errorCode, string message)
{ {
++callbackCalls;
Debug.Log(string.Format("Discord: disconnect {0}: {1}", errorCode, message)); Debug.Log(string.Format("Discord: disconnect {0}: {1}", errorCode, message));
onDisconnect.Invoke(); onDisconnect.Invoke();
} }
public void ErrorCallback(int errorCode, string message) public void ErrorCallback(int errorCode, string message)
{ {
++callbackCalls;
Debug.Log(string.Format("Discord: error {0}: {1}", errorCode, message)); Debug.Log(string.Format("Discord: error {0}: {1}", errorCode, message));
} }
public void JoinCallback(string secret) public void JoinCallback(string secret)
{ {
++callbackCalls;
Debug.Log(string.Format("Discord: join ({0})", secret)); Debug.Log(string.Format("Discord: join ({0})", secret));
onJoin.Invoke(secret);
} }
public void SpectateCallback(string secret) public void SpectateCallback(string secret)
{ {
++callbackCalls;
Debug.Log(string.Format("Discord: spectate ({0})", secret)); Debug.Log(string.Format("Discord: spectate ({0})", secret));
onSpectate.Invoke(secret);
} }
void Start () { public void RequestCallback(ref DiscordRpc.DiscordUser request)
{
Debug.Log(string.Format("Discord: join request {0}#{1}: {2}", request.username, request.discriminator, request.userId));
joinRequest = request;
onJoinRequest.Invoke(request);
} }
void Update () { void Start()
{
}
void Update()
{
DiscordRpc.RunCallbacks(); DiscordRpc.RunCallbacks();
} }
void OnEnable() void OnEnable()
{ {
Debug.Log("Discord: init"); Debug.Log("Discord: init");
callbackCalls = 0;
handlers = new DiscordRpc.EventHandlers(); handlers = new DiscordRpc.EventHandlers();
handlers.readyCallback = ReadyCallback; handlers.readyCallback += ReadyCallback;
handlers.disconnectedCallback += DisconnectedCallback; handlers.disconnectedCallback += DisconnectedCallback;
handlers.errorCallback += ErrorCallback; handlers.errorCallback += ErrorCallback;
handlers.joinCallback += JoinCallback; handlers.joinCallback += JoinCallback;
handlers.spectateCallback += SpectateCallback; handlers.spectateCallback += SpectateCallback;
handlers.requestCallback += RequestCallback;
DiscordRpc.Initialize(applicationId, ref handlers, true, optionalSteamId); DiscordRpc.Initialize(applicationId, ref handlers, true, optionalSteamId);
} }

View File

@ -1,34 +1,138 @@
using System.Runtime.InteropServices; using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using AOT;
public class DiscordRpc public class DiscordRpc
{ {
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [MonoPInvokeCallback(typeof(OnReadyInfo))]
public delegate void ReadyCallback(); public static void ReadyCallback(ref DiscordUser connectedUser) { Callbacks.readyCallback(ref connectedUser); }
public delegate void OnReadyInfo(ref DiscordUser connectedUser);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [MonoPInvokeCallback(typeof(OnDisconnectedInfo))]
public delegate void DisconnectedCallback(int errorCode, string message); public static void DisconnectedCallback(int errorCode, string message) { Callbacks.disconnectedCallback(errorCode, message); }
public delegate void OnDisconnectedInfo(int errorCode, string message);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [MonoPInvokeCallback(typeof(OnErrorInfo))]
public delegate void ErrorCallback(int errorCode, string message); public static void ErrorCallback(int errorCode, string message) { Callbacks.errorCallback(errorCode, message); }
public delegate void OnErrorInfo(int errorCode, string message);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [MonoPInvokeCallback(typeof(OnJoinInfo))]
public delegate void JoinCallback(string secret); public static void JoinCallback(string secret) { Callbacks.joinCallback(secret); }
public delegate void OnJoinInfo(string secret);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [MonoPInvokeCallback(typeof(OnSpectateInfo))]
public delegate void SpectateCallback(string secret); public static void SpectateCallback(string secret) { Callbacks.spectateCallback(secret); }
public delegate void OnSpectateInfo(string secret);
[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 struct EventHandlers
{ {
public ReadyCallback readyCallback; public OnReadyInfo readyCallback;
public DisconnectedCallback disconnectedCallback; public OnDisconnectedInfo disconnectedCallback;
public ErrorCallback errorCallback; public OnErrorInfo errorCallback;
public JoinCallback joinCallback; public OnJoinInfo joinCallback;
public SpectateCallback spectateCallback; public OnSpectateInfo spectateCallback;
public OnRequestInfo requestCallback;
} }
[System.Serializable] [Serializable, StructLayout(LayoutKind.Sequential)]
public struct RichPresence public struct RichPresenceStruct
{ {
public IntPtr state; /* max 128 bytes */
public IntPtr details; /* max 128 bytes */
public long startTimestamp;
public long endTimestamp;
public IntPtr largeImageKey; /* max 32 bytes */
public IntPtr largeImageText; /* max 128 bytes */
public IntPtr smallImageKey; /* max 32 bytes */
public IntPtr smallImageText; /* max 128 bytes */
public IntPtr partyId; /* max 128 bytes */
public int partySize;
public int partyMax;
public int partyPrivacy;
public IntPtr matchSecret; /* max 128 bytes */
public IntPtr joinSecret; /* max 128 bytes */
public IntPtr spectateSecret; /* max 128 bytes */
public bool instance;
}
[Serializable]
public struct DiscordUser
{
public string userId;
public string username;
public string discriminator;
public string avatar;
}
public enum Reply
{
No = 0,
Yes = 1,
Ignore = 2
}
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)]
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();
[DllImport("discord-rpc", EntryPoint = "Discord_RunCallbacks", CallingConvention = CallingConvention.Cdecl)]
public static extern void RunCallbacks();
[DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence", CallingConvention = CallingConvention.Cdecl)]
private static extern void UpdatePresenceNative(ref RichPresenceStruct presence);
[DllImport("discord-rpc", EntryPoint = "Discord_ClearPresence", CallingConvention = CallingConvention.Cdecl)]
public static extern void ClearPresence();
[DllImport("discord-rpc", EntryPoint = "Discord_Respond", CallingConvention = CallingConvention.Cdecl)]
public static extern void Respond(string userId, Reply reply);
[DllImport("discord-rpc", EntryPoint = "Discord_UpdateHandlers", CallingConvention = CallingConvention.Cdecl)]
public static extern void UpdateHandlers(ref EventHandlers handlers);
public static void UpdatePresence(RichPresence presence)
{
var presencestruct = presence.GetStruct();
UpdatePresenceNative(ref presencestruct);
presence.FreeMem();
}
public class RichPresence
{
private RichPresenceStruct _presence;
private readonly List<IntPtr> _buffers = new List<IntPtr>(10);
public string state; /* max 128 bytes */ public string state; /* max 128 bytes */
public string details; /* max 128 bytes */ public string details; /* max 128 bytes */
public long startTimestamp; public long startTimestamp;
@ -40,22 +144,88 @@ public class DiscordRpc
public string partyId; /* max 128 bytes */ public string partyId; /* max 128 bytes */
public int partySize; public int partySize;
public int partyMax; public int partyMax;
public PartyPrivacy partyPrivacy;
public string matchSecret; /* max 128 bytes */ public string matchSecret; /* max 128 bytes */
public string joinSecret; /* max 128 bytes */ public string joinSecret; /* max 128 bytes */
public string spectateSecret; /* max 128 bytes */ public string spectateSecret; /* max 128 bytes */
public bool instance; 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();
} }
[DllImport("discord-rpc", EntryPoint = "Discord_Initialize", CallingConvention = CallingConvention.Cdecl)] _presence.state = StrToPtr(state);
public static extern void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId); _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;
[DllImport("discord-rpc", EntryPoint = "Discord_Shutdown", CallingConvention = CallingConvention.Cdecl)] return _presence;
public static extern void Shutdown();
[DllImport("discord-rpc", EntryPoint = "Discord_RunCallbacks", CallingConvention = CallingConvention.Cdecl)]
public static extern void RunCallbacks();
[DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence", CallingConvention = CallingConvention.Cdecl)]
public static extern void UpdatePresence(ref RichPresence 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);
}
}
}
}

View File

@ -0,0 +1,101 @@
using UnityEditor;
using System.Diagnostics;
using System.IO;
[InitializeOnLoad]
public class ScriptBatch
{
static ScriptBatch()
{
EnsureDLL();
}
public static bool FileExists(string filename)
{
return new FileInfo(filename).Exists;
}
public static bool RunRpcBuildScript()
{
UnityEngine.Debug.Log("Try to run build script");
Process proc = new Process();
#if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
proc.StartInfo.UseShellExecute = false;
// brew installs cmake in /usr/local/bin, which Unity seems to strip from PATH?
string newPath = proc.StartInfo.EnvironmentVariables["PATH"] + ":/usr/local/bin";
proc.StartInfo.EnvironmentVariables["PATH"] = newPath;
#endif
proc.StartInfo.FileName = "python";
proc.StartInfo.Arguments = "build.py unity";
proc.StartInfo.WorkingDirectory = "../..";
proc.Start();
proc.WaitForExit();
return proc.ExitCode == 0;
}
public static void EnsureDLL()
{
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
string[] dstDirs = { "Assets/Plugins", "Assets/Plugins/x86", "Assets/Plugins/x86_64" };
string[] dstDlls = { "Assets/Plugins/x86/discord-rpc.dll", "Assets/Plugins/x86_64/discord-rpc.dll" };
string[] srcDlls = { "../../builds/install/win64-dynamic/bin/discord-rpc.dll", "../../builds/install/win64-dynamic/bin/discord-rpc.dll" };
#elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
string[] dstDirs = { "Assets/Plugins" };
string[] dstDlls = { "Assets/Plugins/discord-rpc.bundle" };
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/discord-rpc.so" };
string[] srcDlls = { "../../builds/install/linux-dynamic/lib/libdiscord-rpc.so" };
#endif
Debug.Assert(dstDlls.Length == srcDlls.Length);
bool exists = true;
foreach (string fname in dstDlls)
{
if (!FileExists(fname))
{
exists = false;
break;
}
}
if (exists)
{
return;
}
exists = true;
foreach (string fname in srcDlls)
{
if (!FileExists(fname))
{
exists = false;
break;
}
}
if (!exists)
{
if (!RunRpcBuildScript())
{
UnityEngine.Debug.LogError("Build failed");
return;
}
}
// make sure the dirs exist
foreach (string dirname in dstDirs)
{
Directory.CreateDirectory(dirname);
}
// Copy dlls
for (int i = 0; i < dstDlls.Length; ++i)
{
FileUtil.CopyFileOrDirectory(srcDlls[i], dstDlls[i]);
}
}
}

View File

@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: e5aecc4633e5f594b85eaa39f49bb402
timeCreated: 1512071254
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: 4ddcc1759a3a2394fa1fa376963639e0
folderAsset: yes
timeCreated: 1501697278
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,27 +0,0 @@
fileFormatVersion: 2
guid: 2aadd6305b09fa94dab94261a8bb8caf
timeCreated: 1501697340
licenseType: Free
PluginImporter:
serializedVersion: 2
iconMap: {}
executionOrder: {}
isPreloaded: 0
isOverridable: 0
platformData:
data:
first:
Any:
second:
enabled: 1
settings: {}
data:
first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
userData:
assetBundleName:
assetBundleVariant:

View File

@ -271,6 +271,80 @@ CanvasRenderer:
m_PrefabParentObject: {fileID: 0} m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0} m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 359174702} 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 --- !u!1 &657463235
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -345,6 +419,128 @@ RectTransform:
m_AnchoredPosition: {x: 16, y: -19.00003} m_AnchoredPosition: {x: 16, y: -19.00003}
m_SizeDelta: {x: 239.20001, y: 37.799988} m_SizeDelta: {x: 239.20001, y: 37.799988}
m_Pivot: {x: 0, y: 1} 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 --- !u!1 &1032248338
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -467,6 +663,80 @@ CanvasRenderer:
m_PrefabParentObject: {fileID: 0} m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0} m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1032248338} 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 --- !u!1 &1470895131
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -616,6 +886,9 @@ RectTransform:
m_Children: m_Children:
- {fileID: 1032248339} - {fileID: 1032248339}
- {fileID: 657463238} - {fileID: 657463238}
- {fileID: 806911717}
- {fileID: 1858885002}
- {fileID: 1238162987}
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_RootOrder: 1 m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
@ -624,6 +897,128 @@ RectTransform:
m_AnchoredPosition: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0}
m_Pivot: {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 --- !u!1 &1929635628
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -653,7 +1048,7 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
presence: presence:
state: Button clicking state: Button clicking
details: details: Best game ever
startTimestamp: 0 startTimestamp: 0
endTimestamp: 0 endTimestamp: 0
largeImageKey: stable-large largeImageKey: stable-large
@ -663,14 +1058,19 @@ MonoBehaviour:
partyId: abcdefg partyId: abcdefg
partySize: 1 partySize: 1
partyMax: 10 partyMax: 10
matchSecret: matchSecret: match_secret
joinSecret: joinSecret: join_secret
spectateSecret: spectateSecret: spectate_secret
instance: 0 instance: 1
applicationId: 345229890980937739 applicationId: 345229890980937739
optionalSteamId: optionalSteamId:
callbackCalls: 0 callbackCalls: 0
clickCounter: 0 clickCounter: 0
joinRequest:
userId:
username:
discriminator:
avatar:
onConnect: onConnect:
m_PersistentCalls: m_PersistentCalls:
m_Calls: m_Calls:
@ -703,6 +1103,92 @@ MonoBehaviour:
m_CallState: 2 m_CallState: 2
m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine, Version=0.0.0.0, Culture=neutral, m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null 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: []
m_TypeName: DiscordJoinEvent, Assembly-CSharp, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null
onSpectate:
m_PersistentCalls:
m_Calls: []
m_TypeName: DiscordJoinEvent, Assembly-CSharp, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null
onJoinRequest:
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: 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 --- !u!4 &1929635630
Transform: Transform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -716,3 +1202,77 @@ Transform:
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_RootOrder: 3 m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 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}

View File

@ -17,3 +17,4 @@ PhysicsManager:
m_EnablePCM: 1 m_EnablePCM: 1
m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
m_AutoSimulation: 1 m_AutoSimulation: 1
m_AutoSyncTransforms: 1

View File

@ -4,4 +4,7 @@
EditorBuildSettings: EditorBuildSettings:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
serializedVersion: 2 serializedVersion: 2
m_Scenes: [] m_Scenes:
- enabled: 1
path: Assets/main.unity
guid: 3b03d21bb25fa494e8694cd6e4b6d769

View File

@ -24,6 +24,7 @@ Physics2DSettings:
m_QueriesStartInColliders: 1 m_QueriesStartInColliders: 1
m_ChangeStopsCallbacks: 0 m_ChangeStopsCallbacks: 0
m_CallbacksOnDisable: 1 m_CallbacksOnDisable: 1
m_AutoSyncTransforms: 1
m_AlwaysShowColliders: 0 m_AlwaysShowColliders: 0
m_ShowColliderSleep: 1 m_ShowColliderSleep: 1
m_ShowColliderContacts: 0 m_ShowColliderContacts: 0

View File

@ -3,14 +3,15 @@
--- !u!129 &1 --- !u!129 &1
PlayerSettings: PlayerSettings:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
serializedVersion: 12 serializedVersion: 13
productGUID: 5eccc60d3e382a346a65f512d6b81b84 productGUID: 5eccc60d3e382a346a65f512d6b81b84
AndroidProfiler: 0 AndroidProfiler: 0
AndroidFilterTouchesWhenObscured: 0
defaultScreenOrientation: 4 defaultScreenOrientation: 4
targetDevice: 2 targetDevice: 2
useOnDemandResources: 0 useOnDemandResources: 0
accelerometerFrequency: 60 accelerometerFrequency: 60
companyName: DefaultCompany companyName: Discord Inc.
productName: button-clicker productName: button-clicker
defaultCursor: {fileID: 0} defaultCursor: {fileID: 0}
cursorHotspot: {x: 0, y: 0} cursorHotspot: {x: 0, y: 0}
@ -38,8 +39,6 @@ PlayerSettings:
width: 1 width: 1
height: 1 height: 1
m_SplashScreenLogos: [] m_SplashScreenLogos: []
m_SplashScreenBackgroundLandscape: {fileID: 0}
m_SplashScreenBackgroundPortrait: {fileID: 0}
m_VirtualRealitySplashScreen: {fileID: 0} m_VirtualRealitySplashScreen: {fileID: 0}
m_HolographicTrackingLossScreen: {fileID: 0} m_HolographicTrackingLossScreen: {fileID: 0}
defaultScreenWidth: 1024 defaultScreenWidth: 1024
@ -49,7 +48,6 @@ PlayerSettings:
m_StereoRenderingPath: 0 m_StereoRenderingPath: 0
m_ActiveColorSpace: 0 m_ActiveColorSpace: 0
m_MTRendering: 1 m_MTRendering: 1
m_MobileMTRendering: 0
m_StackTraceTypes: 010000000100000001000000010000000100000001000000 m_StackTraceTypes: 010000000100000001000000010000000100000001000000
iosShowActivityIndicatorOnLoading: -1 iosShowActivityIndicatorOnLoading: -1
androidShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1
@ -64,8 +62,10 @@ PlayerSettings:
useOSAutorotation: 1 useOSAutorotation: 1
use32BitDisplayBuffer: 1 use32BitDisplayBuffer: 1
disableDepthAndStencilBuffers: 0 disableDepthAndStencilBuffers: 0
androidBlitType: 0
defaultIsFullScreen: 1 defaultIsFullScreen: 1
defaultIsNativeResolution: 1 defaultIsNativeResolution: 1
macRetinaSupport: 1
runInBackground: 0 runInBackground: 0
captureSingleScreen: 0 captureSingleScreen: 0
muteOtherAudioSources: 0 muteOtherAudioSources: 0
@ -95,6 +95,7 @@ PlayerSettings:
xboxEnableHeadOrientation: 0 xboxEnableHeadOrientation: 0
xboxEnableGuest: 0 xboxEnableGuest: 0
xboxEnablePIXSampling: 0 xboxEnablePIXSampling: 0
metalFramebufferOnly: 0
n3dsDisableStereoscopicView: 0 n3dsDisableStereoscopicView: 0
n3dsEnableSharedListOpt: 1 n3dsEnableSharedListOpt: 1
n3dsEnableVSync: 0 n3dsEnableVSync: 0
@ -103,6 +104,7 @@ PlayerSettings:
xboxOneMonoLoggingLevel: 0 xboxOneMonoLoggingLevel: 0
xboxOneLoggingLevel: 1 xboxOneLoggingLevel: 1
xboxOneDisableEsram: 0 xboxOneDisableEsram: 0
xboxOnePresentImmediateThreshold: 0
videoMemoryForVertexBuffers: 0 videoMemoryForVertexBuffers: 0
psp2PowerMode: 0 psp2PowerMode: 0
psp2AcquireBGM: 1 psp2AcquireBGM: 1
@ -134,12 +136,17 @@ PlayerSettings:
daydream: daydream:
depthFormat: 0 depthFormat: 0
useSustainedPerformanceMode: 0 useSustainedPerformanceMode: 0
enableVideoLayer: 0
useProtectedVideoMemory: 0
hololens: hololens:
depthFormat: 1 depthFormat: 1
protectGraphicsMemory: 0 protectGraphicsMemory: 0
useHDRDisplay: 0 useHDRDisplay: 0
m_ColorGamuts: 00000000
targetPixelDensity: 0 targetPixelDensity: 0
resolutionScalingMode: 0 resolutionScalingMode: 0
androidSupportedAspectRatio: 1
androidMaxAspectRatio: 2.1
applicationIdentifier: {} applicationIdentifier: {}
buildNumber: {} buildNumber: {}
AndroidBundleVersionCode: 1 AndroidBundleVersionCode: 1
@ -160,10 +167,10 @@ PlayerSettings:
serializedVersion: 2 serializedVersion: 2
m_Bits: 238 m_Bits: 238
iPhoneSdkVersion: 988 iPhoneSdkVersion: 988
iOSTargetOSVersionString: iOSTargetOSVersionString: 7.0
tvOSSdkVersion: 0 tvOSSdkVersion: 0
tvOSRequireExtendedGameController: 0 tvOSRequireExtendedGameController: 0
tvOSTargetOSVersionString: tvOSTargetOSVersionString: 9.0
uIPrerenderedIcon: 0 uIPrerenderedIcon: 0
uIRequiresPersistentWiFi: 0 uIRequiresPersistentWiFi: 0
uIRequiresFullScreen: 1 uIRequiresFullScreen: 1
@ -220,7 +227,9 @@ PlayerSettings:
AndroidKeyaliasName: AndroidKeyaliasName:
AndroidTVCompatibility: 1 AndroidTVCompatibility: 1
AndroidIsGame: 1 AndroidIsGame: 1
AndroidEnableTango: 0
androidEnableBanner: 1 androidEnableBanner: 1
androidUseLowAccuracyLocation: 0
m_AndroidBanners: m_AndroidBanners:
- width: 320 - width: 320
height: 180 height: 180
@ -231,10 +240,14 @@ PlayerSettings:
m_BuildTargetBatching: [] m_BuildTargetBatching: []
m_BuildTargetGraphicsAPIs: [] m_BuildTargetGraphicsAPIs: []
m_BuildTargetVRSettings: [] m_BuildTargetVRSettings: []
m_BuildTargetEnableVuforiaSettings: []
openGLRequireES31: 0 openGLRequireES31: 0
openGLRequireES31AEP: 0 openGLRequireES31AEP: 0
webPlayerTemplate: APPLICATION:Default
m_TemplateCustomTags: {} m_TemplateCustomTags: {}
mobileMTRendering:
Android: 1
iPhone: 1
tvOS: 1
wiiUTitleID: 0005000011000000 wiiUTitleID: 0005000011000000
wiiUGroupID: 00010000 wiiUGroupID: 00010000
wiiUCommonSaveSize: 4096 wiiUCommonSaveSize: 4096
@ -368,6 +381,9 @@ PlayerSettings:
switchUdpSendBufferSize: 9 switchUdpSendBufferSize: 9
switchUdpReceiveBufferSize: 42 switchUdpReceiveBufferSize: 42
switchSocketBufferEfficiency: 4 switchSocketBufferEfficiency: 4
switchSocketInitializeEnabled: 1
switchNetworkInterfaceManagerInitializeEnabled: 1
switchPlayerConnectionEnabled: 1
ps4NPAgeRating: 12 ps4NPAgeRating: 12
ps4NPTitleSecret: ps4NPTitleSecret:
ps4NPTrophyPackPath: ps4NPTrophyPackPath:

View File

@ -1 +1 @@
m_EditorVersion: 2017.1.0f3 m_EditorVersion: 2017.2.0f3

View File

@ -0,0 +1,4 @@
{
"dependencies": {
}
}

View File

@ -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 */ #define _CRT_SECURE_NO_WARNINGS /* thanks Microsoft */
@ -9,11 +9,12 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include "discord-rpc.h" #include "discord_rpc.h"
static const char* APPLICATION_ID = "345229890980937739"; static const char* APPLICATION_ID = "345229890980937739";
static int FrustrationLevel = 0; static int FrustrationLevel = 0;
static int64_t StartTime; static int64_t StartTime;
static int SendPresence = 1;
static int prompt(char* line, size_t size) static int prompt(char* line, size_t size)
{ {
@ -32,6 +33,7 @@ static int prompt(char* line, size_t size)
static void updateDiscordPresence() static void updateDiscordPresence()
{ {
if (SendPresence) {
char buffer[256]; char buffer[256];
DiscordRichPresence discordPresence; DiscordRichPresence discordPresence;
memset(&discordPresence, 0, sizeof(discordPresence)); memset(&discordPresence, 0, sizeof(discordPresence));
@ -45,16 +47,24 @@ static void updateDiscordPresence()
discordPresence.partyId = "party1234"; discordPresence.partyId = "party1234";
discordPresence.partySize = 1; discordPresence.partySize = 1;
discordPresence.partyMax = 6; discordPresence.partyMax = 6;
discordPresence.partyPrivacy = DISCORD_PARTY_PUBLIC;
discordPresence.matchSecret = "xyzzy"; discordPresence.matchSecret = "xyzzy";
discordPresence.joinSecret = "join"; discordPresence.joinSecret = "join";
discordPresence.spectateSecret = "look"; discordPresence.spectateSecret = "look";
discordPresence.instance = 0; discordPresence.instance = 0;
Discord_UpdatePresence(&discordPresence); 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) static void handleDiscordDisconnected(int errcode, const char* message)
@ -77,13 +87,13 @@ static void handleDiscordSpectate(const char* secret)
printf("\nDiscord: spectate (%s)\n", secret); printf("\nDiscord: spectate (%s)\n", secret);
} }
static void handleDiscordJoinRequest(const DiscordJoinRequest* request) static void handleDiscordJoinRequest(const DiscordUser* request)
{ {
int response = -1; int response = -1;
char yn[4]; char yn[4];
printf("\nDiscord: join request from %s - %s - %s\n", printf("\nDiscord: join request from %s#%s - %s\n",
request->username, request->username,
request->avatarUrl, request->discriminator,
request->userId); request->userId);
do { do {
printf("Accept? (y/n)"); printf("Accept? (y/n)");
@ -143,6 +153,19 @@ static void gameLoop()
continue; 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') { if (line[0] == 'y') {
printf("Reinit Discord.\n"); printf("Reinit Discord.\n");
discordInit(); discordInit();

View File

@ -73,3 +73,6 @@ Intermediate/
# Cache files for the editor to use # Cache files for the editor to use
DerivedDataCache/ DerivedDataCache/
# Library headers must be copied automatically by the build script (build.py unreal)
Plugins/DiscordRpc/Source/ThirdParty/DiscordRpcLibrary/Include

View File

@ -15,9 +15,15 @@
"Installed": false, "Installed": false,
"Modules": [ "Modules": [
{ {
"Name": "discordrpc", "Name": "DiscordRpc",
"Type": "Developer", "Type": "Runtime",
"LoadingPhase": "Default" "LoadingPhase": "PreDefault",
"WhitelistPlatforms" :
[
"Win64",
"Linux",
"Mac"
]
} }
] ]
} }

View File

@ -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"));
}
}

View File

@ -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)

View File

@ -1,18 +1,27 @@
#include "DiscordRpcPrivatePCH.h"
#include "DiscordRpcBlueprint.h" #include "DiscordRpcBlueprint.h"
#include "discord_rpc.h"
#include "discord-rpc.h"
DEFINE_LOG_CATEGORY(Discord) DEFINE_LOG_CATEGORY(Discord)
static UDiscordRpc* self = nullptr; 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) { if (self) {
self->IsConnected = true; self->IsConnected = true;
self->OnConnected.Broadcast(); self->OnConnected.Broadcast(ud);
} }
} }
@ -53,6 +62,24 @@ static void SpectateGameHandler(const char* spectateSecret)
} }
} }
static void JoinRequestHandler(const DiscordUser* request)
{
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(ud);
}
}
void UDiscordRpc::Initialize(const FString& applicationId, void UDiscordRpc::Initialize(const FString& applicationId,
bool autoRegister, bool autoRegister,
const FString& optionalSteamId) const FString& optionalSteamId)
@ -69,6 +96,9 @@ void UDiscordRpc::Initialize(const FString& applicationId,
if (OnSpectate.IsBound()) { if (OnSpectate.IsBound()) {
handlers.spectateGame = SpectateGameHandler; handlers.spectateGame = SpectateGameHandler;
} }
if (OnJoinRequest.IsBound()) {
handlers.joinRequest = JoinRequestHandler;
}
auto appId = StringCast<ANSICHAR>(*applicationId); auto appId = StringCast<ANSICHAR>(*applicationId);
auto steamId = StringCast<ANSICHAR>(*optionalSteamId); auto steamId = StringCast<ANSICHAR>(*optionalSteamId);
Discord_Initialize( Discord_Initialize(
@ -119,12 +149,24 @@ void UDiscordRpc::UpdatePresence()
auto spectateSecret = StringCast<ANSICHAR>(*RichPresence.spectateSecret); auto spectateSecret = StringCast<ANSICHAR>(*RichPresence.spectateSecret);
rp.spectateSecret = spectateSecret.Get(); rp.spectateSecret = spectateSecret.Get();
rp.startTimestamp = RichPresence.startTimestamp; rp.startTimestamp = RichPresence.startTimestamp;
rp.endTimestamp = RichPresence.endTimestamp; rp.endTimestamp = RichPresence.endTimestamp;
rp.partySize = RichPresence.partySize; rp.partySize = RichPresence.partySize;
rp.partyMax = RichPresence.partyMax; rp.partyMax = RichPresence.partyMax;
rp.partyPrivacy = (int)RichPresence.partyPrivacy;
rp.instance = RichPresence.instance; rp.instance = RichPresence.instance;
Discord_UpdatePresence(&rp); 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);
}

View File

@ -0,0 +1,2 @@
#include "Core.h"
#include "DiscordRpc.h"

View File

@ -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);
};

View File

@ -1,21 +1,58 @@
#pragma once #pragma once
#include "Engine.h"
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "Engine.h"
#include "DiscordRpcBlueprint.generated.h" #include "DiscordRpcBlueprint.generated.h"
// unreal's header tool hates clang-format // unreal's header tool hates clang-format
// clang-format off // clang-format off
/**
* Ask to join callback data
*/
USTRUCT(BlueprintType)
struct FDiscordUserData {
GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadOnly)
FString userId;
UPROPERTY(BlueprintReadOnly)
FString username;
UPROPERTY(BlueprintReadOnly)
FString discriminator;
UPROPERTY(BlueprintReadOnly)
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_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(FDiscordDisconnected, int, errorCode, const FString&, errorMessage);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordErrored, 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(FDiscordJoin, const FString&, joinSecret);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordSpectate, const FString&, spectateSecret); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordSpectate, const FString&, spectateSecret);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoinRequest, const FDiscordUserData&, joinRequest);
// clang-format on // clang-format on
@ -50,6 +87,8 @@ struct FDiscordRichPresence {
UPROPERTY(BlueprintReadWrite) UPROPERTY(BlueprintReadWrite)
int partyMax; int partyMax;
UPROPERTY(BlueprintReadWrite) UPROPERTY(BlueprintReadWrite)
EDiscordPartyPrivacy partyPrivacy;
UPROPERTY(BlueprintReadWrite)
FString matchSecret; FString matchSecret;
UPROPERTY(BlueprintReadWrite) UPROPERTY(BlueprintReadWrite)
FString joinSecret; FString joinSecret;
@ -89,6 +128,16 @@ public:
Category = "Discord") Category = "Discord")
void UpdatePresence(); 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, UPROPERTY(BlueprintReadOnly,
meta = (DisplayName = "Is Discord connected", Keywords = "Discord rpc"), meta = (DisplayName = "Is Discord connected", Keywords = "Discord rpc"),
Category = "Discord") Category = "Discord")
@ -119,6 +168,12 @@ public:
Category = "Discord") Category = "Discord")
FDiscordSpectate OnSpectate; FDiscordSpectate OnSpectate;
UPROPERTY(BlueprintAssignable,
meta = (DisplayName = "When Discord another user sends a join request",
Keywords = "Discord rpc"),
Category = "Discord")
FDiscordJoinRequest OnJoinRequest;
UPROPERTY(BlueprintReadWrite, UPROPERTY(BlueprintReadWrite,
meta = (DisplayName = "Rich presence info", Keywords = "Discord rpc"), meta = (DisplayName = "Rich presence info", Keywords = "Discord rpc"),
Category = "Discord") Category = "Discord")

View File

@ -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")));
}
}
}

View File

@ -1,26 +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
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"));
}
}
}

View File

@ -1,50 +0,0 @@
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "discordrpc.h"
#include "Core.h"
#include "ModuleManager.h"
#include "IPluginManager.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)

View File

@ -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;
};

View File

@ -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 ...
}
);
}
}

View File

@ -1,6 +1,6 @@
{ {
"FileVersion": 3, "FileVersion": 3,
"EngineAssociation": "4.16", "EngineAssociation": "4.18",
"Category": "", "Category": "",
"Description": "", "Description": "",
"Modules": [ "Modules": [

View 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

View File

@ -35,30 +35,34 @@ typedef struct DiscordRichPresence {
const char* partyId; /* max 128 bytes */ const char* partyId; /* max 128 bytes */
int partySize; int partySize;
int partyMax; int partyMax;
int partyPrivacy;
const char* matchSecret; /* max 128 bytes */ const char* matchSecret; /* max 128 bytes */
const char* joinSecret; /* max 128 bytes */ const char* joinSecret; /* max 128 bytes */
const char* spectateSecret; /* max 128 bytes */ const char* spectateSecret; /* max 128 bytes */
int8_t instance; int8_t instance;
} DiscordRichPresence; } DiscordRichPresence;
typedef struct DiscordJoinRequest { typedef struct DiscordUser {
char userId[24]; const char* userId;
char username[48]; const char* username;
char avatarUrl[128]; const char* discriminator;
} DiscordJoinRequest; const char* avatar;
} DiscordUser;
typedef struct DiscordEventHandlers { typedef struct DiscordEventHandlers {
void (*ready)(); void (*ready)(const DiscordUser* request);
void (*disconnected)(int errorCode, const char* message); void (*disconnected)(int errorCode, const char* message);
void (*errored)(int errorCode, const char* message); void (*errored)(int errorCode, const char* message);
void (*joinGame)(const char* joinSecret); void (*joinGame)(const char* joinSecret);
void (*spectateGame)(const char* spectateSecret); void (*spectateGame)(const char* spectateSecret);
void (*joinRequest)(const DiscordJoinRequest* request); void (*joinRequest)(const DiscordUser* request);
} DiscordEventHandlers; } DiscordEventHandlers;
#define DISCORD_REPLY_NO 0 #define DISCORD_REPLY_NO 0
#define DISCORD_REPLY_YES 1 #define DISCORD_REPLY_YES 1
#define DISCORD_REPLY_IGNORE 2 #define DISCORD_REPLY_IGNORE 2
#define DISCORD_PARTY_PRIVATE 0
#define DISCORD_PARTY_PUBLIC 1
DISCORD_EXPORT void Discord_Initialize(const char* applicationId, DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
DiscordEventHandlers* handlers, DiscordEventHandlers* handlers,
@ -75,9 +79,12 @@ DISCORD_EXPORT void Discord_UpdateConnection(void);
#endif #endif
DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); 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_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply);
DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */
#endif #endif

View File

@ -1,12 +1,15 @@
include_directories(${PROJECT_SOURCE_DIR}/include) 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(ENABLE_IO_THREAD "Start up a separate I/O thread, otherwise I'd need to call an update function" ON)
option(BUILD_DYNAMIC_LIB "Build library as a DLL" OFF) 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 set(BASE_RPC_SRC
${PROJECT_SOURCE_DIR}/include/discord-rpc.h ${PROJECT_SOURCE_DIR}/include/discord_rpc.h
discord-rpc.cpp discord_rpc.cpp
discord_register.h ${PROJECT_SOURCE_DIR}/include/discord_register.h
rpc_connection.h rpc_connection.h
rpc_connection.cpp rpc_connection.cpp
serialization.h serialization.h
@ -16,19 +19,28 @@ set(BASE_RPC_SRC
msg_queue.h msg_queue.h
) )
if (${BUILD_DYNAMIC_LIB}) if (${BUILD_SHARED_LIBS})
set(RPC_LIBRARY_TYPE SHARED)
if(WIN32) if(WIN32)
set(BASE_RPC_SRC ${BASE_RPC_SRC} dllmain.cpp) set(BASE_RPC_SRC ${BASE_RPC_SRC} dllmain.cpp)
endif(WIN32) endif(WIN32)
else(${BUILD_DYNAMIC_LIB}) endif(${BUILD_SHARED_LIBS})
set(RPC_LIBRARY_TYPE STATIC)
endif(${BUILD_DYNAMIC_LIB})
if(WIN32) if(WIN32)
add_definitions(-DDISCORD_WINDOWS) add_definitions(-DDISCORD_WINDOWS)
set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_win.cpp discord_register_win.cpp) set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_win.cpp discord_register_win.cpp)
add_library(discord-rpc ${RPC_LIBRARY_TYPE} ${BASE_RPC_SRC}) add_library(discord-rpc ${BASE_RPC_SRC})
if (MSVC)
if(USE_STATIC_CRT)
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 target_compile_options(discord-rpc PRIVATE /EHsc
/Wall /Wall
/wd4100 # unreferenced formal parameter /wd4100 # unreferenced formal parameter
@ -43,6 +55,8 @@ if(WIN32)
/wd4946 # reinterpret_cast used between related classes /wd4946 # reinterpret_cast used between related classes
/wd5027 # move assignment operator was implicitly defined as deleted /wd5027 # move assignment operator was implicitly defined as deleted
) )
endif(MSVC)
target_link_libraries(discord-rpc PRIVATE psapi advapi32)
endif(WIN32) endif(WIN32)
if(UNIX) if(UNIX)
@ -56,11 +70,25 @@ if(UNIX)
set(BASE_RPC_SRC ${BASE_RPC_SRC} discord_register_linux.cpp) set(BASE_RPC_SRC ${BASE_RPC_SRC} discord_register_linux.cpp)
endif(APPLE) endif(APPLE)
add_library(discord-rpc ${RPC_LIBRARY_TYPE} ${BASE_RPC_SRC}) add_library(discord-rpc ${BASE_RPC_SRC})
target_link_libraries(discord-rpc PUBLIC pthread) 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 target_compile_options(discord-rpc PRIVATE
-g -g
-Weverything -Wall
-Wextra
-Wpedantic
)
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-unknown-pragmas # pragma push thing doesn't work on clang
-Wno-old-style-cast # it's fine -Wno-old-style-cast # it's fine
-Wno-c++98-compat # that was almost 2 decades ago -Wno-c++98-compat # that was almost 2 decades ago
@ -71,7 +99,10 @@ if(UNIX)
-Wno-exit-time-destructors # not sure about these -Wno-exit-time-destructors # not sure about these
-Wno-global-constructors -Wno-global-constructors
) )
target_compile_options(discord-rpc PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-std=c++14>)
if (${BUILD_SHARED_LIBS})
target_compile_options(discord-rpc PRIVATE -fPIC)
endif (${BUILD_SHARED_LIBS})
if (APPLE) if (APPLE)
target_link_libraries(discord-rpc PRIVATE "-framework AppKit") target_link_libraries(discord-rpc PRIVATE "-framework AppKit")
@ -81,15 +112,17 @@ endif(UNIX)
target_include_directories(discord-rpc PRIVATE ${RAPIDJSON}/include) target_include_directories(discord-rpc PRIVATE ${RAPIDJSON}/include)
if (NOT ${ENABLE_IO_THREAD}) if (NOT ${ENABLE_IO_THREAD})
add_definitions(discord-rpc PUBLIC -DDISCORD_DISABLE_IO_THREAD) target_compile_definitions(discord-rpc PUBLIC -DDISCORD_DISABLE_IO_THREAD)
endif (NOT ${ENABLE_IO_THREAD}) endif (NOT ${ENABLE_IO_THREAD})
if (${BUILD_DYNAMIC_LIB}) if (${BUILD_SHARED_LIBS})
target_compile_definitions(discord-rpc PUBLIC -DDISCORD_DYNAMIC_LIB) target_compile_definitions(discord-rpc PUBLIC -DDISCORD_DYNAMIC_LIB)
target_compile_definitions(discord-rpc PRIVATE -DDISCORD_BUILDING_SDK) target_compile_definitions(discord-rpc PRIVATE -DDISCORD_BUILDING_SDK)
endif(${BUILD_DYNAMIC_LIB}) endif(${BUILD_SHARED_LIBS})
if (CLANG_FORMAT_CMD)
add_dependencies(discord-rpc clangformat) add_dependencies(discord-rpc clangformat)
endif(CLANG_FORMAT_CMD)
# install # install
@ -97,20 +130,18 @@ install(
TARGETS discord-rpc TARGETS discord-rpc
EXPORT "discord-rpc" EXPORT "discord-rpc"
RUNTIME RUNTIME
DESTINATION "bin" DESTINATION "${CMAKE_INSTALL_BINDIR}"
CONFIGURATIONS Release
LIBRARY LIBRARY
DESTINATION "lib" DESTINATION "${CMAKE_INSTALL_LIBDIR}"
CONFIGURATIONS Release
ARCHIVE ARCHIVE
DESTINATION "lib" DESTINATION "${CMAKE_INSTALL_LIBDIR}"
CONFIGURATIONS Release
INCLUDES INCLUDES
DESTINATION "include" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
) )
install( install(
FILES FILES
"../include/discord-rpc.h" "../include/discord_rpc.h"
"../include/discord_register.h"
DESTINATION "include" DESTINATION "include"
) )

View File

@ -1,8 +1,8 @@
#pragma once #pragma once
#include <stdint.h>
#include <algorithm> #include <algorithm>
#include <random> #include <random>
#include <stdint.h>
#include <time.h> #include <time.h>
struct Backoff { struct Backoff {

View File

@ -118,5 +118,8 @@ bool BaseConnection::Read(void* data, size_t length)
} }
Close(); Close();
} }
else if (res == 0) {
Close();
}
return res == (int)length; return res == (int)length;
} }

View File

@ -4,8 +4,8 @@
#define NOMCX #define NOMCX
#define NOSERVICE #define NOSERVICE
#define NOIME #define NOIME
#include <windows.h>
#include <assert.h> #include <assert.h>
#include <windows.h>
int GetProcessId() int GetProcessId()
{ {

View File

@ -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

View File

@ -1,4 +1,5 @@
#include "discord-rpc.h" #include "discord_rpc.h"
#include "discord_register.h"
#include <stdio.h> #include <stdio.h>
#include <errno.h> #include <errno.h>
@ -8,7 +9,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
bool Mkdir(const char* path) static bool Mkdir(const char* path)
{ {
int result = mkdir(path, 0755); int result = mkdir(path, 0755);
if (result == 0) { 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>:// // 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. // 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]; char exePath[1024];
if (!command || !command[0]) { 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; return;
} }
exePath[size] = '\0';
command = exePath; command = exePath;
} }
const char* destopFileFormat = "[Desktop Entry]\n" const char* desktopFileFormat = "[Desktop Entry]\n"
"Name=Game %s\n" "Name=Game %s\n"
"Exec=%s %%u\n" // note: it really wants that %u in there "Exec=%s %%u\n" // note: it really wants that %u in there
"Type=Application\n" "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"; "MimeType=x-scheme-handler/discord-%s;\n";
char desktopFile[2048]; char desktopFile[2048];
int fileLen = snprintf( int fileLen = snprintf(
desktopFile, sizeof(desktopFile), destopFileFormat, applicationId, command, applicationId); desktopFile, sizeof(desktopFile), desktopFileFormat, applicationId, command, applicationId);
if (fileLen <= 0) { if (fileLen <= 0) {
return; return;
} }
@ -85,12 +88,15 @@ extern "C" void Discord_Register(const char* applicationId, const char* command)
"xdg-mime default discord-%s.desktop x-scheme-handler/discord-%s", "xdg-mime default discord-%s.desktop x-scheme-handler/discord-%s",
applicationId, applicationId,
applicationId); applicationId);
system(xdgMimeCommand); if (system(xdgMimeCommand) < 0) {
fprintf(stderr, "Failed to register mime handler\n");
}
} }
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]; char command[256];
sprintf(command, "xdg-open steam://run/%s", steamId); sprintf(command, "xdg-open steam://rungameid/%s", steamId);
Discord_Register(applicationId, command); Discord_Register(applicationId, command);
} }

View File

@ -5,45 +5,28 @@
#include "discord_register.h" #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) 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 // 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 // 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) // 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) { if (!home) {
return; return;
} }
char path[2048]; NSString *path = [[[[[[home stringByAppendingPathComponent:@"Library"]
sprintf(path, "%s/Library/Application Support/discord", home); stringByAppendingPathComponent:@"Application Support"]
Mkdir(path); stringByAppendingPathComponent:@"discord"]
strcat(path, "/games"); stringByAppendingPathComponent:@"games"]
Mkdir(path); stringByAppendingPathComponent:[NSString stringWithUTF8String:applicationId]]
strcat(path, "/"); stringByAppendingPathExtension:@"json"];
strcat(path, applicationId); [[NSFileManager defaultManager] createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
strcat(path, ".json");
FILE* f = fopen(path, "w"); NSString *jsonBuffer = [NSString stringWithFormat:@"{\"command\": \"%s\"}", command];
if (f) { [jsonBuffer writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:nil];
char jsonBuffer[2048];
int len = snprintf(jsonBuffer, sizeof(jsonBuffer), "{\"command\": \"%s\"}", command);
fwrite(jsonBuffer, (size_t)len, 1, f);
fclose(f);
}
} }
static void RegisterURL(const char* applicationId) static void RegisterURL(const char* applicationId)
@ -83,15 +66,15 @@ void Discord_Register(const char* applicationId, const char* command)
} }
else { else {
// raii lite // raii lite
void* pool = [[NSAutoreleasePool alloc] init]; @autoreleasepool {
RegisterURL(applicationId); RegisterURL(applicationId);
[(id)pool drain]; }
} }
} }
void Discord_RegisterSteamGame(const char* applicationId, const char* steamId) void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
{ {
char command[256]; char command[256];
sprintf(command, "steam://run/%s", steamId); snprintf(command, 256, "steam://rungameid/%s", steamId);
Discord_Register(applicationId, command); Discord_Register(applicationId, command);
} }

View File

@ -1,30 +1,88 @@
#include "discord-rpc.h" #include "discord_rpc.h"
#include <stdio.h> #include "discord_register.h"
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#define NOMCX #define NOMCX
#define NOSERVICE #define NOSERVICE
#define NOIME #define NOIME
#include <windows.h> #include <windows.h>
#include <Psapi.h> #include <psapi.h>
#include <Strsafe.h> #include <cstdio>
#pragma comment(lib, "Psapi.lib")
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 // 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>:// // we want to register games so we can run them as discord-<appid>://
// Update the HKEY_CURRENT_USER, because it doesn't seem to require special permissions. // Update the HKEY_CURRENT_USER, because it doesn't seem to require special permissions.
wchar_t exeFilePath[MAX_PATH]; wchar_t exeFilePath[MAX_PATH];
DWORD exeLen = GetModuleFileNameExW(GetCurrentProcess(), nullptr, exeFilePath, MAX_PATH); DWORD exeLen = GetModuleFileNameW(nullptr, exeFilePath, MAX_PATH);
wchar_t openCommand[1024]; wchar_t openCommand[1024];
if (command && command[0]) { if (command && command[0]) {
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command); StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command);
} }
else { else {
lstrcpyW(openCommand, exeFilePath); // StringCbCopyW(openCommand, sizeof(openCommand), exeFilePath);
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", exeFilePath);
} }
wchar_t protocolName[64]; wchar_t protocolName[64];
@ -73,7 +131,7 @@ void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command)
RegCloseKey(key); 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]; wchar_t appId[32];
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32); MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
@ -89,7 +147,8 @@ extern "C" void Discord_Register(const char* applicationId, const char* command)
Discord_RegisterW(appId, wcommand); 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]; wchar_t appId[32];
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32); MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
@ -121,7 +180,7 @@ extern "C" void Discord_RegisterSteamGame(const char* applicationId, const char*
} }
wchar_t command[1024]; wchar_t command[1024];
StringCbPrintfW(command, sizeof(command), L"\"%s\" steam://run/%s", steamPath, wSteamId); StringCbPrintfW(command, sizeof(command), L"\"%s\" steam://rungameid/%s", steamPath, wSteamId);
Discord_RegisterW(appId, command); Discord_RegisterW(appId, command);
} }

View File

@ -1,10 +1,10 @@
#include "discord-rpc.h" #include "discord_rpc.h"
#include "backoff.h" #include "backoff.h"
#include "discord_register.h" #include "discord_register.h"
#include "msg_queue.h"
#include "rpc_connection.h" #include "rpc_connection.h"
#include "serialization.h" #include "serialization.h"
#include "msg_queue.h"
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
@ -32,13 +32,29 @@ struct QueuedMessage {
} }
}; };
struct User {
// snowflake (64bit int), turned into a ascii decimal string, at most 20 chars +1 null
// terminator = 21
char userId[32];
// 32 unicode glyphs is max name size => 4 bytes per glyph in the worst case, +1 for null
// terminator = 129
char username[344];
// 4 decimal digits + 1 null terminator = 5
char discriminator[8];
// optional 'a_' + md5 hex digest (32 bytes) + null terminator = 35
char avatar[128];
// Rounded way up because I'm paranoid about games breaking from future changes in these sizes
};
static RpcConnection* Connection{nullptr}; static RpcConnection* Connection{nullptr};
static DiscordEventHandlers QueuedHandlers{};
static DiscordEventHandlers Handlers{}; static DiscordEventHandlers Handlers{};
static std::atomic_bool WasJustConnected{false}; static std::atomic_bool WasJustConnected{false};
static std::atomic_bool WasJustDisconnected{false}; static std::atomic_bool WasJustDisconnected{false};
static std::atomic_bool GotErrorMessage{false}; static std::atomic_bool GotErrorMessage{false};
static std::atomic_bool WasJoinGame{false}; static std::atomic_bool WasJoinGame{false};
static std::atomic_bool WasSpectateGame{false}; static std::atomic_bool WasSpectateGame{false};
static std::atomic_bool UpdatePresence{false};
static char JoinGameSecret[256]; static char JoinGameSecret[256];
static char SpectateGameSecret[256]; static char SpectateGameSecret[256];
static int LastErrorCode{0}; static int LastErrorCode{0};
@ -46,23 +62,65 @@ static char LastErrorMessage[256];
static int LastDisconnectErrorCode{0}; static int LastDisconnectErrorCode{0};
static char LastDisconnectErrorMessage[256]; static char LastDisconnectErrorMessage[256];
static std::mutex PresenceMutex; static std::mutex PresenceMutex;
static std::mutex HandlerMutex;
static QueuedMessage QueuedPresence{}; static QueuedMessage QueuedPresence{};
static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue; static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue;
static MsgQueue<DiscordJoinRequest, 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 // 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 // backoff from 0.5 seconds to 1 minute
static Backoff ReconnectTimeMs(500, 60 * 1000); static Backoff ReconnectTimeMs(500, 60 * 1000);
static auto NextConnect{std::chrono::system_clock::now()}; static auto NextConnect = std::chrono::system_clock::now();
static int Pid{0}; static int Pid{0};
static int Nonce{1}; static int Nonce{1};
#ifndef DISCORD_DISABLE_IO_THREAD #ifndef DISCORD_DISABLE_IO_THREAD
static std::atomic_bool KeepRunning{true}; static void Discord_UpdateConnection(void);
static std::mutex WaitForIOMutex; class IoThreadHolder {
static std::condition_variable WaitForIOActivity; private:
static std::thread IoThread; std::atomic_bool keepRunning{true};
std::mutex waitForIOMutex;
std::condition_variable waitForIOActivity;
std::thread ioThread;
public:
void Start()
{
keepRunning.store(true);
ioThread = std::thread([&]() {
const std::chrono::duration<int64_t, std::milli> maxWait{500LL};
Discord_UpdateConnection();
while (keepRunning.load()) {
std::unique_lock<std::mutex> lock(waitForIOMutex);
waitForIOActivity.wait_for(lock, maxWait);
Discord_UpdateConnection();
}
});
}
void Notify() { waitForIOActivity.notify_all(); }
void Stop()
{
keepRunning.exchange(false);
Notify();
if (ioThread.joinable()) {
ioThread.join();
}
}
~IoThreadHolder() { Stop(); }
};
#else
class IoThreadHolder {
public:
void Start() {}
void Stop() {}
void Notify() {}
};
#endif // DISCORD_DISABLE_IO_THREAD #endif // DISCORD_DISABLE_IO_THREAD
static IoThreadHolder* IoThread{nullptr};
static void UpdateReconnectTime() static void UpdateReconnectTime()
{ {
@ -71,7 +129,7 @@ static void UpdateReconnectTime()
} }
#ifdef DISCORD_DISABLE_IO_THREAD #ifdef DISCORD_DISABLE_IO_THREAD
DISCORD_EXPORT void Discord_UpdateConnection(void) extern "C" DISCORD_EXPORT void Discord_UpdateConnection(void)
#else #else
static void Discord_UpdateConnection(void) static void Discord_UpdateConnection(void)
#endif #endif
@ -115,8 +173,9 @@ static void Discord_UpdateConnection(void)
continue; continue;
} }
if (strcmp(evtName, "ACTIVITY_JOIN") == 0) {
auto data = GetObjMember(&message, "data"); auto data = GetObjMember(&message, "data");
if (strcmp(evtName, "ACTIVITY_JOIN") == 0) {
auto secret = GetStrMember(data, "secret"); auto secret = GetStrMember(data, "secret");
if (secret) { if (secret) {
StringCopy(JoinGameSecret, secret); StringCopy(JoinGameSecret, secret);
@ -124,7 +183,6 @@ static void Discord_UpdateConnection(void)
} }
} }
else if (strcmp(evtName, "ACTIVITY_SPECTATE") == 0) { else if (strcmp(evtName, "ACTIVITY_SPECTATE") == 0) {
auto data = GetObjMember(&message, "data");
auto secret = GetStrMember(data, "secret"); auto secret = GetStrMember(data, "secret");
if (secret) { if (secret) {
StringCopy(SpectateGameSecret, secret); StringCopy(SpectateGameSecret, secret);
@ -132,20 +190,23 @@ static void Discord_UpdateConnection(void)
} }
} }
else if (strcmp(evtName, "ACTIVITY_JOIN_REQUEST") == 0) { else if (strcmp(evtName, "ACTIVITY_JOIN_REQUEST") == 0) {
auto data = GetObjMember(&message, "data");
auto user = GetObjMember(data, "user"); auto user = GetObjMember(data, "user");
auto userId = GetStrMember(user, "id"); auto userId = GetStrMember(user, "id");
auto username = GetStrMember(user, "username"); auto username = GetStrMember(user, "username");
auto avatarUrl = GetStrMember(user, "avatar"); auto avatar = GetStrMember(user, "avatar");
auto joinReq = JoinAskQueue.GetNextAddMessage(); auto joinReq = JoinAskQueue.GetNextAddMessage();
if (userId && username && joinReq) { if (userId && username && joinReq) {
StringCopy(joinReq->userId, userId); StringCopy(joinReq->userId, userId);
StringCopy(joinReq->username, username); StringCopy(joinReq->username, username);
if (avatarUrl) { auto discriminator = GetStrMember(user, "discriminator");
StringCopy(joinReq->avatarUrl, avatarUrl); if (discriminator) {
StringCopy(joinReq->discriminator, discriminator);
}
if (avatar) {
StringCopy(joinReq->avatar, avatar);
} }
else { else {
joinReq->avatarUrl[0] = 0; joinReq->avatar[0] = 0;
} }
JoinAskQueue.CommitAdd(); JoinAskQueue.CommitAdd();
} }
@ -154,17 +215,17 @@ static void Discord_UpdateConnection(void)
} }
// writes // writes
if (QueuedPresence.length) { if (UpdatePresence.exchange(false) && QueuedPresence.length) {
QueuedMessage local; QueuedMessage local;
PresenceMutex.lock(); {
std::lock_guard<std::mutex> guard(PresenceMutex);
local.Copy(QueuedPresence); local.Copy(QueuedPresence);
QueuedPresence.length = 0; }
PresenceMutex.unlock();
if (!Connection->Write(local.buffer, local.length)) { if (!Connection->Write(local.buffer, local.length)) {
// if we fail to send, requeue // if we fail to send, requeue
PresenceMutex.lock(); std::lock_guard<std::mutex> guard(PresenceMutex);
QueuedPresence.Copy(local); QueuedPresence.Copy(local);
PresenceMutex.unlock(); UpdatePresence.exchange(true);
} }
} }
@ -176,25 +237,11 @@ static void Discord_UpdateConnection(void)
} }
} }
#ifndef DISCORD_DISABLE_IO_THREAD
static void DiscordRpcIo(void)
{
const std::chrono::duration<int64_t, std::milli> maxWait{500LL};
while (KeepRunning.load()) {
Discord_UpdateConnection();
std::unique_lock<std::mutex> lock(WaitForIOMutex);
WaitForIOActivity.wait_for(lock, maxWait);
}
}
#endif
static void SignalIOActivity() static void SignalIOActivity()
{ {
#ifndef DISCORD_DISABLE_IO_THREAD if (IoThread != nullptr) {
WaitForIOActivity.notify_all(); IoThread->Notify();
#endif }
} }
static bool RegisterForEvent(const char* evtName) static bool RegisterForEvent(const char* evtName)
@ -210,11 +257,29 @@ static bool RegisterForEvent(const char* evtName)
return false; return false;
} }
DISCORD_EXPORT void Discord_Initialize(const char* applicationId, 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, DiscordEventHandlers* handlers,
int autoRegister, int autoRegister,
const char* optionalSteamId) const char* optionalSteamId)
{ {
IoThread = new (std::nothrow) IoThreadHolder();
if (IoThread == nullptr) {
return;
}
if (autoRegister) { if (autoRegister) {
if (optionalSteamId && optionalSteamId[0]) { if (optionalSteamId && optionalSteamId[0]) {
Discord_RegisterSteamGame(applicationId, optionalSteamId); Discord_RegisterSteamGame(applicationId, optionalSteamId);
@ -226,10 +291,16 @@ DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
Pid = GetProcessId(); Pid = GetProcessId();
{
std::lock_guard<std::mutex> guard(HandlerMutex);
if (handlers) { if (handlers) {
Handlers = *handlers; QueuedHandlers = *handlers;
} }
else { else {
QueuedHandlers = {};
}
Handlers = {}; Handlers = {};
} }
@ -238,21 +309,33 @@ DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
} }
Connection = RpcConnection::Create(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); WasJustConnected.exchange(true);
ReconnectTimeMs.reset(); 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) { Connection->onDisconnect = [](int err, const char* message) {
LastDisconnectErrorCode = err; LastDisconnectErrorCode = err;
@ -261,13 +344,10 @@ DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
UpdateReconnectTime(); UpdateReconnectTime();
}; };
#ifndef DISCORD_DISABLE_IO_THREAD IoThread->Start();
KeepRunning.store(true);
IoThread = std::thread(DiscordRpcIo);
#endif
} }
DISCORD_EXPORT void Discord_Shutdown() extern "C" DISCORD_EXPORT void Discord_Shutdown(void)
{ {
if (!Connection) { if (!Connection) {
return; return;
@ -275,26 +355,34 @@ DISCORD_EXPORT void Discord_Shutdown()
Connection->onConnect = nullptr; Connection->onConnect = nullptr;
Connection->onDisconnect = nullptr; Connection->onDisconnect = nullptr;
Handlers = {}; Handlers = {};
#ifndef DISCORD_DISABLE_IO_THREAD QueuedPresence.length = 0;
KeepRunning.exchange(false); UpdatePresence.exchange(false);
SignalIOActivity(); if (IoThread != nullptr) {
if (IoThread.joinable()) { IoThread->Stop();
IoThread.join(); delete IoThread;
IoThread = nullptr;
} }
#endif
RpcConnection::Destroy(Connection); RpcConnection::Destroy(Connection);
} }
DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence) extern "C" DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence)
{ {
PresenceMutex.lock(); {
std::lock_guard<std::mutex> guard(PresenceMutex);
QueuedPresence.length = JsonWriteRichPresenceObj( QueuedPresence.length = JsonWriteRichPresenceObj(
QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence); QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
PresenceMutex.unlock(); UpdatePresence.exchange(true);
}
SignalIOActivity(); SignalIOActivity();
} }
DISCORD_EXPORT void Discord_Respond(const char* userId, /* DISCORD_REPLY_ */ int reply) 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 // if we are not connected, let's not batch up stale messages for later
if (!Connection || !Connection->IsOpen()) { if (!Connection || !Connection->IsOpen()) {
@ -309,7 +397,7 @@ DISCORD_EXPORT void Discord_Respond(const char* userId, /* DISCORD_REPLY_ */ int
} }
} }
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 // 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 // of times inbetween calls here. Externally, we want the sequence to seem sane, so any other
@ -324,26 +412,43 @@ DISCORD_EXPORT void Discord_RunCallbacks()
if (isConnected) { if (isConnected) {
// if we are connected, disconnect cb first // if we are connected, disconnect cb first
std::lock_guard<std::mutex> guard(HandlerMutex);
if (wasDisconnected && Handlers.disconnected) { if (wasDisconnected && Handlers.disconnected) {
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage); Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
} }
} }
if (WasJustConnected.exchange(false) && Handlers.ready) { if (WasJustConnected.exchange(false)) {
Handlers.ready(); 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) { if (GotErrorMessage.exchange(false)) {
std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.errored) {
Handlers.errored(LastErrorCode, LastErrorMessage); Handlers.errored(LastErrorCode, LastErrorMessage);
} }
if (WasJoinGame.exchange(false) && Handlers.joinGame) {
Handlers.joinGame(JoinGameSecret);
} }
if (WasSpectateGame.exchange(false) && Handlers.spectateGame) { if (WasJoinGame.exchange(false)) {
std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.joinGame) {
Handlers.joinGame(JoinGameSecret);
}
}
if (WasSpectateGame.exchange(false)) {
std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.spectateGame) {
Handlers.spectateGame(SpectateGameSecret); Handlers.spectateGame(SpectateGameSecret);
} }
}
// Right now this batches up any requests and sends them all in a burst; I could imagine a world // Right now this batches up any requests and sends them all in a burst; I could imagine a world
// where the implementer would rather sequentially accept/reject each one before the next invite // where the implementer would rather sequentially accept/reject each one before the next invite
@ -352,16 +457,48 @@ DISCORD_EXPORT void Discord_RunCallbacks()
// not it should be trivial for the implementer to make a queue themselves. // not it should be trivial for the implementer to make a queue themselves.
while (JoinAskQueue.HavePendingSends()) { while (JoinAskQueue.HavePendingSends()) {
auto req = JoinAskQueue.GetNextSendMessage(); auto req = JoinAskQueue.GetNextSendMessage();
{
std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.joinRequest) { if (Handlers.joinRequest) {
Handlers.joinRequest(req); DiscordUser du{req->userId, req->username, req->discriminator, req->avatar};
Handlers.joinRequest(&du);
}
} }
JoinAskQueue.CommitSend(); JoinAskQueue.CommitSend();
} }
if (!isConnected) { if (!isConnected) {
// if we are not connected, disconnect message last // if we are not connected, disconnect message last
std::lock_guard<std::mutex> guard(HandlerMutex);
if (wasDisconnected && Handlers.disconnected) { if (wasDisconnected && Handlers.disconnected) {
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage); 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;
}

View File

@ -1,5 +1,7 @@
#include <windows.h> #include <windows.h>
// outsmart GCC's missing-declarations warning
BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID);
BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID) BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID)
{ {
return TRUE; return TRUE;

View File

@ -7,7 +7,7 @@
template <typename ElementType, size_t QueueSize> template <typename ElementType, size_t QueueSize>
class MsgQueue { class MsgQueue {
ElementType queue_[QueueSize]{}; ElementType queue_[QueueSize];
std::atomic_uint nextAdd_{0}; std::atomic_uint nextAdd_{0};
std::atomic_uint nextSend_{0}; std::atomic_uint nextSend_{0};
std::atomic_uint pendingSends_{0}; std::atomic_uint pendingSends_{0};

View File

@ -26,13 +26,9 @@ void RpcConnection::Open()
return; return;
} }
if (state == State::Disconnected) { if (state == State::Disconnected && !connection->Open()) {
if (connection->Open()) {
}
else {
return; return;
} }
}
if (state == State::SentHandshake) { if (state == State::SentHandshake) {
JsonDocument message; JsonDocument message;
@ -42,7 +38,7 @@ void RpcConnection::Open()
if (cmd && evt && !strcmp(cmd, "DISPATCH") && !strcmp(evt, "READY")) { if (cmd && evt && !strcmp(cmd, "DISPATCH") && !strcmp(evt, "READY")) {
state = State::Connected; state = State::Connected;
if (onConnect) { if (onConnect) {
onConnect(); onConnect(message);
} }
} }
} }

View File

@ -40,7 +40,7 @@ struct RpcConnection {
BaseConnection* connection{nullptr}; BaseConnection* connection{nullptr};
State state{State::Disconnected}; State state{State::Disconnected};
void (*onConnect)(){nullptr}; void (*onConnect)(JsonDocument& message){nullptr};
void (*onDisconnect)(int errorCode, const char* message){nullptr}; void (*onDisconnect)(int errorCode, const char* message){nullptr};
char appId[64]{}; char appId[64]{};
int lastErrorCode{0}; int lastErrorCode{0};

View File

@ -1,6 +1,6 @@
#include "serialization.h" #include "serialization.h"
#include "connection.h" #include "connection.h"
#include "discord-rpc.h" #include "discord_rpc.h"
template <typename T> template <typename T>
void NumberToString(char* dest, T number) void NumberToString(char* dest, T number)
@ -102,7 +102,7 @@ size_t JsonWriteRichPresenceObj(char* dest,
WriteKey(writer, "pid"); WriteKey(writer, "pid");
writer.Int(pid); writer.Int(pid);
{ if (presence != nullptr) {
WriteObject activity(writer, "activity"); WriteObject activity(writer, "activity");
WriteOptionalString(writer, "state", presence->state); WriteOptionalString(writer, "state", presence->state);
@ -134,15 +134,18 @@ size_t JsonWriteRichPresenceObj(char* dest,
} }
if ((presence->partyId && presence->partyId[0]) || presence->partySize || if ((presence->partyId && presence->partyId[0]) || presence->partySize ||
presence->partyMax) { presence->partyMax || presence->partyPrivacy) {
WriteObject party(writer, "party"); WriteObject party(writer, "party");
WriteOptionalString(writer, "id", presence->partyId); WriteOptionalString(writer, "id", presence->partyId);
if (presence->partySize) { if (presence->partySize && presence->partyMax) {
WriteArray size(writer, "size"); WriteArray size(writer, "size");
writer.Int(presence->partySize); 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(); 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) size_t JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce)
{ {
JsonWriter writer(dest, maxLen); JsonWriter writer(dest, maxLen);

View File

@ -2,6 +2,7 @@
#include <stdint.h> #include <stdint.h>
#ifndef __MINGW32__
#pragma warning(push) #pragma warning(push)
#pragma warning(disable : 4061) // enum is not explicitly handled by a case label #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 : 4464) // relative include path contains
#pragma warning(disable : 4668) // is not defined as a preprocessor macro #pragma warning(disable : 4668) // is not defined as a preprocessor macro
#pragma warning(disable : 6313) // Incorrect operator #pragma warning(disable : 6313) // Incorrect operator
#endif // __MINGW32__
#include "rapidjson/document.h" #include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h" #include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
#ifndef __MINGW32__
#pragma warning(pop) #pragma warning(pop)
#endif // __MINGW32__
// if only there was a standard library function for this // if only there was a standard library function for this
template <size_t Len> template <size_t Len>
@ -43,6 +47,8 @@ size_t JsonWriteRichPresenceObj(char* dest,
const DiscordRichPresence* presence); const DiscordRichPresence* presence);
size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName); 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); 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 // I want to use as few allocations as I can get away with, and to do that with RapidJson, you need
@ -79,6 +85,9 @@ public:
} }
// allocate how much you need in the first place // allocate how much you need in the first place
assert(!originalPtr && !originalSize); assert(!originalPtr && !originalSize);
// unused parameter warning
(void)(originalPtr);
(void)(originalSize);
return Malloc(newSize); return Malloc(newSize);
} }
static void Free(void* ptr) static void Free(void* ptr)