67 Commits

Author SHA1 Message Date
19abe80449 Add a few more null checks. 2017-09-07 09:23:35 -07:00
fa39179be7 Fix up doing builds script, add packaging up results. 2017-09-07 09:05:41 -07:00
3566190a01 Mention unreal example in the readme 2017-09-06 14:28:49 -07:00
592f134d80 format 2017-09-06 14:27:52 -07:00
794bbccd51 Register upgrade (#2)
* Update init to take an optional Steam ID. Add register for steam game. Remove url from cmd line params to launched game.

* Start on a build script
2017-08-30 15:17:47 -07:00
8bceae0a3a autoformat missed these before? 2017-08-29 12:42:10 -07:00
4cf8ca5670 Remove dep on rapidjson/internal 2017-08-28 10:43:36 -07:00
e07806424b copy-n-paste bug 2017-08-28 10:02:37 -07:00
5882823830 OSX build fix 2017-08-21 12:51:38 -07:00
4dcb4afd2e Update presence 2017-08-21 10:12:19 -07:00
6dc12b82c6 Actually hook up callbacks 2017-08-21 10:12:19 -07:00
432166ef1d Expose all the callback events, hook up connect/disconnect events in example 2017-08-21 10:12:19 -07:00
bd995f047d change to a UObject 2017-08-21 10:12:18 -07:00
63b467f81d wip make plugin do stuff 2017-08-21 10:12:18 -07:00
9c6495ab3e Toggleable connected message 2017-08-21 10:12:18 -07:00
21eb1e459b Somehow these settings were lost? 2017-08-21 10:12:18 -07:00
ff24776107 More plugin 2017-08-21 10:12:18 -07:00
70cd486e13 Start on a Discord rpc unreal plugin 2017-08-21 10:12:18 -07:00
7ca912a5ac Adding example ui 2017-08-21 10:12:18 -07:00
10383339cc Create unreal template app 2017-08-21 10:12:18 -07:00
0144c8200c RPC SET_ACTIVITY payload documentation fixes
* `pid` is a mandatory field of your application's process id
* `data` -> `args`
2017-08-18 15:32:25 -07:00
9b07431476 Incorrect RPC payload argument name 2017-08-18 15:07:13 -07:00
ad86b14b79 Added documentation link to readme 2017-08-16 14:00:00 -07:00
93510d492e Delete rich-presence-guide.md
Removing outdated documentation
2017-08-16 13:58:54 -07:00
8dab07035f Update example id 2017-08-16 11:48:27 -07:00
b87c6c9b5b Renamed hard mode integration. Outlined subscribing to events 2017-08-07 16:59:52 -07:00
f45eb6c443 Clarified partyID and timestamps 2017-08-07 16:12:54 -07:00
759af3a89e Merge branch 'master' of github.com:hammerandchisel/discord-rpc 2017-08-07 13:43:26 -07:00
d102b33ed3 Added rpc-only documentation 2017-08-07 13:43:18 -07:00
c0899fd512 Properly link to documentation dashboard image 2017-08-07 11:25:55 -07:00
dbb7b626b0 Added documentation guide + images 2017-08-07 11:00:57 -07:00
88f47486cb update dll 2017-08-03 10:51:06 -07:00
0271889b2e Allow for disconnect/reconnect. 2017-08-03 10:47:27 -07:00
54bef63d67 Update readme to point out other demo 2017-08-03 10:24:08 -07:00
c82cc94538 UI thing 2017-08-03 10:15:22 -07:00
470259e8b0 Unity example: show when connected 2017-08-03 09:52:43 -07:00
f6853fac1b Unity example working better. 2017-08-02 17:52:20 -07:00
f06b187d2d other 64bit warning fix 2017-08-02 17:37:58 -07:00
ee11358d7f null or empty strings should be omitted 2017-08-02 14:57:02 -07:00
7324ae890c squelch warnings in 64bit compile 2017-08-02 14:56:39 -07:00
7fe7e2ab53 Initial pass at a unity example... no callbacks yet. 2017-08-02 11:31:35 -07:00
27e39a0ec5 Initial unity project example 2017-08-02 11:03:20 -07:00
60ad70adc2 Fix ordering of ready/disconnect callbacks. 2017-08-02 10:44:55 -07:00
a7eb65355e missed a format 2017-08-02 10:44:08 -07:00
2311a26eb0 Fix my dll making, also other cmake variable use 2017-08-01 13:32:56 -07:00
67a81b82cd Moar readme 2017-07-31 15:58:46 -07:00
1e971e1161 Start on dllification 2017-07-31 15:58:39 -07:00
120fe1b069 Make these use my helper functions 2017-07-31 15:40:31 -07:00
d5e6c4c11a track open/close state in connection, disconnect on read error, clarify error codes a little 2017-07-31 15:38:12 -07:00
c3c27c730d note max sizes 2017-07-28 16:06:46 -07:00
1b7f782f57 register join and spectate handlers too 2017-07-28 13:53:05 -07:00
059ab337c8 Seed random number generator 2017-07-28 13:52:34 -07:00
52bdc2714d More complete presence data to test with. 2017-07-28 13:43:43 -07:00
98852fba82 Wrap json writer the same way I did reader. More RAII for json writing so I'm less likely to mess it up again. 2017-07-28 13:42:58 -07:00
e69f9fbf71 Just keep a single queued presence message. 2017-07-28 10:59:32 -07:00
a6a9b6259f missed a format I guess 2017-07-28 10:57:22 -07:00
ef60ec40c3 Do/wrap error checking around json reading 2017-07-28 10:03:05 -07:00
6774b5d881 Only queue messages when connected. 2017-07-28 09:45:53 -07:00
6b10bd6c51 Explain backoff 2017-07-28 09:45:11 -07:00
93d9ba10c2 Fixup event names
- GAME_XXXXX not XXXXX_GAME
- Nuked the WantsPresence event
2017-07-28 00:19:10 -07:00
6338a572d3 Size key was missing from the party array 2017-07-27 23:36:22 -07:00
9eb7e41c4b try more pipes 2017-07-27 16:08:17 -07:00
ffab428366 Don't need 2017-07-27 16:08:17 -07:00
20ad7e4ced OSX/linux version initial attempt 2017-07-27 16:08:17 -07:00
bfcfd10baa fix some warnings, format 2017-07-27 16:08:17 -07:00
f617f3b78d Let's just register in init 2017-07-27 13:29:24 -07:00
1f6fd05884 Add registry key to simplify launching 2017-07-27 11:56:19 -07:00
79 changed files with 3999 additions and 496 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/build*/ /build*/
/.vscode/ /.vscode/
/thirdparty/ /thirdparty/
.vs/

View File

@ -52,8 +52,3 @@ add_library(rapidjson STATIC IMPORTED ${RAPIDJSON})
add_subdirectory(src) add_subdirectory(src)
add_subdirectory(examples/send-presence) add_subdirectory(examples/send-presence)
add_custom_target(bundle
WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}"
COMMAND ${CMAKE_COMMAND} -E tar cfvz "${CMAKE_BINARY_DIR}/discord-rpc.tar.gz" .
)

View File

@ -1,16 +1,23 @@
# 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 lib and quick demos that implement the very minimal subset to show current status, and
current status, and one that is more complete. The idea here is to give you an lib that implements have callbacks for where a more complete game would do more things. You can use the lib directly
the rpc connection and wraps sending events, and a basic example that uses it; you can use the lib if you like, or use it as a guide to writing your own if it doesn't suit your game as is.
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. PRs/feedback welcome if you have an improvement everyone might want, or can describe how this
doesn't meet your needs.
## Documentation
The most up to date documentation for Rich Presence can always be found in our [developer site](https://discordapp.com/developers/docs/topics/rich-presence)!
## Usage ## 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) 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. 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
@ -19,17 +26,34 @@ 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; 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. Sometimes I use the generated project files. There are a couple of 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.
| `BUILD_DYNAMIC_LIB` | `OFF` | Build library as a DLL
## 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.

87
build.py Normal file
View File

@ -0,0 +1,87 @@
import click
import os
import subprocess
import sys
import shutil
import zipfile
from contextlib import contextmanager
SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
@contextmanager
def cd(new_dir):
""" Temporarily change current directory """
if new_dir:
old_dir = os.getcwd()
os.chdir(new_dir)
yield
if new_dir:
os.chdir(old_dir)
def mkdir_p(path):
""" mkdir -p """
if not os.path.isdir(path):
os.makedirs(path)
def build_lib(build_name, generator, options):
build_path = os.path.join(SCRIPT_PATH, 'builds', build_name)
install_path = os.path.join(SCRIPT_PATH, 'builds', 'install', 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 = 'ON' if options[key] else 'OFF'
initial_cmake.append('-D%s=%s' %(key, val))
subprocess.check_call(initial_cmake)
subprocess.check_call(['cmake', '--build', '.', '--config', 'Debug'])
subprocess.check_call(['cmake', '--build', '.', '--config', 'Release', '--target', 'install'])
def create_archive():
archive_file_path = os.path.join(SCRIPT_PATH, 'builds', 'discord-rpc.zip')
archive_file = zipfile.ZipFile(archive_file_path, 'w', zipfile.ZIP_DEFLATED)
archive_src_base_path = os.path.join(SCRIPT_PATH, 'builds', 'install')
archive_dst_base_path = 'discord-rpc'
with cd(archive_src_base_path):
for path, subdirs, filenames in os.walk('.'):
for fname in filenames:
fpath = os.path.join(path, fname)
archive_file.write(fpath, os.path.normpath(os.path.join(archive_dst_base_path, fpath)))
@click.command()
@click.option('--clean', is_flag=True)
def main(clean):
os.chdir(SCRIPT_PATH)
if clean:
shutil.rmtree('builds', ignore_errors=True)
if sys.platform.startswith('win'):
generator32 = 'Visual Studio 14 2015'
generator64 = 'Visual Studio 14 2015 Win64'
build_lib('win32-static', generator32, {})
build_lib('win32-dynamic', generator32, {'BUILD_DYNAMIC_LIB': True})
build_lib('win64-static', generator64, {})
build_lib('win64-dynamic', generator64, {'BUILD_DYNAMIC_LIB': True})
# todo: this in some better way
src_dll = os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic', 'src', 'Release', 'discord-rpc.dll')
dst_dll = os.path.join(SCRIPT_PATH, 'examples', 'button-clicker', 'Assets', 'Resources', 'discord-rpc.dll')
shutil.copy(src_dll, dst_dll)
dst_dll = os.path.join(SCRIPT_PATH, 'examples', 'unrealstatus', 'Plugins', 'discordrpc', 'Binaries', 'ThirdParty', 'discordrpcLibrary', 'Win64', 'discord-rpc.dll')
shutil.copy(src_dll, dst_dll)
create_archive()
if __name__ == '__main__':
sys.exit(main())

View File

@ -0,0 +1,99 @@
# 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()` from `src/discord-register.cpp` as a good example of how to properly register an application protocol for use with Discord.
## 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 two new RPC events for Rich Presence power the ability to join and spectate your friends' games. First is the `GAME_JOIN` event:
```json
{
"cmd": "DISPATCH",
"data": {
"secret": "025ed05c71f639de8bfaa0d679d7c94b2fdce12f"
},
"evnt": "GAME_JOIN"
}
```
And second is the `GAME_SPECTATE` event:
```json
{
"cmd": "DISPATCH",
"data": {
"secret": "e7eb30d2ee025ed05c71ea495f770b76454ee4e0"
},
"evnt": "GAME_SPECTATE"
}
```
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": "GAME_JOIN",
"cmd": "SUBSCRIBE"
}
```
```json
{
"nonce": "ae9qdde3-31d0-8989-a8e9-dnakwy174he",
"evt": "GAME_SPECTATE",
"cmd": "SUBSCRIBE"
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

6
examples/button-clicker/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/Library/
/Temp/
/obj/
*.sln
*.csproj
*.userprefs

View File

@ -0,0 +1,89 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DiscordController : MonoBehaviour {
public DiscordRpc.RichPresence presence;
public string applicationId;
public string optionalSteamId;
public int callbackCalls;
public int clickCounter;
public UnityEngine.Events.UnityEvent onConnect;
public UnityEngine.Events.UnityEvent onDisconnect;
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 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));
}
public void SpectateCallback(string secret)
{
++callbackCalls;
Debug.Log(string.Format("Discord: spectate ({0})", secret));
}
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;
DiscordRpc.Initialize(applicationId, ref handlers, true, optionalSteamId);
}
void OnDisable()
{
Debug.Log("Discord: shutdown");
DiscordRpc.Shutdown();
}
void OnDestroy()
{
}
}

View 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:

View File

@ -0,0 +1,61 @@
using System.Runtime.InteropServices;
public class DiscordRpc
{
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
public delegate void ReadyCallback();
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
public delegate void DisconnectedCallback(int errorCode, string message);
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
public delegate void ErrorCallback(int errorCode, string message);
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
public delegate void JoinCallback(string secret);
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
public delegate void SpectateCallback(string secret);
public struct EventHandlers
{
public ReadyCallback readyCallback;
public DisconnectedCallback disconnectedCallback;
public ErrorCallback errorCallback;
public JoinCallback joinCallback;
public SpectateCallback spectateCallback;
}
[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;
}
[DllImport("discord-rpc", EntryPoint = "Discord_Initialize")]
public static extern void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId);
[DllImport("discord-rpc", EntryPoint = "Discord_Shutdown")]
public static extern void Shutdown();
[DllImport("discord-rpc", EntryPoint = "Discord_RunCallbacks")]
public static extern void RunCallbacks();
[DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence")]
public static extern void UpdatePresence(ref RichPresence presence);
}

View 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:

View File

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

View File

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

View File

@ -0,0 +1,718 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 8
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 3
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 11
m_GIWorkflowMode: 1
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_TemporalCoherenceThreshold: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 0
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
serializedVersion: 9
m_Resolution: 2
m_BakeResolution: 40
m_TextureWidth: 1024
m_TextureHeight: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_FinalGather: 0
m_FinalGatherFiltering: 1
m_FinalGatherRayCount: 256
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 0
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 500
m_PVRBounces: 2
m_PVRFiltering: 0
m_PVRFilteringMode: 1
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 5
m_PVRFilteringGaussRadiusAO: 2
m_PVRFilteringAtrousColorSigma: 1
m_PVRFilteringAtrousNormalSigma: 1
m_PVRFilteringAtrousPositionSigma: 1
m_LightingDataAsset: {fileID: 0}
m_UseShadowmask: 1
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 2
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
accuratePlacement: 0
m_NavMeshData: {fileID: 0}
--- !u!1 &134146651
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
serializedVersion: 5
m_Component:
- component: {fileID: 134146656}
- component: {fileID: 134146655}
- component: {fileID: 134146654}
- component: {fileID: 134146653}
- component: {fileID: 134146652}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!81 &134146652
AudioListener:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 134146651}
m_Enabled: 1
--- !u!124 &134146653
Behaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 134146651}
m_Enabled: 1
--- !u!92 &134146654
Behaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 134146651}
m_Enabled: 1
--- !u!20 &134146655
Camera:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 134146651}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 60
orthographic: 1
orthographic size: 5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
m_StereoMirrorMode: 0
--- !u!4 &134146656
Transform:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 134146651}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &359174702
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
serializedVersion: 5
m_Component:
- component: {fileID: 359174703}
- component: {fileID: 359174705}
- component: {fileID: 359174704}
m_Layer: 5
m_Name: Text
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &359174703
RectTransform:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 359174702}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 1032248339}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &359174704
MonoBehaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 359174702}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 10
m_MaxSize: 40
m_Alignment: 4
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: Button
--- !u!222 &359174705
CanvasRenderer:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 359174702}
--- !u!1 &657463235
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
serializedVersion: 5
m_Component:
- component: {fileID: 657463238}
- component: {fileID: 657463237}
- component: {fileID: 657463236}
m_Layer: 5
m_Name: IsConnectedLabel
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &657463236
MonoBehaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 657463235}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.89518255, g: 0.9338235, b: 0.23345588, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 24
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 2
m_MaxSize: 40
m_Alignment: 0
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: Discord Disconnected
--- !u!222 &657463237
CanvasRenderer:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 657463235}
--- !u!224 &657463238
RectTransform:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 657463235}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 1766020814}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 16, y: -19.00003}
m_SizeDelta: {x: 239.20001, y: 37.799988}
m_Pivot: {x: 0, y: 1}
--- !u!1 &1032248338
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
serializedVersion: 5
m_Component:
- component: {fileID: 1032248339}
- component: {fileID: 1032248342}
- component: {fileID: 1032248341}
- component: {fileID: 1032248340}
m_Layer: 5
m_Name: Button
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1032248339
RectTransform:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1032248338}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 359174703}
m_Father: {fileID: 1766020814}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 160, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1032248340
MonoBehaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1032248338}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 1392445389, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 1032248341}
m_OnClick:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 1929635629}
m_MethodName: OnClick
m_Mode: 1
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
m_TypeName: UnityEngine.UI.Button+ButtonClickedEvent, UnityEngine.UI, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null
--- !u!114 &1032248341
MonoBehaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1032248338}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -765806418, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
--- !u!222 &1032248342
CanvasRenderer:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1032248338}
--- !u!1 &1470895131
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
serializedVersion: 5
m_Component:
- component: {fileID: 1470895134}
- component: {fileID: 1470895133}
- component: {fileID: 1470895132}
m_Layer: 0
m_Name: EventSystem
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1470895132
MonoBehaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1470895131}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 1077351063, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_HorizontalAxis: Horizontal
m_VerticalAxis: Vertical
m_SubmitButton: Submit
m_CancelButton: Cancel
m_InputActionsPerSecond: 10
m_RepeatDelay: 0.5
m_ForceModuleActive: 0
--- !u!114 &1470895133
MonoBehaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1470895131}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -619905303, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_FirstSelected: {fileID: 0}
m_sendNavigationEvents: 1
m_DragThreshold: 5
--- !u!4 &1470895134
Transform:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1470895131}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1766020810
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
serializedVersion: 5
m_Component:
- component: {fileID: 1766020814}
- component: {fileID: 1766020813}
- component: {fileID: 1766020812}
- component: {fileID: 1766020811}
m_Layer: 5
m_Name: Canvas
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1766020811
MonoBehaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1766020810}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 1301386320, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreReversedGraphics: 1
m_BlockingObjects: 0
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
--- !u!114 &1766020812
MonoBehaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1766020810}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 1980459831, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_UiScaleMode: 0
m_ReferencePixelsPerUnit: 100
m_ScaleFactor: 1
m_ReferenceResolution: {x: 800, y: 600}
m_ScreenMatchMode: 0
m_MatchWidthOrHeight: 0
m_PhysicalUnit: 3
m_FallbackScreenDPI: 96
m_DefaultSpriteDPI: 96
m_DynamicPixelsPerUnit: 1
--- !u!223 &1766020813
Canvas:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1766020810}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 0
m_Camera: {fileID: 0}
m_PlaneDistance: 100
m_PixelPerfect: 0
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_AdditionalShaderChannelsFlag: 0
m_SortingLayerID: 0
m_SortingOrder: 0
m_TargetDisplay: 0
--- !u!224 &1766020814
RectTransform:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1766020810}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0, y: 0, z: 0}
m_Children:
- {fileID: 1032248339}
- {fileID: 657463238}
m_Father: {fileID: 0}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 0}
--- !u!1 &1929635628
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
serializedVersion: 5
m_Component:
- component: {fileID: 1929635630}
- component: {fileID: 1929635629}
m_Layer: 0
m_Name: Discord
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1929635629
MonoBehaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1929635628}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 27f0a5f59ffffa84c86547736e2e730a, type: 3}
m_Name:
m_EditorClassIdentifier:
presence:
state: Button clicking
details:
startTimestamp: 0
endTimestamp: 0
largeImageKey: stable-large
largeImageText:
smallImageKey: canary-small
smallImageText:
partyId: abcdefg
partySize: 1
partyMax: 10
matchSecret:
joinSecret:
spectateSecret:
instance: 0
applicationId: 345229890980937739
optionalSteamId:
callbackCalls: 0
clickCounter: 0
onConnect:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 657463236}
m_MethodName: set_text
m_Mode: 5
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument: Discord Connected
m_BoolArgument: 1
m_CallState: 2
m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null
onDisconnect:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 657463236}
m_MethodName: set_text
m_Mode: 5
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument: Discord Disconnected
m_BoolArgument: 0
m_CallState: 2
m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null
--- !u!4 &1929635630
Transform:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1929635628}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3b03d21bb25fa494e8694cd6e4b6d769
timeCreated: 1501696924
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View 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

View File

@ -0,0 +1,6 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!236 &1
ClusterInputManager:
m_ObjectHideFlags: 0
m_Inputs: []

View File

@ -0,0 +1,19 @@
%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

View File

@ -0,0 +1,7 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1045 &1
EditorBuildSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Scenes: []

View 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

View File

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

View 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

View 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

View File

@ -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: {}

View File

@ -0,0 +1,36 @@
%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_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

View File

@ -0,0 +1,594 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!129 &1
PlayerSettings:
m_ObjectHideFlags: 0
serializedVersion: 12
productGUID: 5eccc60d3e382a346a65f512d6b81b84
AndroidProfiler: 0
defaultScreenOrientation: 4
targetDevice: 2
useOnDemandResources: 0
accelerometerFrequency: 60
companyName: DefaultCompany
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_SplashScreenBackgroundLandscape: {fileID: 0}
m_SplashScreenBackgroundPortrait: {fileID: 0}
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_MobileMTRendering: 0
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
defaultIsFullScreen: 1
defaultIsNativeResolution: 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
n3dsDisableStereoscopicView: 0
n3dsEnableSharedListOpt: 1
n3dsEnableVSync: 0
ignoreAlphaClear: 0
xboxOneResolution: 0
xboxOneMonoLoggingLevel: 0
xboxOneLoggingLevel: 1
xboxOneDisableEsram: 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
hololens:
depthFormat: 1
protectGraphicsMemory: 0
useHDRDisplay: 0
targetPixelDensity: 0
resolutionScalingMode: 0
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:
tvOSSdkVersion: 0
tvOSRequireExtendedGameController: 0
tvOSTargetOSVersionString:
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
androidEnableBanner: 1
m_AndroidBanners:
- width: 320
height: 180
banner: {fileID: 0}
androidGamepadSupportLevel: 0
resolutionDialogBanner: {fileID: 0}
m_BuildTargetIcons: []
m_BuildTargetBatching: []
m_BuildTargetGraphicsAPIs: []
m_BuildTargetVRSettings: []
openGLRequireES31: 0
openGLRequireES31AEP: 0
webPlayerTemplate: APPLICATION:Default
m_TemplateCustomTags: {}
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
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

View File

@ -0,0 +1 @@
m_EditorVersion: 2017.1.0f3

View 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

View 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

View File

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

View File

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

View File

@ -1,3 +1,10 @@
include_directories(${PROJECT_SOURCE_DIR}/include) include_directories(${PROJECT_SOURCE_DIR}/include)
add_executable(send-presence send-presence.c) add_executable(send-presence send-presence.c)
target_link_libraries(send-presence discord-rpc) target_link_libraries(send-presence discord-rpc)
install(
TARGETS send-presence
RUNTIME
DESTINATION "bin"
CONFIGURATIONS Release
)

View File

@ -11,8 +11,9 @@
#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 void updateDiscordPresence() static void updateDiscordPresence()
{ {
@ -22,6 +23,17 @@ static void updateDiscordPresence()
discordPresence.state = "West of House"; discordPresence.state = "West of House";
sprintf(buffer, "Frustration level: %d", FrustrationLevel); sprintf(buffer, "Frustration level: %d", FrustrationLevel);
discordPresence.details = buffer; 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); Discord_UpdatePresence(&discordPresence);
} }
@ -40,10 +52,14 @@ 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 void handleDiscordSpectate(const char* secret)
{
printf("\nDiscord: spectate (%s)\n", secret);
} }
static int prompt(char* line, size_t size) static int prompt(char* line, size_t size)
@ -52,7 +68,7 @@ static int prompt(char* line, size_t size)
char* nl; char* nl;
printf("\n> "); printf("\n> ");
fflush(stdout); fflush(stdout);
res = fgets(line, size, stdin) ? 1 : 0; res = fgets(line, (int)size, stdin) ? 1 : 0;
line[size - 1] = 0; line[size - 1] = 0;
nl = strchr(line, '\n'); nl = strchr(line, '\n');
if (nl) { if (nl) {
@ -61,17 +77,44 @@ static int prompt(char* line, size_t size)
return res; 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;
Discord_Initialize(APPLICATION_ID, &handlers, 1, NULL);
}
static void gameLoop() 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] == '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 +138,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();

75
examples/unrealstatus/.gitignore vendored Normal file
View File

@ -0,0 +1,75 @@
# 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/

View 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

View File

@ -0,0 +1,7 @@
[/Script/EngineSettings.GeneralProjectSettings]
ProjectID=E5977A24492699DF20B8ADBF736AF6C6
ProjectName=Discord RPC Example
CompanyName=Discord Inc.
Homepage="https://discordapp.com/"
CopyrightNotice=

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -0,0 +1,26 @@
// Fill out your copyright notice in the Description page of Project Settings.
using System.IO;
using UnrealBuildTool;
public class discordrpcLibrary : ModuleRules
{
public discordrpcLibrary(ReadOnlyTargetRules Target) : base(Target)
{
Type = ModuleType.External;
if (Target.Platform == UnrealTargetPlatform.Win64)
{
// Add the import library
PublicLibraryPaths.Add(Path.Combine(ModuleDirectory, "x64", "Release"));
PublicAdditionalLibraries.Add("discord-rpc.lib");
// Delay-load the DLL, so we can load it from the right place first
PublicDelayLoadDLLs.Add("discord-rpc.dll");
}
else if (Target.Platform == UnrealTargetPlatform.Mac)
{
PublicDelayLoadDLLs.Add(Path.Combine(ModuleDirectory, "Mac", "Release", "libdiscord-rpc.dylib"));
}
}
}

View File

@ -0,0 +1,130 @@
#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);
}
}
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;
}
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);
}

View File

@ -0,0 +1,50 @@
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "discordrpc.h"
#include "Core.h"
#include "ModuleManager.h"
#include "IPluginManager.h"
#define LOCTEXT_NAMESPACE "FdiscordrpcModule"
void FdiscordrpcModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified
// in the .uplugin file per-module
// Get the base directory of this plugin
FString BaseDir = IPluginManager::Get().FindPlugin("discordrpc")->GetBaseDir();
// Add on the relative location of the third party dll and load it
FString LibraryPath;
#if PLATFORM_WINDOWS
LibraryPath = FPaths::Combine(
*BaseDir, TEXT("Binaries/ThirdParty/discordrpcLibrary/Win64/discord-rpc.dll"));
#elif PLATFORM_MAC
LibraryPath = FPaths::Combine(
*BaseDir, TEXT("Source/ThirdParty/discordrpcLibrary/Mac/Release/libdiscord-rpc.dylib"));
#endif // PLATFORM_WINDOWS
DiscordLibraryHandle =
!LibraryPath.IsEmpty() ? FPlatformProcess::GetDllHandle(*LibraryPath) : nullptr;
if (!DiscordLibraryHandle) {
FMessageDialog::Open(
EAppMsgType::Ok, LOCTEXT("ThirdPartyLibraryError", "Failed to load discord-rpc library"));
}
}
void FdiscordrpcModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that
// support dynamic reloading,
// we call this function before unloading the module.
// Free the dll handle
FPlatformProcess::FreeDllHandle(DiscordLibraryHandle);
DiscordLibraryHandle = nullptr;
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FdiscordrpcModule, discordrpc)

View File

@ -0,0 +1,126 @@
#pragma once
#include "Engine.h"
#include "CoreMinimal.h"
#include "DiscordRpcBlueprint.generated.h"
// unreal's header tool hates clang-format
// clang-format off
DECLARE_LOG_CATEGORY_EXTERN(Discord, Log, All);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDiscordConnected);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordDisconnected, int, errorCode, const FString&, errorMessage);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordErrored, int, errorCode, const FString&, errorMessage);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoin, const FString&, joinSecret);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordSpectate, const FString&, spectateSecret);
// 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();
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(BlueprintReadWrite,
meta = (DisplayName = "Rich presence info", Keywords = "Discord rpc"),
Category = "Discord")
FDiscordRichPresence RichPresence;
};

View File

@ -0,0 +1,16 @@
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "ModuleManager.h"
class FdiscordrpcModule : public IModuleInterface {
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
private:
/** Handle to the test dll we will load */
void* DiscordLibraryHandle;
};

View File

@ -0,0 +1,65 @@
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class discordrpc : ModuleRules
{
public discordrpc(ReadOnlyTargetRules Target) : base(Target)
{
Definitions.Add("DISCORD_DYNAMIC_LIB=1");
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
"discordrpc/Public"
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
"discordrpc/Private",
"../../../../../include"
// ... add other private include paths required here ...
}
);
PublicLibraryPaths.AddRange(
new string[] {
System.IO.Path.Combine(ModuleDirectory, "../../Binaries/ThirdParty/discordrpcLibrary/", Target.Platform.ToString()),
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"Core",
"discordrpcLibrary",
"Projects"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

View File

@ -0,0 +1,23 @@
{
"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": "Developer",
"LoadingPhase": "Default"
}
]
}

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"

View File

@ -0,0 +1,3 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "unrealstatusGameModeBase.h"

View File

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

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

View File

@ -0,0 +1,19 @@
{
"FileVersion": 3,
"EngineAssociation": "4.16",
"Category": "",
"Description": "",
"Modules": [
{
"Name": "unrealstatus",
"Type": "Runtime",
"LoadingPhase": "Default"
}
],
"TargetPlatforms": [
"LinuxNoEditor",
"MacNoEditor",
"WindowsNoEditor",
"AllDesktop"
]
}

View File

@ -1,25 +1,43 @@
#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;
@ -27,23 +45,25 @@ typedef struct DiscordEventHandlers {
void (*ready)(); void (*ready)();
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);
} DiscordEventHandlers; } DiscordEventHandlers;
void Discord_Initialize(const char* applicationId, DiscordEventHandlers* handlers); DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
void Discord_Shutdown(); DiscordEventHandlers* handlers,
int autoRegister,
const char* optionalSteamId);
DISCORD_EXPORT void Discord_Shutdown();
/* checks for incoming messages, dispatches callbacks */ /* checks for incoming messages, dispatches callbacks */
void Discord_RunCallbacks(); DISCORD_EXPORT void Discord_RunCallbacks();
/* 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();
#endif #endif
void Discord_UpdatePresence(const DiscordRichPresence* presence); DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence);
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */

View File

@ -1,23 +1,50 @@
include_directories(${PROJECT_SOURCE_DIR}/include) include_directories(${PROJECT_SOURCE_DIR}/include)
option(ENABLE_IO_THREAD "Start up a separate I/O thread, otherwise I'd need to call an update function" ON) option(ENABLE_IO_THREAD "Start up a separate I/O thread, otherwise I'd need to call an update function" ON)
option(BUILD_DYNAMIC_LIB "Build library as a DLL" OFF)
if (${ENABLE_IO_THREAD} EQUAL OFF) set(BASE_RPC_SRC
add_definitions(-DDISCORD_DISABLE_IO_THREAD) ${PROJECT_SOURCE_DIR}/include/discord-rpc.h
endif (${ENABLE_IO_THREAD} EQUAL OFF) discord-rpc.cpp
discord-register.h
discord-register.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 rpc_connection.h rpc_connection.cpp serialization.h serialization.cpp connection.h backoff.h) if (${BUILD_DYNAMIC_LIB})
set(RPC_LIBRARY_TYPE SHARED)
set(BASE_RPC_SRC ${BASE_RPC_SRC} dllmain.cpp)
else(${BUILD_DYNAMIC_LIB})
set(RPC_LIBRARY_TYPE STATIC)
endif(${BUILD_DYNAMIC_LIB})
if(WIN32) if(WIN32)
add_library(discord-rpc STATIC ${BASE_RPC_SRC} connection_win.cpp) add_library(discord-rpc ${RPC_LIBRARY_TYPE} ${BASE_RPC_SRC} connection_win.cpp)
target_compile_options(discord-rpc PRIVATE /W4)
endif(WIN32) endif(WIN32)
if(UNIX) if(UNIX)
add_library(discord-rpc STATIC ${BASE_RPC_SRC} connection_unix.cpp) add_library(discord-rpc ${RPC_LIBRARY_TYPE} ${BASE_RPC_SRC} connection_unix.cpp)
target_link_libraries(discord-rpc PUBLIC pthread)
target_compile_options(discord-rpc PRIVATE -g -Wall -std=c++14)
endif(UNIX) endif(UNIX)
target_include_directories(discord-rpc PRIVATE ${RAPIDJSON}/include) target_include_directories(discord-rpc PRIVATE ${RAPIDJSON}/include)
if (NOT ${ENABLE_IO_THREAD})
add_definitions(discord-rpc PUBLIC -DDISCORD_DISABLE_IO_THREAD)
endif (NOT ${ENABLE_IO_THREAD})
if (${BUILD_DYNAMIC_LIB})
target_compile_definitions(discord-rpc PUBLIC -DDISCORD_DYNAMIC_LIB)
target_compile_definitions(discord-rpc PRIVATE -DDISCORD_BUILDING_SDK)
endif(${BUILD_DYNAMIC_LIB})
add_dependencies(discord-rpc clangformat) add_dependencies(discord-rpc clangformat)
# install # install
@ -25,9 +52,17 @@ add_dependencies(discord-rpc clangformat)
install( install(
TARGETS discord-rpc TARGETS discord-rpc
EXPORT "discord-rpc" EXPORT "discord-rpc"
LIBRARY DESTINATION "lib" RUNTIME
ARCHIVE DESTINATION "lib" DESTINATION "bin"
INCLUDES DESTINATION "include" CONFIGURATIONS Release
LIBRARY
DESTINATION "lib"
CONFIGURATIONS Release
ARCHIVE
DESTINATION "lib"
CONFIGURATIONS Release
INCLUDES
DESTINATION "include"
) )
install( install(

View File

@ -3,6 +3,7 @@
#include <stdint.h> #include <stdint.h>
#include <algorithm> #include <algorithm>
#include <random> #include <random>
#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(time(0))
{ {
} }

View File

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

View File

@ -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 = recv(self->sock, data, length, MsgFlags);
{ if (res < 0) {
if (errno == EAGAIN) {
return false;
}
Close();
}
return res == (int)length;
} }

View File

@ -16,8 +16,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 +31,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();
if (lastError == ERROR_FILE_NOT_FOUND) {
if (pipeName[pipeDigit] < L'9') {
pipeName[pipeDigit]++;
continue;
}
}
else if (lastError == ERROR_PIPE_BUSY) {
if (!WaitNamedPipeW(pipeName, 10000)) {
return false; return false;
} }
continue;
if (!WaitNamedPipeW(PipeName, 10000)) {
return false;
} }
return false;
} }
} }
@ -56,25 +65,41 @@ 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; if (self->pipe == INVALID_HANDLE_VALUE) {
return false;
}
return ::WriteFile(self->pipe, data, (DWORD)length, nullptr, nullptr) == TRUE;
} }
bool BaseConnection::Read(void* data, size_t length) bool BaseConnection::Read(void* data, size_t length)
{ {
auto self = reinterpret_cast<BaseConnectionWin*>(this); auto self = reinterpret_cast<BaseConnectionWin*>(this);
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) { if (::ReadFile(self->pipe, data, (DWORD)length, nullptr, nullptr) == TRUE) {
return true; return true;
} }
else {
Close();
} }
} }
}
else {
Close();
}
return false; return false;
} }

137
src/discord-register.cpp Normal file
View File

@ -0,0 +1,137 @@
#include "discord-rpc.h"
#include <stdio.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define NOMCX
#define NOSERVICE
#define NOIME
#include <windows.h>
#include <Psapi.h>
#include <Strsafe.h>
#pragma comment(lib, "Psapi.lib")
#endif
#ifdef _WIN32
void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command)
{
// https://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx
// we want to register games so we can run them as discord-<appid>://
// Update the HKEY_CURRENT_USER, because it doesn't seem to require special permissions.
wchar_t exeFilePath[MAX_PATH];
int exeLen = GetModuleFileNameExW(GetCurrentProcess(), nullptr, exeFilePath, MAX_PATH);
wchar_t openCommand[1024];
const auto commandBufferLen = sizeof(openCommand) / sizeof(*openCommand);
if (command && command[0]) {
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command);
}
else {
lstrcpyW(openCommand, exeFilePath);
}
wchar_t protocolName[64];
StringCbPrintfW(protocolName, sizeof(protocolName), L"discord-%s", 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 = 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 = 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 = 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);
}
#endif
void Discord_Register(const char* applicationId, const char* command)
{
#ifdef _WIN32
wchar_t appId[32];
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
wchar_t openCommand[1024];
const wchar_t* wcommand = nullptr;
if (command && command[0]) {
const auto commandBufferLen = sizeof(openCommand) / sizeof(*openCommand);
MultiByteToWideChar(CP_UTF8, 0, command, -1, openCommand, commandBufferLen);
wcommand = openCommand;
}
Discord_RegisterW(appId, wcommand);
#endif
}
void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
{
#ifdef _WIN32
wchar_t appId[32];
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
wchar_t wSteamId[32];
MultiByteToWideChar(CP_UTF8, 0, steamId, -1, wSteamId, 32);
HKEY key;
auto status = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam", 0, KEY_READ, &key);
if (status != ERROR_SUCCESS) {
fprintf(stderr, "Error opening Steam key\n");
return;
}
wchar_t steamPath[MAX_PATH];
DWORD pathBytes = sizeof(steamPath);
status = RegQueryValueExW(key, L"SteamExe", nullptr, nullptr, (BYTE*)steamPath, &pathBytes);
RegCloseKey(key);
if (status != ERROR_SUCCESS || pathBytes < 1) {
fprintf(stderr, "Error reading SteamExe key\n");
return;
}
DWORD pathChars = pathBytes / sizeof(wchar_t);
for (DWORD i = 0; i < pathChars; ++i) {
if (steamPath[i] == L'/') {
steamPath[i] = L'\\';
}
}
wchar_t command[1024];
StringCbPrintfW(command, sizeof(command), L"\"%s\" steam://run/%s", steamPath, wSteamId);
Discord_RegisterW(appId, command);
#endif
}

4
src/discord-register.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
void Discord_Register(const char* applicationId, const char* command);
void Discord_RegisterSteamGame(const char* applicationId, const char* steamId);

View File

@ -1,11 +1,13 @@
#include "discord-rpc.h" #include "discord-rpc.h"
#include "backoff.h" #include "backoff.h"
#include "discord-register.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>
@ -18,15 +20,21 @@ constexpr size_t MessageQueueSize{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);
}
}
}; };
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,10 +43,14 @@ 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 std::mutex PresenceMutex;
static QueuedMessage QueuedPresence{};
static QueuedMessage SendQueue[MessageQueueSize]{}; static QueuedMessage SendQueue[MessageQueueSize]{};
static std::atomic_uint SendQueueNextAdd{0}; static std::atomic_uint SendQueueNextAdd{0};
static std::atomic_uint SendQueueNextSend{0}; static std::atomic_uint SendQueueNextSend{0};
static std::atomic_uint SendQueuePendingSends{0}; static std::atomic_uint SendQueuePendingSends{0};
// 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};
@ -59,6 +71,11 @@ static void UpdateReconnectTime()
static QueuedMessage* SendQueueGetNextAddMessage() static QueuedMessage* SendQueueGetNextAddMessage()
{ {
// if we are not connected, let's not batch up stale messages for later
if (!Connection || !Connection->IsOpen()) {
return nullptr;
}
// if we are falling behind, bail // if we are falling behind, bail
if (SendQueuePendingSends.load() >= MessageQueueSize) { if (SendQueuePendingSends.load() >= MessageQueueSize) {
return nullptr; return nullptr;
@ -78,6 +95,10 @@ static void SendQueueCommitMessage()
extern "C" void Discord_UpdateConnection() extern "C" void Discord_UpdateConnection()
{ {
if (!Connection) {
return;
}
if (!Connection->IsOpen()) { if (!Connection->IsOpen()) {
if (std::chrono::system_clock::now() >= NextConnect) { if (std::chrono::system_clock::now() >= NextConnect) {
UpdateReconnectTime(); UpdateReconnectTime();
@ -94,20 +115,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,26 +134,40 @@ extern "C" void Discord_UpdateConnection()
continue; continue;
} }
// todo ug if (strcmp(evtName, "GAME_JOIN") == 0) {
if (strcmp(evtName, "PRESENCE_REQUESTED") == 0) { auto data = GetObjMember(&message, "data");
WasPresenceRequested.store(true); auto secret = GetStrMember(data, "secret");
} if (secret) {
else if (strcmp(evtName, "JOIN_GAME") == 0) {
auto data = message.FindMember("data");
auto secret = data->value["secret"].GetString();
StringCopy(JoinGameSecret, secret); StringCopy(JoinGameSecret, secret);
WasJoinGame.store(true); WasJoinGame.store(true);
} }
else if (strcmp(evtName, "SPECTATE_GAME") == 0) { }
auto data = message.FindMember("data"); else if (strcmp(evtName, "GAME_SPECTATE") == 0) {
auto secret = data->value["secret"].GetString(); auto data = GetObjMember(&message, "data");
auto secret = GetStrMember(data, "secret");
if (secret) {
StringCopy(SpectateGameSecret, secret); StringCopy(SpectateGameSecret, secret);
WasSpectateGame.store(true); WasSpectateGame.store(true);
} }
} }
} }
}
// writes // writes
if (QueuedPresence.length) {
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 (SendQueuePendingSends.load()) { while (SendQueuePendingSends.load()) {
auto qmessage = SendQueueGetNextSendMessage(); auto qmessage = SendQueueGetNextSendMessage();
Connection->Write(qmessage->buffer, qmessage->length); Connection->Write(qmessage->buffer, qmessage->length);
@ -179,8 +210,20 @@ bool RegisterForEvent(const char* evtName)
return false; return false;
} }
extern "C" void Discord_Initialize(const char* applicationId, DiscordEventHandlers* handlers) extern "C" 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 +233,21 @@ 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("GAME_JOIN");
} }
if (Handlers.spectateGame) { if (Handlers.spectateGame) {
RegisterForEvent("SPECTATE_GAME"); RegisterForEvent("GAME_SPECTATE");
} }
}; };
Connection->onDisconnect = [](int err, const char* message) { Connection->onDisconnect = [](int err, const char* message) {
@ -215,12 +258,16 @@ extern "C" void Discord_Initialize(const char* applicationId, DiscordEventHandle
}; };
#ifndef DISCORD_DISABLE_IO_THREAD #ifndef DISCORD_DISABLE_IO_THREAD
KeepRunning.store(true);
IoThread = std::thread(DiscordRpcIo); IoThread = std::thread(DiscordRpcIo);
#endif #endif
} }
extern "C" void Discord_Shutdown() extern "C" void Discord_Shutdown()
{ {
if (!Connection) {
return;
}
Connection->onConnect = nullptr; Connection->onConnect = nullptr;
Connection->onDisconnect = nullptr; Connection->onDisconnect = nullptr;
Handlers = {}; Handlers = {};
@ -236,31 +283,39 @@ extern "C" void Discord_Shutdown()
extern "C" void Discord_UpdatePresence(const DiscordRichPresence* presence) extern "C" void Discord_UpdatePresence(const DiscordRichPresence* presence)
{ {
auto qmessage = SendQueueGetNextAddMessage(); PresenceMutex.lock();
if (qmessage) { QueuedPresence.length = JsonWriteRichPresenceObj(
qmessage->length = JsonWriteRichPresenceObj( QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
qmessage->buffer, sizeof(qmessage->buffer), Nonce++, Pid, presence); PresenceMutex.unlock();
SendQueueCommitMessage();
SignalIOActivity(); SignalIOActivity();
}
} }
extern "C" void Discord_RunCallbacks() extern "C" void Discord_RunCallbacks()
{ {
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);
bool isConnected = Connection->IsOpen();
if (isConnected) {
// if we are connected, disconnect cb first
if (wasDisconnected && Handlers.disconnected) {
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage); 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 +325,11 @@ extern "C" void Discord_RunCallbacks()
if (WasSpectateGame.exchange(false) && Handlers.spectateGame) { if (WasSpectateGame.exchange(false) && Handlers.spectateGame) {
Handlers.spectateGame(SpectateGameSecret); Handlers.spectateGame(SpectateGameSecret);
} }
if (!isConnected) {
// if we are not connected, disconnect message last
if (wasDisconnected && Handlers.disconnected) {
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
}
}
} }

6
src/dllmain.cpp Normal file
View File

@ -0,0 +1,6 @@
#include <windows.h>
BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID)
{
return TRUE;
}

View File

@ -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;
} }
@ -133,7 +131,7 @@ bool RpcConnection::Read(JsonDocument& message)
break; break;
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;

View File

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

View File

@ -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,10 +34,39 @@ 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);
} }
@ -23,54 +75,41 @@ void WriteOptionalString(JsonWriter& w, T& k, const char* value)
void JsonWriteNonce(JsonWriter& writer, int nonce) 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);
JsonWriteNonce(writer, nonce);
WriteKey(writer, "cmd");
writer.String("SET_ACTIVITY");
{
WriteObject args(writer, "args");
WriteKey(writer, "pid"); WriteKey(writer, "pid");
writer.Int(pid); writer.Int(pid);
WriteKey(writer, "activity"); {
writer.StartObject(); WriteObject activity(writer, "activity");
WriteOptionalString(writer, "state", presence->state); WriteOptionalString(writer, "state", presence->state);
WriteOptionalString(writer, "details", presence->details); WriteOptionalString(writer, "details", presence->details);
if (presence->startTimestamp || presence->endTimestamp) { if (presence->startTimestamp || presence->endTimestamp) {
WriteKey(writer, "timestamps"); WriteObject timestamps(writer, "timestamps");
writer.StartObject();
if (presence->startTimestamp) { if (presence->startTimestamp) {
WriteKey(writer, "start"); WriteKey(writer, "start");
@ -81,86 +120,71 @@ size_t JsonWriteRichPresenceObj(char* dest,
WriteKey(writer, "end"); WriteKey(writer, "end");
writer.Int64(presence->endTimestamp); writer.Int64(presence->endTimestamp);
} }
writer.EndObject();
} }
if (presence->largeImageKey || presence->largeImageText || presence->smallImageKey || if ((presence->largeImageKey && presence->largeImageKey[0]) ||
presence->smallImageText) { (presence->largeImageText && presence->largeImageText[0]) ||
WriteKey(writer, "assets"); (presence->smallImageKey && presence->smallImageKey[0]) ||
writer.StartObject(); (presence->smallImageText && presence->smallImageText[0])) {
WriteObject assets(writer, "assets");
WriteOptionalString(writer, "large_image", presence->largeImageKey); WriteOptionalString(writer, "large_image", presence->largeImageKey);
WriteOptionalString(writer, "large_text", presence->largeImageText); WriteOptionalString(writer, "large_text", presence->largeImageText);
WriteOptionalString(writer, "small_image", presence->smallImageKey); WriteOptionalString(writer, "small_image", presence->smallImageKey);
WriteOptionalString(writer, "small_text", presence->smallImageText); WriteOptionalString(writer, "small_text", presence->smallImageText);
writer.EndObject();
} }
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); WriteOptionalString(writer, "id", presence->partyId);
if (presence->partySize) { if (presence->partySize) {
writer.StartArray(); WriteArray size(writer, "size");
writer.Int(presence->partySize); writer.Int(presence->partySize);
if (0 < presence->partyMax) { if (0 < presence->partyMax) {
writer.Int(presence->partyMax); writer.Int(presence->partyMax);
} }
}
writer.EndArray();
} }
writer.EndObject(); if ((presence->matchSecret && presence->matchSecret[0]) ||
} (presence->joinSecret && presence->joinSecret[0]) ||
(presence->spectateSecret && presence->spectateSecret[0])) {
if (presence->matchSecret || presence->joinSecret || presence->spectateSecret) { WriteObject secrets(writer, "secrets");
WriteKey(writer, "secrets");
writer.StartObject();
WriteOptionalString(writer, "match", presence->matchSecret); WriteOptionalString(writer, "match", presence->matchSecret);
WriteOptionalString(writer, "join", presence->joinSecret); WriteOptionalString(writer, "join", presence->joinSecret);
WriteOptionalString(writer, "spectate", presence->spectateSecret); WriteOptionalString(writer, "spectate", presence->spectateSecret);
writer.EndObject();
} }
writer.Key("instance"); writer.Key("instance");
writer.Bool(presence->instance != 0); writer.Bool(presence->instance != 0);
}
}
}
writer.EndObject(); // activity return writer.Size();
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(); {
WriteObject obj(writer);
WriteKey(writer, "v"); WriteKey(writer, "v");
writer.Int(version); writer.Int(version);
WriteKey(writer, "client_id"); WriteKey(writer, "client_id");
writer.String(applicationId); writer.String(applicationId);
writer.EndObject(); }
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);
@ -169,8 +193,7 @@ size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const cha
WriteKey(writer, "evt"); WriteKey(writer, "evt");
writer.String(evtName); writer.String(evtName);
}
writer.EndObject(); return writer.Size();
return sb.GetSize();
} }

View File

@ -5,13 +5,12 @@
#include "rapidjson/document.h" #include "rapidjson/document.h"
#include "rapidjson/writer.h" #include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h" #include "rapidjson/stringbuffer.h"
#include "rapidjson/internal/itoa.h"
// 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;
@ -35,8 +34,7 @@ size_t JsonWriteRichPresenceObj(char* dest,
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);
// 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:
@ -71,7 +69,11 @@ public:
assert(!originalPtr && !originalSize); assert(!originalPtr && !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>
@ -116,8 +118,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 +155,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;
}

View File

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

View File

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

View File

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

View File

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