Compare commits
160 Commits
Author | SHA1 | Date | |
---|---|---|---|
94ee4e64d9 | |||
bd294d51a8 | |||
b85758ec19 | |||
ec6af6132d | |||
f99a260b07 | |||
2c609b1d5f | |||
839ba32671 | |||
6a59509b7b | |||
b0e31a9e25 | |||
2d0661c906 | |||
265ea814f5 | |||
8990824c9c | |||
8f9013cea6 | |||
085e0e7326 | |||
b3102db5c9 | |||
5438d6bf22 | |||
b9f9b08606 | |||
e5bdd61223 | |||
1555405d83 | |||
051a1eeb70 | |||
3852d83d12 | |||
d8122e7d69 | |||
060182f366 | |||
19ed45ac25 | |||
be7fda7c50 | |||
50ea4e61c6 | |||
82439911c6 | |||
0d24fabdf4 | |||
79eea99d19 | |||
c053b72f58 | |||
1a278528a2 | |||
c1a8899d02 | |||
b01d0a8af3 | |||
8cae35ea46 | |||
8307a1ad83 | |||
8af28e46be | |||
2af5adca3d | |||
7300d1caa8 | |||
7c3e28870e | |||
9130707086 | |||
49b23040c6 | |||
522c304b32 | |||
5d5bc82e26 | |||
49b7703334 | |||
706847dd47 | |||
2c566b208b | |||
95be02d4ce | |||
c834a2e6d9 | |||
6a963456ed | |||
c5d3481c47 | |||
2b248a6cef | |||
c9cf6b3f41 | |||
704c56d13f | |||
5085d23dd1 | |||
1675d5d2dc | |||
bf6e0fddd5 | |||
18f6d878e0 | |||
364606f7e9 | |||
b206dd44f0 | |||
d121bbe709 | |||
cfd6470946 | |||
f22f299330 | |||
767b15184f | |||
6e744d228f | |||
a1ab6c96f2 | |||
0ea7ddbd5a | |||
2f54e62c23 | |||
54abef2624 | |||
c5d70514ac | |||
57316cbaee | |||
9d4d145e7f | |||
7dc663a170 | |||
f872b4e49c | |||
ca5d70a5f9 | |||
ee9c504d1c | |||
127eadcb89 | |||
a7808a20ed | |||
3bdb88d918 | |||
aa79c70bf9 | |||
a089aab53e | |||
dafd85c39f | |||
b1d6a7c0fc | |||
e4b3ef63b7 | |||
86ca320cb9 | |||
6cdc830544 | |||
990c8d4be6 | |||
6fa00223ad | |||
39ff0bf3e4 | |||
25b6f1dcde | |||
e7cdfaa64e | |||
a5a56bcf68 | |||
11e74bca5e | |||
fb87e7c193 | |||
19abe80449 | |||
fa39179be7 | |||
3566190a01 | |||
592f134d80 | |||
794bbccd51 | |||
8bceae0a3a | |||
4cf8ca5670 | |||
e07806424b | |||
5882823830 | |||
4dcb4afd2e | |||
6dc12b82c6 | |||
432166ef1d | |||
bd995f047d | |||
63b467f81d | |||
9c6495ab3e | |||
21eb1e459b | |||
ff24776107 | |||
70cd486e13 | |||
7ca912a5ac | |||
10383339cc | |||
0144c8200c | |||
9b07431476 | |||
ad86b14b79 | |||
93510d492e | |||
8dab07035f | |||
b87c6c9b5b | |||
f45eb6c443 | |||
759af3a89e | |||
d102b33ed3 | |||
c0899fd512 | |||
dbb7b626b0 | |||
88f47486cb | |||
0271889b2e | |||
54bef63d67 | |||
c82cc94538 | |||
470259e8b0 | |||
f6853fac1b | |||
f06b187d2d | |||
ee11358d7f | |||
7324ae890c | |||
7fe7e2ab53 | |||
27e39a0ec5 | |||
60ad70adc2 | |||
a7eb65355e | |||
2311a26eb0 | |||
67a81b82cd | |||
1e971e1161 | |||
120fe1b069 | |||
d5e6c4c11a | |||
c3c27c730d | |||
1b7f782f57 | |||
059ab337c8 | |||
52bdc2714d | |||
98852fba82 | |||
e69f9fbf71 | |||
a6a9b6259f | |||
ef60ec40c3 | |||
6774b5d881 | |||
6b10bd6c51 | |||
93d9ba10c2 | |||
6338a572d3 | |||
9eb7e41c4b | |||
ffab428366 | |||
20ad7e4ced | |||
bfcfd10baa | |||
f617f3b78d | |||
1f6fd05884 |
@ -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
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
/build*/
|
/build*/
|
||||||
/.vscode/
|
/.vscode/
|
||||||
/thirdparty/
|
/thirdparty/
|
||||||
|
.vs/
|
||||||
|
47
.travis.yml
Normal file
47
.travis.yml
Normal 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 --config Release ..
|
||||||
|
- cmake --build . -- -j2
|
@ -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,22 +12,18 @@ 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
|
||||||
@ -32,7 +32,7 @@ execute_process(
|
|||||||
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_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_SOURCE_DIR}/thirdparty/v1.1.0.tar.gz)
|
||||||
@ -44,16 +44,13 @@ if (NOT RAPIDJSONTEST)
|
|||||||
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_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)
|
||||||
add_subdirectory(examples/send-presence)
|
if (BUILD_EXAMPLES)
|
||||||
|
add_subdirectory(examples/send-presence)
|
||||||
add_custom_target(bundle
|
endif(BUILD_EXAMPLES)
|
||||||
WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E tar cfvz "${CMAKE_BINARY_DIR}/discord-rpc.tar.gz" .
|
|
||||||
)
|
|
||||||
|
19
LICENSE
Normal file
19
LICENSE
Normal 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.
|
80
README.md
80
README.md
@ -1,35 +1,83 @@
|
|||||||
# Discord RPC
|
# Discord RPC
|
||||||
|
|
||||||
This is a lib and a couple of quick demos, one that implements the very minimal subset to show
|
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.
|
||||||
current status, and one that is more complete. The idea here is to give you an lib that implements
|
|
||||||
the rpc connection and wraps sending events, and a basic example that uses it; 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.
|
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).
|
||||||
|
|
||||||
## Usage
|
## Documentation
|
||||||
|
|
||||||
First, head on over to the [Discord developers site](https://discordapp.com/developers/applications/me)
|
The most up to date documentation for Rich Presence can always be found in our [developer site](https://discordapp.com/developers/docs/rich-presence/how-to)!
|
||||||
and make yourself an app. Keep track of `Client ID` -- you'll need it here.
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
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) and make yourself an app. Keep track of `Client ID` -- you'll need it here to pass to the init function.
|
||||||
|
|
||||||
### 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 file that should be able to generate the lib for you; I use it like this:
|
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
|
||||||
cd build
|
cd build
|
||||||
cmake .. -DCMAKE_INSTALL_PREFIX=/path/to/that
|
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 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 |
|
||||||
|
|------|---------|------|
|
||||||
|
| `ENABLE_IO_THREAD` | `ON` | When enabled, we start up a thread to do io processing, if disabled you should call `Discord_UpdateConnection` yourself.
|
||||||
|
| `USE_STATIC_CRT` | `OFF` | (Windows) Enable to statically link the CRT, avoiding requiring users install the redistributable package. (The prebuilt binaries enable this option)
|
||||||
|
| [`BUILD_SHARED_LIBS`](https://cmake.org/cmake/help/v3.7/variable/BUILD_SHARED_LIBS.html) | `OFF` | Build library as a DLL
|
||||||
|
|
||||||
|
## Continuous Builds
|
||||||
|
|
||||||
|
Why do we have three of these? Three times the fun!
|
||||||
|
|
||||||
|
| CI | badge |
|
||||||
|
|----|-------|
|
||||||
|
| TravisCI | [](https://travis-ci.org/discordapp/discord-rpc)
|
||||||
|
| AppVeyor | [](https://ci.appveyor.com/project/crmarsh/discord-rpc)
|
||||||
|
| Buildkite (internal) | [](https://buildkite.com/discord/discord-rpc)
|
||||||
|
|
||||||
## 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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Sample: unrealstatus
|
||||||
|
|
||||||
|
This is a sample [Unreal](https://www.unrealengine.com) project that wraps the DLL version of the library with an Unreal plugin, exposes a blueprint class for interacting with it, and uses that to make a very simple UI.
|
||||||
|
|
||||||
|
## 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.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/) |
|
||||||
|
| [SwordRPC](https://github.com/Azoy/SwordRPC) | [Swift](https://swift.org) |
|
||||||
|
17
appveyor.yml
Normal file
17
appveyor.yml
Normal 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
|
259
build.py
Executable file
259
build.py
Executable file
@ -0,0 +1,259 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import zipfile
|
||||||
|
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__))
|
||||||
|
# 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 = os.environ['WindowsSdkDir']
|
||||||
|
return os.path.join(sdk_dir, 'bin', 'x86', 'signtool.exe')
|
||||||
|
elif PLATFORM == 'osx':
|
||||||
|
return '/usr/bin/codesign'
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def cd(new_dir):
|
||||||
|
""" Temporarily change current directory """
|
||||||
|
if new_dir:
|
||||||
|
old_dir = os.getcwd()
|
||||||
|
os.chdir(new_dir)
|
||||||
|
yield
|
||||||
|
if new_dir:
|
||||||
|
os.chdir(old_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def mkdir_p(path):
|
||||||
|
""" mkdir -p """
|
||||||
|
if not os.path.isdir(path):
|
||||||
|
click.secho('Making ' + path, fg='yellow')
|
||||||
|
os.makedirs(path)
|
||||||
|
|
||||||
|
|
||||||
|
@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()
|
||||||
|
def unity():
|
||||||
|
""" todo: build unity project """
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.pass_context
|
||||||
|
def for_unity(ctx):
|
||||||
|
""" build just dynamic libs for use in unity project """
|
||||||
|
ctx.invoke(
|
||||||
|
libs,
|
||||||
|
clean=False,
|
||||||
|
static=False,
|
||||||
|
shared=True,
|
||||||
|
skip_formatter=True,
|
||||||
|
just_release=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
||||||
|
)
|
||||||
|
|
||||||
|
click.echo('--- Copying libs and header into unreal example')
|
||||||
|
|
||||||
|
UNREAL_PROJECT_PATH = os.path.join(SCRIPT_PATH, 'examples', 'unrealstatus', 'Plugins', 'discordrpc')
|
||||||
|
BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic', 'src', 'Release')
|
||||||
|
|
||||||
|
UNREAL_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Win64')
|
||||||
|
mkdir_p(UNREAL_DLL_PATH)
|
||||||
|
shutil.copy(os.path.join(BUILD_BASE_PATH, 'discord-rpc.dll'), UNREAL_DLL_PATH)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
UNREAL_LIB_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Win64')
|
||||||
|
mkdir_p(UNREAL_LIB_PATH)
|
||||||
|
shutil.copy(os.path.join(BUILD_BASE_PATH, 'discord-rpc.lib'), UNREAL_LIB_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
install_path = os.path.join(INSTALL_ROOT, build_name)
|
||||||
|
mkdir_p(build_path)
|
||||||
|
mkdir_p(install_path)
|
||||||
|
with cd(build_path):
|
||||||
|
initial_cmake = [
|
||||||
|
'cmake',
|
||||||
|
SCRIPT_PATH,
|
||||||
|
'-DCMAKE_INSTALL_PREFIX=%s' % os.path.join('..', 'install', build_name)
|
||||||
|
]
|
||||||
|
if generator:
|
||||||
|
initial_cmake.extend(['-G', generator])
|
||||||
|
for key in options:
|
||||||
|
val = options[key]
|
||||||
|
if type(val) is bool:
|
||||||
|
val = 'ON' if val else 'OFF'
|
||||||
|
initial_cmake.append('-D%s=%s' % (key, val))
|
||||||
|
click.echo('--- Building ' + build_name)
|
||||||
|
subprocess.check_call(initial_cmake)
|
||||||
|
if not just_release:
|
||||||
|
subprocess.check_call(['cmake', '--build', '.', '--config', 'Debug'])
|
||||||
|
subprocess.check_call(['cmake', '--build', '.', '--config', 'Release', '--target', 'install'])
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
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_src_base_path = INSTALL_ROOT
|
||||||
|
archive_dst_base_path = 'discord-rpc'
|
||||||
|
with cd(archive_src_base_path):
|
||||||
|
for path, _, filenames in os.walk('.'):
|
||||||
|
for fname in filenames:
|
||||||
|
fpath = os.path.join(path, fname)
|
||||||
|
dst_path = os.path.normpath(os.path.join(archive_dst_base_path, fpath))
|
||||||
|
click.echo('Adding ' + dst_path)
|
||||||
|
archive_file.write(fpath, dst_path)
|
||||||
|
|
||||||
|
|
||||||
|
@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', 'Hammer & Chisel 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('--static', is_flag=True)
|
||||||
|
@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:
|
||||||
|
shutil.rmtree('builds', ignore_errors=True)
|
||||||
|
|
||||||
|
mkdir_p('builds')
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if PLATFORM == 'win':
|
||||||
|
generator32 = 'Visual Studio 14 2015'
|
||||||
|
generator64 = 'Visual Studio 14 2015 Win64'
|
||||||
|
if static:
|
||||||
|
build_lib('win32-static', generator32, static_options, just_release)
|
||||||
|
build_lib('win64-static', generator64, static_options, just_release)
|
||||||
|
if shared:
|
||||||
|
build_lib('win32-dynamic', generator32, dynamic_options, just_release)
|
||||||
|
build_lib('win64-dynamic', generator64, dynamic_options, just_release)
|
||||||
|
elif PLATFORM == 'osx':
|
||||||
|
if static:
|
||||||
|
build_lib('osx-static', None, static_options, just_release)
|
||||||
|
if shared:
|
||||||
|
build_lib('osx-dynamic', None, dynamic_options, just_release)
|
||||||
|
elif PLATFORM == 'linux':
|
||||||
|
if static:
|
||||||
|
build_lib('linux-static', None, static_options, just_release)
|
||||||
|
if shared:
|
||||||
|
build_lib('linux-dynamic', None, dynamic_options, just_release)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
os.chdir(SCRIPT_PATH)
|
||||||
|
sys.exit(cli())
|
127
documentation/hard-mode.md
Normal file
127
documentation/hard-mode.md
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
# Hard Mode: Roll Your Own Client
|
||||||
|
|
||||||
|
Discord's Rich Presence feature is designed as an obfuscated addition to our existing [RPC infrastructure](https://discordapp.com/developers/docs/topics/rpc). The standalone library and header files make it easy for any dev to drop it into their game.
|
||||||
|
|
||||||
|
Our library communicates with Discord over the local Discord RPC socket. We've already done the work in connecting properly, handling disconnects and reconnects, and other RPC intracacies, but those who have done this implementation for our private alpha Voice and Chat SDK can simply make use of the new RPC commands and events to implement Rich Presence.
|
||||||
|
|
||||||
|
## Hark! A warning!
|
||||||
|
|
||||||
|
By committing to an RPC-only integration, you decide to forego the work our library and header file have done for you in the way of error handling, state storage, disconnecting and reconnecting, and other quality of life abstractions. While simply implementing the new RPC command and events will enable Rich Presence for your game, we highly suggest that you do your best to mimic the functionality of the SDK the most that you can. It ensure not only code quality on your part, but also an excellent experience on the part of your players.
|
||||||
|
|
||||||
|
## Application Protocol Registration
|
||||||
|
|
||||||
|
One thing that cannot be explicitly done over RPC is registering an application protocol for your game. If you choose to do an RPC-only implementation, you will have to register your application protocol yourself in the format of `discord-[your_app_id]://`. You can use `Discord_Register()` as a good(?) example of how to properly register an application protocol for use with Discord. For OSX and Linux it is probably simpler to handle the protocol registration as part of your install/packaging.
|
||||||
|
|
||||||
|
## New RPC Command
|
||||||
|
|
||||||
|
The new RPC command for Rich Presence is `SET_ACTIVITY`. The fields are similar to what is outlined in the SDK; we've combined similar fields into objects for the sake of less data on the wire.
|
||||||
|
|
||||||
|
The one major difference is the `party.size` field. It is an array with a size of two. The first element is the current party size, `partySize` from the main documentation. The second element is the maximum party size, `partyMax` from the main documentation.
|
||||||
|
|
||||||
|
Below is a full example of a `SET_ACTIVITY` command. Field restrictions like size are the same as outlined in the main documentation.
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"cmd": "SET_ACTIVITY",
|
||||||
|
"args": {
|
||||||
|
"pid": 9999, // Your application's process id - required field
|
||||||
|
"activity": {
|
||||||
|
"state": "In a Group",
|
||||||
|
"details": "Competitive | In a Match",
|
||||||
|
"timestamps": {
|
||||||
|
"start": time(nullptr),
|
||||||
|
"end": time(nullptr) + ((60 * 5) + 23)
|
||||||
|
},
|
||||||
|
"assets": {
|
||||||
|
"large_image": "numbani_map",
|
||||||
|
"large_text": "Numbani",
|
||||||
|
"small_image": "pharah_profile",
|
||||||
|
"small_text": "Pharah"
|
||||||
|
},
|
||||||
|
"party": {
|
||||||
|
"id": GameEngine.GetPartyId(),
|
||||||
|
"size": [3, 6]
|
||||||
|
},
|
||||||
|
"secrets": {
|
||||||
|
"join": "025ed05c71f639de8bfaa0d679d7c94b2fdce12f",
|
||||||
|
"spectate": "e7eb30d2ee025ed05c71ea495f770b76454ee4e0",
|
||||||
|
"match": "4b2fdce12f639de8bfa7e3591b71a0d679d7c93f"
|
||||||
|
},
|
||||||
|
"instance": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nonce": "647d814a-4cf8-4fbb-948f-898abd24f55b"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## New RPC Events
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
"cmd": "DISPATCH",
|
||||||
|
"data": {
|
||||||
|
"secret": "025ed05c71f639de8bfaa0d679d7c94b2fdce12f"
|
||||||
|
},
|
||||||
|
"evt": "ACTIVITY_JOIN"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Second is the `ACTIVITY_SPECTATE` event:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "DISPATCH",
|
||||||
|
"data": {
|
||||||
|
"secret": "e7eb30d2ee025ed05c71ea495f770b76454ee4e0"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"secret": "e459ca99273f59909dd16ed97865f3ad"
|
||||||
|
},
|
||||||
|
"evt": "ACTIVITY_JOIN_REQUEST"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to receive these events, you need to [subscribe](https://discordapp.com/developers/docs/topics/rpc#subscribe) to them like so:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nonce": "be9a6de3-31d0-4767-a8e9-4818c5690015",
|
||||||
|
"evt": "ACTIVITY_JOIN",
|
||||||
|
"cmd": "SUBSCRIBE"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nonce": "ae9qdde3-31d0-8989-a8e9-dnakwy174he",
|
||||||
|
"evt": "ACTIVITY_SPECTATE",
|
||||||
|
"cmd": "SUBSCRIBE"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nonce": "5dc0c062-98c6-47a0-8922-bbb52e9d6afa",
|
||||||
|
"evt": "ACTIVITY_JOIN_REQUEST",
|
||||||
|
"cmd": "SUBSCRIBE"
|
||||||
|
}
|
||||||
|
```
|
BIN
documentation/images/rp-dev-dashboard.png
Normal file
BIN
documentation/images/rp-dev-dashboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 318 KiB |
BIN
documentation/images/rp-profile-view.png
Normal file
BIN
documentation/images/rp-profile-view.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 MiB |
BIN
documentation/images/rp-secret-example.png
Normal file
BIN
documentation/images/rp-secret-example.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
8
examples/button-clicker/.gitignore
vendored
Normal file
8
examples/button-clicker/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/Library/
|
||||||
|
/Temp/
|
||||||
|
/obj/
|
||||||
|
/Assets/Plugins/
|
||||||
|
/Assets/Plugins.meta
|
||||||
|
*.sln
|
||||||
|
*.csproj
|
||||||
|
*.userprefs
|
101
examples/button-clicker/Assets/BuildHelper.cs
Normal file
101
examples/button-clicker/Assets/BuildHelper.cs
Normal 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 for_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/x86/discord-rpc.so", "Assets/Plugins/x86_64/discord-rpc.so" };
|
||||||
|
string[] srcDlls = { "../../builds/install/linux-dynamic/bin/discord-rpc.dll", "../../builds/install/win64-dynamic/bin/discord-rpc.dll" };
|
||||||
|
#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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
examples/button-clicker/Assets/BuildHelper.cs.meta
Normal file
13
examples/button-clicker/Assets/BuildHelper.cs.meta
Normal 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:
|
129
examples/button-clicker/Assets/DiscordController.cs
Normal file
129
examples/button-clicker/Assets/DiscordController.cs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
[System.Serializable]
|
||||||
|
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.JoinRequest> { }
|
||||||
|
|
||||||
|
public class DiscordController : MonoBehaviour
|
||||||
|
{
|
||||||
|
public DiscordRpc.RichPresence presence;
|
||||||
|
public string applicationId;
|
||||||
|
public string optionalSteamId;
|
||||||
|
public int callbackCalls;
|
||||||
|
public int clickCounter;
|
||||||
|
public DiscordRpc.JoinRequest joinRequest;
|
||||||
|
public UnityEngine.Events.UnityEvent onConnect;
|
||||||
|
public UnityEngine.Events.UnityEvent onDisconnect;
|
||||||
|
public UnityEngine.Events.UnityEvent hasResponded;
|
||||||
|
public DiscordJoinEvent onJoin;
|
||||||
|
public DiscordJoinEvent onSpectate;
|
||||||
|
public DiscordJoinRequestEvent onJoinRequest;
|
||||||
|
|
||||||
|
DiscordRpc.EventHandlers handlers;
|
||||||
|
|
||||||
|
public void OnClick()
|
||||||
|
{
|
||||||
|
Debug.Log("Discord: on click!");
|
||||||
|
clickCounter++;
|
||||||
|
|
||||||
|
presence.details = string.Format("Button clicked {0} times", clickCounter);
|
||||||
|
|
||||||
|
DiscordRpc.UpdatePresence(ref presence);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestRespondYes()
|
||||||
|
{
|
||||||
|
Debug.Log("Discord: responding yes to Ask to Join request");
|
||||||
|
DiscordRpc.Respond(joinRequest.userId, DiscordRpc.Reply.Yes);
|
||||||
|
hasResponded.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestRespondNo()
|
||||||
|
{
|
||||||
|
Debug.Log("Discord: responding no to Ask to Join request");
|
||||||
|
DiscordRpc.Respond(joinRequest.userId, DiscordRpc.Reply.No);
|
||||||
|
hasResponded.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReadyCallback()
|
||||||
|
{
|
||||||
|
++callbackCalls;
|
||||||
|
Debug.Log("Discord: ready");
|
||||||
|
onConnect.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisconnectedCallback(int errorCode, string message)
|
||||||
|
{
|
||||||
|
++callbackCalls;
|
||||||
|
Debug.Log(string.Format("Discord: disconnect {0}: {1}", errorCode, message));
|
||||||
|
onDisconnect.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ErrorCallback(int errorCode, string message)
|
||||||
|
{
|
||||||
|
++callbackCalls;
|
||||||
|
Debug.Log(string.Format("Discord: error {0}: {1}", errorCode, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void JoinCallback(string secret)
|
||||||
|
{
|
||||||
|
++callbackCalls;
|
||||||
|
Debug.Log(string.Format("Discord: join ({0})", secret));
|
||||||
|
onJoin.Invoke(secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SpectateCallback(string secret)
|
||||||
|
{
|
||||||
|
++callbackCalls;
|
||||||
|
Debug.Log(string.Format("Discord: spectate ({0})", secret));
|
||||||
|
onSpectate.Invoke(secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestCallback(ref DiscordRpc.JoinRequest request)
|
||||||
|
{
|
||||||
|
++callbackCalls;
|
||||||
|
Debug.Log(string.Format("Discord: join request {0}#{1}: {2}", request.username, request.discriminator, request.userId));
|
||||||
|
joinRequest = request;
|
||||||
|
onJoinRequest.Invoke(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
DiscordRpc.RunCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnEnable()
|
||||||
|
{
|
||||||
|
Debug.Log("Discord: init");
|
||||||
|
callbackCalls = 0;
|
||||||
|
|
||||||
|
handlers = new DiscordRpc.EventHandlers();
|
||||||
|
handlers.readyCallback = ReadyCallback;
|
||||||
|
handlers.disconnectedCallback += DisconnectedCallback;
|
||||||
|
handlers.errorCallback += ErrorCallback;
|
||||||
|
handlers.joinCallback += JoinCallback;
|
||||||
|
handlers.spectateCallback += SpectateCallback;
|
||||||
|
handlers.requestCallback += RequestCallback;
|
||||||
|
DiscordRpc.Initialize(applicationId, ref handlers, true, optionalSteamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDisable()
|
||||||
|
{
|
||||||
|
Debug.Log("Discord: shutdown");
|
||||||
|
DiscordRpc.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDestroy()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
12
examples/button-clicker/Assets/DiscordController.cs.meta
Normal file
12
examples/button-clicker/Assets/DiscordController.cs.meta
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 27f0a5f59ffffa84c86547736e2e730a
|
||||||
|
timeCreated: 1501697692
|
||||||
|
licenseType: Free
|
||||||
|
MonoImporter:
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
87
examples/button-clicker/Assets/DiscordRpc.cs
Normal file
87
examples/button-clicker/Assets/DiscordRpc.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
public class DiscordRpc
|
||||||
|
{
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void ReadyCallback();
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void DisconnectedCallback(int errorCode, string message);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void ErrorCallback(int errorCode, string message);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void JoinCallback(string secret);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void SpectateCallback(string secret);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void RequestCallback(ref JoinRequest request);
|
||||||
|
|
||||||
|
public struct EventHandlers
|
||||||
|
{
|
||||||
|
public ReadyCallback readyCallback;
|
||||||
|
public DisconnectedCallback disconnectedCallback;
|
||||||
|
public ErrorCallback errorCallback;
|
||||||
|
public JoinCallback joinCallback;
|
||||||
|
public SpectateCallback spectateCallback;
|
||||||
|
public RequestCallback requestCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.Serializable]
|
||||||
|
public struct RichPresence
|
||||||
|
{
|
||||||
|
public string state; /* max 128 bytes */
|
||||||
|
public string details; /* max 128 bytes */
|
||||||
|
public long startTimestamp;
|
||||||
|
public long endTimestamp;
|
||||||
|
public string largeImageKey; /* max 32 bytes */
|
||||||
|
public string largeImageText; /* max 128 bytes */
|
||||||
|
public string smallImageKey; /* max 32 bytes */
|
||||||
|
public string smallImageText; /* max 128 bytes */
|
||||||
|
public string partyId; /* max 128 bytes */
|
||||||
|
public int partySize;
|
||||||
|
public int partyMax;
|
||||||
|
public string matchSecret; /* max 128 bytes */
|
||||||
|
public string joinSecret; /* max 128 bytes */
|
||||||
|
public string spectateSecret; /* max 128 bytes */
|
||||||
|
public bool instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.Serializable]
|
||||||
|
public struct JoinRequest
|
||||||
|
{
|
||||||
|
public string userId;
|
||||||
|
public string username;
|
||||||
|
public string discriminator;
|
||||||
|
public string avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Reply
|
||||||
|
{
|
||||||
|
No = 0,
|
||||||
|
Yes = 1,
|
||||||
|
Ignore = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("discord-rpc", EntryPoint = "Discord_Initialize", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId);
|
||||||
|
|
||||||
|
[DllImport("discord-rpc", EntryPoint = "Discord_Shutdown", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void Shutdown();
|
||||||
|
|
||||||
|
[DllImport("discord-rpc", EntryPoint = "Discord_RunCallbacks", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void RunCallbacks();
|
||||||
|
|
||||||
|
[DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void UpdatePresence(ref RichPresence 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);
|
||||||
|
}
|
||||||
|
|
12
examples/button-clicker/Assets/DiscordRpc.cs.meta
Normal file
12
examples/button-clicker/Assets/DiscordRpc.cs.meta
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b4474a677de9d80409e98c5393ec5b1e
|
||||||
|
timeCreated: 1501697692
|
||||||
|
licenseType: Free
|
||||||
|
MonoImporter:
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
1278
examples/button-clicker/Assets/main.unity
Normal file
1278
examples/button-clicker/Assets/main.unity
Normal file
File diff suppressed because it is too large
Load Diff
8
examples/button-clicker/Assets/main.unity.meta
Normal file
8
examples/button-clicker/Assets/main.unity.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3b03d21bb25fa494e8694cd6e4b6d769
|
||||||
|
timeCreated: 1501696924
|
||||||
|
licenseType: Free
|
||||||
|
DefaultImporter:
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
17
examples/button-clicker/ProjectSettings/AudioManager.asset
Normal file
17
examples/button-clicker/ProjectSettings/AudioManager.asset
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!11 &1
|
||||||
|
AudioManager:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_Volume: 1
|
||||||
|
Rolloff Scale: 1
|
||||||
|
Doppler Factor: 1
|
||||||
|
Default Speaker Mode: 2
|
||||||
|
m_SampleRate: 0
|
||||||
|
m_DSPBufferSize: 0
|
||||||
|
m_VirtualVoiceCount: 512
|
||||||
|
m_RealVoiceCount: 32
|
||||||
|
m_SpatializerPlugin:
|
||||||
|
m_AmbisonicDecoderPlugin:
|
||||||
|
m_DisableAudio: 0
|
||||||
|
m_VirtualizeEffects: 1
|
@ -0,0 +1,6 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!236 &1
|
||||||
|
ClusterInputManager:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_Inputs: []
|
@ -0,0 +1,20 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!55 &1
|
||||||
|
PhysicsManager:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 3
|
||||||
|
m_Gravity: {x: 0, y: -9.81, z: 0}
|
||||||
|
m_DefaultMaterial: {fileID: 0}
|
||||||
|
m_BounceThreshold: 2
|
||||||
|
m_SleepThreshold: 0.005
|
||||||
|
m_DefaultContactOffset: 0.01
|
||||||
|
m_DefaultSolverIterations: 6
|
||||||
|
m_DefaultSolverVelocityIterations: 1
|
||||||
|
m_QueriesHitBackfaces: 0
|
||||||
|
m_QueriesHitTriggers: 1
|
||||||
|
m_EnableAdaptiveForce: 0
|
||||||
|
m_EnablePCM: 1
|
||||||
|
m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
m_AutoSimulation: 1
|
||||||
|
m_AutoSyncTransforms: 1
|
@ -0,0 +1,10 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!1045 &1
|
||||||
|
EditorBuildSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Scenes:
|
||||||
|
- enabled: 1
|
||||||
|
path: Assets/main.unity
|
||||||
|
guid: 3b03d21bb25fa494e8694cd6e4b6d769
|
16
examples/button-clicker/ProjectSettings/EditorSettings.asset
Normal file
16
examples/button-clicker/ProjectSettings/EditorSettings.asset
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!159 &1
|
||||||
|
EditorSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 4
|
||||||
|
m_ExternalVersionControlSupport: Visible Meta Files
|
||||||
|
m_SerializationMode: 2
|
||||||
|
m_DefaultBehaviorMode: 1
|
||||||
|
m_SpritePackerMode: 4
|
||||||
|
m_SpritePackerPaddingPower: 1
|
||||||
|
m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd
|
||||||
|
m_ProjectGenerationRootNamespace:
|
||||||
|
m_UserGeneratedProjectSuffix:
|
||||||
|
m_CollabEditorSettings:
|
||||||
|
inProgressEnabled: 1
|
@ -0,0 +1,61 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!30 &1
|
||||||
|
GraphicsSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 12
|
||||||
|
m_Deferred:
|
||||||
|
m_Mode: 1
|
||||||
|
m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_DeferredReflections:
|
||||||
|
m_Mode: 1
|
||||||
|
m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_ScreenSpaceShadows:
|
||||||
|
m_Mode: 1
|
||||||
|
m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_LegacyDeferred:
|
||||||
|
m_Mode: 1
|
||||||
|
m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_DepthNormals:
|
||||||
|
m_Mode: 1
|
||||||
|
m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_MotionVectors:
|
||||||
|
m_Mode: 1
|
||||||
|
m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_LightHalo:
|
||||||
|
m_Mode: 1
|
||||||
|
m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_LensFlare:
|
||||||
|
m_Mode: 1
|
||||||
|
m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_AlwaysIncludedShaders:
|
||||||
|
- {fileID: 7, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
- {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
- {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
- {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
- {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
- {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_PreloadedShaders: []
|
||||||
|
m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000,
|
||||||
|
type: 0}
|
||||||
|
m_CustomRenderPipeline: {fileID: 0}
|
||||||
|
m_TransparencySortMode: 0
|
||||||
|
m_TransparencySortAxis: {x: 0, y: 0, z: 1}
|
||||||
|
m_DefaultRenderingPath: 1
|
||||||
|
m_DefaultMobileRenderingPath: 1
|
||||||
|
m_TierSettings: []
|
||||||
|
m_LightmapStripping: 0
|
||||||
|
m_FogStripping: 0
|
||||||
|
m_InstancingStripping: 0
|
||||||
|
m_LightmapKeepPlain: 1
|
||||||
|
m_LightmapKeepDirCombined: 1
|
||||||
|
m_LightmapKeepDynamicPlain: 1
|
||||||
|
m_LightmapKeepDynamicDirCombined: 1
|
||||||
|
m_LightmapKeepShadowMask: 1
|
||||||
|
m_LightmapKeepSubtractive: 1
|
||||||
|
m_FogKeepLinear: 1
|
||||||
|
m_FogKeepExp: 1
|
||||||
|
m_FogKeepExp2: 1
|
||||||
|
m_AlbedoSwatchInfos: []
|
||||||
|
m_LightsUseLinearIntensity: 0
|
||||||
|
m_LightsUseColorTemperature: 0
|
295
examples/button-clicker/ProjectSettings/InputManager.asset
Normal file
295
examples/button-clicker/ProjectSettings/InputManager.asset
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!13 &1
|
||||||
|
InputManager:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Axes:
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Horizontal
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton: left
|
||||||
|
positiveButton: right
|
||||||
|
altNegativeButton: a
|
||||||
|
altPositiveButton: d
|
||||||
|
gravity: 3
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 3
|
||||||
|
snap: 1
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Vertical
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton: down
|
||||||
|
positiveButton: up
|
||||||
|
altNegativeButton: s
|
||||||
|
altPositiveButton: w
|
||||||
|
gravity: 3
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 3
|
||||||
|
snap: 1
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Fire1
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton: left ctrl
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton: mouse 0
|
||||||
|
gravity: 1000
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 1000
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Fire2
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton: left alt
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton: mouse 1
|
||||||
|
gravity: 1000
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 1000
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Fire3
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton: left shift
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton: mouse 2
|
||||||
|
gravity: 1000
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 1000
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Jump
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton: space
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton:
|
||||||
|
gravity: 1000
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 1000
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Mouse X
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton:
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton:
|
||||||
|
gravity: 0
|
||||||
|
dead: 0
|
||||||
|
sensitivity: 0.1
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 1
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Mouse Y
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton:
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton:
|
||||||
|
gravity: 0
|
||||||
|
dead: 0
|
||||||
|
sensitivity: 0.1
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 1
|
||||||
|
axis: 1
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Mouse ScrollWheel
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton:
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton:
|
||||||
|
gravity: 0
|
||||||
|
dead: 0
|
||||||
|
sensitivity: 0.1
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 1
|
||||||
|
axis: 2
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Horizontal
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton:
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton:
|
||||||
|
gravity: 0
|
||||||
|
dead: 0.19
|
||||||
|
sensitivity: 1
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 2
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Vertical
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton:
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton:
|
||||||
|
gravity: 0
|
||||||
|
dead: 0.19
|
||||||
|
sensitivity: 1
|
||||||
|
snap: 0
|
||||||
|
invert: 1
|
||||||
|
type: 2
|
||||||
|
axis: 1
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Fire1
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton: joystick button 0
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton:
|
||||||
|
gravity: 1000
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 1000
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Fire2
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton: joystick button 1
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton:
|
||||||
|
gravity: 1000
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 1000
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Fire3
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton: joystick button 2
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton:
|
||||||
|
gravity: 1000
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 1000
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Jump
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton: joystick button 3
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton:
|
||||||
|
gravity: 1000
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 1000
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Submit
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton: return
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton: joystick button 0
|
||||||
|
gravity: 1000
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 1000
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Submit
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton: enter
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton: space
|
||||||
|
gravity: 1000
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 1000
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: Cancel
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton:
|
||||||
|
positiveButton: escape
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton: joystick button 1
|
||||||
|
gravity: 1000
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 1000
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 0
|
||||||
|
joyNum: 0
|
89
examples/button-clicker/ProjectSettings/NavMeshAreas.asset
Normal file
89
examples/button-clicker/ProjectSettings/NavMeshAreas.asset
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!126 &1
|
||||||
|
NavMeshProjectSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 2
|
||||||
|
areas:
|
||||||
|
- name: Walkable
|
||||||
|
cost: 1
|
||||||
|
- name: Not Walkable
|
||||||
|
cost: 1
|
||||||
|
- name: Jump
|
||||||
|
cost: 2
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
- name:
|
||||||
|
cost: 1
|
||||||
|
m_LastAgentTypeID: -887442657
|
||||||
|
m_Settings:
|
||||||
|
- serializedVersion: 2
|
||||||
|
agentTypeID: 0
|
||||||
|
agentRadius: 0.5
|
||||||
|
agentHeight: 2
|
||||||
|
agentSlope: 45
|
||||||
|
agentClimb: 0.75
|
||||||
|
ledgeDropHeight: 0
|
||||||
|
maxJumpAcrossDistance: 0
|
||||||
|
minRegionArea: 2
|
||||||
|
manualCellSize: 0
|
||||||
|
cellSize: 0.16666667
|
||||||
|
manualTileSize: 0
|
||||||
|
tileSize: 256
|
||||||
|
accuratePlacement: 0
|
||||||
|
m_SettingNames:
|
||||||
|
- Humanoid
|
@ -0,0 +1,8 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!149 &1
|
||||||
|
NetworkManager:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_DebugLevel: 0
|
||||||
|
m_Sendrate: 15
|
||||||
|
m_AssetToPrefab: {}
|
@ -0,0 +1,37 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!19 &1
|
||||||
|
Physics2DSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 3
|
||||||
|
m_Gravity: {x: 0, y: -9.81}
|
||||||
|
m_DefaultMaterial: {fileID: 0}
|
||||||
|
m_VelocityIterations: 8
|
||||||
|
m_PositionIterations: 3
|
||||||
|
m_VelocityThreshold: 1
|
||||||
|
m_MaxLinearCorrection: 0.2
|
||||||
|
m_MaxAngularCorrection: 8
|
||||||
|
m_MaxTranslationSpeed: 100
|
||||||
|
m_MaxRotationSpeed: 360
|
||||||
|
m_BaumgarteScale: 0.2
|
||||||
|
m_BaumgarteTimeOfImpactScale: 0.75
|
||||||
|
m_TimeToSleep: 0.5
|
||||||
|
m_LinearSleepTolerance: 0.01
|
||||||
|
m_AngularSleepTolerance: 2
|
||||||
|
m_DefaultContactOffset: 0.01
|
||||||
|
m_AutoSimulation: 1
|
||||||
|
m_QueriesHitTriggers: 1
|
||||||
|
m_QueriesStartInColliders: 1
|
||||||
|
m_ChangeStopsCallbacks: 0
|
||||||
|
m_CallbacksOnDisable: 1
|
||||||
|
m_AutoSyncTransforms: 1
|
||||||
|
m_AlwaysShowColliders: 0
|
||||||
|
m_ShowColliderSleep: 1
|
||||||
|
m_ShowColliderContacts: 0
|
||||||
|
m_ShowColliderAABB: 0
|
||||||
|
m_ContactArrowScale: 0.2
|
||||||
|
m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412}
|
||||||
|
m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432}
|
||||||
|
m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745}
|
||||||
|
m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804}
|
||||||
|
m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
610
examples/button-clicker/ProjectSettings/ProjectSettings.asset
Normal file
610
examples/button-clicker/ProjectSettings/ProjectSettings.asset
Normal file
@ -0,0 +1,610 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!129 &1
|
||||||
|
PlayerSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 13
|
||||||
|
productGUID: 5eccc60d3e382a346a65f512d6b81b84
|
||||||
|
AndroidProfiler: 0
|
||||||
|
AndroidFilterTouchesWhenObscured: 0
|
||||||
|
defaultScreenOrientation: 4
|
||||||
|
targetDevice: 2
|
||||||
|
useOnDemandResources: 0
|
||||||
|
accelerometerFrequency: 60
|
||||||
|
companyName: Discord Inc.
|
||||||
|
productName: button-clicker
|
||||||
|
defaultCursor: {fileID: 0}
|
||||||
|
cursorHotspot: {x: 0, y: 0}
|
||||||
|
m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1}
|
||||||
|
m_ShowUnitySplashScreen: 1
|
||||||
|
m_ShowUnitySplashLogo: 1
|
||||||
|
m_SplashScreenOverlayOpacity: 1
|
||||||
|
m_SplashScreenAnimation: 1
|
||||||
|
m_SplashScreenLogoStyle: 1
|
||||||
|
m_SplashScreenDrawMode: 0
|
||||||
|
m_SplashScreenBackgroundAnimationZoom: 1
|
||||||
|
m_SplashScreenLogoAnimationZoom: 1
|
||||||
|
m_SplashScreenBackgroundLandscapeAspect: 1
|
||||||
|
m_SplashScreenBackgroundPortraitAspect: 1
|
||||||
|
m_SplashScreenBackgroundLandscapeUvs:
|
||||||
|
serializedVersion: 2
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: 1
|
||||||
|
height: 1
|
||||||
|
m_SplashScreenBackgroundPortraitUvs:
|
||||||
|
serializedVersion: 2
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: 1
|
||||||
|
height: 1
|
||||||
|
m_SplashScreenLogos: []
|
||||||
|
m_VirtualRealitySplashScreen: {fileID: 0}
|
||||||
|
m_HolographicTrackingLossScreen: {fileID: 0}
|
||||||
|
defaultScreenWidth: 1024
|
||||||
|
defaultScreenHeight: 768
|
||||||
|
defaultScreenWidthWeb: 960
|
||||||
|
defaultScreenHeightWeb: 600
|
||||||
|
m_StereoRenderingPath: 0
|
||||||
|
m_ActiveColorSpace: 0
|
||||||
|
m_MTRendering: 1
|
||||||
|
m_StackTraceTypes: 010000000100000001000000010000000100000001000000
|
||||||
|
iosShowActivityIndicatorOnLoading: -1
|
||||||
|
androidShowActivityIndicatorOnLoading: -1
|
||||||
|
tizenShowActivityIndicatorOnLoading: -1
|
||||||
|
iosAppInBackgroundBehavior: 0
|
||||||
|
displayResolutionDialog: 1
|
||||||
|
iosAllowHTTPDownload: 1
|
||||||
|
allowedAutorotateToPortrait: 1
|
||||||
|
allowedAutorotateToPortraitUpsideDown: 1
|
||||||
|
allowedAutorotateToLandscapeRight: 1
|
||||||
|
allowedAutorotateToLandscapeLeft: 1
|
||||||
|
useOSAutorotation: 1
|
||||||
|
use32BitDisplayBuffer: 1
|
||||||
|
disableDepthAndStencilBuffers: 0
|
||||||
|
androidBlitType: 0
|
||||||
|
defaultIsFullScreen: 1
|
||||||
|
defaultIsNativeResolution: 1
|
||||||
|
macRetinaSupport: 1
|
||||||
|
runInBackground: 0
|
||||||
|
captureSingleScreen: 0
|
||||||
|
muteOtherAudioSources: 0
|
||||||
|
Prepare IOS For Recording: 0
|
||||||
|
Force IOS Speakers When Recording: 0
|
||||||
|
submitAnalytics: 1
|
||||||
|
usePlayerLog: 1
|
||||||
|
bakeCollisionMeshes: 0
|
||||||
|
forceSingleInstance: 0
|
||||||
|
resizableWindow: 0
|
||||||
|
useMacAppStoreValidation: 0
|
||||||
|
macAppStoreCategory: public.app-category.games
|
||||||
|
gpuSkinning: 0
|
||||||
|
graphicsJobs: 0
|
||||||
|
xboxPIXTextureCapture: 0
|
||||||
|
xboxEnableAvatar: 0
|
||||||
|
xboxEnableKinect: 0
|
||||||
|
xboxEnableKinectAutoTracking: 0
|
||||||
|
xboxEnableFitness: 0
|
||||||
|
visibleInBackground: 1
|
||||||
|
allowFullscreenSwitch: 1
|
||||||
|
graphicsJobMode: 0
|
||||||
|
macFullscreenMode: 2
|
||||||
|
d3d9FullscreenMode: 1
|
||||||
|
d3d11FullscreenMode: 1
|
||||||
|
xboxSpeechDB: 0
|
||||||
|
xboxEnableHeadOrientation: 0
|
||||||
|
xboxEnableGuest: 0
|
||||||
|
xboxEnablePIXSampling: 0
|
||||||
|
metalFramebufferOnly: 0
|
||||||
|
n3dsDisableStereoscopicView: 0
|
||||||
|
n3dsEnableSharedListOpt: 1
|
||||||
|
n3dsEnableVSync: 0
|
||||||
|
ignoreAlphaClear: 0
|
||||||
|
xboxOneResolution: 0
|
||||||
|
xboxOneMonoLoggingLevel: 0
|
||||||
|
xboxOneLoggingLevel: 1
|
||||||
|
xboxOneDisableEsram: 0
|
||||||
|
xboxOnePresentImmediateThreshold: 0
|
||||||
|
videoMemoryForVertexBuffers: 0
|
||||||
|
psp2PowerMode: 0
|
||||||
|
psp2AcquireBGM: 1
|
||||||
|
wiiUTVResolution: 0
|
||||||
|
wiiUGamePadMSAA: 1
|
||||||
|
wiiUSupportsNunchuk: 0
|
||||||
|
wiiUSupportsClassicController: 0
|
||||||
|
wiiUSupportsBalanceBoard: 0
|
||||||
|
wiiUSupportsMotionPlus: 0
|
||||||
|
wiiUSupportsProController: 0
|
||||||
|
wiiUAllowScreenCapture: 1
|
||||||
|
wiiUControllerCount: 0
|
||||||
|
m_SupportedAspectRatios:
|
||||||
|
4:3: 1
|
||||||
|
5:4: 1
|
||||||
|
16:10: 1
|
||||||
|
16:9: 1
|
||||||
|
Others: 1
|
||||||
|
bundleVersion: 1.0
|
||||||
|
preloadedAssets: []
|
||||||
|
metroInputSource: 0
|
||||||
|
m_HolographicPauseOnTrackingLoss: 1
|
||||||
|
xboxOneDisableKinectGpuReservation: 0
|
||||||
|
xboxOneEnable7thCore: 0
|
||||||
|
vrSettings:
|
||||||
|
cardboard:
|
||||||
|
depthFormat: 0
|
||||||
|
enableTransitionView: 0
|
||||||
|
daydream:
|
||||||
|
depthFormat: 0
|
||||||
|
useSustainedPerformanceMode: 0
|
||||||
|
enableVideoLayer: 0
|
||||||
|
useProtectedVideoMemory: 0
|
||||||
|
hololens:
|
||||||
|
depthFormat: 1
|
||||||
|
protectGraphicsMemory: 0
|
||||||
|
useHDRDisplay: 0
|
||||||
|
m_ColorGamuts: 00000000
|
||||||
|
targetPixelDensity: 0
|
||||||
|
resolutionScalingMode: 0
|
||||||
|
androidSupportedAspectRatio: 1
|
||||||
|
androidMaxAspectRatio: 2.1
|
||||||
|
applicationIdentifier: {}
|
||||||
|
buildNumber: {}
|
||||||
|
AndroidBundleVersionCode: 1
|
||||||
|
AndroidMinSdkVersion: 16
|
||||||
|
AndroidTargetSdkVersion: 0
|
||||||
|
AndroidPreferredInstallLocation: 1
|
||||||
|
aotOptions:
|
||||||
|
stripEngineCode: 1
|
||||||
|
iPhoneStrippingLevel: 0
|
||||||
|
iPhoneScriptCallOptimization: 0
|
||||||
|
ForceInternetPermission: 0
|
||||||
|
ForceSDCardPermission: 0
|
||||||
|
CreateWallpaper: 0
|
||||||
|
APKExpansionFiles: 0
|
||||||
|
keepLoadedShadersAlive: 0
|
||||||
|
StripUnusedMeshComponents: 0
|
||||||
|
VertexChannelCompressionMask:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 238
|
||||||
|
iPhoneSdkVersion: 988
|
||||||
|
iOSTargetOSVersionString: 7.0
|
||||||
|
tvOSSdkVersion: 0
|
||||||
|
tvOSRequireExtendedGameController: 0
|
||||||
|
tvOSTargetOSVersionString: 9.0
|
||||||
|
uIPrerenderedIcon: 0
|
||||||
|
uIRequiresPersistentWiFi: 0
|
||||||
|
uIRequiresFullScreen: 1
|
||||||
|
uIStatusBarHidden: 1
|
||||||
|
uIExitOnSuspend: 0
|
||||||
|
uIStatusBarStyle: 0
|
||||||
|
iPhoneSplashScreen: {fileID: 0}
|
||||||
|
iPhoneHighResSplashScreen: {fileID: 0}
|
||||||
|
iPhoneTallHighResSplashScreen: {fileID: 0}
|
||||||
|
iPhone47inSplashScreen: {fileID: 0}
|
||||||
|
iPhone55inPortraitSplashScreen: {fileID: 0}
|
||||||
|
iPhone55inLandscapeSplashScreen: {fileID: 0}
|
||||||
|
iPadPortraitSplashScreen: {fileID: 0}
|
||||||
|
iPadHighResPortraitSplashScreen: {fileID: 0}
|
||||||
|
iPadLandscapeSplashScreen: {fileID: 0}
|
||||||
|
iPadHighResLandscapeSplashScreen: {fileID: 0}
|
||||||
|
appleTVSplashScreen: {fileID: 0}
|
||||||
|
tvOSSmallIconLayers: []
|
||||||
|
tvOSLargeIconLayers: []
|
||||||
|
tvOSTopShelfImageLayers: []
|
||||||
|
tvOSTopShelfImageWideLayers: []
|
||||||
|
iOSLaunchScreenType: 0
|
||||||
|
iOSLaunchScreenPortrait: {fileID: 0}
|
||||||
|
iOSLaunchScreenLandscape: {fileID: 0}
|
||||||
|
iOSLaunchScreenBackgroundColor:
|
||||||
|
serializedVersion: 2
|
||||||
|
rgba: 0
|
||||||
|
iOSLaunchScreenFillPct: 100
|
||||||
|
iOSLaunchScreenSize: 100
|
||||||
|
iOSLaunchScreenCustomXibPath:
|
||||||
|
iOSLaunchScreeniPadType: 0
|
||||||
|
iOSLaunchScreeniPadImage: {fileID: 0}
|
||||||
|
iOSLaunchScreeniPadBackgroundColor:
|
||||||
|
serializedVersion: 2
|
||||||
|
rgba: 0
|
||||||
|
iOSLaunchScreeniPadFillPct: 100
|
||||||
|
iOSLaunchScreeniPadSize: 100
|
||||||
|
iOSLaunchScreeniPadCustomXibPath:
|
||||||
|
iOSDeviceRequirements: []
|
||||||
|
iOSURLSchemes: []
|
||||||
|
iOSBackgroundModes: 0
|
||||||
|
iOSMetalForceHardShadows: 0
|
||||||
|
metalEditorSupport: 1
|
||||||
|
metalAPIValidation: 1
|
||||||
|
iOSRenderExtraFrameOnPause: 0
|
||||||
|
appleDeveloperTeamID:
|
||||||
|
iOSManualSigningProvisioningProfileID:
|
||||||
|
tvOSManualSigningProvisioningProfileID:
|
||||||
|
appleEnableAutomaticSigning: 0
|
||||||
|
AndroidTargetDevice: 0
|
||||||
|
AndroidSplashScreenScale: 0
|
||||||
|
androidSplashScreen: {fileID: 0}
|
||||||
|
AndroidKeystoreName:
|
||||||
|
AndroidKeyaliasName:
|
||||||
|
AndroidTVCompatibility: 1
|
||||||
|
AndroidIsGame: 1
|
||||||
|
AndroidEnableTango: 0
|
||||||
|
androidEnableBanner: 1
|
||||||
|
androidUseLowAccuracyLocation: 0
|
||||||
|
m_AndroidBanners:
|
||||||
|
- width: 320
|
||||||
|
height: 180
|
||||||
|
banner: {fileID: 0}
|
||||||
|
androidGamepadSupportLevel: 0
|
||||||
|
resolutionDialogBanner: {fileID: 0}
|
||||||
|
m_BuildTargetIcons: []
|
||||||
|
m_BuildTargetBatching: []
|
||||||
|
m_BuildTargetGraphicsAPIs: []
|
||||||
|
m_BuildTargetVRSettings: []
|
||||||
|
m_BuildTargetEnableVuforiaSettings: []
|
||||||
|
openGLRequireES31: 0
|
||||||
|
openGLRequireES31AEP: 0
|
||||||
|
m_TemplateCustomTags: {}
|
||||||
|
mobileMTRendering:
|
||||||
|
Android: 1
|
||||||
|
iPhone: 1
|
||||||
|
tvOS: 1
|
||||||
|
wiiUTitleID: 0005000011000000
|
||||||
|
wiiUGroupID: 00010000
|
||||||
|
wiiUCommonSaveSize: 4096
|
||||||
|
wiiUAccountSaveSize: 2048
|
||||||
|
wiiUOlvAccessKey: 0
|
||||||
|
wiiUTinCode: 0
|
||||||
|
wiiUJoinGameId: 0
|
||||||
|
wiiUJoinGameModeMask: 0000000000000000
|
||||||
|
wiiUCommonBossSize: 0
|
||||||
|
wiiUAccountBossSize: 0
|
||||||
|
wiiUAddOnUniqueIDs: []
|
||||||
|
wiiUMainThreadStackSize: 3072
|
||||||
|
wiiULoaderThreadStackSize: 1024
|
||||||
|
wiiUSystemHeapSize: 128
|
||||||
|
wiiUTVStartupScreen: {fileID: 0}
|
||||||
|
wiiUGamePadStartupScreen: {fileID: 0}
|
||||||
|
wiiUDrcBufferDisabled: 0
|
||||||
|
wiiUProfilerLibPath:
|
||||||
|
playModeTestRunnerEnabled: 0
|
||||||
|
actionOnDotNetUnhandledException: 1
|
||||||
|
enableInternalProfiler: 0
|
||||||
|
logObjCUncaughtExceptions: 1
|
||||||
|
enableCrashReportAPI: 0
|
||||||
|
cameraUsageDescription:
|
||||||
|
locationUsageDescription:
|
||||||
|
microphoneUsageDescription:
|
||||||
|
switchNetLibKey:
|
||||||
|
switchSocketMemoryPoolSize: 6144
|
||||||
|
switchSocketAllocatorPoolSize: 128
|
||||||
|
switchSocketConcurrencyLimit: 14
|
||||||
|
switchScreenResolutionBehavior: 2
|
||||||
|
switchUseCPUProfiler: 0
|
||||||
|
switchApplicationID: 0x01004b9000490000
|
||||||
|
switchNSODependencies:
|
||||||
|
switchTitleNames_0:
|
||||||
|
switchTitleNames_1:
|
||||||
|
switchTitleNames_2:
|
||||||
|
switchTitleNames_3:
|
||||||
|
switchTitleNames_4:
|
||||||
|
switchTitleNames_5:
|
||||||
|
switchTitleNames_6:
|
||||||
|
switchTitleNames_7:
|
||||||
|
switchTitleNames_8:
|
||||||
|
switchTitleNames_9:
|
||||||
|
switchTitleNames_10:
|
||||||
|
switchTitleNames_11:
|
||||||
|
switchPublisherNames_0:
|
||||||
|
switchPublisherNames_1:
|
||||||
|
switchPublisherNames_2:
|
||||||
|
switchPublisherNames_3:
|
||||||
|
switchPublisherNames_4:
|
||||||
|
switchPublisherNames_5:
|
||||||
|
switchPublisherNames_6:
|
||||||
|
switchPublisherNames_7:
|
||||||
|
switchPublisherNames_8:
|
||||||
|
switchPublisherNames_9:
|
||||||
|
switchPublisherNames_10:
|
||||||
|
switchPublisherNames_11:
|
||||||
|
switchIcons_0: {fileID: 0}
|
||||||
|
switchIcons_1: {fileID: 0}
|
||||||
|
switchIcons_2: {fileID: 0}
|
||||||
|
switchIcons_3: {fileID: 0}
|
||||||
|
switchIcons_4: {fileID: 0}
|
||||||
|
switchIcons_5: {fileID: 0}
|
||||||
|
switchIcons_6: {fileID: 0}
|
||||||
|
switchIcons_7: {fileID: 0}
|
||||||
|
switchIcons_8: {fileID: 0}
|
||||||
|
switchIcons_9: {fileID: 0}
|
||||||
|
switchIcons_10: {fileID: 0}
|
||||||
|
switchIcons_11: {fileID: 0}
|
||||||
|
switchSmallIcons_0: {fileID: 0}
|
||||||
|
switchSmallIcons_1: {fileID: 0}
|
||||||
|
switchSmallIcons_2: {fileID: 0}
|
||||||
|
switchSmallIcons_3: {fileID: 0}
|
||||||
|
switchSmallIcons_4: {fileID: 0}
|
||||||
|
switchSmallIcons_5: {fileID: 0}
|
||||||
|
switchSmallIcons_6: {fileID: 0}
|
||||||
|
switchSmallIcons_7: {fileID: 0}
|
||||||
|
switchSmallIcons_8: {fileID: 0}
|
||||||
|
switchSmallIcons_9: {fileID: 0}
|
||||||
|
switchSmallIcons_10: {fileID: 0}
|
||||||
|
switchSmallIcons_11: {fileID: 0}
|
||||||
|
switchManualHTML:
|
||||||
|
switchAccessibleURLs:
|
||||||
|
switchLegalInformation:
|
||||||
|
switchMainThreadStackSize: 1048576
|
||||||
|
switchPresenceGroupId: 0x01004b9000490000
|
||||||
|
switchLogoHandling: 0
|
||||||
|
switchReleaseVersion: 0
|
||||||
|
switchDisplayVersion: 1.0.0
|
||||||
|
switchStartupUserAccount: 0
|
||||||
|
switchTouchScreenUsage: 0
|
||||||
|
switchSupportedLanguagesMask: 0
|
||||||
|
switchLogoType: 0
|
||||||
|
switchApplicationErrorCodeCategory:
|
||||||
|
switchUserAccountSaveDataSize: 0
|
||||||
|
switchUserAccountSaveDataJournalSize: 0
|
||||||
|
switchApplicationAttribute: 0
|
||||||
|
switchCardSpecSize: 4
|
||||||
|
switchCardSpecClock: 25
|
||||||
|
switchRatingsMask: 0
|
||||||
|
switchRatingsInt_0: 0
|
||||||
|
switchRatingsInt_1: 0
|
||||||
|
switchRatingsInt_2: 0
|
||||||
|
switchRatingsInt_3: 0
|
||||||
|
switchRatingsInt_4: 0
|
||||||
|
switchRatingsInt_5: 0
|
||||||
|
switchRatingsInt_6: 0
|
||||||
|
switchRatingsInt_7: 0
|
||||||
|
switchRatingsInt_8: 0
|
||||||
|
switchRatingsInt_9: 0
|
||||||
|
switchRatingsInt_10: 0
|
||||||
|
switchRatingsInt_11: 0
|
||||||
|
switchLocalCommunicationIds_0: 0x01004b9000490000
|
||||||
|
switchLocalCommunicationIds_1:
|
||||||
|
switchLocalCommunicationIds_2:
|
||||||
|
switchLocalCommunicationIds_3:
|
||||||
|
switchLocalCommunicationIds_4:
|
||||||
|
switchLocalCommunicationIds_5:
|
||||||
|
switchLocalCommunicationIds_6:
|
||||||
|
switchLocalCommunicationIds_7:
|
||||||
|
switchParentalControl: 0
|
||||||
|
switchAllowsScreenshot: 1
|
||||||
|
switchDataLossConfirmation: 0
|
||||||
|
switchSupportedNpadStyles: 3
|
||||||
|
switchSocketConfigEnabled: 0
|
||||||
|
switchTcpInitialSendBufferSize: 32
|
||||||
|
switchTcpInitialReceiveBufferSize: 64
|
||||||
|
switchTcpAutoSendBufferSizeMax: 256
|
||||||
|
switchTcpAutoReceiveBufferSizeMax: 256
|
||||||
|
switchUdpSendBufferSize: 9
|
||||||
|
switchUdpReceiveBufferSize: 42
|
||||||
|
switchSocketBufferEfficiency: 4
|
||||||
|
switchSocketInitializeEnabled: 1
|
||||||
|
switchNetworkInterfaceManagerInitializeEnabled: 1
|
||||||
|
switchPlayerConnectionEnabled: 1
|
||||||
|
ps4NPAgeRating: 12
|
||||||
|
ps4NPTitleSecret:
|
||||||
|
ps4NPTrophyPackPath:
|
||||||
|
ps4ParentalLevel: 11
|
||||||
|
ps4ContentID: ED1633-NPXX51362_00-0000000000000000
|
||||||
|
ps4Category: 0
|
||||||
|
ps4MasterVersion: 01.00
|
||||||
|
ps4AppVersion: 01.00
|
||||||
|
ps4AppType: 0
|
||||||
|
ps4ParamSfxPath:
|
||||||
|
ps4VideoOutPixelFormat: 0
|
||||||
|
ps4VideoOutInitialWidth: 1920
|
||||||
|
ps4VideoOutBaseModeInitialWidth: 1920
|
||||||
|
ps4VideoOutReprojectionRate: 120
|
||||||
|
ps4PronunciationXMLPath:
|
||||||
|
ps4PronunciationSIGPath:
|
||||||
|
ps4BackgroundImagePath:
|
||||||
|
ps4StartupImagePath:
|
||||||
|
ps4SaveDataImagePath:
|
||||||
|
ps4SdkOverride:
|
||||||
|
ps4BGMPath:
|
||||||
|
ps4ShareFilePath:
|
||||||
|
ps4ShareOverlayImagePath:
|
||||||
|
ps4PrivacyGuardImagePath:
|
||||||
|
ps4NPtitleDatPath:
|
||||||
|
ps4RemotePlayKeyAssignment: -1
|
||||||
|
ps4RemotePlayKeyMappingDir:
|
||||||
|
ps4PlayTogetherPlayerCount: 0
|
||||||
|
ps4EnterButtonAssignment: 1
|
||||||
|
ps4ApplicationParam1: 0
|
||||||
|
ps4ApplicationParam2: 0
|
||||||
|
ps4ApplicationParam3: 0
|
||||||
|
ps4ApplicationParam4: 0
|
||||||
|
ps4DownloadDataSize: 0
|
||||||
|
ps4GarlicHeapSize: 2048
|
||||||
|
ps4ProGarlicHeapSize: 2560
|
||||||
|
ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ
|
||||||
|
ps4pnSessions: 1
|
||||||
|
ps4pnPresence: 1
|
||||||
|
ps4pnFriends: 1
|
||||||
|
ps4pnGameCustomData: 1
|
||||||
|
playerPrefsSupport: 0
|
||||||
|
restrictedAudioUsageRights: 0
|
||||||
|
ps4UseResolutionFallback: 0
|
||||||
|
ps4ReprojectionSupport: 0
|
||||||
|
ps4UseAudio3dBackend: 0
|
||||||
|
ps4SocialScreenEnabled: 0
|
||||||
|
ps4ScriptOptimizationLevel: 0
|
||||||
|
ps4Audio3dVirtualSpeakerCount: 14
|
||||||
|
ps4attribCpuUsage: 0
|
||||||
|
ps4PatchPkgPath:
|
||||||
|
ps4PatchLatestPkgPath:
|
||||||
|
ps4PatchChangeinfoPath:
|
||||||
|
ps4PatchDayOne: 0
|
||||||
|
ps4attribUserManagement: 0
|
||||||
|
ps4attribMoveSupport: 0
|
||||||
|
ps4attrib3DSupport: 0
|
||||||
|
ps4attribShareSupport: 0
|
||||||
|
ps4attribExclusiveVR: 0
|
||||||
|
ps4disableAutoHideSplash: 0
|
||||||
|
ps4videoRecordingFeaturesUsed: 0
|
||||||
|
ps4contentSearchFeaturesUsed: 0
|
||||||
|
ps4attribEyeToEyeDistanceSettingVR: 0
|
||||||
|
ps4IncludedModules: []
|
||||||
|
monoEnv:
|
||||||
|
psp2Splashimage: {fileID: 0}
|
||||||
|
psp2NPTrophyPackPath:
|
||||||
|
psp2NPSupportGBMorGJP: 0
|
||||||
|
psp2NPAgeRating: 12
|
||||||
|
psp2NPTitleDatPath:
|
||||||
|
psp2NPCommsID:
|
||||||
|
psp2NPCommunicationsID:
|
||||||
|
psp2NPCommsPassphrase:
|
||||||
|
psp2NPCommsSig:
|
||||||
|
psp2ParamSfxPath:
|
||||||
|
psp2ManualPath:
|
||||||
|
psp2LiveAreaGatePath:
|
||||||
|
psp2LiveAreaBackroundPath:
|
||||||
|
psp2LiveAreaPath:
|
||||||
|
psp2LiveAreaTrialPath:
|
||||||
|
psp2PatchChangeInfoPath:
|
||||||
|
psp2PatchOriginalPackage:
|
||||||
|
psp2PackagePassword: F69AzBlax3CF3EDNhm3soLBPh71Yexui
|
||||||
|
psp2KeystoneFile:
|
||||||
|
psp2MemoryExpansionMode: 0
|
||||||
|
psp2DRMType: 0
|
||||||
|
psp2StorageType: 0
|
||||||
|
psp2MediaCapacity: 0
|
||||||
|
psp2DLCConfigPath:
|
||||||
|
psp2ThumbnailPath:
|
||||||
|
psp2BackgroundPath:
|
||||||
|
psp2SoundPath:
|
||||||
|
psp2TrophyCommId:
|
||||||
|
psp2TrophyPackagePath:
|
||||||
|
psp2PackagedResourcesPath:
|
||||||
|
psp2SaveDataQuota: 10240
|
||||||
|
psp2ParentalLevel: 1
|
||||||
|
psp2ShortTitle: Not Set
|
||||||
|
psp2ContentID: IV0000-ABCD12345_00-0123456789ABCDEF
|
||||||
|
psp2Category: 0
|
||||||
|
psp2MasterVersion: 01.00
|
||||||
|
psp2AppVersion: 01.00
|
||||||
|
psp2TVBootMode: 0
|
||||||
|
psp2EnterButtonAssignment: 2
|
||||||
|
psp2TVDisableEmu: 0
|
||||||
|
psp2AllowTwitterDialog: 1
|
||||||
|
psp2Upgradable: 0
|
||||||
|
psp2HealthWarning: 0
|
||||||
|
psp2UseLibLocation: 0
|
||||||
|
psp2InfoBarOnStartup: 0
|
||||||
|
psp2InfoBarColor: 0
|
||||||
|
psp2ScriptOptimizationLevel: 0
|
||||||
|
psmSplashimage: {fileID: 0}
|
||||||
|
splashScreenBackgroundSourceLandscape: {fileID: 0}
|
||||||
|
splashScreenBackgroundSourcePortrait: {fileID: 0}
|
||||||
|
spritePackerPolicy:
|
||||||
|
webGLMemorySize: 256
|
||||||
|
webGLExceptionSupport: 1
|
||||||
|
webGLNameFilesAsHashes: 0
|
||||||
|
webGLDataCaching: 0
|
||||||
|
webGLDebugSymbols: 0
|
||||||
|
webGLEmscriptenArgs:
|
||||||
|
webGLModulesDirectory:
|
||||||
|
webGLTemplate: APPLICATION:Default
|
||||||
|
webGLAnalyzeBuildSize: 0
|
||||||
|
webGLUseEmbeddedResources: 0
|
||||||
|
webGLUseWasm: 0
|
||||||
|
webGLCompressionFormat: 1
|
||||||
|
scriptingDefineSymbols: {}
|
||||||
|
platformArchitecture: {}
|
||||||
|
scriptingBackend: {}
|
||||||
|
incrementalIl2cppBuild: {}
|
||||||
|
additionalIl2CppArgs:
|
||||||
|
scriptingRuntimeVersion: 0
|
||||||
|
apiCompatibilityLevelPerPlatform: {}
|
||||||
|
m_RenderingPath: 1
|
||||||
|
m_MobileRenderingPath: 1
|
||||||
|
metroPackageName: button-clicker
|
||||||
|
metroPackageVersion:
|
||||||
|
metroCertificatePath:
|
||||||
|
metroCertificatePassword:
|
||||||
|
metroCertificateSubject:
|
||||||
|
metroCertificateIssuer:
|
||||||
|
metroCertificateNotAfter: 0000000000000000
|
||||||
|
metroApplicationDescription: button-clicker
|
||||||
|
wsaImages: {}
|
||||||
|
metroTileShortName:
|
||||||
|
metroCommandLineArgsFile:
|
||||||
|
metroTileShowName: 0
|
||||||
|
metroMediumTileShowName: 0
|
||||||
|
metroLargeTileShowName: 0
|
||||||
|
metroWideTileShowName: 0
|
||||||
|
metroDefaultTileSize: 1
|
||||||
|
metroTileForegroundText: 2
|
||||||
|
metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0}
|
||||||
|
metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628,
|
||||||
|
a: 1}
|
||||||
|
metroSplashScreenUseBackgroundColor: 0
|
||||||
|
platformCapabilities: {}
|
||||||
|
metroFTAName:
|
||||||
|
metroFTAFileTypes: []
|
||||||
|
metroProtocolName:
|
||||||
|
metroCompilationOverrides: 1
|
||||||
|
tizenProductDescription:
|
||||||
|
tizenProductURL:
|
||||||
|
tizenSigningProfileName:
|
||||||
|
tizenGPSPermissions: 0
|
||||||
|
tizenMicrophonePermissions: 0
|
||||||
|
tizenDeploymentTarget:
|
||||||
|
tizenDeploymentTargetType: -1
|
||||||
|
tizenMinOSVersion: 1
|
||||||
|
n3dsUseExtSaveData: 0
|
||||||
|
n3dsCompressStaticMem: 1
|
||||||
|
n3dsExtSaveDataNumber: 0x12345
|
||||||
|
n3dsStackSize: 131072
|
||||||
|
n3dsTargetPlatform: 2
|
||||||
|
n3dsRegion: 7
|
||||||
|
n3dsMediaSize: 0
|
||||||
|
n3dsLogoStyle: 3
|
||||||
|
n3dsTitle: GameName
|
||||||
|
n3dsProductCode:
|
||||||
|
n3dsApplicationId: 0xFF3FF
|
||||||
|
stvDeviceAddress:
|
||||||
|
stvProductDescription:
|
||||||
|
stvProductAuthor:
|
||||||
|
stvProductAuthorEmail:
|
||||||
|
stvProductLink:
|
||||||
|
stvProductCategory: 0
|
||||||
|
XboxOneProductId:
|
||||||
|
XboxOneUpdateKey:
|
||||||
|
XboxOneSandboxId:
|
||||||
|
XboxOneContentId:
|
||||||
|
XboxOneTitleId:
|
||||||
|
XboxOneSCId:
|
||||||
|
XboxOneGameOsOverridePath:
|
||||||
|
XboxOnePackagingOverridePath:
|
||||||
|
XboxOneAppManifestOverridePath:
|
||||||
|
XboxOnePackageEncryption: 0
|
||||||
|
XboxOnePackageUpdateGranularity: 2
|
||||||
|
XboxOneDescription:
|
||||||
|
XboxOneLanguage:
|
||||||
|
- enus
|
||||||
|
XboxOneCapability: []
|
||||||
|
XboxOneGameRating: {}
|
||||||
|
XboxOneIsContentPackage: 0
|
||||||
|
XboxOneEnableGPUVariability: 0
|
||||||
|
XboxOneSockets: {}
|
||||||
|
XboxOneSplashScreen: {fileID: 0}
|
||||||
|
XboxOneAllowedProductIds: []
|
||||||
|
XboxOnePersistentLocalStorageSize: 0
|
||||||
|
xboxOneScriptCompiler: 0
|
||||||
|
vrEditorSettings:
|
||||||
|
daydream:
|
||||||
|
daydreamIconForeground: {fileID: 0}
|
||||||
|
daydreamIconBackground: {fileID: 0}
|
||||||
|
cloudServicesEnabled: {}
|
||||||
|
facebookSdkVersion: 7.9.4
|
||||||
|
apiCompatibilityLevel: 2
|
||||||
|
cloudProjectId:
|
||||||
|
projectName:
|
||||||
|
organizationId:
|
||||||
|
cloudEnabled: 0
|
||||||
|
enableNativePlatformBackendsForNewInputSystem: 0
|
||||||
|
disableOldInputManagerSupport: 0
|
@ -0,0 +1 @@
|
|||||||
|
m_EditorVersion: 2017.2.0f3
|
193
examples/button-clicker/ProjectSettings/QualitySettings.asset
Normal file
193
examples/button-clicker/ProjectSettings/QualitySettings.asset
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!47 &1
|
||||||
|
QualitySettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 5
|
||||||
|
m_CurrentQuality: 5
|
||||||
|
m_QualitySettings:
|
||||||
|
- serializedVersion: 2
|
||||||
|
name: Very Low
|
||||||
|
pixelLightCount: 0
|
||||||
|
shadows: 0
|
||||||
|
shadowResolution: 0
|
||||||
|
shadowProjection: 1
|
||||||
|
shadowCascades: 1
|
||||||
|
shadowDistance: 15
|
||||||
|
shadowNearPlaneOffset: 3
|
||||||
|
shadowCascade2Split: 0.33333334
|
||||||
|
shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
|
||||||
|
shadowmaskMode: 0
|
||||||
|
blendWeights: 1
|
||||||
|
textureQuality: 1
|
||||||
|
anisotropicTextures: 0
|
||||||
|
antiAliasing: 0
|
||||||
|
softParticles: 0
|
||||||
|
softVegetation: 0
|
||||||
|
realtimeReflectionProbes: 0
|
||||||
|
billboardsFaceCameraPosition: 0
|
||||||
|
vSyncCount: 0
|
||||||
|
lodBias: 0.3
|
||||||
|
maximumLODLevel: 0
|
||||||
|
particleRaycastBudget: 4
|
||||||
|
asyncUploadTimeSlice: 2
|
||||||
|
asyncUploadBufferSize: 4
|
||||||
|
resolutionScalingFixedDPIFactor: 1
|
||||||
|
excludedTargetPlatforms: []
|
||||||
|
- serializedVersion: 2
|
||||||
|
name: Low
|
||||||
|
pixelLightCount: 0
|
||||||
|
shadows: 0
|
||||||
|
shadowResolution: 0
|
||||||
|
shadowProjection: 1
|
||||||
|
shadowCascades: 1
|
||||||
|
shadowDistance: 20
|
||||||
|
shadowNearPlaneOffset: 3
|
||||||
|
shadowCascade2Split: 0.33333334
|
||||||
|
shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
|
||||||
|
shadowmaskMode: 0
|
||||||
|
blendWeights: 2
|
||||||
|
textureQuality: 0
|
||||||
|
anisotropicTextures: 0
|
||||||
|
antiAliasing: 0
|
||||||
|
softParticles: 0
|
||||||
|
softVegetation: 0
|
||||||
|
realtimeReflectionProbes: 0
|
||||||
|
billboardsFaceCameraPosition: 0
|
||||||
|
vSyncCount: 0
|
||||||
|
lodBias: 0.4
|
||||||
|
maximumLODLevel: 0
|
||||||
|
particleRaycastBudget: 16
|
||||||
|
asyncUploadTimeSlice: 2
|
||||||
|
asyncUploadBufferSize: 4
|
||||||
|
resolutionScalingFixedDPIFactor: 1
|
||||||
|
excludedTargetPlatforms: []
|
||||||
|
- serializedVersion: 2
|
||||||
|
name: Medium
|
||||||
|
pixelLightCount: 1
|
||||||
|
shadows: 1
|
||||||
|
shadowResolution: 0
|
||||||
|
shadowProjection: 1
|
||||||
|
shadowCascades: 1
|
||||||
|
shadowDistance: 20
|
||||||
|
shadowNearPlaneOffset: 3
|
||||||
|
shadowCascade2Split: 0.33333334
|
||||||
|
shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
|
||||||
|
shadowmaskMode: 0
|
||||||
|
blendWeights: 2
|
||||||
|
textureQuality: 0
|
||||||
|
anisotropicTextures: 1
|
||||||
|
antiAliasing: 0
|
||||||
|
softParticles: 0
|
||||||
|
softVegetation: 0
|
||||||
|
realtimeReflectionProbes: 0
|
||||||
|
billboardsFaceCameraPosition: 0
|
||||||
|
vSyncCount: 1
|
||||||
|
lodBias: 0.7
|
||||||
|
maximumLODLevel: 0
|
||||||
|
particleRaycastBudget: 64
|
||||||
|
asyncUploadTimeSlice: 2
|
||||||
|
asyncUploadBufferSize: 4
|
||||||
|
resolutionScalingFixedDPIFactor: 1
|
||||||
|
excludedTargetPlatforms: []
|
||||||
|
- serializedVersion: 2
|
||||||
|
name: High
|
||||||
|
pixelLightCount: 2
|
||||||
|
shadows: 2
|
||||||
|
shadowResolution: 1
|
||||||
|
shadowProjection: 1
|
||||||
|
shadowCascades: 2
|
||||||
|
shadowDistance: 40
|
||||||
|
shadowNearPlaneOffset: 3
|
||||||
|
shadowCascade2Split: 0.33333334
|
||||||
|
shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
|
||||||
|
shadowmaskMode: 1
|
||||||
|
blendWeights: 2
|
||||||
|
textureQuality: 0
|
||||||
|
anisotropicTextures: 1
|
||||||
|
antiAliasing: 0
|
||||||
|
softParticles: 0
|
||||||
|
softVegetation: 1
|
||||||
|
realtimeReflectionProbes: 1
|
||||||
|
billboardsFaceCameraPosition: 1
|
||||||
|
vSyncCount: 1
|
||||||
|
lodBias: 1
|
||||||
|
maximumLODLevel: 0
|
||||||
|
particleRaycastBudget: 256
|
||||||
|
asyncUploadTimeSlice: 2
|
||||||
|
asyncUploadBufferSize: 4
|
||||||
|
resolutionScalingFixedDPIFactor: 1
|
||||||
|
excludedTargetPlatforms: []
|
||||||
|
- serializedVersion: 2
|
||||||
|
name: Very High
|
||||||
|
pixelLightCount: 3
|
||||||
|
shadows: 2
|
||||||
|
shadowResolution: 2
|
||||||
|
shadowProjection: 1
|
||||||
|
shadowCascades: 2
|
||||||
|
shadowDistance: 70
|
||||||
|
shadowNearPlaneOffset: 3
|
||||||
|
shadowCascade2Split: 0.33333334
|
||||||
|
shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
|
||||||
|
shadowmaskMode: 1
|
||||||
|
blendWeights: 4
|
||||||
|
textureQuality: 0
|
||||||
|
anisotropicTextures: 2
|
||||||
|
antiAliasing: 2
|
||||||
|
softParticles: 1
|
||||||
|
softVegetation: 1
|
||||||
|
realtimeReflectionProbes: 1
|
||||||
|
billboardsFaceCameraPosition: 1
|
||||||
|
vSyncCount: 1
|
||||||
|
lodBias: 1.5
|
||||||
|
maximumLODLevel: 0
|
||||||
|
particleRaycastBudget: 1024
|
||||||
|
asyncUploadTimeSlice: 2
|
||||||
|
asyncUploadBufferSize: 4
|
||||||
|
resolutionScalingFixedDPIFactor: 1
|
||||||
|
excludedTargetPlatforms: []
|
||||||
|
- serializedVersion: 2
|
||||||
|
name: Ultra
|
||||||
|
pixelLightCount: 4
|
||||||
|
shadows: 2
|
||||||
|
shadowResolution: 2
|
||||||
|
shadowProjection: 1
|
||||||
|
shadowCascades: 4
|
||||||
|
shadowDistance: 150
|
||||||
|
shadowNearPlaneOffset: 3
|
||||||
|
shadowCascade2Split: 0.33333334
|
||||||
|
shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
|
||||||
|
shadowmaskMode: 1
|
||||||
|
blendWeights: 4
|
||||||
|
textureQuality: 0
|
||||||
|
anisotropicTextures: 2
|
||||||
|
antiAliasing: 2
|
||||||
|
softParticles: 1
|
||||||
|
softVegetation: 1
|
||||||
|
realtimeReflectionProbes: 1
|
||||||
|
billboardsFaceCameraPosition: 1
|
||||||
|
vSyncCount: 1
|
||||||
|
lodBias: 2
|
||||||
|
maximumLODLevel: 0
|
||||||
|
particleRaycastBudget: 4096
|
||||||
|
asyncUploadTimeSlice: 2
|
||||||
|
asyncUploadBufferSize: 4
|
||||||
|
resolutionScalingFixedDPIFactor: 1
|
||||||
|
excludedTargetPlatforms: []
|
||||||
|
m_PerPlatformDefaultQuality:
|
||||||
|
Android: 2
|
||||||
|
Nintendo 3DS: 5
|
||||||
|
Nintendo Switch: 5
|
||||||
|
PS4: 5
|
||||||
|
PSM: 5
|
||||||
|
PSP2: 2
|
||||||
|
Samsung TV: 2
|
||||||
|
Standalone: 5
|
||||||
|
Tizen: 2
|
||||||
|
Web: 5
|
||||||
|
WebGL: 3
|
||||||
|
WiiU: 5
|
||||||
|
Windows Store Apps: 5
|
||||||
|
XboxOne: 5
|
||||||
|
iPhone: 2
|
||||||
|
tvOS: 2
|
43
examples/button-clicker/ProjectSettings/TagManager.asset
Normal file
43
examples/button-clicker/ProjectSettings/TagManager.asset
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!78 &1
|
||||||
|
TagManager:
|
||||||
|
serializedVersion: 2
|
||||||
|
tags: []
|
||||||
|
layers:
|
||||||
|
- Default
|
||||||
|
- TransparentFX
|
||||||
|
- Ignore Raycast
|
||||||
|
-
|
||||||
|
- Water
|
||||||
|
- UI
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
m_SortingLayers:
|
||||||
|
- name: Default
|
||||||
|
uniqueID: 0
|
||||||
|
locked: 0
|
@ -0,0 +1,9 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!5 &1
|
||||||
|
TimeManager:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
Fixed Timestep: 0.02
|
||||||
|
Maximum Allowed Timestep: 0.33333334
|
||||||
|
m_TimeScale: 1
|
||||||
|
Maximum Particle Timestep: 0.03
|
@ -0,0 +1,34 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!310 &1
|
||||||
|
UnityConnectSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_Enabled: 0
|
||||||
|
m_TestMode: 0
|
||||||
|
m_TestEventUrl:
|
||||||
|
m_TestConfigUrl:
|
||||||
|
m_TestInitMode: 0
|
||||||
|
CrashReportingSettings:
|
||||||
|
m_EventUrl: https://perf-events.cloud.unity3d.com/api/events/crashes
|
||||||
|
m_Enabled: 0
|
||||||
|
m_CaptureEditorExceptions: 1
|
||||||
|
UnityPurchasingSettings:
|
||||||
|
m_Enabled: 0
|
||||||
|
m_TestMode: 0
|
||||||
|
UnityAnalyticsSettings:
|
||||||
|
m_Enabled: 0
|
||||||
|
m_InitializeOnStartup: 1
|
||||||
|
m_TestMode: 0
|
||||||
|
m_TestEventUrl:
|
||||||
|
m_TestConfigUrl:
|
||||||
|
UnityAdsSettings:
|
||||||
|
m_Enabled: 0
|
||||||
|
m_InitializeOnStartup: 1
|
||||||
|
m_TestMode: 0
|
||||||
|
m_EnabledPlatforms: 4294967295
|
||||||
|
m_IosGameId:
|
||||||
|
m_AndroidGameId:
|
||||||
|
m_GameIds: {}
|
||||||
|
m_GameId:
|
||||||
|
PerformanceReportingSettings:
|
||||||
|
m_Enabled: 0
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,21 @@
|
|||||||
include_directories(${PROJECT_SOURCE_DIR}/include)
|
include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||||
add_executable(send-presence send-presence.c)
|
add_executable(
|
||||||
|
send-presence
|
||||||
|
MACOSX_BUNDLE
|
||||||
|
send-presence.c
|
||||||
|
)
|
||||||
|
set_target_properties(send-presence PROPERTIES
|
||||||
|
MACOSX_BUNDLE_BUNDLE_NAME "Send Presence"
|
||||||
|
MACOSX_BUNDLE_GUI_IDENTIFIER "com.discordapp.examples.send-presence"
|
||||||
|
)
|
||||||
target_link_libraries(send-presence discord-rpc)
|
target_link_libraries(send-presence discord-rpc)
|
||||||
|
|
||||||
|
install(
|
||||||
|
TARGETS send-presence
|
||||||
|
RUNTIME
|
||||||
|
DESTINATION "bin"
|
||||||
|
CONFIGURATIONS Release
|
||||||
|
BUNDLE
|
||||||
|
DESTINATION "bin"
|
||||||
|
CONFIGURATIONS Release
|
||||||
|
)
|
@ -11,21 +11,53 @@
|
|||||||
|
|
||||||
#include "discord-rpc.h"
|
#include "discord-rpc.h"
|
||||||
|
|
||||||
static const char* APPLICATION_ID = "338030514596216832";
|
static const char* APPLICATION_ID = "345229890980937739";
|
||||||
static int FrustrationLevel = 0;
|
static int FrustrationLevel = 0;
|
||||||
|
static int64_t StartTime;
|
||||||
|
static int SendPresence = 1;
|
||||||
|
|
||||||
|
static int prompt(char* line, size_t size)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
char* nl;
|
||||||
|
printf("\n> ");
|
||||||
|
fflush(stdout);
|
||||||
|
res = fgets(line, (int)size, stdin) ? 1 : 0;
|
||||||
|
line[size - 1] = 0;
|
||||||
|
nl = strchr(line, '\n');
|
||||||
|
if (nl) {
|
||||||
|
*nl = 0;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
static void updateDiscordPresence()
|
static void updateDiscordPresence()
|
||||||
{
|
{
|
||||||
char buffer[256];
|
if (SendPresence) {
|
||||||
DiscordRichPresence discordPresence;
|
char buffer[256];
|
||||||
memset(&discordPresence, 0, sizeof(discordPresence));
|
DiscordRichPresence discordPresence;
|
||||||
discordPresence.state = "West of House";
|
memset(&discordPresence, 0, sizeof(discordPresence));
|
||||||
sprintf(buffer, "Frustration level: %d", FrustrationLevel);
|
discordPresence.state = "West of House";
|
||||||
discordPresence.details = buffer;
|
sprintf(buffer, "Frustration level: %d", FrustrationLevel);
|
||||||
Discord_UpdatePresence(&discordPresence);
|
discordPresence.details = buffer;
|
||||||
|
discordPresence.startTimestamp = StartTime;
|
||||||
|
discordPresence.endTimestamp = time(0) + 5 * 60;
|
||||||
|
discordPresence.largeImageKey = "canary-large";
|
||||||
|
discordPresence.smallImageKey = "ptb-small";
|
||||||
|
discordPresence.partyId = "party1234";
|
||||||
|
discordPresence.partySize = 1;
|
||||||
|
discordPresence.partyMax = 6;
|
||||||
|
discordPresence.matchSecret = "xyzzy";
|
||||||
|
discordPresence.joinSecret = "join";
|
||||||
|
discordPresence.spectateSecret = "look";
|
||||||
|
discordPresence.instance = 0;
|
||||||
|
Discord_UpdatePresence(&discordPresence);
|
||||||
|
} else {
|
||||||
|
Discord_ClearPresence();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleDiscordReady()
|
static void handleDiscordReady(void)
|
||||||
{
|
{
|
||||||
printf("\nDiscord: ready\n");
|
printf("\nDiscord: ready\n");
|
||||||
}
|
}
|
||||||
@ -40,25 +72,60 @@ static void handleDiscordError(int errcode, const char* message)
|
|||||||
printf("\nDiscord: error (%d: %s)\n", errcode, message);
|
printf("\nDiscord: error (%d: %s)\n", errcode, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleDiscordPresenceRequested()
|
static void handleDiscordJoin(const char* secret)
|
||||||
{
|
{
|
||||||
printf("\nDiscord: requests presence\n");
|
printf("\nDiscord: join (%s)\n", secret);
|
||||||
updateDiscordPresence();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int prompt(char* line, size_t size)
|
static void handleDiscordSpectate(const char* secret)
|
||||||
{
|
{
|
||||||
int res;
|
printf("\nDiscord: spectate (%s)\n", secret);
|
||||||
char* nl;
|
}
|
||||||
printf("\n> ");
|
|
||||||
fflush(stdout);
|
static void handleDiscordJoinRequest(const DiscordJoinRequest* request)
|
||||||
res = fgets(line, size, stdin) ? 1 : 0;
|
{
|
||||||
line[size - 1] = 0;
|
int response = -1;
|
||||||
nl = strchr(line, '\n');
|
char yn[4];
|
||||||
if (nl) {
|
printf("\nDiscord: join request from %s - %s - %s\n",
|
||||||
*nl = 0;
|
request->username,
|
||||||
|
request->avatar,
|
||||||
|
request->userId);
|
||||||
|
do {
|
||||||
|
printf("Accept? (y/n)");
|
||||||
|
if (!prompt(yn, sizeof(yn))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!yn[0]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yn[0] == 'y') {
|
||||||
|
response = DISCORD_REPLY_YES;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yn[0] == 'n') {
|
||||||
|
response = DISCORD_REPLY_NO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (1);
|
||||||
|
if (response != -1) {
|
||||||
|
Discord_Respond(request->userId, response);
|
||||||
}
|
}
|
||||||
return res;
|
}
|
||||||
|
|
||||||
|
static void discordInit()
|
||||||
|
{
|
||||||
|
DiscordEventHandlers handlers;
|
||||||
|
memset(&handlers, 0, sizeof(handlers));
|
||||||
|
handlers.ready = handleDiscordReady;
|
||||||
|
handlers.disconnected = handleDiscordDisconnected;
|
||||||
|
handlers.errored = handleDiscordError;
|
||||||
|
handlers.joinGame = handleDiscordJoin;
|
||||||
|
handlers.spectateGame = handleDiscordSpectate;
|
||||||
|
handlers.joinRequest = handleDiscordJoinRequest;
|
||||||
|
Discord_Initialize(APPLICATION_ID, &handlers, 1, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gameLoop()
|
static void gameLoop()
|
||||||
@ -66,12 +133,39 @@ static void gameLoop()
|
|||||||
char line[512];
|
char line[512];
|
||||||
char* space;
|
char* space;
|
||||||
|
|
||||||
|
StartTime = time(0);
|
||||||
|
|
||||||
printf("You are standing in an open field west of a white house.\n");
|
printf("You are standing in an open field west of a white house.\n");
|
||||||
while (prompt(line, sizeof(line))) {
|
while (prompt(line, sizeof(line))) {
|
||||||
if (line[0]) {
|
if (line[0]) {
|
||||||
if (line[0] == 'q') {
|
if (line[0] == 'q') {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (line[0] == 't') {
|
||||||
|
printf("Shutting off Discord.\n");
|
||||||
|
Discord_Shutdown();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line[0] == 'c') {
|
||||||
|
if (SendPresence) {
|
||||||
|
printf("Clearing presence information.\n");
|
||||||
|
SendPresence = 0;
|
||||||
|
} else {
|
||||||
|
printf("Restoring presence information.\n");
|
||||||
|
SendPresence = 1;
|
||||||
|
}
|
||||||
|
updateDiscordPresence();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line[0] == 'y') {
|
||||||
|
printf("Reinit Discord.\n");
|
||||||
|
discordInit();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (time(NULL) & 1) {
|
if (time(NULL) & 1) {
|
||||||
printf("I don't understand that.\n");
|
printf("I don't understand that.\n");
|
||||||
}
|
}
|
||||||
@ -95,15 +189,9 @@ static void gameLoop()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main()
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
DiscordEventHandlers handlers;
|
discordInit();
|
||||||
memset(&handlers, 0, sizeof(handlers));
|
|
||||||
handlers.ready = handleDiscordReady;
|
|
||||||
handlers.disconnected = handleDiscordDisconnected;
|
|
||||||
handlers.errored = handleDiscordError;
|
|
||||||
handlers.presenceRequested = handleDiscordPresenceRequested;
|
|
||||||
Discord_Initialize(APPLICATION_ID, &handlers);
|
|
||||||
|
|
||||||
gameLoop();
|
gameLoop();
|
||||||
|
|
||||||
|
78
examples/unrealstatus/.gitignore
vendored
Normal file
78
examples/unrealstatus/.gitignore
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# Visual Studio 2015 user specific files
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
# Visual Studio 2015 database file
|
||||||
|
*.VC.db
|
||||||
|
|
||||||
|
# Compiled Object files
|
||||||
|
*.slo
|
||||||
|
*.lo
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Compiled Dynamic libraries
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
|
||||||
|
# Fortran module files
|
||||||
|
*.mod
|
||||||
|
|
||||||
|
# Compiled Static libraries
|
||||||
|
*.lai
|
||||||
|
*.la
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
*.ipa
|
||||||
|
|
||||||
|
# These project files can be generated by the engine
|
||||||
|
*.xcodeproj
|
||||||
|
*.xcworkspace
|
||||||
|
*.sln
|
||||||
|
*.suo
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.VC.db
|
||||||
|
*.VC.opendb
|
||||||
|
|
||||||
|
# Precompiled Assets
|
||||||
|
SourceArt/**/*.png
|
||||||
|
SourceArt/**/*.tga
|
||||||
|
|
||||||
|
# Binary Files
|
||||||
|
Binaries/
|
||||||
|
|
||||||
|
# Builds
|
||||||
|
Build/*
|
||||||
|
|
||||||
|
# Whitelist PakBlacklist-<BuildConfiguration>.txt files
|
||||||
|
!Build/*/
|
||||||
|
Build/*/**
|
||||||
|
!Build/*/PakBlacklist*.txt
|
||||||
|
|
||||||
|
# Don't ignore icon files in Build
|
||||||
|
!Build/**/*.ico
|
||||||
|
|
||||||
|
# Built data for maps
|
||||||
|
*_BuiltData.uasset
|
||||||
|
|
||||||
|
# Configuration files generated by the Editor
|
||||||
|
Saved/*
|
||||||
|
|
||||||
|
# Compiled source files for the engine to use
|
||||||
|
Intermediate/
|
||||||
|
|
||||||
|
# Cache files for the editor to use
|
||||||
|
DerivedDataCache/
|
||||||
|
|
||||||
|
# Library headers must be copied automatically by the build script (build.py unreal)
|
||||||
|
Plugins/DiscordRpc/Source/ThirdParty/DiscordRpcLibrary/Include
|
0
examples/unrealstatus/Config/DefaultEditor.ini
Normal file
0
examples/unrealstatus/Config/DefaultEditor.ini
Normal file
54
examples/unrealstatus/Config/DefaultEngine.ini
Normal file
54
examples/unrealstatus/Config/DefaultEngine.ini
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
[URL]
|
||||||
|
|
||||||
|
[/Script/HardwareTargeting.HardwareTargetingSettings]
|
||||||
|
TargetedHardwareClass=Desktop
|
||||||
|
AppliedTargetedHardwareClass=Desktop
|
||||||
|
DefaultGraphicsPerformance=Maximum
|
||||||
|
AppliedDefaultGraphicsPerformance=Maximum
|
||||||
|
|
||||||
|
[/Script/Engine.EndUserSettings]
|
||||||
|
bSendAnonymousUsageDataToEpic=False
|
||||||
|
|
||||||
|
[/Script/Engine.PhysicsSettings]
|
||||||
|
DefaultGravityZ=-980.000000
|
||||||
|
DefaultTerminalVelocity=4000.000000
|
||||||
|
DefaultFluidFriction=0.300000
|
||||||
|
SimulateScratchMemorySize=262144
|
||||||
|
RagdollAggregateThreshold=4
|
||||||
|
TriangleMeshTriangleMinAreaThreshold=5.000000
|
||||||
|
bEnableAsyncScene=False
|
||||||
|
bEnableShapeSharing=False
|
||||||
|
bEnablePCM=False
|
||||||
|
bEnableStabilization=False
|
||||||
|
bWarnMissingLocks=True
|
||||||
|
bEnable2DPhysics=False
|
||||||
|
LockedAxis=Invalid
|
||||||
|
DefaultDegreesOfFreedom=Full3D
|
||||||
|
BounceThresholdVelocity=200.000000
|
||||||
|
FrictionCombineMode=Average
|
||||||
|
RestitutionCombineMode=Average
|
||||||
|
MaxAngularVelocity=3600.000000
|
||||||
|
MaxDepenetrationVelocity=0.000000
|
||||||
|
ContactOffsetMultiplier=0.010000
|
||||||
|
MinContactOffset=0.000100
|
||||||
|
MaxContactOffset=1.000000
|
||||||
|
bSimulateSkeletalMeshOnDedicatedServer=True
|
||||||
|
DefaultShapeComplexity=CTF_UseSimpleAndComplex
|
||||||
|
bDefaultHasComplexCollision=True
|
||||||
|
bSuppressFaceRemapTable=False
|
||||||
|
bSupportUVFromHitResults=False
|
||||||
|
bDisableActiveActors=False
|
||||||
|
bDisableCCD=False
|
||||||
|
MaxPhysicsDeltaTime=0.033333
|
||||||
|
bSubstepping=False
|
||||||
|
bSubsteppingAsync=False
|
||||||
|
MaxSubstepDeltaTime=0.016667
|
||||||
|
MaxSubsteps=6
|
||||||
|
SyncSceneSmoothingFactor=0.000000
|
||||||
|
AsyncSceneSmoothingFactor=0.990000
|
||||||
|
InitialAverageFrameRate=0.016667
|
||||||
|
|
||||||
|
[/Script/EngineSettings.GameMapsSettings]
|
||||||
|
EditorStartupMap=/Game/ShowTheUILevel.ShowTheUILevel
|
||||||
|
GameDefaultMap=/Game/ShowTheUILevel.ShowTheUILevel
|
||||||
|
|
7
examples/unrealstatus/Config/DefaultGame.ini
Normal file
7
examples/unrealstatus/Config/DefaultGame.ini
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[/Script/EngineSettings.GeneralProjectSettings]
|
||||||
|
ProjectID=E5977A24492699DF20B8ADBF736AF6C6
|
||||||
|
ProjectName=Discord RPC Example
|
||||||
|
CompanyName=Discord Inc.
|
||||||
|
Homepage="https://discordapp.com/"
|
||||||
|
CopyrightNotice=
|
||||||
|
|
BIN
examples/unrealstatus/Content/MainScreenBP.uasset
Normal file
BIN
examples/unrealstatus/Content/MainScreenBP.uasset
Normal file
Binary file not shown.
BIN
examples/unrealstatus/Content/MouseGameModeBP.uasset
Normal file
BIN
examples/unrealstatus/Content/MouseGameModeBP.uasset
Normal file
Binary file not shown.
BIN
examples/unrealstatus/Content/MousePlayerControllerBP.uasset
Normal file
BIN
examples/unrealstatus/Content/MousePlayerControllerBP.uasset
Normal file
Binary file not shown.
BIN
examples/unrealstatus/Content/ShowTheUILevel.umap
Normal file
BIN
examples/unrealstatus/Content/ShowTheUILevel.umap
Normal file
Binary file not shown.
29
examples/unrealstatus/Plugins/discordrpc/DiscordRpc.uplugin
Normal file
29
examples/unrealstatus/Plugins/discordrpc/DiscordRpc.uplugin
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"FileVersion": 3,
|
||||||
|
"Version": 1,
|
||||||
|
"VersionName": "1.0",
|
||||||
|
"FriendlyName": "Discord RPC",
|
||||||
|
"Description": "Wrap the Discord RPC library.",
|
||||||
|
"Category": "Messaging",
|
||||||
|
"CreatedBy": "Chris Marsh <chris@discordapp.com>",
|
||||||
|
"CreatedByURL": "https://discordapp.com/",
|
||||||
|
"DocsURL": "",
|
||||||
|
"MarketplaceURL": "",
|
||||||
|
"SupportURL": "",
|
||||||
|
"CanContainContent": true,
|
||||||
|
"IsBetaVersion": true,
|
||||||
|
"Installed": false,
|
||||||
|
"Modules": [
|
||||||
|
{
|
||||||
|
"Name": "DiscordRpc",
|
||||||
|
"Type": "Runtime",
|
||||||
|
"LoadingPhase": "PreDefault",
|
||||||
|
"WhitelistPlatforms" :
|
||||||
|
[
|
||||||
|
"Win64",
|
||||||
|
"Linux",
|
||||||
|
"Mac"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
examples/unrealstatus/Plugins/discordrpc/Resources/Icon128.png
Normal file
BIN
examples/unrealstatus/Plugins/discordrpc/Resources/Icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.6 KiB |
BIN
examples/unrealstatus/Plugins/discordrpc/Resources/discord.png
Normal file
BIN
examples/unrealstatus/Plugins/discordrpc/Resources/discord.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.6 KiB |
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
public class DiscordRpc : ModuleRules
|
||||||
|
{
|
||||||
|
#if WITH_FORWARDED_MODULE_RULES_CTOR
|
||||||
|
public DiscordRpc(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
#else
|
||||||
|
public DiscordRpc(TargetInfo Target)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
Definitions.Add("DISCORD_DYNAMIC_LIB=1");
|
||||||
|
|
||||||
|
PublicIncludePaths.AddRange(
|
||||||
|
new string[] {
|
||||||
|
"DiscordRpc/Public"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
PrivateIncludePaths.AddRange(
|
||||||
|
new string[] {
|
||||||
|
"DiscordRpc/Private"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"Core",
|
||||||
|
"DiscordRpcLibrary"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
PrivateDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"CoreUObject",
|
||||||
|
"Engine",
|
||||||
|
"Slate",
|
||||||
|
"SlateCore",
|
||||||
|
"Projects"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
DynamicallyLoadedModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
// ... add any modules that your module loads dynamically here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
string BaseDirectory = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", "Source", "ThirdParty", "DiscordRpcLibrary"));
|
||||||
|
PublicIncludePaths.Add(Path.Combine(BaseDirectory, "Include"));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
// 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)
|
@ -0,0 +1,150 @@
|
|||||||
|
#include "DiscordRpcPrivatePCH.h"
|
||||||
|
#include "DiscordRpcBlueprint.h"
|
||||||
|
#include "discord-rpc.h"
|
||||||
|
|
||||||
|
DEFINE_LOG_CATEGORY(Discord)
|
||||||
|
|
||||||
|
static UDiscordRpc* self = nullptr;
|
||||||
|
|
||||||
|
static void ReadyHandler()
|
||||||
|
{
|
||||||
|
UE_LOG(Discord, Log, TEXT("Discord connected"));
|
||||||
|
if (self) {
|
||||||
|
self->IsConnected = true;
|
||||||
|
self->OnConnected.Broadcast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DisconnectHandler(int errorCode, const char* message)
|
||||||
|
{
|
||||||
|
auto msg = FString(message);
|
||||||
|
UE_LOG(Discord, Log, TEXT("Discord disconnected (%d): %s"), errorCode, *msg);
|
||||||
|
if (self) {
|
||||||
|
self->IsConnected = false;
|
||||||
|
self->OnDisconnected.Broadcast(errorCode, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ErroredHandler(int errorCode, const char* message)
|
||||||
|
{
|
||||||
|
auto msg = FString(message);
|
||||||
|
UE_LOG(Discord, Log, TEXT("Discord error (%d): %s"), errorCode, *msg);
|
||||||
|
if (self) {
|
||||||
|
self->OnErrored.Broadcast(errorCode, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void JoinGameHandler(const char* joinSecret)
|
||||||
|
{
|
||||||
|
auto secret = FString(joinSecret);
|
||||||
|
UE_LOG(Discord, Log, TEXT("Discord join %s"), *secret);
|
||||||
|
if (self) {
|
||||||
|
self->OnJoin.Broadcast(secret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SpectateGameHandler(const char* spectateSecret)
|
||||||
|
{
|
||||||
|
auto secret = FString(spectateSecret);
|
||||||
|
UE_LOG(Discord, Log, TEXT("Discord spectate %s"), *secret);
|
||||||
|
if (self) {
|
||||||
|
self->OnSpectate.Broadcast(secret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void JoinRequestHandler(const DiscordJoinRequest* request)
|
||||||
|
{
|
||||||
|
FDiscordJoinRequestData jr;
|
||||||
|
jr.userId = ANSI_TO_TCHAR(request->userId);
|
||||||
|
jr.username = ANSI_TO_TCHAR(request->username);
|
||||||
|
jr.discriminator = ANSI_TO_TCHAR(request->discriminator);
|
||||||
|
jr.avatar = ANSI_TO_TCHAR(request->avatar);
|
||||||
|
UE_LOG(Discord, Log, TEXT("Discord join request from %s#%s"), *jr.username, *jr.discriminator);
|
||||||
|
if (self) {
|
||||||
|
self->OnJoinRequest.Broadcast(jr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDiscordRpc::Initialize(const FString& applicationId,
|
||||||
|
bool autoRegister,
|
||||||
|
const FString& optionalSteamId)
|
||||||
|
{
|
||||||
|
self = this;
|
||||||
|
IsConnected = false;
|
||||||
|
DiscordEventHandlers handlers{};
|
||||||
|
handlers.ready = ReadyHandler;
|
||||||
|
handlers.disconnected = DisconnectHandler;
|
||||||
|
handlers.errored = ErroredHandler;
|
||||||
|
if (OnJoin.IsBound()) {
|
||||||
|
handlers.joinGame = JoinGameHandler;
|
||||||
|
}
|
||||||
|
if (OnSpectate.IsBound()) {
|
||||||
|
handlers.spectateGame = SpectateGameHandler;
|
||||||
|
}
|
||||||
|
if (OnJoinRequest.IsBound()) {
|
||||||
|
handlers.joinRequest = JoinRequestHandler;
|
||||||
|
}
|
||||||
|
auto appId = StringCast<ANSICHAR>(*applicationId);
|
||||||
|
auto steamId = StringCast<ANSICHAR>(*optionalSteamId);
|
||||||
|
Discord_Initialize(
|
||||||
|
(const char*)appId.Get(), &handlers, autoRegister, (const char*)steamId.Get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDiscordRpc::Shutdown()
|
||||||
|
{
|
||||||
|
Discord_Shutdown();
|
||||||
|
self = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDiscordRpc::RunCallbacks()
|
||||||
|
{
|
||||||
|
Discord_RunCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDiscordRpc::UpdatePresence()
|
||||||
|
{
|
||||||
|
DiscordRichPresence rp{};
|
||||||
|
|
||||||
|
auto state = StringCast<ANSICHAR>(*RichPresence.state);
|
||||||
|
rp.state = state.Get();
|
||||||
|
|
||||||
|
auto details = StringCast<ANSICHAR>(*RichPresence.details);
|
||||||
|
rp.details = details.Get();
|
||||||
|
|
||||||
|
auto largeImageKey = StringCast<ANSICHAR>(*RichPresence.largeImageKey);
|
||||||
|
rp.largeImageKey = largeImageKey.Get();
|
||||||
|
|
||||||
|
auto largeImageText = StringCast<ANSICHAR>(*RichPresence.largeImageText);
|
||||||
|
rp.largeImageText = largeImageText.Get();
|
||||||
|
|
||||||
|
auto smallImageKey = StringCast<ANSICHAR>(*RichPresence.smallImageKey);
|
||||||
|
rp.smallImageKey = smallImageKey.Get();
|
||||||
|
|
||||||
|
auto smallImageText = StringCast<ANSICHAR>(*RichPresence.smallImageText);
|
||||||
|
rp.smallImageText = smallImageText.Get();
|
||||||
|
|
||||||
|
auto partyId = StringCast<ANSICHAR>(*RichPresence.partyId);
|
||||||
|
rp.partyId = partyId.Get();
|
||||||
|
|
||||||
|
auto matchSecret = StringCast<ANSICHAR>(*RichPresence.matchSecret);
|
||||||
|
rp.matchSecret = matchSecret.Get();
|
||||||
|
|
||||||
|
auto joinSecret = StringCast<ANSICHAR>(*RichPresence.joinSecret);
|
||||||
|
rp.joinSecret = joinSecret.Get();
|
||||||
|
|
||||||
|
auto spectateSecret = StringCast<ANSICHAR>(*RichPresence.spectateSecret);
|
||||||
|
rp.spectateSecret = spectateSecret.Get();
|
||||||
|
|
||||||
|
rp.startTimestamp = RichPresence.startTimestamp;
|
||||||
|
rp.endTimestamp = RichPresence.endTimestamp;
|
||||||
|
rp.partySize = RichPresence.partySize;
|
||||||
|
rp.partyMax = RichPresence.partyMax;
|
||||||
|
rp.instance = RichPresence.instance;
|
||||||
|
|
||||||
|
Discord_UpdatePresence(&rp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDiscordRpc::ClearPresence()
|
||||||
|
{
|
||||||
|
Discord_ClearPresence();
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
#include "Core.h"
|
||||||
|
#include "DiscordRpc.h"
|
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ModuleManager.h"
|
||||||
|
|
||||||
|
class FDiscordRpcModule : public IModuleInterface {
|
||||||
|
public:
|
||||||
|
/** IModuleInterface implementation */
|
||||||
|
virtual void StartupModule() override;
|
||||||
|
virtual void ShutdownModule() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** Handle to the test dll we will load */
|
||||||
|
void* DiscordRpcLibraryHandle;
|
||||||
|
|
||||||
|
/** StartupModule is covered with defines, these functions are the place to put breakpoints */
|
||||||
|
static bool LoadDependency(const FString& Dir, const FString& Name, void*& Handle);
|
||||||
|
static void FreeDependency(void*& Handle);
|
||||||
|
};
|
@ -0,0 +1,154 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Engine.h"
|
||||||
|
#include "DiscordRpcBlueprint.generated.h"
|
||||||
|
|
||||||
|
// unreal's header tool hates clang-format
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask to join callback data
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDiscordJoinRequestData {
|
||||||
|
GENERATED_USTRUCT_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
FString userId;
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
FString username;
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
FString discriminator;
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
FString avatar;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
DECLARE_LOG_CATEGORY_EXTERN(Discord, Log, All);
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDiscordConnected);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordDisconnected, int, errorCode, const FString&, errorMessage);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordErrored, int, errorCode, const FString&, errorMessage);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoin, const FString&, joinSecret);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordSpectate, const FString&, spectateSecret);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoinRequest, const FDiscordJoinRequestData&, joinRequest);
|
||||||
|
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rich presence data
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDiscordRichPresence {
|
||||||
|
GENERATED_USTRUCT_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
FString state;
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
FString details;
|
||||||
|
// todo, timestamps are 64bit, does that even matter?
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
int startTimestamp;
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
int endTimestamp;
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
FString largeImageKey;
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
FString largeImageText;
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
FString smallImageKey;
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
FString smallImageText;
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
FString partyId;
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
int partySize;
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
int partyMax;
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
FString matchSecret;
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
FString joinSecret;
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
FString spectateSecret;
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
bool instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UCLASS(BlueprintType, meta = (DisplayName = "Discord RPC"), Category = "Discord")
|
||||||
|
class DISCORDRPC_API UDiscordRpc : public UObject {
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UFUNCTION(BlueprintCallable,
|
||||||
|
meta = (DisplayName = "Initialize connection", Keywords = "Discord rpc"),
|
||||||
|
Category = "Discord")
|
||||||
|
void Initialize(const FString& applicationId,
|
||||||
|
bool autoRegister,
|
||||||
|
const FString& optionalSteamId);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,
|
||||||
|
meta = (DisplayName = "Shut down connection", Keywords = "Discord rpc"),
|
||||||
|
Category = "Discord")
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,
|
||||||
|
meta = (DisplayName = "Check for callbacks", Keywords = "Discord rpc"),
|
||||||
|
Category = "Discord")
|
||||||
|
void RunCallbacks();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,
|
||||||
|
meta = (DisplayName = "Send presence", Keywords = "Discord rpc"),
|
||||||
|
Category = "Discord")
|
||||||
|
void UpdatePresence();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,
|
||||||
|
meta = (DisplayName = "Clear presence", Keywords = "Discord rpc"),
|
||||||
|
Category = "Discord")
|
||||||
|
void ClearPresence();
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly,
|
||||||
|
meta = (DisplayName = "Is Discord connected", Keywords = "Discord rpc"),
|
||||||
|
Category = "Discord")
|
||||||
|
bool IsConnected;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable,
|
||||||
|
meta = (DisplayName = "On connection", Keywords = "Discord rpc"),
|
||||||
|
Category = "Discord")
|
||||||
|
FDiscordConnected OnConnected;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable,
|
||||||
|
meta = (DisplayName = "On disconnection", Keywords = "Discord rpc"),
|
||||||
|
Category = "Discord")
|
||||||
|
FDiscordDisconnected OnDisconnected;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable,
|
||||||
|
meta = (DisplayName = "On error message", Keywords = "Discord rpc"),
|
||||||
|
Category = "Discord")
|
||||||
|
FDiscordErrored OnErrored;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable,
|
||||||
|
meta = (DisplayName = "When Discord user presses join", Keywords = "Discord rpc"),
|
||||||
|
Category = "Discord")
|
||||||
|
FDiscordJoin OnJoin;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable,
|
||||||
|
meta = (DisplayName = "When Discord user presses spectate", Keywords = "Discord rpc"),
|
||||||
|
Category = "Discord")
|
||||||
|
FDiscordSpectate OnSpectate;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable,
|
||||||
|
meta = (DisplayName = "When Discord another user sends a join request",
|
||||||
|
Keywords = "Discord rpc"),
|
||||||
|
Category = "Discord")
|
||||||
|
FDiscordJoinRequest OnJoinRequest;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadWrite,
|
||||||
|
meta = (DisplayName = "Rich presence info", Keywords = "Discord rpc"),
|
||||||
|
Category = "Discord")
|
||||||
|
FDiscordRichPresence RichPresence;
|
||||||
|
};
|
@ -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")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
examples/unrealstatus/Source/unrealstatus.Target.cs
Normal file
14
examples/unrealstatus/Source/unrealstatus.Target.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class unrealstatusTarget : TargetRules
|
||||||
|
{
|
||||||
|
public unrealstatusTarget(TargetInfo Target) : base(Target)
|
||||||
|
{
|
||||||
|
Type = TargetType.Game;
|
||||||
|
|
||||||
|
ExtraModuleNames.AddRange( new string[] { "unrealstatus" } );
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class unrealstatus : ModuleRules
|
||||||
|
{
|
||||||
|
public unrealstatus(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
|
||||||
|
|
||||||
|
PrivateDependencyModuleNames.AddRange(new string[] { });
|
||||||
|
|
||||||
|
// Uncomment if you are using Slate UI
|
||||||
|
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
|
||||||
|
|
||||||
|
// Uncomment if you are using online features
|
||||||
|
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
|
||||||
|
|
||||||
|
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#include "unrealstatus.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
|
IMPLEMENT_PRIMARY_GAME_MODULE(FDefaultGameModuleImpl, unrealstatus, "unrealstatus");
|
5
examples/unrealstatus/Source/unrealstatus/unrealstatus.h
Normal file
5
examples/unrealstatus/Source/unrealstatus/unrealstatus.h
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
@ -0,0 +1,3 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#include "unrealstatusGameModeBase.h"
|
@ -0,0 +1,15 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/GameModeBase.h"
|
||||||
|
#include "unrealstatusGameModeBase.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class UNREALSTATUS_API AunrealstatusGameModeBase : public AGameModeBase {
|
||||||
|
GENERATED_BODY()
|
||||||
|
};
|
14
examples/unrealstatus/Source/unrealstatusEditor.Target.cs
Normal file
14
examples/unrealstatus/Source/unrealstatusEditor.Target.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class unrealstatusEditorTarget : TargetRules
|
||||||
|
{
|
||||||
|
public unrealstatusEditorTarget(TargetInfo Target) : base(Target)
|
||||||
|
{
|
||||||
|
Type = TargetType.Editor;
|
||||||
|
|
||||||
|
ExtraModuleNames.AddRange( new string[] { "unrealstatus" } );
|
||||||
|
}
|
||||||
|
}
|
19
examples/unrealstatus/unrealstatus.uproject
Normal file
19
examples/unrealstatus/unrealstatus.uproject
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"FileVersion": 3,
|
||||||
|
"EngineAssociation": "4.18",
|
||||||
|
"Category": "",
|
||||||
|
"Description": "",
|
||||||
|
"Modules": [
|
||||||
|
{
|
||||||
|
"Name": "unrealstatus",
|
||||||
|
"Type": "Runtime",
|
||||||
|
"LoadingPhase": "Default"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TargetPlatforms": [
|
||||||
|
"LinuxNoEditor",
|
||||||
|
"MacNoEditor",
|
||||||
|
"WindowsNoEditor",
|
||||||
|
"AllDesktop"
|
||||||
|
]
|
||||||
|
}
|
@ -1,49 +1,84 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct DiscordRichPresence {
|
typedef struct DiscordRichPresence {
|
||||||
const char* state;
|
const char* state; /* max 128 bytes */
|
||||||
const char* details;
|
const char* details; /* max 128 bytes */
|
||||||
int64_t startTimestamp;
|
int64_t startTimestamp;
|
||||||
int64_t endTimestamp;
|
int64_t endTimestamp;
|
||||||
const char* largeImageKey;
|
const char* largeImageKey; /* max 32 bytes */
|
||||||
const char* largeImageText;
|
const char* largeImageText; /* max 128 bytes */
|
||||||
const char* smallImageKey;
|
const char* smallImageKey; /* max 32 bytes */
|
||||||
const char* smallImageText;
|
const char* smallImageText; /* max 128 bytes */
|
||||||
const char* partyId;
|
const char* partyId; /* max 128 bytes */
|
||||||
int partySize;
|
int partySize;
|
||||||
int partyMax;
|
int partyMax;
|
||||||
const char* matchSecret;
|
const char* matchSecret; /* max 128 bytes */
|
||||||
const char* joinSecret;
|
const char* joinSecret; /* max 128 bytes */
|
||||||
const char* spectateSecret;
|
const char* spectateSecret; /* max 128 bytes */
|
||||||
int8_t instance;
|
int8_t instance;
|
||||||
} DiscordRichPresence;
|
} DiscordRichPresence;
|
||||||
|
|
||||||
|
typedef struct DiscordJoinRequest {
|
||||||
|
const char* userId;
|
||||||
|
const char* username;
|
||||||
|
const char* discriminator;
|
||||||
|
const char* avatar;
|
||||||
|
} DiscordJoinRequest;
|
||||||
|
|
||||||
typedef struct DiscordEventHandlers {
|
typedef struct DiscordEventHandlers {
|
||||||
void (*ready)();
|
void (*ready)(void);
|
||||||
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 (*presenceRequested)();
|
|
||||||
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);
|
||||||
} DiscordEventHandlers;
|
} DiscordEventHandlers;
|
||||||
|
|
||||||
void Discord_Initialize(const char* applicationId, DiscordEventHandlers* handlers);
|
#define DISCORD_REPLY_NO 0
|
||||||
void Discord_Shutdown();
|
#define DISCORD_REPLY_YES 1
|
||||||
|
#define DISCORD_REPLY_IGNORE 2
|
||||||
|
|
||||||
|
DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
|
||||||
|
DiscordEventHandlers* handlers,
|
||||||
|
int autoRegister,
|
||||||
|
const char* optionalSteamId);
|
||||||
|
DISCORD_EXPORT void Discord_Shutdown(void);
|
||||||
|
|
||||||
/* checks for incoming messages, dispatches callbacks */
|
/* checks for incoming messages, dispatches callbacks */
|
||||||
void Discord_RunCallbacks();
|
DISCORD_EXPORT void Discord_RunCallbacks(void);
|
||||||
|
|
||||||
/* If you disable the lib starting its own io thread, you'll need to call this from your own */
|
/* If you disable the lib starting its own io thread, you'll need to call this from your own */
|
||||||
#ifdef DISCORD_DISABLE_IO_THREAD
|
#ifdef DISCORD_DISABLE_IO_THREAD
|
||||||
void Discord_UpdateConnection();
|
DISCORD_EXPORT void Discord_UpdateConnection(void);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} /* extern "C" */
|
} /* extern "C" */
|
||||||
|
@ -1,33 +1,130 @@
|
|||||||
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(USE_STATIC_CRT "Use /MT[d] for dynamic library" OFF)
|
||||||
|
|
||||||
if (${ENABLE_IO_THREAD} EQUAL OFF)
|
set(CMAKE_CXX_STANDARD 14)
|
||||||
add_definitions(-DDISCORD_DISABLE_IO_THREAD)
|
|
||||||
endif (${ENABLE_IO_THREAD} EQUAL OFF)
|
|
||||||
|
|
||||||
set(BASE_RPC_SRC ${PROJECT_SOURCE_DIR}/include/discord-rpc.h discord-rpc.cpp rpc_connection.h rpc_connection.cpp serialization.h serialization.cpp connection.h backoff.h)
|
set(BASE_RPC_SRC
|
||||||
|
${PROJECT_SOURCE_DIR}/include/discord-rpc.h
|
||||||
|
discord-rpc.cpp
|
||||||
|
discord_register.h
|
||||||
|
rpc_connection.h
|
||||||
|
rpc_connection.cpp
|
||||||
|
serialization.h
|
||||||
|
serialization.cpp
|
||||||
|
connection.h
|
||||||
|
backoff.h
|
||||||
|
msg_queue.h
|
||||||
|
)
|
||||||
|
|
||||||
|
if (${BUILD_SHARED_LIBS})
|
||||||
|
if(WIN32)
|
||||||
|
set(BASE_RPC_SRC ${BASE_RPC_SRC} dllmain.cpp)
|
||||||
|
endif(WIN32)
|
||||||
|
endif(${BUILD_SHARED_LIBS})
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
add_library(discord-rpc STATIC ${BASE_RPC_SRC} connection_win.cpp)
|
add_definitions(-DDISCORD_WINDOWS)
|
||||||
|
set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_win.cpp discord_register_win.cpp)
|
||||||
|
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
|
||||||
|
/Wall
|
||||||
|
/wd4100 # unreferenced formal parameter
|
||||||
|
/wd4514 # unreferenced inline
|
||||||
|
/wd4625 # copy constructor deleted
|
||||||
|
/wd5026 # move constructor deleted
|
||||||
|
/wd4626 # move assignment operator deleted
|
||||||
|
/wd4668 # not defined preprocessor macro
|
||||||
|
/wd4710 # function not inlined
|
||||||
|
/wd4711 # function was inlined
|
||||||
|
/wd4820 # structure padding
|
||||||
|
/wd4946 # reinterpret_cast used between related classes
|
||||||
|
/wd5027 # move assignment operator was implicitly defined as deleted
|
||||||
|
)
|
||||||
|
endif(MSVC)
|
||||||
|
target_link_libraries(discord-rpc PRIVATE psapi)
|
||||||
endif(WIN32)
|
endif(WIN32)
|
||||||
|
|
||||||
if(UNIX)
|
if(UNIX)
|
||||||
add_library(discord-rpc STATIC ${BASE_RPC_SRC} connection_unix.cpp)
|
set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_unix.cpp)
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
add_definitions(-DDISCORD_OSX)
|
||||||
|
set(BASE_RPC_SRC ${BASE_RPC_SRC} discord_register_osx.m)
|
||||||
|
else (APPLE)
|
||||||
|
add_definitions(-DDISCORD_LINUX)
|
||||||
|
set(BASE_RPC_SRC ${BASE_RPC_SRC} discord_register_linux.cpp)
|
||||||
|
endif(APPLE)
|
||||||
|
|
||||||
|
add_library(discord-rpc ${BASE_RPC_SRC})
|
||||||
|
target_link_libraries(discord-rpc PUBLIC pthread)
|
||||||
|
target_compile_options(discord-rpc PRIVATE
|
||||||
|
-g
|
||||||
|
-Wall
|
||||||
|
-Wextra
|
||||||
|
-Wpedantic
|
||||||
|
-Werror
|
||||||
|
-Wno-unknown-pragmas # pragma push thing doesn't work on clang
|
||||||
|
-Wno-old-style-cast # it's fine
|
||||||
|
-Wno-c++98-compat # that was almost 2 decades ago
|
||||||
|
-Wno-c++98-compat-pedantic
|
||||||
|
-Wno-missing-noreturn
|
||||||
|
-Wno-padded # structure padding
|
||||||
|
-Wno-covered-switch-default
|
||||||
|
-Wno-exit-time-destructors # not sure about these
|
||||||
|
-Wno-global-constructors
|
||||||
|
)
|
||||||
|
|
||||||
|
if (${BUILD_SHARED_LIBS})
|
||||||
|
target_compile_options(discord-rpc PRIVATE -fPIC)
|
||||||
|
endif (${BUILD_SHARED_LIBS})
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
target_link_libraries(discord-rpc PRIVATE "-framework AppKit")
|
||||||
|
endif (APPLE)
|
||||||
endif(UNIX)
|
endif(UNIX)
|
||||||
|
|
||||||
target_include_directories(discord-rpc PRIVATE ${RAPIDJSON}/include)
|
target_include_directories(discord-rpc PRIVATE ${RAPIDJSON}/include)
|
||||||
|
|
||||||
add_dependencies(discord-rpc clangformat)
|
if (NOT ${ENABLE_IO_THREAD})
|
||||||
|
target_compile_definitions(discord-rpc PUBLIC -DDISCORD_DISABLE_IO_THREAD)
|
||||||
|
endif (NOT ${ENABLE_IO_THREAD})
|
||||||
|
|
||||||
|
if (${BUILD_SHARED_LIBS})
|
||||||
|
target_compile_definitions(discord-rpc PUBLIC -DDISCORD_DYNAMIC_LIB)
|
||||||
|
target_compile_definitions(discord-rpc PRIVATE -DDISCORD_BUILDING_SDK)
|
||||||
|
endif(${BUILD_SHARED_LIBS})
|
||||||
|
|
||||||
|
if (CLANG_FORMAT_CMD)
|
||||||
|
add_dependencies(discord-rpc clangformat)
|
||||||
|
endif(CLANG_FORMAT_CMD)
|
||||||
|
|
||||||
# install
|
# install
|
||||||
|
|
||||||
install(
|
install(
|
||||||
TARGETS discord-rpc
|
TARGETS discord-rpc
|
||||||
EXPORT "discord-rpc"
|
EXPORT "discord-rpc"
|
||||||
LIBRARY DESTINATION "lib"
|
RUNTIME
|
||||||
ARCHIVE DESTINATION "lib"
|
DESTINATION "${CMAKE_INSTALL_BINDIR}"
|
||||||
INCLUDES DESTINATION "include"
|
LIBRARY
|
||||||
|
DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||||
|
ARCHIVE
|
||||||
|
DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||||
|
INCLUDES
|
||||||
|
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||||
)
|
)
|
||||||
|
|
||||||
install(
|
install(
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
struct Backoff {
|
struct Backoff {
|
||||||
int64_t minAmount;
|
int64_t minAmount;
|
||||||
@ -19,6 +20,7 @@ struct Backoff {
|
|||||||
, maxAmount(max)
|
, maxAmount(max)
|
||||||
, current(min)
|
, current(min)
|
||||||
, fails(0)
|
, fails(0)
|
||||||
|
, randGenerator((uint64_t)time(0))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ int GetProcessId();
|
|||||||
struct BaseConnection {
|
struct BaseConnection {
|
||||||
static BaseConnection* Create();
|
static BaseConnection* Create();
|
||||||
static void Destroy(BaseConnection*&);
|
static void Destroy(BaseConnection*&);
|
||||||
|
bool isOpen{false};
|
||||||
bool Open();
|
bool Open();
|
||||||
bool Close();
|
bool Close();
|
||||||
bool Write(const void* data, size_t length);
|
bool Write(const void* data, size_t length);
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
#include "connection.h"
|
#include "connection.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/un.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
int GetProcessId()
|
int GetProcessId()
|
||||||
@ -8,52 +14,109 @@ int GetProcessId()
|
|||||||
return ::getpid();
|
return ::getpid();
|
||||||
}
|
}
|
||||||
|
|
||||||
const int RpcVersion = 1;
|
struct BaseConnectionUnix : public BaseConnection {
|
||||||
const int NumFrames = 4;
|
int sock{-1};
|
||||||
|
|
||||||
struct RpcConnectionUnix : public RpcConnection {
|
|
||||||
int pipe{-1};
|
|
||||||
RpcMessageFrame frames[NumFrames];
|
|
||||||
int nextFrame{0};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*static*/ RpcConnection* RpcConnection::Create(const char* applicationId)
|
static BaseConnectionUnix Connection;
|
||||||
|
static sockaddr_un PipeAddr{};
|
||||||
|
#ifdef MSG_NOSIGNAL
|
||||||
|
static int MsgFlags = MSG_NOSIGNAL;
|
||||||
|
#else
|
||||||
|
static int MsgFlags = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const char* GetTempPath()
|
||||||
{
|
{
|
||||||
return new RpcConnectionUnix;
|
const char* temp = getenv("XDG_RUNTIME_DIR");
|
||||||
|
temp = temp ? temp : getenv("TMPDIR");
|
||||||
|
temp = temp ? temp : getenv("TMP");
|
||||||
|
temp = temp ? temp : getenv("TEMP");
|
||||||
|
temp = temp ? temp : "/tmp";
|
||||||
|
return temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*static*/ void RpcConnection::Destroy(RpcConnection*& c)
|
/*static*/ BaseConnection* BaseConnection::Create()
|
||||||
{
|
{
|
||||||
auto self = reinterpret_cast<RpcConnectionUnix*&>(c);
|
PipeAddr.sun_family = AF_UNIX;
|
||||||
delete self;
|
return &Connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/ void BaseConnection::Destroy(BaseConnection*& c)
|
||||||
|
{
|
||||||
|
auto self = reinterpret_cast<BaseConnectionUnix*>(c);
|
||||||
|
self->Close();
|
||||||
c = nullptr;
|
c = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RpcConnection::Open()
|
bool BaseConnection::Open()
|
||||||
{
|
{
|
||||||
|
const char* tempPath = GetTempPath();
|
||||||
|
auto self = reinterpret_cast<BaseConnectionUnix*>(this);
|
||||||
|
self->sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (self->sock == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fcntl(self->sock, F_SETFL, O_NONBLOCK);
|
||||||
|
#ifdef SO_NOSIGPIPE
|
||||||
|
int optval = 1;
|
||||||
|
setsockopt(self->sock, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (int pipeNum = 0; pipeNum < 10; ++pipeNum) {
|
||||||
|
snprintf(
|
||||||
|
PipeAddr.sun_path, sizeof(PipeAddr.sun_path), "%s/discord-ipc-%d", tempPath, pipeNum);
|
||||||
|
int err = connect(self->sock, (const sockaddr*)&PipeAddr, sizeof(PipeAddr));
|
||||||
|
if (err == 0) {
|
||||||
|
self->isOpen = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self->Close();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RpcConnection::Close()
|
bool BaseConnection::Close()
|
||||||
{
|
{
|
||||||
|
auto self = reinterpret_cast<BaseConnectionUnix*>(this);
|
||||||
|
if (self->sock == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
close(self->sock);
|
||||||
|
self->sock = -1;
|
||||||
|
self->isOpen = false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RpcConnection::Write(const void* data, size_t length)
|
bool BaseConnection::Write(const void* data, size_t length)
|
||||||
{
|
{
|
||||||
|
auto self = reinterpret_cast<BaseConnectionUnix*>(this);
|
||||||
|
|
||||||
|
if (self->sock == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t sentBytes = send(self->sock, data, length, MsgFlags);
|
||||||
|
if (sentBytes < 0) {
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
return sentBytes == (ssize_t)length;
|
||||||
}
|
}
|
||||||
|
|
||||||
RpcMessageFrame* RpcConnection::Read()
|
bool BaseConnection::Read(void* data, size_t length)
|
||||||
{
|
{
|
||||||
return nullptr;
|
auto self = reinterpret_cast<BaseConnectionUnix*>(this);
|
||||||
}
|
|
||||||
|
|
||||||
RpcMessageFrame* RpcConnection::GetNextFrame()
|
if (self->sock == -1) {
|
||||||
{
|
return false;
|
||||||
auto self = reinterpret_cast<RpcConnectionUnix*>(this);
|
}
|
||||||
auto result = &(self->frames[self->nextFrame]);
|
|
||||||
self->nextFrame = (self->nextFrame + 1) % NumFrames;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RpcConnection::WriteFrame(RpcMessageFrame* frame)
|
int res = (int)recv(self->sock, data, length, MsgFlags);
|
||||||
{
|
if (res < 0) {
|
||||||
|
if (errno == EAGAIN) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
return res == (int)length;
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,12 @@
|
|||||||
#define NOMCX
|
#define NOMCX
|
||||||
#define NOSERVICE
|
#define NOSERVICE
|
||||||
#define NOIME
|
#define NOIME
|
||||||
|
#include <assert.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
int GetProcessId()
|
int GetProcessId()
|
||||||
{
|
{
|
||||||
return ::GetCurrentProcessId();
|
return (int)::GetCurrentProcessId();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BaseConnectionWin : public BaseConnection {
|
struct BaseConnectionWin : public BaseConnection {
|
||||||
@ -16,8 +17,6 @@ struct BaseConnectionWin : public BaseConnection {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static BaseConnectionWin Connection;
|
static BaseConnectionWin Connection;
|
||||||
// static const wchar_t* PipeName = L"\\\\?\\pipe\\discord-ipc";
|
|
||||||
static const wchar_t* PipeName = L"\\\\?\\pipe\\discord-ipc-0";
|
|
||||||
|
|
||||||
/*static*/ BaseConnection* BaseConnection::Create()
|
/*static*/ BaseConnection* BaseConnection::Create()
|
||||||
{
|
{
|
||||||
@ -33,21 +32,32 @@ static const wchar_t* PipeName = L"\\\\?\\pipe\\discord-ipc-0";
|
|||||||
|
|
||||||
bool BaseConnection::Open()
|
bool BaseConnection::Open()
|
||||||
{
|
{
|
||||||
|
wchar_t pipeName[]{L"\\\\?\\pipe\\discord-ipc-0"};
|
||||||
|
const size_t pipeDigit = sizeof(pipeName) / sizeof(wchar_t) - 2;
|
||||||
|
pipeName[pipeDigit] = L'0';
|
||||||
auto self = reinterpret_cast<BaseConnectionWin*>(this);
|
auto self = reinterpret_cast<BaseConnectionWin*>(this);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
self->pipe = ::CreateFileW(
|
self->pipe = ::CreateFileW(
|
||||||
PipeName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
|
pipeName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||||
if (self->pipe != INVALID_HANDLE_VALUE) {
|
if (self->pipe != INVALID_HANDLE_VALUE) {
|
||||||
|
self->isOpen = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetLastError() != ERROR_PIPE_BUSY) {
|
auto lastError = GetLastError();
|
||||||
return false;
|
if (lastError == ERROR_FILE_NOT_FOUND) {
|
||||||
|
if (pipeName[pipeDigit] < L'9') {
|
||||||
|
pipeName[pipeDigit]++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if (lastError == ERROR_PIPE_BUSY) {
|
||||||
if (!WaitNamedPipeW(PipeName, 10000)) {
|
if (!WaitNamedPipeW(pipeName, 10000)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,25 +66,63 @@ bool BaseConnection::Close()
|
|||||||
auto self = reinterpret_cast<BaseConnectionWin*>(this);
|
auto self = reinterpret_cast<BaseConnectionWin*>(this);
|
||||||
::CloseHandle(self->pipe);
|
::CloseHandle(self->pipe);
|
||||||
self->pipe = INVALID_HANDLE_VALUE;
|
self->pipe = INVALID_HANDLE_VALUE;
|
||||||
|
self->isOpen = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseConnection::Write(const void* data, size_t length)
|
bool BaseConnection::Write(const void* data, size_t length)
|
||||||
{
|
{
|
||||||
|
if (length == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
auto self = reinterpret_cast<BaseConnectionWin*>(this);
|
auto self = reinterpret_cast<BaseConnectionWin*>(this);
|
||||||
return ::WriteFile(self->pipe, data, length, nullptr, nullptr) == TRUE;
|
assert(self);
|
||||||
|
if (!self) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (self->pipe == INVALID_HANDLE_VALUE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
assert(data);
|
||||||
|
if (!data) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const DWORD bytesLength = (DWORD)length;
|
||||||
|
DWORD bytesWritten = 0;
|
||||||
|
return ::WriteFile(self->pipe, data, bytesLength, &bytesWritten, nullptr) == TRUE &&
|
||||||
|
bytesWritten == bytesLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseConnection::Read(void* data, size_t length)
|
bool BaseConnection::Read(void* data, size_t length)
|
||||||
{
|
{
|
||||||
|
assert(data);
|
||||||
|
if (!data) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
auto self = reinterpret_cast<BaseConnectionWin*>(this);
|
auto self = reinterpret_cast<BaseConnectionWin*>(this);
|
||||||
|
assert(self);
|
||||||
|
if (!self) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (self->pipe == INVALID_HANDLE_VALUE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
DWORD bytesAvailable = 0;
|
DWORD bytesAvailable = 0;
|
||||||
if (::PeekNamedPipe(self->pipe, nullptr, 0, nullptr, &bytesAvailable, nullptr)) {
|
if (::PeekNamedPipe(self->pipe, nullptr, 0, nullptr, &bytesAvailable, nullptr)) {
|
||||||
if (bytesAvailable >= length) {
|
if (bytesAvailable >= length) {
|
||||||
if (::ReadFile(self->pipe, data, length, nullptr, nullptr) == TRUE) {
|
DWORD bytesToRead = (DWORD)length;
|
||||||
|
DWORD bytesRead = 0;
|
||||||
|
if (::ReadFile(self->pipe, data, bytesToRead, &bytesRead, nullptr) == TRUE) {
|
||||||
|
assert(bytesToRead == bytesRead);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
Close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
Close();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
#include "discord-rpc.h"
|
#include "discord-rpc.h"
|
||||||
|
|
||||||
#include "backoff.h"
|
#include "backoff.h"
|
||||||
|
#include "discord_register.h"
|
||||||
|
#include "msg_queue.h"
|
||||||
#include "rpc_connection.h"
|
#include "rpc_connection.h"
|
||||||
#include "serialization.h"
|
#include "serialization.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#ifndef DISCORD_DISABLE_IO_THREAD
|
#ifndef DISCORD_DISABLE_IO_THREAD
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
@ -14,19 +17,40 @@
|
|||||||
|
|
||||||
constexpr size_t MaxMessageSize{16 * 1024};
|
constexpr size_t MaxMessageSize{16 * 1024};
|
||||||
constexpr size_t MessageQueueSize{8};
|
constexpr size_t MessageQueueSize{8};
|
||||||
|
constexpr size_t JoinQueueSize{8};
|
||||||
|
|
||||||
struct QueuedMessage {
|
struct QueuedMessage {
|
||||||
size_t length;
|
size_t length;
|
||||||
char buffer[MaxMessageSize];
|
char buffer[MaxMessageSize];
|
||||||
|
|
||||||
|
void Copy(const QueuedMessage& other)
|
||||||
|
{
|
||||||
|
length = other.length;
|
||||||
|
if (length) {
|
||||||
|
memcpy(buffer, other.buffer, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct JoinRequest {
|
||||||
|
// 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 char ApplicationId[64]{};
|
|
||||||
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 WasPresenceRequested{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 char JoinGameSecret[256];
|
static char JoinGameSecret[256];
|
||||||
@ -35,21 +59,63 @@ static int LastErrorCode{0};
|
|||||||
static char LastErrorMessage[256];
|
static char LastErrorMessage[256];
|
||||||
static int LastDisconnectErrorCode{0};
|
static int LastDisconnectErrorCode{0};
|
||||||
static char LastDisconnectErrorMessage[256];
|
static char LastDisconnectErrorMessage[256];
|
||||||
static QueuedMessage SendQueue[MessageQueueSize]{};
|
static std::mutex PresenceMutex;
|
||||||
static std::atomic_uint SendQueueNextAdd{0};
|
static QueuedMessage QueuedPresence{};
|
||||||
static std::atomic_uint SendQueueNextSend{0};
|
static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue;
|
||||||
static std::atomic_uint SendQueuePendingSends{0};
|
static MsgQueue<JoinRequest, JoinQueueSize> JoinAskQueue;
|
||||||
|
|
||||||
|
// 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
|
||||||
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};
|
||||||
|
while (keepRunning.load()) {
|
||||||
|
Discord_UpdateConnection();
|
||||||
|
std::unique_lock<std::mutex> lock(waitForIOMutex);
|
||||||
|
waitForIOActivity.wait_for(lock, maxWait);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
static void UpdateReconnectTime()
|
static void UpdateReconnectTime()
|
||||||
{
|
{
|
||||||
@ -57,27 +123,16 @@ static void UpdateReconnectTime()
|
|||||||
std::chrono::duration<int64_t, std::milli>{ReconnectTimeMs.nextDelay()};
|
std::chrono::duration<int64_t, std::milli>{ReconnectTimeMs.nextDelay()};
|
||||||
}
|
}
|
||||||
|
|
||||||
static QueuedMessage* SendQueueGetNextAddMessage()
|
#ifdef DISCORD_DISABLE_IO_THREAD
|
||||||
|
extern "C" DISCORD_EXPORT void Discord_UpdateConnection(void)
|
||||||
|
#else
|
||||||
|
static void Discord_UpdateConnection(void)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
// if we are falling behind, bail
|
if (!Connection) {
|
||||||
if (SendQueuePendingSends.load() >= MessageQueueSize) {
|
return;
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
auto index = (SendQueueNextAdd++) % MessageQueueSize;
|
|
||||||
return &SendQueue[index];
|
|
||||||
}
|
|
||||||
static QueuedMessage* SendQueueGetNextSendMessage()
|
|
||||||
{
|
|
||||||
auto index = (SendQueueNextSend++) % MessageQueueSize;
|
|
||||||
return &SendQueue[index];
|
|
||||||
}
|
|
||||||
static void SendQueueCommitMessage()
|
|
||||||
{
|
|
||||||
SendQueuePendingSends++;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" void Discord_UpdateConnection()
|
|
||||||
{
|
|
||||||
if (!Connection->IsOpen()) {
|
if (!Connection->IsOpen()) {
|
||||||
if (std::chrono::system_clock::now() >= NextConnect) {
|
if (std::chrono::system_clock::now() >= NextConnect) {
|
||||||
UpdateReconnectTime();
|
UpdateReconnectTime();
|
||||||
@ -94,20 +149,16 @@ extern "C" void Discord_UpdateConnection()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* evtName = nullptr;
|
const char* evtName = GetStrMember(&message, "evt");
|
||||||
auto evt = message.FindMember("evt");
|
const char* nonce = GetStrMember(&message, "nonce");
|
||||||
if (evt != message.MemberEnd() && evt->value.IsString()) {
|
|
||||||
evtName = evt->value.GetString();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto nonce = message.FindMember("nonce");
|
if (nonce) {
|
||||||
if (nonce != message.MemberEnd() && nonce->value.IsString()) {
|
|
||||||
// in responses only -- should use to match up response when needed.
|
// in responses only -- should use to match up response when needed.
|
||||||
|
|
||||||
if (evtName && strcmp(evtName, "ERROR") == 0) {
|
if (evtName && strcmp(evtName, "ERROR") == 0) {
|
||||||
auto data = message.FindMember("data");
|
auto data = GetObjMember(&message, "data");
|
||||||
LastErrorCode = data->value["code"].GetInt();
|
LastErrorCode = GetIntMember(data, "code");
|
||||||
StringCopy(LastErrorMessage, data->value["message"].GetString());
|
StringCopy(LastErrorMessage, GetStrMember(data, "message", ""));
|
||||||
GotErrorMessage.store(true);
|
GotErrorMessage.store(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,70 +168,102 @@ extern "C" void Discord_UpdateConnection()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo ug
|
auto data = GetObjMember(&message, "data");
|
||||||
if (strcmp(evtName, "PRESENCE_REQUESTED") == 0) {
|
|
||||||
WasPresenceRequested.store(true);
|
if (strcmp(evtName, "ACTIVITY_JOIN") == 0) {
|
||||||
|
auto secret = GetStrMember(data, "secret");
|
||||||
|
if (secret) {
|
||||||
|
StringCopy(JoinGameSecret, secret);
|
||||||
|
WasJoinGame.store(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (strcmp(evtName, "JOIN_GAME") == 0) {
|
else if (strcmp(evtName, "ACTIVITY_SPECTATE") == 0) {
|
||||||
auto data = message.FindMember("data");
|
auto secret = GetStrMember(data, "secret");
|
||||||
auto secret = data->value["secret"].GetString();
|
if (secret) {
|
||||||
StringCopy(JoinGameSecret, secret);
|
StringCopy(SpectateGameSecret, secret);
|
||||||
WasJoinGame.store(true);
|
WasSpectateGame.store(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (strcmp(evtName, "SPECTATE_GAME") == 0) {
|
else if (strcmp(evtName, "ACTIVITY_JOIN_REQUEST") == 0) {
|
||||||
auto data = message.FindMember("data");
|
auto user = GetObjMember(data, "user");
|
||||||
auto secret = data->value["secret"].GetString();
|
auto userId = GetStrMember(user, "id");
|
||||||
StringCopy(SpectateGameSecret, secret);
|
auto username = GetStrMember(user, "username");
|
||||||
WasSpectateGame.store(true);
|
auto avatar = GetStrMember(user, "avatar");
|
||||||
|
auto joinReq = JoinAskQueue.GetNextAddMessage();
|
||||||
|
if (userId && username && joinReq) {
|
||||||
|
StringCopy(joinReq->userId, userId);
|
||||||
|
StringCopy(joinReq->username, username);
|
||||||
|
auto discriminator = GetStrMember(user, "discriminator");
|
||||||
|
if (discriminator) {
|
||||||
|
StringCopy(joinReq->discriminator, discriminator);
|
||||||
|
}
|
||||||
|
if (avatar) {
|
||||||
|
StringCopy(joinReq->avatar, avatar);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
joinReq->avatar[0] = 0;
|
||||||
|
}
|
||||||
|
JoinAskQueue.CommitAdd();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// writes
|
// writes
|
||||||
while (SendQueuePendingSends.load()) {
|
if (QueuedPresence.length) {
|
||||||
auto qmessage = SendQueueGetNextSendMessage();
|
QueuedMessage local;
|
||||||
|
PresenceMutex.lock();
|
||||||
|
local.Copy(QueuedPresence);
|
||||||
|
QueuedPresence.length = 0;
|
||||||
|
PresenceMutex.unlock();
|
||||||
|
if (!Connection->Write(local.buffer, local.length)) {
|
||||||
|
// if we fail to send, requeue
|
||||||
|
PresenceMutex.lock();
|
||||||
|
QueuedPresence.Copy(local);
|
||||||
|
PresenceMutex.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (SendQueue.HavePendingSends()) {
|
||||||
|
auto qmessage = SendQueue.GetNextSendMessage();
|
||||||
Connection->Write(qmessage->buffer, qmessage->length);
|
Connection->Write(qmessage->buffer, qmessage->length);
|
||||||
--SendQueuePendingSends;
|
SendQueue.CommitSend();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef DISCORD_DISABLE_IO_THREAD
|
static void SignalIOActivity()
|
||||||
void DiscordRpcIo()
|
|
||||||
{
|
{
|
||||||
const std::chrono::duration<int64_t, std::milli> maxWait{500LL};
|
IoThread.Notify();
|
||||||
|
|
||||||
while (KeepRunning.load()) {
|
|
||||||
Discord_UpdateConnection();
|
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(WaitForIOMutex);
|
|
||||||
WaitForIOActivity.wait_for(lock, maxWait);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void SignalIOActivity()
|
|
||||||
{
|
|
||||||
#ifndef DISCORD_DISABLE_IO_THREAD
|
|
||||||
WaitForIOActivity.notify_all();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RegisterForEvent(const char* evtName)
|
static bool RegisterForEvent(const char* evtName)
|
||||||
{
|
{
|
||||||
auto qmessage = SendQueueGetNextAddMessage();
|
auto qmessage = SendQueue.GetNextAddMessage();
|
||||||
if (qmessage) {
|
if (qmessage) {
|
||||||
qmessage->length =
|
qmessage->length =
|
||||||
JsonWriteSubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
|
JsonWriteSubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
|
||||||
SendQueueCommitMessage();
|
SendQueue.CommitAdd();
|
||||||
SignalIOActivity();
|
SignalIOActivity();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Discord_Initialize(const char* applicationId, DiscordEventHandlers* handlers)
|
extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
|
||||||
|
DiscordEventHandlers* handlers,
|
||||||
|
int autoRegister,
|
||||||
|
const char* optionalSteamId)
|
||||||
{
|
{
|
||||||
|
if (autoRegister) {
|
||||||
|
if (optionalSteamId && optionalSteamId[0]) {
|
||||||
|
Discord_RegisterSteamGame(applicationId, optionalSteamId);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Discord_Register(applicationId, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Pid = GetProcessId();
|
Pid = GetProcessId();
|
||||||
|
|
||||||
if (handlers) {
|
if (handlers) {
|
||||||
@ -190,21 +273,25 @@ extern "C" void Discord_Initialize(const char* applicationId, DiscordEventHandle
|
|||||||
Handlers = {};
|
Handlers = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Connection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Connection = RpcConnection::Create(applicationId);
|
Connection = RpcConnection::Create(applicationId);
|
||||||
Connection->onConnect = []() {
|
Connection->onConnect = []() {
|
||||||
WasJustConnected.exchange(true);
|
WasJustConnected.exchange(true);
|
||||||
ReconnectTimeMs.reset();
|
ReconnectTimeMs.reset();
|
||||||
|
|
||||||
if (Handlers.presenceRequested) {
|
|
||||||
RegisterForEvent("PRESENCE_REQUESTED");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Handlers.joinGame) {
|
if (Handlers.joinGame) {
|
||||||
RegisterForEvent("JOIN_GAME");
|
RegisterForEvent("ACTIVITY_JOIN");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Handlers.spectateGame) {
|
if (Handlers.spectateGame) {
|
||||||
RegisterForEvent("SPECTATE_GAME");
|
RegisterForEvent("ACTIVITY_SPECTATE");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Handlers.joinRequest) {
|
||||||
|
RegisterForEvent("ACTIVITY_JOIN_REQUEST");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Connection->onDisconnect = [](int err, const char* message) {
|
Connection->onDisconnect = [](int err, const char* message) {
|
||||||
@ -214,53 +301,76 @@ extern "C" void Discord_Initialize(const char* applicationId, DiscordEventHandle
|
|||||||
UpdateReconnectTime();
|
UpdateReconnectTime();
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifndef DISCORD_DISABLE_IO_THREAD
|
IoThread.Start();
|
||||||
IoThread = std::thread(DiscordRpcIo);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Discord_Shutdown()
|
extern "C" DISCORD_EXPORT void Discord_Shutdown(void)
|
||||||
{
|
{
|
||||||
|
if (!Connection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Connection->onConnect = nullptr;
|
Connection->onConnect = nullptr;
|
||||||
Connection->onDisconnect = nullptr;
|
Connection->onDisconnect = nullptr;
|
||||||
Handlers = {};
|
Handlers = {};
|
||||||
#ifndef DISCORD_DISABLE_IO_THREAD
|
IoThread.Stop();
|
||||||
KeepRunning.exchange(false);
|
|
||||||
SignalIOActivity();
|
|
||||||
if (IoThread.joinable()) {
|
|
||||||
IoThread.join();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
RpcConnection::Destroy(Connection);
|
RpcConnection::Destroy(Connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Discord_UpdatePresence(const DiscordRichPresence* presence)
|
extern "C" DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence)
|
||||||
{
|
{
|
||||||
auto qmessage = SendQueueGetNextAddMessage();
|
PresenceMutex.lock();
|
||||||
|
QueuedPresence.length = JsonWriteRichPresenceObj(
|
||||||
|
QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
|
||||||
|
PresenceMutex.unlock();
|
||||||
|
SignalIOActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" DISCORD_EXPORT void Discord_ClearPresence(void)
|
||||||
|
{
|
||||||
|
Discord_UpdatePresence(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" DISCORD_EXPORT void Discord_Respond(const char* userId, /* DISCORD_REPLY_ */ int reply)
|
||||||
|
{
|
||||||
|
// if we are not connected, let's not batch up stale messages for later
|
||||||
|
if (!Connection || !Connection->IsOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto qmessage = SendQueue.GetNextAddMessage();
|
||||||
if (qmessage) {
|
if (qmessage) {
|
||||||
qmessage->length = JsonWriteRichPresenceObj(
|
qmessage->length =
|
||||||
qmessage->buffer, sizeof(qmessage->buffer), Nonce++, Pid, presence);
|
JsonWriteJoinReply(qmessage->buffer, sizeof(qmessage->buffer), userId, reply, Nonce++);
|
||||||
SendQueueCommitMessage();
|
SendQueue.CommitAdd();
|
||||||
SignalIOActivity();
|
SignalIOActivity();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Discord_RunCallbacks()
|
extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void)
|
||||||
{
|
{
|
||||||
if (GotErrorMessage.exchange(false) && Handlers.errored) {
|
// Note on some weirdness: internally we might connect, get other signals, disconnect any number
|
||||||
Handlers.errored(LastErrorCode, LastErrorMessage);
|
// of times inbetween calls here. Externally, we want the sequence to seem sane, so any other
|
||||||
|
// signals are book-ended by calls to ready and disconnect.
|
||||||
|
|
||||||
|
if (!Connection) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WasJustDisconnected.exchange(false) && Handlers.disconnected) {
|
bool wasDisconnected = WasJustDisconnected.exchange(false);
|
||||||
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
bool isConnected = Connection->IsOpen();
|
||||||
|
|
||||||
|
if (isConnected) {
|
||||||
|
// if we are connected, disconnect cb first
|
||||||
|
if (wasDisconnected && Handlers.disconnected) {
|
||||||
|
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WasJustConnected.exchange(false) && Handlers.ready) {
|
if (WasJustConnected.exchange(false) && Handlers.ready) {
|
||||||
Handlers.ready();
|
Handlers.ready();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WasPresenceRequested.exchange(false) && Handlers.presenceRequested) {
|
if (GotErrorMessage.exchange(false) && Handlers.errored) {
|
||||||
Handlers.presenceRequested();
|
Handlers.errored(LastErrorCode, LastErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WasJoinGame.exchange(false) && Handlers.joinGame) {
|
if (WasJoinGame.exchange(false) && Handlers.joinGame) {
|
||||||
@ -270,4 +380,25 @@ extern "C" void Discord_RunCallbacks()
|
|||||||
if (WasSpectateGame.exchange(false) && Handlers.spectateGame) {
|
if (WasSpectateGame.exchange(false) && 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
|
||||||
|
// where the implementer would rather sequentially accept/reject each one before the next invite
|
||||||
|
// is sent. I left it this way because I could also imagine wanting to process these all and
|
||||||
|
// maybe show them in one common dialog and/or start fetching the avatars in parallel, and if
|
||||||
|
// not it should be trivial for the implementer to make a queue themselves.
|
||||||
|
while (JoinAskQueue.HavePendingSends()) {
|
||||||
|
auto req = JoinAskQueue.GetNextSendMessage();
|
||||||
|
if (Handlers.joinRequest) {
|
||||||
|
DiscordJoinRequest djr{req->userId, req->username, req->discriminator, req->avatar};
|
||||||
|
Handlers.joinRequest(&djr);
|
||||||
|
}
|
||||||
|
JoinAskQueue.CommitSend();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
// if we are not connected, disconnect message last
|
||||||
|
if (wasDisconnected && Handlers.disconnected) {
|
||||||
|
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
12
src/discord_register.h
Normal file
12
src/discord_register.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#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
|
98
src/discord_register_linux.cpp
Normal file
98
src/discord_register_linux.cpp
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
#include "discord-rpc.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
bool Mkdir(const char* path)
|
||||||
|
{
|
||||||
|
int result = mkdir(path, 0755);
|
||||||
|
if (result == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (errno == EEXIST) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
// Add a desktop file and update some mime handlers so that xdg-open does the right thing.
|
||||||
|
|
||||||
|
const char* home = getenv("HOME");
|
||||||
|
if (!home) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char exePath[1024];
|
||||||
|
if (!command || !command[0]) {
|
||||||
|
if (readlink("/proc/self/exe", exePath, sizeof(exePath)) <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
command = exePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* destopFileFormat = "[Desktop Entry]\n"
|
||||||
|
"Name=Game %s\n"
|
||||||
|
"Exec=%s %%u\n" // note: it really wants that %u in there
|
||||||
|
"Type=Application\n"
|
||||||
|
"NoDisplay=true\n"
|
||||||
|
"Categories=Discord;Games;\n"
|
||||||
|
"MimeType=x-scheme-handler/discord-%s;\n";
|
||||||
|
char desktopFile[2048];
|
||||||
|
int fileLen = snprintf(
|
||||||
|
desktopFile, sizeof(desktopFile), destopFileFormat, applicationId, command, applicationId);
|
||||||
|
if (fileLen <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char desktopFilename[256];
|
||||||
|
snprintf(desktopFilename, sizeof(desktopFilename), "/discord-%s.desktop", applicationId);
|
||||||
|
|
||||||
|
char desktopFilePath[1024];
|
||||||
|
snprintf(desktopFilePath, sizeof(desktopFilePath), "%s/.local", home);
|
||||||
|
if (!Mkdir(desktopFilePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
strcat(desktopFilePath, "/share");
|
||||||
|
if (!Mkdir(desktopFilePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
strcat(desktopFilePath, "/applications");
|
||||||
|
if (!Mkdir(desktopFilePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
strcat(desktopFilePath, desktopFilename);
|
||||||
|
|
||||||
|
FILE* fp = fopen(desktopFilePath, "w");
|
||||||
|
if (fp) {
|
||||||
|
fwrite(desktopFile, 1, fileLen, fp);
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char xdgMimeCommand[1024];
|
||||||
|
snprintf(xdgMimeCommand,
|
||||||
|
sizeof(xdgMimeCommand),
|
||||||
|
"xdg-mime default discord-%s.desktop x-scheme-handler/discord-%s",
|
||||||
|
applicationId,
|
||||||
|
applicationId);
|
||||||
|
if (system(xdgMimeCommand) < 0) {
|
||||||
|
fprintf(stderr, "Failed to register mime handler\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
|
||||||
|
{
|
||||||
|
char command[256];
|
||||||
|
sprintf(command, "xdg-open steam://rungameid/%s", steamId);
|
||||||
|
Discord_Register(applicationId, command);
|
||||||
|
}
|
80
src/discord_register_osx.m
Normal file
80
src/discord_register_osx.m
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#import <AppKit/AppKit.h>
|
||||||
|
|
||||||
|
#include "discord_register.h"
|
||||||
|
|
||||||
|
static void RegisterCommand(const char* applicationId, const char* command)
|
||||||
|
{
|
||||||
|
// There does not appear to be a way to register arbitrary commands on OSX, so instead we'll save the command
|
||||||
|
// to a file in the Discord config path, and when it is needed, Discord can try to load the file there, open
|
||||||
|
// the command therein (will pass to js's window.open, so requires a url-like thing)
|
||||||
|
|
||||||
|
// Note: will not work for sandboxed apps
|
||||||
|
NSString *home = NSHomeDirectory();
|
||||||
|
if (!home) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *path = [[[[[[home stringByAppendingPathComponent:@"Library"]
|
||||||
|
stringByAppendingPathComponent:@"Application Support"]
|
||||||
|
stringByAppendingPathComponent:@"discord"]
|
||||||
|
stringByAppendingPathComponent:@"games"]
|
||||||
|
stringByAppendingPathComponent:[NSString stringWithUTF8String:applicationId]]
|
||||||
|
stringByAppendingPathExtension:@"json"];
|
||||||
|
[[NSFileManager defaultManager] createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
|
||||||
|
|
||||||
|
NSString *jsonBuffer = [NSString stringWithFormat:@"{\"command\": \"%s\"}", command];
|
||||||
|
[jsonBuffer writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RegisterURL(const char* applicationId)
|
||||||
|
{
|
||||||
|
char url[256];
|
||||||
|
snprintf(url, sizeof(url), "discord-%s", applicationId);
|
||||||
|
CFStringRef cfURL = CFStringCreateWithCString(NULL, url, kCFStringEncodingUTF8);
|
||||||
|
|
||||||
|
NSString* myBundleId = [[NSBundle mainBundle] bundleIdentifier];
|
||||||
|
if (!myBundleId) {
|
||||||
|
fprintf(stderr, "No bundle id found\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSURL* myURL = [[NSBundle mainBundle] bundleURL];
|
||||||
|
if (!myURL) {
|
||||||
|
fprintf(stderr, "No bundle url found\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSStatus status = LSSetDefaultHandlerForURLScheme(cfURL, (__bridge CFStringRef)myBundleId);
|
||||||
|
if (status != noErr) {
|
||||||
|
fprintf(stderr, "Error in LSSetDefaultHandlerForURLScheme: %d\n", (int)status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = LSRegisterURL((__bridge CFURLRef)myURL, true);
|
||||||
|
if (status != noErr) {
|
||||||
|
fprintf(stderr, "Error in LSRegisterURL: %d\n", (int)status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Discord_Register(const char* applicationId, const char* command)
|
||||||
|
{
|
||||||
|
if (command) {
|
||||||
|
RegisterCommand(applicationId, command);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// raii lite
|
||||||
|
@autoreleasepool {
|
||||||
|
RegisterURL(applicationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
|
||||||
|
{
|
||||||
|
char command[256];
|
||||||
|
snprintf(command, 256, "steam://rungameid/%s", steamId);
|
||||||
|
Discord_Register(applicationId, command);
|
||||||
|
}
|
169
src/discord_register_win.cpp
Normal file
169
src/discord_register_win.cpp
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
#include "discord-rpc.h"
|
||||||
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#define NOMCX
|
||||||
|
#define NOSERVICE
|
||||||
|
#define NOIME
|
||||||
|
#include <windows.h>
|
||||||
|
#include <psapi.h>
|
||||||
|
#include <cwchar>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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__
|
||||||
|
/// strsafe.h fixes
|
||||||
|
#define StringCbPrintfW snwprintf
|
||||||
|
LPWSTR StringCbCopyW(LPWSTR a, size_t l, LPCWSTR b)
|
||||||
|
{
|
||||||
|
a[l-1] = 0;
|
||||||
|
return wcsncpy(a, b, l - 1); // does not set the last byte to 0 on overflow, so it's set to 0 above
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#include <strsafe.h>
|
||||||
|
#endif // __MINGW32__
|
||||||
|
|
||||||
|
/// winreg.h fixes
|
||||||
|
#ifndef LSTATUS
|
||||||
|
#define LSTATUS LONG
|
||||||
|
#endif
|
||||||
|
#ifdef RegSetKeyValueW
|
||||||
|
#undefine RegSetKeyValueW
|
||||||
|
#endif
|
||||||
|
#define RegSetKeyValueW regset
|
||||||
|
LSTATUS regset(HKEY hkey, LPCWSTR subkey, LPCWSTR name, DWORD type, const void *data, DWORD len)
|
||||||
|
{
|
||||||
|
HKEY hsubkey = NULL;
|
||||||
|
LSTATUS ret;
|
||||||
|
if (subkey && subkey[0]) /* need to create the subkey */
|
||||||
|
{
|
||||||
|
if ((ret = RegCreateKeyW( hkey, subkey, &hsubkey )) != ERROR_SUCCESS) return ret;
|
||||||
|
hkey = hsubkey;
|
||||||
|
}
|
||||||
|
ret = RegSetValueExW( hkey, name, 0, type, (const BYTE*)data, len );
|
||||||
|
if (hsubkey) RegCloseKey( hsubkey );
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command)
|
||||||
|
{
|
||||||
|
// https://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx
|
||||||
|
// we want to register games so we can run them as discord-<appid>://
|
||||||
|
// Update the HKEY_CURRENT_USER, because it doesn't seem to require special permissions.
|
||||||
|
|
||||||
|
wchar_t exeFilePath[MAX_PATH];
|
||||||
|
DWORD exeLen = GetModuleFileNameW(nullptr, exeFilePath, MAX_PATH);
|
||||||
|
wchar_t openCommand[1024];
|
||||||
|
|
||||||
|
if (command && command[0]) {
|
||||||
|
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
StringCbCopyW(openCommand, sizeof(openCommand), exeFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t protocolName[64];
|
||||||
|
StringCbPrintfW(protocolName, sizeof(protocolName), L"discord-%s", applicationId);
|
||||||
|
wchar_t protocolDescription[128];
|
||||||
|
StringCbPrintfW(
|
||||||
|
protocolDescription, sizeof(protocolDescription), L"URL:Run game %s protocol", applicationId);
|
||||||
|
wchar_t urlProtocol = 0;
|
||||||
|
|
||||||
|
wchar_t keyName[256];
|
||||||
|
StringCbPrintfW(keyName, sizeof(keyName), L"Software\\Classes\\%s", protocolName);
|
||||||
|
HKEY key;
|
||||||
|
auto status =
|
||||||
|
RegCreateKeyExW(HKEY_CURRENT_USER, keyName, 0, nullptr, 0, KEY_WRITE, nullptr, &key, nullptr);
|
||||||
|
if (status != ERROR_SUCCESS) {
|
||||||
|
fprintf(stderr, "Error creating key\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DWORD len;
|
||||||
|
LSTATUS result;
|
||||||
|
len = (DWORD)lstrlenW(protocolDescription) + 1;
|
||||||
|
result =
|
||||||
|
RegSetKeyValueW(key, nullptr, nullptr, REG_SZ, protocolDescription, len * sizeof(wchar_t));
|
||||||
|
if (FAILED(result)) {
|
||||||
|
fprintf(stderr, "Error writing description\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
len = (DWORD)lstrlenW(protocolDescription) + 1;
|
||||||
|
result = RegSetKeyValueW(key, nullptr, L"URL Protocol", REG_SZ, &urlProtocol, sizeof(wchar_t));
|
||||||
|
if (FAILED(result)) {
|
||||||
|
fprintf(stderr, "Error writing description\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
result = RegSetKeyValueW(
|
||||||
|
key, L"DefaultIcon", nullptr, REG_SZ, exeFilePath, (exeLen + 1) * sizeof(wchar_t));
|
||||||
|
if (FAILED(result)) {
|
||||||
|
fprintf(stderr, "Error writing icon\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
len = (DWORD)lstrlenW(openCommand) + 1;
|
||||||
|
result = RegSetKeyValueW(
|
||||||
|
key, L"shell\\open\\command", nullptr, REG_SZ, openCommand, len * sizeof(wchar_t));
|
||||||
|
if (FAILED(result)) {
|
||||||
|
fprintf(stderr, "Error writing command\n");
|
||||||
|
}
|
||||||
|
RegCloseKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void Discord_Register(const char* applicationId, const char* command)
|
||||||
|
{
|
||||||
|
wchar_t appId[32];
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
|
||||||
|
|
||||||
|
wchar_t openCommand[1024];
|
||||||
|
const wchar_t* wcommand = nullptr;
|
||||||
|
if (command && command[0]) {
|
||||||
|
const auto commandBufferLen = sizeof(openCommand) / sizeof(*openCommand);
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, command, -1, openCommand, commandBufferLen);
|
||||||
|
wcommand = openCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
Discord_RegisterW(appId, wcommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
|
||||||
|
{
|
||||||
|
wchar_t appId[32];
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
|
||||||
|
|
||||||
|
wchar_t wSteamId[32];
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, steamId, -1, wSteamId, 32);
|
||||||
|
|
||||||
|
HKEY key;
|
||||||
|
auto status = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam", 0, KEY_READ, &key);
|
||||||
|
if (status != ERROR_SUCCESS) {
|
||||||
|
fprintf(stderr, "Error opening Steam key\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t steamPath[MAX_PATH];
|
||||||
|
DWORD pathBytes = sizeof(steamPath);
|
||||||
|
status = RegQueryValueExW(key, L"SteamExe", nullptr, nullptr, (BYTE*)steamPath, &pathBytes);
|
||||||
|
RegCloseKey(key);
|
||||||
|
if (status != ERROR_SUCCESS || pathBytes < 1) {
|
||||||
|
fprintf(stderr, "Error reading SteamExe key\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD pathChars = pathBytes / sizeof(wchar_t);
|
||||||
|
for (DWORD i = 0; i < pathChars; ++i) {
|
||||||
|
if (steamPath[i] == L'/') {
|
||||||
|
steamPath[i] = L'\\';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t command[1024];
|
||||||
|
StringCbPrintfW(command, sizeof(command), L"\"%s\" steam://rungameid/%s", steamPath, wSteamId);
|
||||||
|
|
||||||
|
Discord_RegisterW(appId, command);
|
||||||
|
}
|
6
src/dllmain.cpp
Normal file
6
src/dllmain.cpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID)
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
36
src/msg_queue.h
Normal file
36
src/msg_queue.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
// A simple queue. No locks, but only works with a single thread as producer and a single thread as
|
||||||
|
// a consumer. Mutex up as needed.
|
||||||
|
|
||||||
|
template <typename ElementType, size_t QueueSize>
|
||||||
|
class MsgQueue {
|
||||||
|
ElementType queue_[QueueSize]{};
|
||||||
|
std::atomic_uint nextAdd_{0};
|
||||||
|
std::atomic_uint nextSend_{0};
|
||||||
|
std::atomic_uint pendingSends_{0};
|
||||||
|
|
||||||
|
public:
|
||||||
|
MsgQueue() {}
|
||||||
|
|
||||||
|
ElementType* GetNextAddMessage()
|
||||||
|
{
|
||||||
|
// if we are falling behind, bail
|
||||||
|
if (pendingSends_.load() >= QueueSize) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto index = (nextAdd_++) % QueueSize;
|
||||||
|
return &queue_[index];
|
||||||
|
}
|
||||||
|
void CommitAdd() { ++pendingSends_; }
|
||||||
|
|
||||||
|
bool HavePendingSends() const { return pendingSends_.load() != 0; }
|
||||||
|
ElementType* GetNextSendMessage()
|
||||||
|
{
|
||||||
|
auto index = (nextSend_++) % QueueSize;
|
||||||
|
return &queue_[index];
|
||||||
|
}
|
||||||
|
void CommitSend() { --pendingSends_; }
|
||||||
|
};
|
@ -17,6 +17,7 @@ static RpcConnection Instance;
|
|||||||
{
|
{
|
||||||
c->Close();
|
c->Close();
|
||||||
BaseConnection::Destroy(c->connection);
|
BaseConnection::Destroy(c->connection);
|
||||||
|
c = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RpcConnection::Open()
|
void RpcConnection::Open()
|
||||||
@ -36,16 +37,9 @@ void RpcConnection::Open()
|
|||||||
if (state == State::SentHandshake) {
|
if (state == State::SentHandshake) {
|
||||||
JsonDocument message;
|
JsonDocument message;
|
||||||
if (Read(message)) {
|
if (Read(message)) {
|
||||||
auto cmd = message.FindMember("cmd");
|
auto cmd = GetStrMember(&message, "cmd");
|
||||||
if (cmd == message.MemberEnd() || !cmd->value.IsString()) {
|
auto evt = GetStrMember(&message, "evt");
|
||||||
return;
|
if (cmd && evt && !strcmp(cmd, "DISPATCH") && !strcmp(evt, "READY")) {
|
||||||
}
|
|
||||||
auto evt = message.FindMember("evt");
|
|
||||||
if (evt == message.MemberEnd() || !evt->value.IsString()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!strcmp(cmd->value.GetString(), "DISPATCH") &&
|
|
||||||
!strcmp(evt->value.GetString(), "READY")) {
|
|
||||||
state = State::Connected;
|
state = State::Connected;
|
||||||
if (onConnect) {
|
if (onConnect) {
|
||||||
onConnect();
|
onConnect();
|
||||||
@ -55,8 +49,8 @@ void RpcConnection::Open()
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sendFrame.opcode = Opcode::Handshake;
|
sendFrame.opcode = Opcode::Handshake;
|
||||||
sendFrame.length =
|
sendFrame.length = (uint32_t)JsonWriteHandshakeObj(
|
||||||
JsonWriteHandshakeObj(sendFrame.message, sizeof(sendFrame.message), RpcVersion, appId);
|
sendFrame.message, sizeof(sendFrame.message), RpcVersion, appId);
|
||||||
|
|
||||||
if (connection->Write(&sendFrame, sizeof(MessageFrameHeader) + sendFrame.length)) {
|
if (connection->Write(&sendFrame, sizeof(MessageFrameHeader) + sendFrame.length)) {
|
||||||
state = State::SentHandshake;
|
state = State::SentHandshake;
|
||||||
@ -80,7 +74,7 @@ bool RpcConnection::Write(const void* data, size_t length)
|
|||||||
{
|
{
|
||||||
sendFrame.opcode = Opcode::Frame;
|
sendFrame.opcode = Opcode::Frame;
|
||||||
memcpy(sendFrame.message, data, length);
|
memcpy(sendFrame.message, data, length);
|
||||||
sendFrame.length = length;
|
sendFrame.length = (uint32_t)length;
|
||||||
if (!connection->Write(&sendFrame, sizeof(MessageFrameHeader) + length)) {
|
if (!connection->Write(&sendFrame, sizeof(MessageFrameHeader) + length)) {
|
||||||
Close();
|
Close();
|
||||||
return false;
|
return false;
|
||||||
@ -97,13 +91,18 @@ bool RpcConnection::Read(JsonDocument& message)
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
bool didRead = connection->Read(&readFrame, sizeof(MessageFrameHeader));
|
bool didRead = connection->Read(&readFrame, sizeof(MessageFrameHeader));
|
||||||
if (!didRead) {
|
if (!didRead) {
|
||||||
|
if (!connection->isOpen) {
|
||||||
|
lastErrorCode = (int)ErrorCode::PipeClosed;
|
||||||
|
StringCopy(lastErrorMessage, "Pipe closed");
|
||||||
|
Close();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readFrame.length > 0) {
|
if (readFrame.length > 0) {
|
||||||
didRead = connection->Read(readFrame.message, readFrame.length);
|
didRead = connection->Read(readFrame.message, readFrame.length);
|
||||||
if (!didRead) {
|
if (!didRead) {
|
||||||
lastErrorCode = -2;
|
lastErrorCode = (int)ErrorCode::ReadCorrupt;
|
||||||
StringCopy(lastErrorMessage, "Partial data in frame");
|
StringCopy(lastErrorMessage, "Partial data in frame");
|
||||||
Close();
|
Close();
|
||||||
return false;
|
return false;
|
||||||
@ -114,9 +113,8 @@ bool RpcConnection::Read(JsonDocument& message)
|
|||||||
switch (readFrame.opcode) {
|
switch (readFrame.opcode) {
|
||||||
case Opcode::Close: {
|
case Opcode::Close: {
|
||||||
message.ParseInsitu(readFrame.message);
|
message.ParseInsitu(readFrame.message);
|
||||||
lastErrorCode = message["code"].GetInt();
|
lastErrorCode = GetIntMember(&message, "code");
|
||||||
const auto& m = message["message"];
|
StringCopy(lastErrorMessage, GetStrMember(&message, "message", ""));
|
||||||
StringCopy(lastErrorMessage, m.GetString());
|
|
||||||
Close();
|
Close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -131,9 +129,10 @@ bool RpcConnection::Read(JsonDocument& message)
|
|||||||
break;
|
break;
|
||||||
case Opcode::Pong:
|
case Opcode::Pong:
|
||||||
break;
|
break;
|
||||||
|
case Opcode::Handshake:
|
||||||
default:
|
default:
|
||||||
// something bad happened
|
// something bad happened
|
||||||
lastErrorCode = -1;
|
lastErrorCode = (int)ErrorCode::ReadCorrupt;
|
||||||
StringCopy(lastErrorMessage, "Bad ipc frame");
|
StringCopy(lastErrorMessage, "Bad ipc frame");
|
||||||
Close();
|
Close();
|
||||||
return false;
|
return false;
|
||||||
|
@ -8,6 +8,12 @@
|
|||||||
constexpr size_t MaxRpcFrameSize = 64 * 1024;
|
constexpr size_t MaxRpcFrameSize = 64 * 1024;
|
||||||
|
|
||||||
struct RpcConnection {
|
struct RpcConnection {
|
||||||
|
enum class ErrorCode : int {
|
||||||
|
Success = 0,
|
||||||
|
PipeClosed = 1,
|
||||||
|
ReadCorrupt = 2,
|
||||||
|
};
|
||||||
|
|
||||||
enum class Opcode : uint32_t {
|
enum class Opcode : uint32_t {
|
||||||
Handshake = 0,
|
Handshake = 0,
|
||||||
Frame = 1,
|
Frame = 1,
|
||||||
|
@ -2,7 +2,30 @@
|
|||||||
#include "connection.h"
|
#include "connection.h"
|
||||||
#include "discord-rpc.h"
|
#include "discord-rpc.h"
|
||||||
|
|
||||||
MallocAllocator MallocAllocatorInst;
|
template <typename T>
|
||||||
|
void NumberToString(char* dest, T number)
|
||||||
|
{
|
||||||
|
if (!number) {
|
||||||
|
*dest++ = '0';
|
||||||
|
*dest++ = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (number < 0) {
|
||||||
|
*dest++ = '-';
|
||||||
|
number = -number;
|
||||||
|
}
|
||||||
|
char temp[32];
|
||||||
|
int place = 0;
|
||||||
|
while (number) {
|
||||||
|
auto digit = number % 10;
|
||||||
|
number = number / 10;
|
||||||
|
temp[place++] = '0' + (char)digit;
|
||||||
|
}
|
||||||
|
for (--place; place >= 0; --place) {
|
||||||
|
*dest++ = temp[place];
|
||||||
|
}
|
||||||
|
*dest = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// it's ever so slightly faster to not have to strlen the key
|
// it's ever so slightly faster to not have to strlen the key
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -11,166 +34,196 @@ void WriteKey(JsonWriter& w, T& k)
|
|||||||
w.Key(k, sizeof(T) - 1);
|
w.Key(k, sizeof(T) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct WriteObject {
|
||||||
|
JsonWriter& writer;
|
||||||
|
WriteObject(JsonWriter& w)
|
||||||
|
: writer(w)
|
||||||
|
{
|
||||||
|
writer.StartObject();
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
WriteObject(JsonWriter& w, T& name)
|
||||||
|
: writer(w)
|
||||||
|
{
|
||||||
|
WriteKey(writer, name);
|
||||||
|
writer.StartObject();
|
||||||
|
}
|
||||||
|
~WriteObject() { writer.EndObject(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WriteArray {
|
||||||
|
JsonWriter& writer;
|
||||||
|
template <typename T>
|
||||||
|
WriteArray(JsonWriter& w, T& name)
|
||||||
|
: writer(w)
|
||||||
|
{
|
||||||
|
WriteKey(writer, name);
|
||||||
|
writer.StartArray();
|
||||||
|
}
|
||||||
|
~WriteArray() { writer.EndArray(); }
|
||||||
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void WriteOptionalString(JsonWriter& w, T& k, const char* value)
|
void WriteOptionalString(JsonWriter& w, T& k, const char* value)
|
||||||
{
|
{
|
||||||
if (value) {
|
if (value && value[0]) {
|
||||||
w.Key(k, sizeof(T) - 1);
|
w.Key(k, sizeof(T) - 1);
|
||||||
w.String(value);
|
w.String(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void JsonWriteNonce(JsonWriter& writer, int nonce)
|
static void JsonWriteNonce(JsonWriter& writer, int nonce)
|
||||||
{
|
{
|
||||||
WriteKey(writer, "nonce");
|
WriteKey(writer, "nonce");
|
||||||
char nonceBuffer[32]{};
|
char nonceBuffer[32];
|
||||||
rapidjson::internal::i32toa(nonce, nonceBuffer);
|
NumberToString(nonceBuffer, nonce);
|
||||||
writer.String(nonceBuffer);
|
writer.String(nonceBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void JsonWriteCommandStart(JsonWriter& writer, int nonce, const char* cmd)
|
|
||||||
{
|
|
||||||
writer.StartObject();
|
|
||||||
|
|
||||||
JsonWriteNonce(writer, nonce);
|
|
||||||
|
|
||||||
WriteKey(writer, "cmd");
|
|
||||||
writer.String(cmd);
|
|
||||||
|
|
||||||
WriteKey(writer, "args");
|
|
||||||
writer.StartObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
void JsonWriteCommandEnd(JsonWriter& writer)
|
|
||||||
{
|
|
||||||
writer.EndObject(); // args
|
|
||||||
writer.EndObject(); // top level
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t JsonWriteRichPresenceObj(char* dest,
|
size_t JsonWriteRichPresenceObj(char* dest,
|
||||||
size_t maxLen,
|
size_t maxLen,
|
||||||
int nonce,
|
int nonce,
|
||||||
int pid,
|
int pid,
|
||||||
const DiscordRichPresence* presence)
|
const DiscordRichPresence* presence)
|
||||||
{
|
{
|
||||||
DirectStringBuffer sb(dest, maxLen);
|
JsonWriter writer(dest, maxLen);
|
||||||
StackAllocator wa;
|
|
||||||
JsonWriter writer(sb, &wa, WriterNestingLevels);
|
|
||||||
|
|
||||||
JsonWriteCommandStart(writer, nonce, "SET_ACTIVITY");
|
{
|
||||||
|
WriteObject top(writer);
|
||||||
|
|
||||||
WriteKey(writer, "pid");
|
JsonWriteNonce(writer, nonce);
|
||||||
writer.Int(pid);
|
|
||||||
|
|
||||||
WriteKey(writer, "activity");
|
WriteKey(writer, "cmd");
|
||||||
writer.StartObject();
|
writer.String("SET_ACTIVITY");
|
||||||
|
|
||||||
WriteOptionalString(writer, "state", presence->state);
|
{
|
||||||
WriteOptionalString(writer, "details", presence->details);
|
WriteObject args(writer, "args");
|
||||||
|
|
||||||
if (presence->startTimestamp || presence->endTimestamp) {
|
WriteKey(writer, "pid");
|
||||||
WriteKey(writer, "timestamps");
|
writer.Int(pid);
|
||||||
writer.StartObject();
|
|
||||||
|
|
||||||
if (presence->startTimestamp) {
|
if (presence != nullptr)
|
||||||
WriteKey(writer, "start");
|
{
|
||||||
writer.Int64(presence->startTimestamp);
|
WriteObject activity(writer, "activity");
|
||||||
}
|
|
||||||
|
|
||||||
if (presence->endTimestamp) {
|
WriteOptionalString(writer, "state", presence->state);
|
||||||
WriteKey(writer, "end");
|
WriteOptionalString(writer, "details", presence->details);
|
||||||
writer.Int64(presence->endTimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.EndObject();
|
if (presence->startTimestamp || presence->endTimestamp) {
|
||||||
}
|
WriteObject timestamps(writer, "timestamps");
|
||||||
|
|
||||||
if (presence->largeImageKey || presence->largeImageText || presence->smallImageKey ||
|
if (presence->startTimestamp) {
|
||||||
presence->smallImageText) {
|
WriteKey(writer, "start");
|
||||||
WriteKey(writer, "assets");
|
writer.Int64(presence->startTimestamp);
|
||||||
writer.StartObject();
|
}
|
||||||
|
|
||||||
WriteOptionalString(writer, "large_image", presence->largeImageKey);
|
if (presence->endTimestamp) {
|
||||||
WriteOptionalString(writer, "large_text", presence->largeImageText);
|
WriteKey(writer, "end");
|
||||||
WriteOptionalString(writer, "small_image", presence->smallImageKey);
|
writer.Int64(presence->endTimestamp);
|
||||||
WriteOptionalString(writer, "small_text", presence->smallImageText);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
writer.EndObject();
|
if ((presence->largeImageKey && presence->largeImageKey[0]) ||
|
||||||
}
|
(presence->largeImageText && presence->largeImageText[0]) ||
|
||||||
|
(presence->smallImageKey && presence->smallImageKey[0]) ||
|
||||||
|
(presence->smallImageText && presence->smallImageText[0])) {
|
||||||
|
WriteObject assets(writer, "assets");
|
||||||
|
WriteOptionalString(writer, "large_image", presence->largeImageKey);
|
||||||
|
WriteOptionalString(writer, "large_text", presence->largeImageText);
|
||||||
|
WriteOptionalString(writer, "small_image", presence->smallImageKey);
|
||||||
|
WriteOptionalString(writer, "small_text", presence->smallImageText);
|
||||||
|
}
|
||||||
|
|
||||||
if (presence->partyId || presence->partySize || presence->partyMax) {
|
if ((presence->partyId && presence->partyId[0]) || presence->partySize ||
|
||||||
WriteKey(writer, "party");
|
presence->partyMax) {
|
||||||
writer.StartObject();
|
WriteObject party(writer, "party");
|
||||||
|
WriteOptionalString(writer, "id", presence->partyId);
|
||||||
|
if (presence->partySize) {
|
||||||
|
WriteArray size(writer, "size");
|
||||||
|
writer.Int(presence->partySize);
|
||||||
|
if (0 < presence->partyMax) {
|
||||||
|
writer.Int(presence->partyMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WriteOptionalString(writer, "id", presence->partyId);
|
if ((presence->matchSecret && presence->matchSecret[0]) ||
|
||||||
if (presence->partySize) {
|
(presence->joinSecret && presence->joinSecret[0]) ||
|
||||||
writer.StartArray();
|
(presence->spectateSecret && presence->spectateSecret[0])) {
|
||||||
|
WriteObject secrets(writer, "secrets");
|
||||||
|
WriteOptionalString(writer, "match", presence->matchSecret);
|
||||||
|
WriteOptionalString(writer, "join", presence->joinSecret);
|
||||||
|
WriteOptionalString(writer, "spectate", presence->spectateSecret);
|
||||||
|
}
|
||||||
|
|
||||||
writer.Int(presence->partySize);
|
writer.Key("instance");
|
||||||
if (0 < presence->partyMax) {
|
writer.Bool(presence->instance != 0);
|
||||||
writer.Int(presence->partyMax);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.EndArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.EndObject();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (presence->matchSecret || presence->joinSecret || presence->spectateSecret) {
|
return writer.Size();
|
||||||
WriteKey(writer, "secrets");
|
|
||||||
writer.StartObject();
|
|
||||||
|
|
||||||
WriteOptionalString(writer, "match", presence->matchSecret);
|
|
||||||
WriteOptionalString(writer, "join", presence->joinSecret);
|
|
||||||
WriteOptionalString(writer, "spectate", presence->spectateSecret);
|
|
||||||
|
|
||||||
writer.EndObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.Key("instance");
|
|
||||||
writer.Bool(presence->instance != 0);
|
|
||||||
|
|
||||||
writer.EndObject(); // activity
|
|
||||||
|
|
||||||
JsonWriteCommandEnd(writer);
|
|
||||||
|
|
||||||
return sb.GetSize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t JsonWriteHandshakeObj(char* dest, size_t maxLen, int version, const char* applicationId)
|
size_t JsonWriteHandshakeObj(char* dest, size_t maxLen, int version, const char* applicationId)
|
||||||
{
|
{
|
||||||
DirectStringBuffer sb(dest, maxLen);
|
JsonWriter writer(dest, maxLen);
|
||||||
StackAllocator wa;
|
|
||||||
JsonWriter writer(sb, &wa, WriterNestingLevels);
|
|
||||||
|
|
||||||
writer.StartObject();
|
{
|
||||||
WriteKey(writer, "v");
|
WriteObject obj(writer);
|
||||||
writer.Int(version);
|
WriteKey(writer, "v");
|
||||||
WriteKey(writer, "client_id");
|
writer.Int(version);
|
||||||
writer.String(applicationId);
|
WriteKey(writer, "client_id");
|
||||||
writer.EndObject();
|
writer.String(applicationId);
|
||||||
|
}
|
||||||
|
|
||||||
return sb.GetSize();
|
return writer.Size();
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
DirectStringBuffer sb(dest, maxLen);
|
JsonWriter writer(dest, maxLen);
|
||||||
StackAllocator wa;
|
|
||||||
JsonWriter writer(sb, &wa, WriterNestingLevels);
|
|
||||||
|
|
||||||
writer.StartObject();
|
{
|
||||||
|
WriteObject obj(writer);
|
||||||
|
|
||||||
JsonWriteNonce(writer, nonce);
|
JsonWriteNonce(writer, nonce);
|
||||||
|
|
||||||
WriteKey(writer, "cmd");
|
WriteKey(writer, "cmd");
|
||||||
writer.String("SUBSCRIBE");
|
writer.String("SUBSCRIBE");
|
||||||
|
|
||||||
WriteKey(writer, "evt");
|
WriteKey(writer, "evt");
|
||||||
writer.String(evtName);
|
writer.String(evtName);
|
||||||
|
}
|
||||||
|
|
||||||
writer.EndObject();
|
return writer.Size();
|
||||||
|
}
|
||||||
return sb.GetSize();
|
|
||||||
|
size_t JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce)
|
||||||
|
{
|
||||||
|
JsonWriter writer(dest, maxLen);
|
||||||
|
|
||||||
|
{
|
||||||
|
WriteObject obj(writer);
|
||||||
|
|
||||||
|
WriteKey(writer, "cmd");
|
||||||
|
if (reply == DISCORD_REPLY_YES) {
|
||||||
|
writer.String("SEND_ACTIVITY_JOIN_INVITE");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
writer.String("CLOSE_ACTIVITY_JOIN_REQUEST");
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteKey(writer, "args");
|
||||||
|
{
|
||||||
|
WriteObject args(writer);
|
||||||
|
|
||||||
|
WriteKey(writer, "user_id");
|
||||||
|
writer.String(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonWriteNonce(writer, nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
return writer.Size();
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,29 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifndef __MINGW32__
|
||||||
|
#pragma warning(push)
|
||||||
|
|
||||||
|
#pragma warning(disable : 4061) // enum is not explicitly handled by a case label
|
||||||
|
#pragma warning(disable : 4365) // signed/unsigned mismatch
|
||||||
|
#pragma warning(disable : 4464) // relative include path contains
|
||||||
|
#pragma warning(disable : 4668) // is not defined as a preprocessor macro
|
||||||
|
#pragma warning(disable : 6313) // Incorrect operator
|
||||||
|
#endif // __MINGW32__
|
||||||
|
|
||||||
#include "rapidjson/document.h"
|
#include "rapidjson/document.h"
|
||||||
#include "rapidjson/writer.h"
|
|
||||||
#include "rapidjson/stringbuffer.h"
|
#include "rapidjson/stringbuffer.h"
|
||||||
#include "rapidjson/internal/itoa.h"
|
#include "rapidjson/writer.h"
|
||||||
|
|
||||||
|
#ifndef __MINGW32__
|
||||||
|
#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>
|
||||||
inline size_t StringCopy(char (&dest)[Len], const char* src)
|
inline size_t StringCopy(char (&dest)[Len], const char* src)
|
||||||
{
|
{
|
||||||
if (!dest || !src || !Len) {
|
if (!src || !Len) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
size_t copied;
|
size_t copied;
|
||||||
@ -34,9 +47,10 @@ 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 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
|
||||||
// to supply some of
|
// to supply some of your own allocators for stuff rather than use the defaults
|
||||||
// your own allocators for stuff rather than use the defaults
|
|
||||||
|
|
||||||
class LinearAllocator {
|
class LinearAllocator {
|
||||||
public:
|
public:
|
||||||
@ -69,9 +83,16 @@ 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) { /* shrug */}
|
static void Free(void* ptr)
|
||||||
|
{
|
||||||
|
/* shrug */
|
||||||
|
(void)ptr;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t Size>
|
template <size_t Size>
|
||||||
@ -107,7 +128,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Flush() {}
|
void Flush() {}
|
||||||
size_t GetSize() const { return current_ - buffer_; }
|
size_t GetSize() const { return (size_t)(current_ - buffer_); }
|
||||||
};
|
};
|
||||||
|
|
||||||
using MallocAllocator = rapidjson::CrtAllocator;
|
using MallocAllocator = rapidjson::CrtAllocator;
|
||||||
@ -116,8 +137,23 @@ using UTF8 = rapidjson::UTF8<char>;
|
|||||||
// Writer appears to need about 16 bytes per nested object level (with 64bit size_t)
|
// Writer appears to need about 16 bytes per nested object level (with 64bit size_t)
|
||||||
using StackAllocator = FixedLinearAllocator<2048>;
|
using StackAllocator = FixedLinearAllocator<2048>;
|
||||||
constexpr size_t WriterNestingLevels = 2048 / (2 * sizeof(size_t));
|
constexpr size_t WriterNestingLevels = 2048 / (2 * sizeof(size_t));
|
||||||
using JsonWriter =
|
using JsonWriterBase =
|
||||||
rapidjson::Writer<DirectStringBuffer, UTF8, UTF8, StackAllocator, rapidjson::kWriteNoFlags>;
|
rapidjson::Writer<DirectStringBuffer, UTF8, UTF8, StackAllocator, rapidjson::kWriteNoFlags>;
|
||||||
|
class JsonWriter : public JsonWriterBase {
|
||||||
|
public:
|
||||||
|
DirectStringBuffer stringBuffer_;
|
||||||
|
StackAllocator stackAlloc_;
|
||||||
|
|
||||||
|
JsonWriter(char* dest, size_t maxLen)
|
||||||
|
: JsonWriterBase(stringBuffer_, &stackAlloc_, WriterNestingLevels)
|
||||||
|
, stringBuffer_(dest, maxLen)
|
||||||
|
, stackAlloc_()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Size() const { return stringBuffer_.GetSize(); }
|
||||||
|
};
|
||||||
|
|
||||||
using JsonDocumentBase = rapidjson::GenericDocument<UTF8, PoolAllocator, StackAllocator>;
|
using JsonDocumentBase = rapidjson::GenericDocument<UTF8, PoolAllocator, StackAllocator>;
|
||||||
class JsonDocument : public JsonDocumentBase {
|
class JsonDocument : public JsonDocumentBase {
|
||||||
public:
|
public:
|
||||||
@ -138,3 +174,40 @@ public:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using JsonValue = rapidjson::GenericValue<UTF8, PoolAllocator>;
|
||||||
|
|
||||||
|
inline JsonValue* GetObjMember(JsonValue* obj, const char* name)
|
||||||
|
{
|
||||||
|
if (obj) {
|
||||||
|
auto member = obj->FindMember(name);
|
||||||
|
if (member != obj->MemberEnd() && member->value.IsObject()) {
|
||||||
|
return &member->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int GetIntMember(JsonValue* obj, const char* name, int notFoundDefault = 0)
|
||||||
|
{
|
||||||
|
if (obj) {
|
||||||
|
auto member = obj->FindMember(name);
|
||||||
|
if (member != obj->MemberEnd() && member->value.IsInt()) {
|
||||||
|
return member->value.GetInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return notFoundDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char* GetStrMember(JsonValue* obj,
|
||||||
|
const char* name,
|
||||||
|
const char* notFoundDefault = nullptr)
|
||||||
|
{
|
||||||
|
if (obj) {
|
||||||
|
auto member = obj->FindMember(name);
|
||||||
|
if (member != obj->MemberEnd() && member->value.IsString()) {
|
||||||
|
return member->value.GetString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return notFoundDefault;
|
||||||
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "test-rpc-server",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"server": "node rpc-server.js",
|
|
||||||
"client": "node test-client.js"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
|
|
||||||
const VERSION = 1;
|
|
||||||
|
|
||||||
const OPCODES = {
|
|
||||||
HANDSHAKE: 0,
|
|
||||||
FRAME: 1,
|
|
||||||
CLOSE: 2,
|
|
||||||
PING: 3,
|
|
||||||
PONG: 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
let PipePath;
|
|
||||||
if (process.platform == 'win32') {
|
|
||||||
PipePath = '\\\\?\\pipe\\discord-ipc';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const temp = process.env.XDG_RUNTIME_DIR || process.env.TMPDIR || process.env.TMP || process.env.TEMP || '/tmp';
|
|
||||||
PipePath = path.join(temp, 'discord-ipc');
|
|
||||||
}
|
|
||||||
|
|
||||||
class RpcMessage {
|
|
||||||
|
|
||||||
static serialize(opcode, obj) {
|
|
||||||
const serializedJson = JSON.stringify(obj);
|
|
||||||
const msgLen = serializedJson.length;
|
|
||||||
let buff = Buffer.alloc(8 + msgLen);
|
|
||||||
buff.writeInt32LE(opcode, 0);
|
|
||||||
buff.writeInt32LE(msgLen, 4);
|
|
||||||
buff.write(serializedJson, 8, serializedJson.length, 'utf-8');
|
|
||||||
return buff;
|
|
||||||
}
|
|
||||||
|
|
||||||
static handshake(id) {
|
|
||||||
const opcode = OPCODES.HANDSHAKE;
|
|
||||||
return RpcMessage.serialize(opcode, {
|
|
||||||
client_id: id,
|
|
||||||
v: VERSION
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static send(obj) {
|
|
||||||
const opcode = OPCODES.FRAME;
|
|
||||||
return RpcMessage.serialize(opcode, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
static sendClose(code, message) {
|
|
||||||
const opcode = OPCODES.CLOSE;
|
|
||||||
return RpcMessage.serialize(opcode, {code, message});
|
|
||||||
}
|
|
||||||
|
|
||||||
static sendPing(message) {
|
|
||||||
const opcode = OPCODES.PING;
|
|
||||||
return RpcMessage.serialize(opcode, {message});
|
|
||||||
}
|
|
||||||
|
|
||||||
static deserialize(buff) {
|
|
||||||
const opcode = buff.readInt32LE(0);
|
|
||||||
const msgLen = buff.readInt32LE(4);
|
|
||||||
if (msgLen == 0) {
|
|
||||||
return {opcode, data: ''};
|
|
||||||
}
|
|
||||||
if (buff.length < (msgLen + 8)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const msg = buff.toString('utf-8', 8, msgLen + 8);
|
|
||||||
try {
|
|
||||||
return {opcode, data: JSON.parse(msg)};
|
|
||||||
} catch(e) {
|
|
||||||
console.log(`failed to parse "${msg}"`);
|
|
||||||
console.error(e);
|
|
||||||
return {opcode: OPCODES.CLOSE, message: e.message};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {OPCODES, PipePath, RpcMessage};
|
|
@ -1,75 +0,0 @@
|
|||||||
const net = require('net');
|
|
||||||
const repl = require('repl');
|
|
||||||
const {PipePath, RpcMessage} = require('./rpc-message');
|
|
||||||
|
|
||||||
let connectionNonce = 0;
|
|
||||||
global.connections = {};
|
|
||||||
|
|
||||||
const server = net.createServer(function(sock) {
|
|
||||||
connectionNonce += 1;
|
|
||||||
console.log('Server: on connection', connectionNonce);
|
|
||||||
let myConnection = connectionNonce;
|
|
||||||
let messages = 0;
|
|
||||||
|
|
||||||
global.connections[myConnection] = sock;
|
|
||||||
|
|
||||||
sock.on('data', function(data) {
|
|
||||||
messages++;
|
|
||||||
const msgObj = RpcMessage.deserialize(data);
|
|
||||||
if (msgObj != null) {
|
|
||||||
const {opcode, data} = msgObj;
|
|
||||||
console.log(`\nServer (${myConnection}): got opcode: ${opcode}, data: ${JSON.stringify(data)}`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log('\nServer: got some data', data.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
sock.on('end', function() {
|
|
||||||
delete global.connections[myConnection];
|
|
||||||
console.log('\nServer: on end', myConnection);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
server.on('close', function(){
|
|
||||||
console.log('\nServer: on close');
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
server.listen(PipePath, function(){
|
|
||||||
console.log('\nServer: on listening');
|
|
||||||
});
|
|
||||||
} catch(e) {
|
|
||||||
console.error('\nServer: could not start:', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
const replServer = repl.start({prompt: '> ', useGlobal: true, breakEvalOnSigint: true});
|
|
||||||
replServer.defineCommand('kill', {
|
|
||||||
help: 'Kill a client',
|
|
||||||
action(who) {
|
|
||||||
this.bufferedCommand = '';
|
|
||||||
who = parseInt(who, 10);
|
|
||||||
const sock = global.connections[who];
|
|
||||||
if (sock) {
|
|
||||||
console.log('killing', who);
|
|
||||||
sock.end(RpcMessage.sendClose(123, 'killed'));
|
|
||||||
}
|
|
||||||
this.displayPrompt();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
replServer.defineCommand('ping', {
|
|
||||||
help: 'Ping all clients',
|
|
||||||
action() {
|
|
||||||
this.bufferedCommand = '';
|
|
||||||
Object.keys(global.connections).forEach((who) => {
|
|
||||||
const sock = global.connections[who];
|
|
||||||
if (sock) {
|
|
||||||
console.log('pinging', who);
|
|
||||||
sock.write(RpcMessage.sendPing('hello'));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.displayPrompt();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
|||||||
const net = require('net');
|
|
||||||
const {OPCODES, PipePath, RpcMessage} = require('./rpc-message');
|
|
||||||
|
|
||||||
const APP_ID = '12345678910';
|
|
||||||
global.isConnected = false;
|
|
||||||
global.timeoutId = null;
|
|
||||||
|
|
||||||
function sendMesg(testUpdatesToSend, stream) {
|
|
||||||
const msgObj = {
|
|
||||||
state: (testUpdatesToSend % 2 == 0) ? 'In a match' : 'In Lobby',
|
|
||||||
details: 'Excited'
|
|
||||||
};
|
|
||||||
console.log('Client: send update:', msgObj);
|
|
||||||
stream.write(RpcMessage.send(msgObj));
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendMessageLoop(testUpdatesToSend, interval, stream) {
|
|
||||||
global.timeoutId = null;
|
|
||||||
if (!global.isConnected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendMesg(testUpdatesToSend, stream);
|
|
||||||
if (testUpdatesToSend > 1) {
|
|
||||||
global.timeoutId = setTimeout(() => {sendMessageLoop(testUpdatesToSend - 1, interval, stream)}, interval);
|
|
||||||
} else {
|
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = net.connect(PipePath, function(stream) {
|
|
||||||
console.log('Client: on connection');
|
|
||||||
global.isConnected = true;
|
|
||||||
client.write(RpcMessage.handshake(APP_ID));
|
|
||||||
sendMessageLoop(10, 3000, client);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('data', function(data) {
|
|
||||||
const msgObj = RpcMessage.deserialize(data);
|
|
||||||
if (msgObj != null) {
|
|
||||||
const {opcode, data} = msgObj;
|
|
||||||
console.log(`Client: got opcode: ${opcode}, data: ${JSON.stringify(data)}`);
|
|
||||||
|
|
||||||
if (opcode == OPCODES.CLOSE) {
|
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.log('Client: got some data', data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('end', function() {
|
|
||||||
global.isConnected = false;
|
|
||||||
console.log('Client: on end');
|
|
||||||
});
|
|
||||||
|
|
||||||
function shutdown() {
|
|
||||||
if (global.timeoutId !== null) {
|
|
||||||
clearTimeout(global.timeoutId);
|
|
||||||
global.timeoutId = null;
|
|
||||||
}
|
|
||||||
client.end();
|
|
||||||
}
|
|
Reference in New Issue
Block a user