├── html ├── .gitignore ├── src │ ├── constants.js │ ├── blank.png │ ├── service │ │ ├── webapi.js │ │ ├── sqlite.js │ │ └── gamelog.js │ ├── security.js │ ├── repository │ │ ├── config.js │ │ └── shared.js │ ├── vr.scss │ ├── app.dark.scss │ └── app.scss ├── images │ ├── base_status_off.png │ ├── invalid_status.png │ ├── other_status_off.png │ ├── base_status_error.png │ ├── base_status_ready.png │ ├── headset_status_off.png │ ├── other_status_error.png │ ├── other_status_ready.png │ ├── tracker_status_off.png │ ├── base_status_ready_low.png │ ├── base_status_searching.gif │ ├── base_status_standby.png │ ├── controller_status_off.png │ ├── headset_status_error.png │ ├── headset_status_ready.png │ ├── other_status_standby.png │ ├── tracker_status_ready.png │ ├── base_status_ready_alert.png │ ├── controller_status_error.png │ ├── controller_status_ready.png │ ├── headset_status_standby.png │ ├── other_status_ready_low.png │ ├── other_status_searching.gif │ ├── tracker_status_standby.png │ ├── controller_status_standby.png │ ├── headset_status_ready_alert.png │ ├── headset_status_ready_low.png │ ├── headset_status_searching.gif │ ├── other_status_ready_alert.png │ ├── tracker_status_ready_alert.png │ ├── tracker_status_ready_low.png │ ├── tracker_status_searching.gif │ ├── base_status_searching_alert.gif │ ├── controller_status_ready_low.png │ ├── controller_status_searching.gif │ ├── other_status_searching_alert.gif │ ├── controller_status_ready_alert.png │ ├── headset_status_searching_alert.gif │ ├── tracker_status_searching_alert.gif │ └── controller_status_searching_alert.gif ├── package.json ├── .eslintrc.js └── webpack.config.js ├── VRCX.ico ├── AssetBundleCacher ├── AssetBundleCacher_Data │ ├── app.info │ ├── data.unity3d │ ├── Resources │ │ └── unity default resources │ ├── boot.config │ └── il2cpp_data │ │ ├── etc │ │ └── mono │ │ │ ├── browscap.ini │ │ │ ├── 2.0 │ │ │ ├── Browsers │ │ │ │ └── Compat.browser │ │ │ ├── settings.map │ │ │ └── web.config │ │ │ ├── 4.0 │ │ │ ├── Browsers │ │ │ │ └── Compat.browser │ │ │ └── settings.map │ │ │ ├── 4.5 │ │ │ ├── Browsers │ │ │ │ └── Compat.browser │ │ │ └── settings.map │ │ │ └── config │ │ ├── Metadata │ │ └── global-metadata.dat │ │ └── Resources │ │ └── mscorlib.dll-resources.dat ├── GameAssembly.dll ├── AssetBundleCacher.exe ├── WinPixEventRuntime.dll └── README.md ├── VRChatRPC ├── VRChatRPC.dll └── README.txt ├── OpenVR ├── win32 │ └── openvr_api.dll └── win64 │ └── openvr_api.dll ├── librsync.net ├── Blake2Sharp.dll ├── librsync.net.dll └── README.md ├── make-junction.cmd ├── App.config ├── Properties ├── Settings.settings ├── Settings.Designer.cs ├── AssemblyInfo.cs ├── Resources.Designer.cs └── Resources.resx ├── make-zip-7z.cmd ├── make-zip.cmd ├── NoopDragHandler.cs ├── WinApi.cs ├── Util.cs ├── .github └── workflows │ └── github_actions.yml ├── VRCX.sln ├── LICENSE ├── Program.cs ├── JsonSerializer.cs ├── VRChatRPC.cs ├── VRForm.cs ├── SharedVariable.cs ├── CefService.cs ├── CpuMonitor.cs ├── .gitattributes ├── VRCXStorage.cs ├── MainForm.Designer.cs ├── VRForm.Designer.cs ├── SQLite.cs ├── README.md ├── .gitignore ├── MainForm.cs ├── VRForm.resx ├── Discord.cs ├── MainForm.resx ├── OffScreenBrowser.cs ├── VRCX.csproj └── WebApi.cs /html/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /VRCX.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/VRCX.ico -------------------------------------------------------------------------------- /html/src/constants.js: -------------------------------------------------------------------------------- 1 | export var appVersion = 'VRCX 2021.05.26.1'; 2 | -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/app.info: -------------------------------------------------------------------------------- 1 | UnityTools 2 | AssetBundleCacher -------------------------------------------------------------------------------- /html/src/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/src/blank.png -------------------------------------------------------------------------------- /VRChatRPC/VRChatRPC.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/VRChatRPC/VRChatRPC.dll -------------------------------------------------------------------------------- /OpenVR/win32/openvr_api.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/OpenVR/win32/openvr_api.dll -------------------------------------------------------------------------------- /OpenVR/win64/openvr_api.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/OpenVR/win64/openvr_api.dll -------------------------------------------------------------------------------- /VRChatRPC/README.txt: -------------------------------------------------------------------------------- 1 | it grabs some data from VRChat process.. use at own risk. 2 | 3 | by pypy (mina#5656) -------------------------------------------------------------------------------- /librsync.net/Blake2Sharp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/librsync.net/Blake2Sharp.dll -------------------------------------------------------------------------------- /librsync.net/librsync.net.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/librsync.net/librsync.net.dll -------------------------------------------------------------------------------- /html/images/base_status_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/base_status_off.png -------------------------------------------------------------------------------- /html/images/invalid_status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/invalid_status.png -------------------------------------------------------------------------------- /html/images/other_status_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/other_status_off.png -------------------------------------------------------------------------------- /AssetBundleCacher/GameAssembly.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/AssetBundleCacher/GameAssembly.dll -------------------------------------------------------------------------------- /html/images/base_status_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/base_status_error.png -------------------------------------------------------------------------------- /html/images/base_status_ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/base_status_ready.png -------------------------------------------------------------------------------- /html/images/headset_status_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/headset_status_off.png -------------------------------------------------------------------------------- /html/images/other_status_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/other_status_error.png -------------------------------------------------------------------------------- /html/images/other_status_ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/other_status_ready.png -------------------------------------------------------------------------------- /html/images/tracker_status_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/tracker_status_off.png -------------------------------------------------------------------------------- /html/images/base_status_ready_low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/base_status_ready_low.png -------------------------------------------------------------------------------- /html/images/base_status_searching.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/base_status_searching.gif -------------------------------------------------------------------------------- /html/images/base_status_standby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/base_status_standby.png -------------------------------------------------------------------------------- /html/images/controller_status_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/controller_status_off.png -------------------------------------------------------------------------------- /html/images/headset_status_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/headset_status_error.png -------------------------------------------------------------------------------- /html/images/headset_status_ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/headset_status_ready.png -------------------------------------------------------------------------------- /html/images/other_status_standby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/other_status_standby.png -------------------------------------------------------------------------------- /html/images/tracker_status_ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/tracker_status_ready.png -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/AssetBundleCacher/AssetBundleCacher.exe -------------------------------------------------------------------------------- /html/images/base_status_ready_alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/base_status_ready_alert.png -------------------------------------------------------------------------------- /html/images/controller_status_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/controller_status_error.png -------------------------------------------------------------------------------- /html/images/controller_status_ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/controller_status_ready.png -------------------------------------------------------------------------------- /html/images/headset_status_standby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/headset_status_standby.png -------------------------------------------------------------------------------- /html/images/other_status_ready_low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/other_status_ready_low.png -------------------------------------------------------------------------------- /html/images/other_status_searching.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/other_status_searching.gif -------------------------------------------------------------------------------- /html/images/tracker_status_standby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/tracker_status_standby.png -------------------------------------------------------------------------------- /AssetBundleCacher/WinPixEventRuntime.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/AssetBundleCacher/WinPixEventRuntime.dll -------------------------------------------------------------------------------- /html/images/controller_status_standby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/controller_status_standby.png -------------------------------------------------------------------------------- /html/images/headset_status_ready_alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/headset_status_ready_alert.png -------------------------------------------------------------------------------- /html/images/headset_status_ready_low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/headset_status_ready_low.png -------------------------------------------------------------------------------- /html/images/headset_status_searching.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/headset_status_searching.gif -------------------------------------------------------------------------------- /html/images/other_status_ready_alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/other_status_ready_alert.png -------------------------------------------------------------------------------- /html/images/tracker_status_ready_alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/tracker_status_ready_alert.png -------------------------------------------------------------------------------- /html/images/tracker_status_ready_low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/tracker_status_ready_low.png -------------------------------------------------------------------------------- /html/images/tracker_status_searching.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/tracker_status_searching.gif -------------------------------------------------------------------------------- /html/images/base_status_searching_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/base_status_searching_alert.gif -------------------------------------------------------------------------------- /html/images/controller_status_ready_low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/controller_status_ready_low.png -------------------------------------------------------------------------------- /html/images/controller_status_searching.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/controller_status_searching.gif -------------------------------------------------------------------------------- /html/images/other_status_searching_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/other_status_searching_alert.gif -------------------------------------------------------------------------------- /html/images/controller_status_ready_alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/controller_status_ready_alert.png -------------------------------------------------------------------------------- /html/images/headset_status_searching_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/headset_status_searching_alert.gif -------------------------------------------------------------------------------- /html/images/tracker_status_searching_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/tracker_status_searching_alert.gif -------------------------------------------------------------------------------- /html/images/controller_status_searching_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/html/images/controller_status_searching_alert.gif -------------------------------------------------------------------------------- /make-junction.cmd: -------------------------------------------------------------------------------- 1 | mklink /J "%~dp0\bin\x64\Debug\html" "%~dp0\html\dist" 2 | mklink /J "%~dp0\bin\x64\Release\html" "%~dp0\html\dist" 3 | pause 4 | -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/data.unity3d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/AssetBundleCacher/AssetBundleCacher_Data/data.unity3d -------------------------------------------------------------------------------- /librsync.net/README.md: -------------------------------------------------------------------------------- 1 | # librsync.net 2 | 3 | Source code is available here: [https://github.com/braddodson/librsync.net](https://github.com/braddodson/librsync.net) 4 | -------------------------------------------------------------------------------- /AssetBundleCacher/README.md: -------------------------------------------------------------------------------- 1 | # Asset Bundle Cacher 2 | 3 | Source code is available here: [https://github.com/ZettaiVR/AssetBundleCacher](https://github.com/ZettaiVR/AssetBundleCacher) 4 | -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/Resources/unity default resources: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/AssetBundleCacher/AssetBundleCacher_Data/Resources/unity default resources -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/boot.config: -------------------------------------------------------------------------------- 1 | gfx-enable-native-gfx-jobs= 2 | wait-for-native-debugger=0 3 | scripting-runtime-version=latest 4 | vr-enabled=0 5 | hdr-display-enabled=0 6 | -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/il2cpp_data/etc/mono/browscap.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/AssetBundleCacher/AssetBundleCacher_Data/il2cpp_data/etc/mono/browscap.ini -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/il2cpp_data/Metadata/global-metadata.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/AssetBundleCacher/AssetBundleCacher_Data/il2cpp_data/Metadata/global-metadata.dat -------------------------------------------------------------------------------- /App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/il2cpp_data/Resources/mscorlib.dll-resources.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/VRCX/master/AssetBundleCacher/AssetBundleCacher_Data/il2cpp_data/Resources/mscorlib.dll-resources.dat -------------------------------------------------------------------------------- /Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /make-zip-7z.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | for /f %%a in ('powershell -Command "Get-Date -format yyyyMMdd"') do set TODAY=%%a 4 | set ZIP_NAME=VRCX_%TODAY%.zip 5 | echo %ZIP_NAME% 6 | rem using 7-Zip (https://www.7-zip.org) 7 | cd "%~dp0\bin\x64\Release" 8 | "C:\Program Files\7-Zip\7z.exe" a -tzip %ZIP_NAME% * -mx=7 -xr0!cache -xr0!userdata -xr0!*.log -xr0!VRCX.json -xr0!VRCX.sqlite3 9 | cd "%~dp0" 10 | move "%~dp0\bin\x64\Release\%ZIP_NAME%" "%~dp0" 11 | pause 12 | -------------------------------------------------------------------------------- /make-zip.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | for /f %%a in ('powershell -Command "Get-Date -format yyyyMMdd"') do set TODAY=%%a 4 | set ZIP_NAME=VRCX_%TODAY%.zip 5 | echo %ZIP_NAME% 6 | rem using bandizip (https://www.bandisoft.com/bandizip) 7 | cd "%~dp0\bin\x64\Release" 8 | bz c -l:9 -r -storeroot:yes -ex:"cache;userdata;*.log;VRCX.json;VRCX.sqlite3" -cmt:"https://github.com/pypy-vrc/VRCX" %ZIP_NAME% * 9 | cd "%~dp0" 10 | move "%~dp0\bin\x64\Release\%ZIP_NAME%" "%~dp0" 11 | pause 12 | -------------------------------------------------------------------------------- /html/src/service/webapi.js: -------------------------------------------------------------------------------- 1 | // requires binding of WebApi 2 | 3 | class WebApiService { 4 | clearCookies() { 5 | return WebApi.ClearCookies(); 6 | } 7 | 8 | execute(options) { 9 | return new Promise((resolve, reject) => { 10 | WebApi.Execute(options, (err, response) => { 11 | if (err !== null) { 12 | reject(err); 13 | return; 14 | } 15 | resolve(response); 16 | }); 17 | }); 18 | } 19 | } 20 | 21 | var self = new WebApiService(); 22 | window.webApiService = self; 23 | 24 | export { 25 | self as default, 26 | WebApiService 27 | }; 28 | -------------------------------------------------------------------------------- /NoopDragHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2019 pypy. All rights reserved. 2 | // 3 | // This work is licensed under the terms of the MIT license. 4 | // For a copy, see . 5 | 6 | using System.Collections.Generic; 7 | using CefSharp; 8 | using CefSharp.Enums; 9 | 10 | namespace VRCX 11 | { 12 | public class NoopDragHandler : IDragHandler 13 | { 14 | bool IDragHandler.OnDragEnter(IWebBrowser chromiumWebBrowser, IBrowser browser, IDragData dragData, DragOperationsMask mask) 15 | { 16 | return true; 17 | } 18 | 19 | void IDragHandler.OnDraggableRegionsChanged(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IList regions) 20 | { 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /html/src/service/sqlite.js: -------------------------------------------------------------------------------- 1 | // requires binding of SQLite 2 | 3 | class SQLiteService { 4 | execute(callback, sql, args = null) { 5 | return new Promise((resolve, reject) => { 6 | SQLite.Execute((err, data) => { 7 | if (err !== null) { 8 | reject(err); 9 | } else if (data === null) { 10 | resolve(); 11 | } else { 12 | callback(data); 13 | } 14 | }, sql, args); 15 | }); 16 | } 17 | 18 | executeNonQuery(sql, args = null) { 19 | return SQLite.ExecuteNonQuery(sql, args); 20 | } 21 | } 22 | 23 | var self = new SQLiteService(); 24 | window.sqliteService = self; 25 | 26 | export { 27 | self as default, 28 | SQLiteService 29 | }; 30 | -------------------------------------------------------------------------------- /WinApi.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2019 pypy. All rights reserved. 2 | // 3 | // This work is licensed under the terms of the MIT license. 4 | // For a copy, see . 5 | 6 | using System; 7 | using System.Runtime.InteropServices; 8 | 9 | namespace VRCX 10 | { 11 | public static class WinApi 12 | { 13 | [DllImport("kernel32.dll", SetLastError = false)] 14 | public static extern void CopyMemory(IntPtr destination, IntPtr source, uint length); 15 | 16 | [DllImport("user32.dll", SetLastError = true)] 17 | public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); 18 | 19 | [DllImport("user32.dll", SetLastError = true)] 20 | public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Util.cs: -------------------------------------------------------------------------------- 1 | using CefSharp; 2 | 3 | namespace VRCX 4 | { 5 | public static class Util 6 | { 7 | public static void ApplyJavascriptBindings(IJavascriptObjectRepository repository) 8 | { 9 | repository.NameConverter = null; 10 | repository.Register("AppApi", AppApi.Instance, true); 11 | repository.Register("SharedVariable", SharedVariable.Instance, false); 12 | repository.Register("WebApi", WebApi.Instance, true); 13 | repository.Register("VRCXStorage", VRCXStorage.Instance, false); 14 | repository.Register("SQLite", SQLite.Instance, true); 15 | repository.Register("LogWatcher", LogWatcher.Instance, true); 16 | repository.Register("Discord", Discord.Instance, true); 17 | repository.Register("AssetBundleCacher", AssetBundleCacher.Instance, true); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/github_actions.yml: -------------------------------------------------------------------------------- 1 | name: VRCX 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build_dotnet: 13 | runs-on: windows-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Setup Nuget.exe 18 | uses: nuget/setup-nuget@v1 19 | - name: Restore packages 20 | run: nuget restore VRCX.sln 21 | - name: Setup MSBuild.exe 22 | uses: microsoft/setup-msbuild@v1.0.2 23 | - name: Build with MSBuild 24 | run: msbuild VRCX.sln -p:Configuration=Release -p:Platform=x64 25 | 26 | build_node: 27 | runs-on: ubuntu-latest 28 | defaults: 29 | run: 30 | working-directory: html 31 | 32 | steps: 33 | - uses: actions/checkout@v2 34 | - name: Use Node.js ${{ matrix.node-version }} 35 | uses: actions/setup-node@v1 36 | with: 37 | node-version: ${{ matrix.node-version }} 38 | - name: Restore dependencies 39 | run: npm ci 40 | - name: Build 41 | run: npm run production 42 | -------------------------------------------------------------------------------- /VRCX.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.168 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRCX", "VRCX.csproj", "{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Release|x64 = Release|x64 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Debug|x64.ActiveCfg = Debug|x64 15 | {D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Debug|x64.Build.0 = Debug|x64 16 | {D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Release|x64.ActiveCfg = Release|x64 17 | {D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Release|x64.Build.0 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {797D384F-D118-4CBB-9450-17949F9EFCA4} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 pypy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace VRCX.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해 6 | // 제어됩니다. 어셈블리와 관련된 정보를 수정하려면 7 | // 이러한 특성 값을 변경하세요. 8 | [assembly: AssemblyTitle("VRCX")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("VRCX")] 13 | [assembly: AssemblyCopyright("pypy")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에 18 | // 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면 19 | // 해당 형식에 대해 ComVisible 특성을 true로 설정하세요. 20 | [assembly: ComVisible(false)] 21 | 22 | // 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다. 23 | [assembly: Guid("d9f66f2e-3ed9-4d53-a6ac-adcc1513562a")] 24 | 25 | // 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다. 26 | // 27 | // 주 버전 28 | // 부 버전 29 | // 빌드 번호 30 | // 수정 버전 31 | // 32 | // 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호가 자동으로 33 | // 지정되도록 할 수 있습니다. 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "webpack --config webpack.config.js --mode development", 5 | "watch": "webpack --config webpack.config.js --mode development --watch", 6 | "prod": "webpack --config webpack.config.js --mode production", 7 | "development": "npm run dev", 8 | "production": "npm run prod" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/pypy-vrc/VRCX.git" 13 | }, 14 | "keywords": [ 15 | "vrchat" 16 | ], 17 | "author": "pypy ", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/pypy-vrc/VRCX/issues" 21 | }, 22 | "homepage": "https://github.com/pypy-vrc/VRCX#readme", 23 | "devDependencies": { 24 | "animate.css": "^4.1.1", 25 | "copy-webpack-plugin": "^9.0.0", 26 | "css-loader": "^5.2.6", 27 | "element-ui": "^2.15.2", 28 | "famfamfam-flags": "^1.0.0", 29 | "file-loader": "^6.2.0", 30 | "html-webpack-plugin": "^5.3.1", 31 | "mini-css-extract-plugin": "^1.6.0", 32 | "normalize.css": "^8.0.1", 33 | "noty": "^3.2.0-beta-deprecated", 34 | "pug": "^3.0.2", 35 | "pug-plain-loader": "^1.1.0", 36 | "raw-loader": "^4.0.2", 37 | "sass": "^1.35.1", 38 | "sass-loader": "^12.1.0", 39 | "terser-webpack-plugin": "^5.1.3", 40 | "url-loader": "^4.1.1", 41 | "vue": "^2.6.14", 42 | "vue-data-tables": "^3.4.5", 43 | "vue-lazyload": "^1.3.3", 44 | "vue-swatches": "^2.1.1", 45 | "vuejs-toggle-switch": "^1.3.3", 46 | "webpack": "^5.39.0", 47 | "webpack-cli": "^4.7.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/il2cpp_data/etc/mono/2.0/Browsers/Compat.browser: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/il2cpp_data/etc/mono/4.0/Browsers/Compat.browser: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/il2cpp_data/etc/mono/4.5/Browsers/Compat.browser: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2019 pypy. All rights reserved. 2 | // 3 | // This work is licensed under the terms of the MIT license. 4 | // For a copy, see . 5 | 6 | using System; 7 | using System.Windows.Forms; 8 | 9 | namespace VRCX 10 | { 11 | public class Program 12 | { 13 | public static string BaseDirectory { get; private set; } 14 | 15 | static Program() 16 | { 17 | BaseDirectory = AppDomain.CurrentDomain.BaseDirectory; 18 | } 19 | 20 | [STAThread] 21 | private static void Main() 22 | { 23 | try 24 | { 25 | Run(); 26 | } 27 | catch (Exception e) 28 | { 29 | MessageBox.Show(e.ToString(), "PLEASE REPORT TO PYPY", MessageBoxButtons.OK, MessageBoxIcon.Error); 30 | Environment.Exit(0); 31 | } 32 | } 33 | 34 | private static void Run() 35 | { 36 | Application.EnableVisualStyles(); 37 | Application.SetCompatibleTextRenderingDefault(false); 38 | 39 | SQLite.Instance.Init(); 40 | VRCXStorage.Load(); 41 | CpuMonitor.Instance.Init(); 42 | Discord.Instance.Init(); 43 | WebApi.Instance.Init(); 44 | LogWatcher.Instance.Init(); 45 | 46 | CefService.Instance.Init(); 47 | VRCXVR.Instance.Init(); 48 | Application.Run(new MainForm()); 49 | WebApi.Instance.SaveCookies(); 50 | VRCXVR.Instance.Exit(); 51 | CefService.Instance.Exit(); 52 | 53 | LogWatcher.Instance.Exit(); 54 | WebApi.Instance.Exit(); 55 | 56 | Discord.Instance.Exit(); 57 | CpuMonitor.Instance.Exit(); 58 | VRCXStorage.Save(); 59 | SQLite.Instance.Exit(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /JsonSerializer.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2019 pypy. All rights reserved. 2 | // 3 | // This work is licensed under the terms of the MIT license. 4 | // For a copy, see . 5 | 6 | using Newtonsoft.Json; 7 | using System.IO; 8 | using System.Text; 9 | 10 | namespace VRCX 11 | { 12 | public static class JsonSerializer 13 | { 14 | public static void Serialize(string path, T obj) 15 | { 16 | try 17 | { 18 | using (var file = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) 19 | using (var stream = new StreamWriter(file, Encoding.UTF8)) 20 | using (var writer = new JsonTextWriter(stream)) 21 | { 22 | var serializer = Newtonsoft.Json.JsonSerializer.CreateDefault(); 23 | serializer.Formatting = Formatting.Indented; 24 | serializer.Serialize(writer, obj, typeof(T)); 25 | } 26 | } 27 | catch 28 | { 29 | } 30 | } 31 | 32 | public static bool Deserialize(string path, ref T obj) where T : new() 33 | { 34 | try 35 | { 36 | using (var file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 37 | using (var stream = new StreamReader(file, Encoding.UTF8)) 38 | using (var reader = new JsonTextReader(stream)) 39 | { 40 | var o = Newtonsoft.Json.JsonSerializer.CreateDefault().Deserialize(reader); 41 | if (o == null) 42 | { 43 | o = new T(); 44 | } 45 | obj = o; 46 | return true; 47 | } 48 | } 49 | catch 50 | { 51 | } 52 | return false; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /VRChatRPC.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2019 pypy. All rights reserved. 2 | // 3 | // This work is licensed under the terms of the MIT license. 4 | // For a copy, see . 5 | 6 | using System; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | 10 | namespace VRCX 11 | { 12 | public class VRChatRPC 13 | { 14 | public static readonly VRChatRPC Instance; 15 | 16 | [DllImport("VRChatRPC", CallingConvention = CallingConvention.Cdecl)] 17 | private static extern bool VRChatRPC_000(); 18 | 19 | [DllImport("VRChatRPC", CallingConvention = CallingConvention.Cdecl)] 20 | private static extern int VRChatRPC_001([Out] byte[] data, int size); 21 | 22 | [DllImport("VRChatRPC", CallingConvention = CallingConvention.Cdecl)] 23 | private static extern IntPtr VRChatRPC_002(); 24 | 25 | static VRChatRPC() 26 | { 27 | Instance = new VRChatRPC(); 28 | } 29 | 30 | public bool Update() 31 | { 32 | return VRChatRPC_000(); 33 | } 34 | 35 | public string GetAuthSessionTicket() 36 | { 37 | var a = new byte[1024]; 38 | var n = VRChatRPC_001(a, 1024); 39 | return BitConverter.ToString(a, 0, n).Replace("-", string.Empty); 40 | } 41 | 42 | public string GetPersonaName() 43 | { 44 | var ptr = VRChatRPC_002(); 45 | if (ptr != IntPtr.Zero) 46 | { 47 | int n = 0; 48 | while (Marshal.ReadByte(ptr, n) != 0) 49 | { 50 | ++n; 51 | } 52 | if (n > 0) 53 | { 54 | var a = new byte[n]; 55 | Marshal.Copy(ptr, a, 0, a.Length); 56 | return Encoding.UTF8.GetString(a); 57 | } 58 | } 59 | return string.Empty; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /html/src/security.js: -------------------------------------------------------------------------------- 1 | const defaultAESKey = new TextEncoder().encode( 2 | 'https://github.com/pypy-vrc/VRCX' 3 | ) 4 | 5 | const hexToUint8Array = (hexStr) => { 6 | const r = hexStr.match(/.{1,2}/g) 7 | if (!r) return null 8 | return new Uint8Array(r.map((b) => parseInt(b, 16))) 9 | } 10 | 11 | const uint8ArrayToHex = (arr) => 12 | arr.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '') 13 | 14 | function stdAESKey(key) { 15 | const tKey = new TextEncoder().encode(key) 16 | let sk = tKey 17 | if (key.length < 32) { 18 | sk = new Uint8Array(32) 19 | sk.set(tKey) 20 | sk.set(defaultAESKey.slice(key.length, 32), key.length) 21 | } 22 | return sk.slice(0, 32) 23 | } 24 | 25 | async function encrypt(plaintext, key) { 26 | let iv = window.crypto.getRandomValues(new Uint8Array(12)) 27 | let sharedKey = await window.crypto.subtle.importKey( 28 | 'raw', 29 | stdAESKey(key), 30 | { name: 'AES-GCM', length: 256 }, 31 | true, 32 | ['encrypt'] 33 | ) 34 | let cipher = await window.crypto.subtle.encrypt( 35 | { name: 'AES-GCM', iv }, 36 | sharedKey, 37 | new TextEncoder().encode(plaintext) 38 | ) 39 | let ciphertext = new Uint8Array(cipher) 40 | let encrypted = new Uint8Array(iv.length + ciphertext.byteLength) 41 | encrypted.set(iv, 0) 42 | encrypted.set(ciphertext, iv.length) 43 | return uint8ArrayToHex(encrypted) 44 | } 45 | 46 | async function decrypt(ciphertext, key) { 47 | let text = hexToUint8Array(ciphertext) 48 | if (!text) return '' 49 | let sharedKey = await window.crypto.subtle.importKey( 50 | 'raw', 51 | stdAESKey(key), 52 | { name: 'AES-GCM', length: 256 }, 53 | true, 54 | ['decrypt'] 55 | ) 56 | let plaintext = await window.crypto.subtle.decrypt( 57 | { name: 'AES-GCM', iv: text.slice(0, 12) }, 58 | sharedKey, 59 | text.slice(12) 60 | ) 61 | return new TextDecoder().decode(new Uint8Array(plaintext)) 62 | } 63 | 64 | export default { 65 | decrypt, 66 | encrypt, 67 | } 68 | -------------------------------------------------------------------------------- /VRForm.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2019 pypy. All rights reserved. 2 | // 3 | // This work is licensed under the terms of the MIT license. 4 | // For a copy, see . 5 | 6 | using System.IO; 7 | using System.Windows.Forms; 8 | using CefSharp; 9 | using CefSharp.WinForms; 10 | 11 | namespace VRCX 12 | { 13 | public partial class VRForm : Form 14 | { 15 | public static VRForm Instance; 16 | private ChromiumWebBrowser _browser1; 17 | private ChromiumWebBrowser _browser2; 18 | 19 | public VRForm() 20 | { 21 | Instance = this; 22 | InitializeComponent(); 23 | 24 | _browser1 = new ChromiumWebBrowser( 25 | Path.Combine(Program.BaseDirectory, "html/vr.html?1") 26 | ) 27 | { 28 | DragHandler = new NoopDragHandler(), 29 | BrowserSettings = 30 | { 31 | DefaultEncoding = "UTF-8", 32 | }, 33 | Dock = DockStyle.Fill 34 | }; 35 | 36 | _browser2 = new ChromiumWebBrowser( 37 | Path.Combine(Program.BaseDirectory, "html/vr.html?2") 38 | ) 39 | { 40 | DragHandler = new NoopDragHandler(), 41 | BrowserSettings = 42 | { 43 | DefaultEncoding = "UTF-8", 44 | }, 45 | Dock = DockStyle.Fill 46 | }; 47 | 48 | Util.ApplyJavascriptBindings(_browser1.JavascriptObjectRepository); 49 | Util.ApplyJavascriptBindings(_browser2.JavascriptObjectRepository); 50 | 51 | panel1.Controls.Add(_browser1); 52 | panel2.Controls.Add(_browser2); 53 | } 54 | 55 | private void button_refresh_Click(object sender, System.EventArgs e) 56 | { 57 | _browser1.ExecuteScriptAsync("location.reload()"); 58 | _browser2.ExecuteScriptAsync("location.reload()"); 59 | VRCXVR.Instance.Refresh(); 60 | } 61 | 62 | private void button_devtools_Click(object sender, System.EventArgs e) 63 | { 64 | _browser1.ShowDevTools(); 65 | _browser2.ShowDevTools(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /SharedVariable.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2020 pypy. All rights reserved. 2 | // 3 | // This work is licensed under the terms of the MIT license. 4 | // For a copy, see . 5 | 6 | using System.Collections.Generic; 7 | using System.Threading; 8 | 9 | namespace VRCX 10 | { 11 | public class SharedVariable 12 | { 13 | public static readonly SharedVariable Instance; 14 | private readonly ReaderWriterLockSlim m_MapLock; 15 | private readonly Dictionary m_Map; 16 | 17 | static SharedVariable() 18 | { 19 | Instance = new SharedVariable(); 20 | } 21 | 22 | public SharedVariable() 23 | { 24 | m_MapLock = new ReaderWriterLockSlim(); 25 | m_Map = new Dictionary(); 26 | } 27 | 28 | public void Clear() 29 | { 30 | m_MapLock.EnterWriteLock(); 31 | try 32 | { 33 | m_Map.Clear(); 34 | } 35 | finally 36 | { 37 | m_MapLock.ExitWriteLock(); 38 | } 39 | } 40 | 41 | public string Get(string key) 42 | { 43 | m_MapLock.EnterReadLock(); 44 | try 45 | { 46 | if (m_Map.TryGetValue(key, out string value) == true) 47 | { 48 | return value; 49 | } 50 | } 51 | finally 52 | { 53 | m_MapLock.ExitReadLock(); 54 | } 55 | 56 | return null; 57 | } 58 | 59 | public void Set(string key, string value) 60 | { 61 | m_MapLock.EnterWriteLock(); 62 | try 63 | { 64 | m_Map[key] = value; 65 | } 66 | finally 67 | { 68 | m_MapLock.ExitWriteLock(); 69 | } 70 | } 71 | 72 | public bool Remove(string key) 73 | { 74 | m_MapLock.EnterWriteLock(); 75 | try 76 | { 77 | return m_Map.Remove(key); 78 | } 79 | finally 80 | { 81 | m_MapLock.ExitWriteLock(); 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /CefService.cs: -------------------------------------------------------------------------------- 1 | using CefSharp; 2 | using CefSharp.WinForms; 3 | using System; 4 | using System.IO; 5 | 6 | namespace VRCX 7 | { 8 | public class CefService 9 | { 10 | public static readonly CefService Instance; 11 | 12 | static CefService() 13 | { 14 | Instance = new CefService(); 15 | } 16 | 17 | internal void Init() 18 | { 19 | var cefSettings = new CefSettings 20 | { 21 | CachePath = Path.Combine(Program.BaseDirectory, "cache"), 22 | UserDataPath = Path.Combine(Program.BaseDirectory, "userdata"), 23 | IgnoreCertificateErrors = true, 24 | LogSeverity = LogSeverity.Disable, 25 | WindowlessRenderingEnabled = true, 26 | PersistSessionCookies = true, 27 | PersistUserPreferences = true 28 | }; 29 | 30 | /*cefSettings.RegisterScheme(new CefCustomScheme 31 | { 32 | SchemeName = "vrcx", 33 | DomainName = "app", 34 | SchemeHandlerFactory = new FolderSchemeHandlerFactory(Application.StartupPath + "/../../../html") 35 | });*/ 36 | 37 | cefSettings.CefCommandLineArgs.Add("ignore-certificate-errors"); 38 | cefSettings.CefCommandLineArgs.Add("disable-plugins"); 39 | cefSettings.CefCommandLineArgs.Add("disable-spell-checking"); 40 | cefSettings.CefCommandLineArgs.Add("disable-pdf-extension"); 41 | cefSettings.CefCommandLineArgs.Add("disable-extensions"); 42 | cefSettings.CefCommandLineArgs["autoplay-policy"] = "no-user-gesture-required"; 43 | // cefSettings.CefCommandLineArgs.Add("allow-universal-access-from-files"); 44 | cefSettings.CefCommandLineArgs.Add("disable-web-security"); 45 | cefSettings.SetOffScreenRenderingBestPerformanceArgs(); 46 | 47 | CefSharpSettings.WcfEnabled = true; // TOOD: REMOVE THIS LINE YO 48 | CefSharpSettings.ShutdownOnExit = false; 49 | 50 | // Enable High-DPI support on Windows 7 or newer 51 | Cef.EnableHighDPISupport(); 52 | 53 | if (Cef.Initialize(cefSettings) == false) 54 | { 55 | throw new Exception("Cef.Initialize()"); 56 | } 57 | } 58 | 59 | internal void Exit() 60 | { 61 | Cef.Shutdown(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /CpuMonitor.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2019 pypy. All rights reserved. 2 | // 3 | // This work is licensed under the terms of the MIT license. 4 | // For a copy, see . 5 | 6 | using System.Diagnostics; 7 | using System.Threading; 8 | 9 | namespace VRCX 10 | { 11 | public class CpuMonitor 12 | { 13 | public static readonly CpuMonitor Instance; 14 | public float CpuUsage; 15 | private PerformanceCounter _performanceCounter; 16 | private Timer _timer; 17 | 18 | static CpuMonitor() 19 | { 20 | Instance = new CpuMonitor(); 21 | } 22 | 23 | public CpuMonitor() 24 | { 25 | try 26 | { 27 | _performanceCounter = new PerformanceCounter( 28 | "Processor Information", 29 | "% Processor Utility", 30 | "_Total", 31 | true 32 | ); 33 | } 34 | catch 35 | { 36 | } 37 | 38 | // fallback 39 | if (_performanceCounter == null) 40 | { 41 | try 42 | { 43 | _performanceCounter = new PerformanceCounter( 44 | "Processor", 45 | "% Processor Time", 46 | "_Total", 47 | true 48 | ); 49 | } 50 | catch 51 | { 52 | } 53 | } 54 | 55 | _timer = new Timer(TimerCallback, null, -1, -1); 56 | } 57 | 58 | internal void Init() 59 | { 60 | _timer.Change(1000, 1000); 61 | } 62 | 63 | internal void Exit() 64 | { 65 | lock (this) 66 | { 67 | _timer.Change(-1, -1); 68 | _performanceCounter?.Dispose(); 69 | } 70 | } 71 | 72 | private void TimerCallback(object state) 73 | { 74 | lock (this) 75 | { 76 | try 77 | { 78 | if (_performanceCounter != null) 79 | { 80 | CpuUsage = _performanceCounter.NextValue(); 81 | } 82 | } 83 | catch 84 | { 85 | } 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /html/src/service/gamelog.js: -------------------------------------------------------------------------------- 1 | // requires binding of LogWatcher 2 | 3 | // 4 | var contextMap = new Map(); 5 | 6 | function parseRawGameLog(dt, type, args) { 7 | var gameLog = { 8 | dt, 9 | type 10 | }; 11 | 12 | switch (type) { 13 | case 'location': 14 | gameLog.location = args[0]; 15 | gameLog.worldName = args[1]; 16 | break; 17 | 18 | case 'player-joined': 19 | gameLog.userDisplayName = args[0]; 20 | gameLog.userType = args[1]; 21 | break; 22 | 23 | case 'player-left': 24 | gameLog.userDisplayName = args[0]; 25 | break; 26 | 27 | case 'notification': 28 | gameLog.json = args[0]; 29 | break; 30 | 31 | case 'portal-spawn': 32 | gameLog.userDisplayName = args[0]; 33 | break; 34 | 35 | case 'event': 36 | gameLog.event = args[0]; 37 | break; 38 | 39 | case 'video-play': 40 | gameLog.videoURL = args[0]; 41 | gameLog.displayName = args[1]; 42 | break; 43 | 44 | default: 45 | break; 46 | } 47 | 48 | return gameLog; 49 | } 50 | 51 | class GameLogService { 52 | async poll() { 53 | var rawGameLogs = await LogWatcher.Get(); 54 | var gameLogs = []; 55 | var now = Date.now(); 56 | 57 | for (var [fileName, dt, type, ...args] of rawGameLogs) { 58 | var context = contextMap.get(fileName); 59 | if (typeof context === 'undefined') { 60 | context = { 61 | updatedAt: null, 62 | 63 | // location 64 | location: null 65 | }; 66 | contextMap.set(fileName, context); 67 | } 68 | 69 | var gameLog = parseRawGameLog(dt, type, args); 70 | 71 | switch (gameLog.type) { 72 | case 'location': 73 | context.location = gameLog.location; 74 | break; 75 | 76 | default: 77 | break; 78 | } 79 | 80 | context.updatedAt = now; 81 | 82 | gameLogs.push(gameLog); 83 | } 84 | 85 | return gameLogs; 86 | } 87 | 88 | async reset() { 89 | await LogWatcher.Reset(); 90 | contextMap.clear(); 91 | } 92 | } 93 | 94 | var self = new GameLogService(); 95 | window.gameLogService = self; 96 | 97 | export { 98 | self as default, 99 | GameLogService as LogWatcherService 100 | }; 101 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /html/src/repository/config.js: -------------------------------------------------------------------------------- 1 | import sqliteService from '../service/sqlite.js'; 2 | import sharedRepository, { SharedRepository } from './shared.js'; 3 | 4 | var dirtyKeySet = new Set(); 5 | 6 | function transformKey(key) { 7 | return `config:${String(key).toLowerCase()}`; 8 | } 9 | 10 | async function syncLoop() { 11 | if (dirtyKeySet.size > 0) { 12 | try { 13 | await sqliteService.executeNonQuery('BEGIN'); 14 | try { 15 | for (var key of dirtyKeySet) { 16 | var value = sharedRepository.getString(key); 17 | if (value === null) { 18 | await sqliteService.executeNonQuery( 19 | 'DELETE FROM configs WHERE `key` = @key', 20 | { 21 | '@key': key 22 | } 23 | ); 24 | } else { 25 | await sqliteService.executeNonQuery( 26 | 'INSERT OR REPLACE INTO configs (`key`, `value`) VALUES (@key, @value)', 27 | { 28 | '@key': key, 29 | '@value': value 30 | } 31 | ); 32 | } 33 | } 34 | dirtyKeySet.clear(); 35 | } finally { 36 | await sqliteService.executeNonQuery('COMMIT'); 37 | } 38 | } catch (err) { 39 | console.error(err); 40 | } 41 | } 42 | setTimeout(syncLoop, 100); 43 | } 44 | 45 | class ConfigRepository extends SharedRepository { 46 | async init() { 47 | await sqliteService.executeNonQuery( 48 | 'CREATE TABLE IF NOT EXISTS configs (`key` TEXT PRIMARY KEY, `value` TEXT)' 49 | ); 50 | await sqliteService.execute( 51 | ([key, value]) => sharedRepository.setString(key, value), 52 | 'SELECT `key`, `value` FROM configs' 53 | ); 54 | syncLoop(); 55 | } 56 | 57 | remove(key) { 58 | key = transformKey(key); 59 | sharedRepository.remove(key); 60 | dirtyKeySet.add(key); 61 | } 62 | 63 | getString(key, defaultValue = null) { 64 | key = transformKey(key); 65 | return sharedRepository.getString(key, defaultValue); 66 | } 67 | 68 | setString(key, value) { 69 | key = transformKey(key); 70 | value = String(value); 71 | sharedRepository.setString(key, value); 72 | dirtyKeySet.add(key); 73 | } 74 | } 75 | 76 | var self = new ConfigRepository(); 77 | window.configRepository = self; 78 | 79 | export { 80 | self as default, 81 | ConfigRepository 82 | }; 83 | -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/il2cpp_data/etc/mono/2.0/settings.map: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | 11 | 20 | 23 | 24 | 25 | 26 | 29 | 30 | 33 | 34 | 43 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/il2cpp_data/etc/mono/4.0/settings.map: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | 11 | 20 | 23 | 24 | 25 | 26 | 29 | 30 | 33 | 34 | 43 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/il2cpp_data/etc/mono/4.5/settings.map: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | 11 | 20 | 23 | 24 | 25 | 26 | 29 | 30 | 33 | 34 | 43 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace VRCX.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("VRCX.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /html/src/repository/shared.js: -------------------------------------------------------------------------------- 1 | // requires binding of SharedVariable 2 | 3 | function transformKey(key) { 4 | return String(key).toLowerCase(); 5 | } 6 | 7 | class SharedRepository { 8 | remove(key) { 9 | key = transformKey(key); 10 | return SharedVariable.Remove(key); 11 | } 12 | 13 | getString(key, defaultValue = null) { 14 | key = transformKey(key); 15 | var value = SharedVariable.Get(key); 16 | if (value === null) { 17 | return defaultValue; 18 | } 19 | return value; 20 | } 21 | 22 | setString(key, value) { 23 | key = transformKey(key); 24 | value = String(value); 25 | SharedVariable.Set(key, value); 26 | } 27 | 28 | getBool(key, defaultValue = null) { 29 | var value = this.getString(key, null); 30 | if (value === null) { 31 | return defaultValue; 32 | } 33 | return value === 'true'; 34 | } 35 | 36 | setBool(key, value) { 37 | this.setString(key, value ? 'true' : 'false'); 38 | } 39 | 40 | getInt(key, defaultValue = null) { 41 | var value = this.getString(key, null); 42 | if (value === null) { 43 | return defaultValue; 44 | } 45 | value = parseInt(value, 10); 46 | if (isNaN(value) === true) { 47 | return defaultValue; 48 | } 49 | return value; 50 | } 51 | 52 | setInt(key, value) { 53 | this.setString(key, value); 54 | } 55 | 56 | getFloat(key, defaultValue = null) { 57 | var value = this.getString(key, null); 58 | if (value === null) { 59 | return defaultValue; 60 | } 61 | value = parseFloat(value); 62 | if (isNaN(value) === true) { 63 | return defaultValue; 64 | } 65 | return value; 66 | } 67 | 68 | setFloat(key, value) { 69 | this.setString(key, value); 70 | } 71 | 72 | getObject(key, defaultValue = null) { 73 | var value = this.getString(key, null); 74 | if (value === null) { 75 | return defaultValue; 76 | } 77 | try { 78 | value = JSON.parse(value); 79 | } catch (err) { 80 | } 81 | if (value !== Object(value)) { 82 | return defaultValue; 83 | } 84 | return value; 85 | } 86 | 87 | setObject(key, value) { 88 | this.setString(key, JSON.stringify(value)); 89 | } 90 | 91 | getArray(key, defaultValue = null) { 92 | var value = this.getObject(key, null); 93 | if (Array.isArray(value) === false) { 94 | return defaultValue; 95 | } 96 | return value; 97 | } 98 | 99 | setArray(key, value) { 100 | this.setObject(key, value); 101 | } 102 | } 103 | 104 | var self = new SharedRepository(); 105 | window.sharedRepository = self; 106 | 107 | export { 108 | self as default, 109 | SharedRepository 110 | }; 111 | -------------------------------------------------------------------------------- /html/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | commonjs: true, 6 | es2020: true 7 | }, 8 | parserOptions: { 9 | sourceType: 'module', 10 | impliedStrict: true 11 | }, 12 | globals: { 13 | CefSharp: 'readonly', 14 | VRCX: 'readonly', 15 | VRCXStorage: 'readonly', 16 | SQLite: 'readonly', 17 | LogWatcher: 'readonly', 18 | Discord: 'readonly', 19 | AppApi: 'readonly', 20 | SharedVariable: 'readonly', 21 | WebApi: 'readonly' 22 | }, 23 | extends: 'eslint:all', 24 | rules: { 25 | 'array-bracket-newline': 0, 26 | 'array-element-newline': 0, 27 | 'block-scoped-var': 0, 28 | camelcase: 0, 29 | 'capitalized-comments': 0, 30 | 'class-methods-use-this': 0, 31 | complexity: 0, 32 | 'func-names': 0, 33 | 'func-style': 0, 34 | 'function-call-argument-newline': 0, 35 | 'guard-for-in': 0, 36 | 'id-length': 0, 37 | indent: 0, 38 | 'linebreak-style': 0, 39 | 'lines-around-comment': 0, 40 | 'max-depth': 0, 41 | 'max-len': 0, 42 | 'max-lines': 0, 43 | 'max-lines-per-function': 0, 44 | 'max-statements': 0, 45 | 'multiline-comment-style': 0, 46 | 'multiline-ternary': 0, 47 | 'newline-per-chained-call': 0, 48 | 'new-cap': 0, 49 | 'no-await-in-loop': 0, 50 | 'no-bitwise': 0, 51 | 'no-console': 0, 52 | 'no-continue': 0, 53 | 'no-control-regex': 0, 54 | 'no-empty': [ 55 | 'error', 56 | { 57 | allowEmptyCatch: true 58 | } 59 | ], 60 | 'no-extra-parens': 0, 61 | 'no-invalid-this': 0, 62 | 'no-magic-numbers': 0, 63 | 'no-mixed-operators': 0, 64 | 'no-negated-condition': 0, 65 | 'no-param-reassign': 0, 66 | 'no-plusplus': 0, 67 | 'no-redeclare': 0, 68 | 'no-ternary': 0, 69 | 'no-underscore-dangle': 0, 70 | 'no-var': 0, 71 | 'no-warning-comments': 0, 72 | 'object-curly-spacing': [ 73 | 'error', 74 | 'always' 75 | ], 76 | 'object-property-newline': 0, 77 | 'one-var': 0, 78 | 'padded-blocks': 0, 79 | 'prefer-arrow-callback': 0, 80 | 'prefer-destructuring': 0, 81 | 'prefer-named-capture-group': 0, 82 | quotes: [ 83 | 'error', 84 | 'single', 85 | { 86 | avoidEscape: true 87 | } 88 | ], 89 | 'quote-props': 0, 90 | 'require-unicode-regexp': 0, 91 | 'sort-imports': 0, 92 | 'sort-keys': 0, 93 | 'sort-vars': 0, 94 | 'space-before-function-paren': [ 95 | 'error', 96 | { 97 | named: 'never' 98 | } 99 | ], 100 | strict: 0, 101 | 'vars-on-top': 0, 102 | 'wrap-regex': 0 103 | } 104 | }; 105 | -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/il2cpp_data/etc/mono/config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /VRCXStorage.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2019 pypy. All rights reserved. 2 | // 3 | // This work is licensed under the terms of the MIT license. 4 | // For a copy, see . 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Threading; 10 | 11 | namespace VRCX 12 | { 13 | public class VRCXStorage 14 | { 15 | public static readonly VRCXStorage Instance; 16 | private static readonly ReaderWriterLockSlim m_Lock = new ReaderWriterLockSlim(); 17 | private static Dictionary m_Storage = new Dictionary(); 18 | private static string m_JsonPath = Path.Combine(Program.BaseDirectory, "VRCX.json"); 19 | private static bool m_Dirty; 20 | 21 | static VRCXStorage() 22 | { 23 | Instance = new VRCXStorage(); 24 | } 25 | 26 | public static void Load() 27 | { 28 | m_Lock.EnterWriteLock(); 29 | try 30 | { 31 | JsonSerializer.Deserialize(m_JsonPath, ref m_Storage); 32 | m_Dirty = false; 33 | } 34 | finally 35 | { 36 | m_Lock.ExitWriteLock(); 37 | } 38 | } 39 | 40 | public static void Save() 41 | { 42 | m_Lock.EnterReadLock(); 43 | try 44 | { 45 | if (m_Dirty) 46 | { 47 | JsonSerializer.Serialize(m_JsonPath, m_Storage); 48 | m_Dirty = false; 49 | } 50 | } 51 | finally 52 | { 53 | m_Lock.ExitReadLock(); 54 | } 55 | } 56 | 57 | public void Flush() 58 | { 59 | Save(); 60 | } 61 | 62 | public void Clear() 63 | { 64 | m_Lock.EnterWriteLock(); 65 | try 66 | { 67 | if (m_Storage.Count > 0) 68 | { 69 | m_Storage.Clear(); 70 | m_Dirty = true; 71 | } 72 | } 73 | finally 74 | { 75 | m_Lock.ExitWriteLock(); 76 | } 77 | } 78 | 79 | public bool Remove(string key) 80 | { 81 | m_Lock.EnterWriteLock(); 82 | try 83 | { 84 | var result = m_Storage.Remove(key); 85 | if (result) 86 | { 87 | m_Dirty = true; 88 | } 89 | return result; 90 | } 91 | finally 92 | { 93 | m_Lock.ExitWriteLock(); 94 | } 95 | } 96 | 97 | public string Get(string key) 98 | { 99 | m_Lock.EnterReadLock(); 100 | try 101 | { 102 | return m_Storage.TryGetValue(key, out string value) 103 | ? value 104 | : string.Empty; 105 | } 106 | finally 107 | { 108 | m_Lock.ExitReadLock(); 109 | } 110 | } 111 | 112 | public void Set(string key, string value) 113 | { 114 | m_Lock.EnterWriteLock(); 115 | try 116 | { 117 | m_Storage[key] = value; 118 | m_Dirty = true; 119 | } 120 | finally 121 | { 122 | m_Lock.ExitWriteLock(); 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /html/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CopyPlugin = require('copy-webpack-plugin'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 5 | const TerserPlugin = require('terser-webpack-plugin'); 6 | 7 | module.exports = { 8 | entry: { 9 | app: [ 10 | './src/app.js', 11 | './src/app.scss' 12 | ], 13 | 'app.dark': './src/app.dark.scss', 14 | vr: [ 15 | './src/vr.js', 16 | './src/vr.scss' 17 | ] 18 | }, 19 | output: { 20 | filename: '[name].js', 21 | library: { 22 | type: 'window' 23 | } 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.pug$/, 29 | oneOf: [ 30 | { 31 | resourceQuery: /^\?vue/, 32 | use: 'pug-plain-loader' 33 | }, 34 | { 35 | use: ['raw-loader', 'pug-plain-loader'] 36 | } 37 | ] 38 | }, 39 | { 40 | test: /\.s?css$/, 41 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'] 42 | }, 43 | { 44 | test: /\.(eot|png|svg|ttf|woff)/, 45 | use: { 46 | loader: 'url-loader', 47 | options: { 48 | limit: false, 49 | name: 'assets/[name].[ext]' 50 | } 51 | } 52 | } 53 | ] 54 | }, 55 | resolve: { 56 | extensions: ['.css', '.js', '.scss'], 57 | alias: { 58 | vue: path.join( 59 | __dirname, 60 | './node_modules/vue/dist/vue.common.prod.js' 61 | ) 62 | } 63 | }, 64 | performance: { 65 | hints: false 66 | }, 67 | devtool: 'inline-source-map', 68 | target: ['web', 'es2020'], 69 | stats: { 70 | preset: 'errors-only', 71 | builtAt: true, 72 | timings: true 73 | }, 74 | plugins: [ 75 | new MiniCssExtractPlugin({ 76 | filename: '[name].css' 77 | }), 78 | new HtmlWebpackPlugin({ 79 | filename: 'index.html', 80 | template: './src/index.pug', 81 | inject: false, 82 | minify: false 83 | }), 84 | new HtmlWebpackPlugin({ 85 | filename: 'vr.html', 86 | template: './src/vr.pug', 87 | inject: false, 88 | minify: false 89 | }), 90 | new CopyPlugin({ 91 | patterns: [ 92 | // assets 93 | { 94 | from: './images/', 95 | to: './images/' 96 | }, 97 | // // vscode-codicons 98 | // { 99 | // from: './node_modules/vscode-codicons/dist/codicon.css', 100 | // to: 'vendor/vscode-codicons/' 101 | // }, 102 | // { 103 | // from: './node_modules/vscode-codicons/dist/codicon.ttf', 104 | // to: 'vendor/vscode-codicons/' 105 | // }, 106 | // // fontawesome 107 | // { 108 | // from: './node_modules/@fortawesome/fontawesome-free/webfonts/', 109 | // to: 'vendor/fontawesome/webfonts/' 110 | // }, 111 | // { 112 | // from: './node_modules/@fortawesome/fontawesome-free/css/all.min.css', 113 | // to: 'vendor/fontawesome/css/' 114 | // }, 115 | // // element-plus 116 | // { 117 | // from: './node_modules/element-plus/lib/theme-chalk/fonts/', 118 | // to: 'vendor/element-plus/lib/theme-chalk/fonts/' 119 | // }, 120 | // { 121 | // from: './node_modules/element-plus/lib/theme-chalk/index.css', 122 | // to: 'vendor/element-plus/lib/theme-chalk/' 123 | // } 124 | ] 125 | }) 126 | ], 127 | optimization: { 128 | minimizer: [ 129 | new TerserPlugin({ 130 | extractComments: false 131 | }) 132 | ] 133 | } 134 | }; 135 | -------------------------------------------------------------------------------- /MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2019 pypy. All rights reserved. 2 | // 3 | // This work is licensed under the terms of the MIT license. 4 | // For a copy, see . 5 | 6 | namespace VRCX 7 | { 8 | partial class MainForm 9 | { 10 | /// 11 | /// 필수 디자이너 변수입니다. 12 | /// 13 | private System.ComponentModel.IContainer components = null; 14 | 15 | /// 16 | /// 사용 중인 모든 리소스를 정리합니다. 17 | /// 18 | /// 관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다. 19 | protected override void Dispose(bool disposing) 20 | { 21 | if (disposing && (components != null)) 22 | { 23 | components.Dispose(); 24 | } 25 | base.Dispose(disposing); 26 | } 27 | 28 | #region Windows Form 디자이너에서 생성한 코드 29 | 30 | /// 31 | /// 디자이너 지원에 필요한 메서드입니다. 32 | /// 이 메서드의 내용을 코드 편집기로 수정하지 마세요. 33 | /// 34 | private void InitializeComponent() 35 | { 36 | this.components = new System.ComponentModel.Container(); 37 | this.TrayMenu = new System.Windows.Forms.ContextMenuStrip(this.components); 38 | this.TrayMenu_Open = new System.Windows.Forms.ToolStripMenuItem(); 39 | this.TrayMenu_Separator = new System.Windows.Forms.ToolStripSeparator(); 40 | this.TrayMenu_Quit = new System.Windows.Forms.ToolStripMenuItem(); 41 | this.TrayIcon = new System.Windows.Forms.NotifyIcon(this.components); 42 | this.TrayMenu.SuspendLayout(); 43 | this.SuspendLayout(); 44 | // 45 | // TrayMenu 46 | // 47 | this.TrayMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 48 | this.TrayMenu_Open, 49 | this.TrayMenu_Separator, 50 | this.TrayMenu_Quit}); 51 | this.TrayMenu.Name = "TrayMenu"; 52 | this.TrayMenu.Size = new System.Drawing.Size(132, 54); 53 | // 54 | // TrayMenu_Open 55 | // 56 | this.TrayMenu_Open.Name = "TrayMenu_Open"; 57 | this.TrayMenu_Open.Size = new System.Drawing.Size(131, 22); 58 | this.TrayMenu_Open.Text = "Open"; 59 | this.TrayMenu_Open.Click += new System.EventHandler(this.TrayMenu_Open_Click); 60 | // 61 | // TrayMenu_Separator 62 | // 63 | this.TrayMenu_Separator.Name = "TrayMenu_Separator"; 64 | this.TrayMenu_Separator.Size = new System.Drawing.Size(128, 6); 65 | // 66 | // TrayMenu_Quit 67 | // 68 | this.TrayMenu_Quit.Name = "TrayMenu_Quit"; 69 | this.TrayMenu_Quit.Size = new System.Drawing.Size(131, 22); 70 | this.TrayMenu_Quit.Text = "Quit VRCX"; 71 | this.TrayMenu_Quit.Click += new System.EventHandler(this.TrayMenu_Quit_Click); 72 | // 73 | // TrayIcon 74 | // 75 | this.TrayIcon.ContextMenuStrip = this.TrayMenu; 76 | this.TrayIcon.Text = "VRCX"; 77 | this.TrayIcon.Visible = true; 78 | this.TrayIcon.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.TrayIcon_MouseDoubleClick); 79 | // 80 | // MainForm 81 | // 82 | this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); 83 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; 84 | this.ClientSize = new System.Drawing.Size(842, 561); 85 | this.MinimumSize = new System.Drawing.Size(320, 240); 86 | this.Name = "MainForm"; 87 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 88 | this.Text = "VRCX"; 89 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing); 90 | this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.MainForm_FormClosed); 91 | this.Load += new System.EventHandler(this.MainForm_Load); 92 | this.Move += new System.EventHandler(this.MainForm_Move); 93 | this.Resize += new System.EventHandler(this.MainForm_Resize); 94 | this.TrayMenu.ResumeLayout(false); 95 | this.ResumeLayout(false); 96 | 97 | } 98 | 99 | #endregion 100 | 101 | private System.Windows.Forms.ContextMenuStrip TrayMenu; 102 | private System.Windows.Forms.ToolStripMenuItem TrayMenu_Open; 103 | private System.Windows.Forms.ToolStripSeparator TrayMenu_Separator; 104 | private System.Windows.Forms.ToolStripMenuItem TrayMenu_Quit; 105 | private System.Windows.Forms.NotifyIcon TrayIcon; 106 | } 107 | } -------------------------------------------------------------------------------- /VRForm.Designer.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2019 pypy. All rights reserved. 2 | // 3 | // This work is licensed under the terms of the MIT license. 4 | // For a copy, see . 5 | 6 | namespace VRCX 7 | { 8 | partial class VRForm 9 | { 10 | /// 11 | /// 필수 디자이너 변수입니다. 12 | /// 13 | private System.ComponentModel.IContainer components = null; 14 | 15 | /// 16 | /// 사용 중인 모든 리소스를 정리합니다. 17 | /// 18 | /// 관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다. 19 | protected override void Dispose(bool disposing) 20 | { 21 | if (disposing && (components != null)) 22 | { 23 | components.Dispose(); 24 | } 25 | base.Dispose(disposing); 26 | } 27 | 28 | #region Windows Form 디자이너에서 생성한 코드 29 | 30 | /// 31 | /// 디자이너 지원에 필요한 메서드입니다. 32 | /// 이 메서드의 내용을 코드 편집기로 수정하지 마세요. 33 | /// 34 | private void InitializeComponent() 35 | { 36 | this.components = new System.ComponentModel.Container(); 37 | this.timer = new System.Windows.Forms.Timer(this.components); 38 | this.panel1 = new System.Windows.Forms.Panel(); 39 | this.panel2 = new System.Windows.Forms.Panel(); 40 | this.button_refresh = new System.Windows.Forms.Button(); 41 | this.button_devtools = new System.Windows.Forms.Button(); 42 | this.SuspendLayout(); 43 | // 44 | // panel1 45 | // 46 | this.panel1.Location = new System.Drawing.Point(0, 0); 47 | this.panel1.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 48 | this.panel1.Name = "panel1"; 49 | this.panel1.Size = new System.Drawing.Size(731, 768); 50 | this.panel1.TabIndex = 0; 51 | // 52 | // panel2 53 | // 54 | this.panel2.Location = new System.Drawing.Point(740, 0); 55 | this.panel2.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 56 | this.panel2.Name = "panel2"; 57 | this.panel2.Size = new System.Drawing.Size(731, 768); 58 | this.panel2.TabIndex = 1; 59 | // 60 | // button_refresh 61 | // 62 | this.button_refresh.Location = new System.Drawing.Point(17, 777); 63 | this.button_refresh.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 64 | this.button_refresh.Name = "button_refresh"; 65 | this.button_refresh.Size = new System.Drawing.Size(107, 34); 66 | this.button_refresh.TabIndex = 27; 67 | this.button_refresh.Text = "Refresh"; 68 | this.button_refresh.UseVisualStyleBackColor = true; 69 | this.button_refresh.Click += new System.EventHandler(this.button_refresh_Click); 70 | // 71 | // button_devtools 72 | // 73 | this.button_devtools.Location = new System.Drawing.Point(133, 777); 74 | this.button_devtools.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 75 | this.button_devtools.Name = "button_devtools"; 76 | this.button_devtools.Size = new System.Drawing.Size(107, 34); 77 | this.button_devtools.TabIndex = 27; 78 | this.button_devtools.Text = "DevTools"; 79 | this.button_devtools.UseVisualStyleBackColor = true; 80 | this.button_devtools.Click += new System.EventHandler(this.button_devtools_Click); 81 | // 82 | // VRForm 83 | // 84 | this.AutoScaleDimensions = new System.Drawing.SizeF(144F, 144F); 85 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; 86 | this.ClientSize = new System.Drawing.Size(1483, 830); 87 | this.Controls.Add(this.button_devtools); 88 | this.Controls.Add(this.button_refresh); 89 | this.Controls.Add(this.panel2); 90 | this.Controls.Add(this.panel1); 91 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 92 | this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 93 | this.MaximizeBox = false; 94 | this.Name = "VRForm"; 95 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 96 | this.Text = "VR"; 97 | this.ResumeLayout(false); 98 | 99 | } 100 | 101 | #endregion 102 | 103 | private System.Windows.Forms.Timer timer; 104 | private System.Windows.Forms.Panel panel1; 105 | private System.Windows.Forms.Panel panel2; 106 | private System.Windows.Forms.Button button_refresh; 107 | private System.Windows.Forms.Button button_devtools; 108 | } 109 | } -------------------------------------------------------------------------------- /SQLite.cs: -------------------------------------------------------------------------------- 1 | using CefSharp; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data.SQLite; 5 | using System.IO; 6 | using System.Threading; 7 | 8 | namespace VRCX 9 | { 10 | public class SQLite 11 | { 12 | public static readonly SQLite Instance; 13 | private readonly ReaderWriterLockSlim m_ConnectionLock; 14 | private readonly SQLiteConnection m_Connection; 15 | 16 | static SQLite() 17 | { 18 | Instance = new SQLite(); 19 | } 20 | 21 | public SQLite() 22 | { 23 | m_ConnectionLock = new ReaderWriterLockSlim(); 24 | 25 | var dataSource = Path.Combine(Program.BaseDirectory, "VRCX.sqlite3"); 26 | m_Connection = new SQLiteConnection($"Data Source=\"{dataSource}\";Version=3;PRAGMA locking_mode=NORMAL;PRAGMA busy_timeout=5000", true); 27 | } 28 | 29 | internal void Init() 30 | { 31 | m_Connection.Open(); 32 | } 33 | 34 | internal void Exit() 35 | { 36 | m_Connection.Close(); 37 | m_Connection.Dispose(); 38 | } 39 | 40 | public void Execute(IJavascriptCallback callback, string sql, IDictionary args = null) 41 | { 42 | try 43 | { 44 | m_ConnectionLock.EnterReadLock(); 45 | try 46 | { 47 | using (var command = new SQLiteCommand(sql, m_Connection)) 48 | { 49 | if (args != null) 50 | { 51 | foreach (var arg in args) 52 | { 53 | command.Parameters.Add(new SQLiteParameter(arg.Key, arg.Value)); 54 | } 55 | } 56 | using (var reader = command.ExecuteReader()) 57 | { 58 | while (reader.Read() == true) 59 | { 60 | var values = new object[reader.FieldCount]; 61 | reader.GetValues(values); 62 | if (callback.CanExecute == true) 63 | { 64 | callback.ExecuteAsync(null, values); 65 | } 66 | } 67 | } 68 | } 69 | if (callback.CanExecute == true) 70 | { 71 | callback.ExecuteAsync(null, null); 72 | } 73 | } 74 | finally 75 | { 76 | m_ConnectionLock.ExitReadLock(); 77 | } 78 | } 79 | catch (Exception e) 80 | { 81 | if (callback.CanExecute == true) 82 | { 83 | callback.ExecuteAsync(e.Message, null); 84 | } 85 | } 86 | 87 | callback.Dispose(); 88 | } 89 | 90 | public void Execute(Action callback, string sql, IDictionary args = null) 91 | { 92 | m_ConnectionLock.EnterReadLock(); 93 | try 94 | { 95 | using (var command = new SQLiteCommand(sql, m_Connection)) 96 | { 97 | if (args != null) 98 | { 99 | foreach (var arg in args) 100 | { 101 | command.Parameters.Add(new SQLiteParameter(arg.Key, arg.Value)); 102 | } 103 | } 104 | using (var reader = command.ExecuteReader()) 105 | { 106 | while (reader.Read() == true) 107 | { 108 | var values = new object[reader.FieldCount]; 109 | reader.GetValues(values); 110 | callback(values); 111 | } 112 | } 113 | } 114 | } 115 | catch 116 | { 117 | } 118 | finally 119 | { 120 | m_ConnectionLock.ExitReadLock(); 121 | } 122 | } 123 | 124 | public int ExecuteNonQuery(string sql, IDictionary args = null) 125 | { 126 | int result = -1; 127 | 128 | m_ConnectionLock.EnterWriteLock(); 129 | try 130 | { 131 | using (var command = new SQLiteCommand(sql, m_Connection)) 132 | { 133 | if (args != null) 134 | { 135 | foreach (var arg in args) 136 | { 137 | command.Parameters.Add(new SQLiteParameter(arg.Key, arg.Value)); 138 | } 139 | } 140 | result = command.ExecuteNonQuery(); 141 | } 142 | } 143 | finally 144 | { 145 | m_ConnectionLock.ExitWriteLock(); 146 | } 147 | 148 | return result; 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VRCX 2 | 3 | [![GitHub Workflow Status](https://github.com/pypy-vrc/VRCX/actions/workflows/github_actions.yml/badge.svg)](https://github.com/pypy-vrc/VRCX/actions/workflows/github_actions.yml) 4 | [![VRCX Discord Invite](https://img.shields.io/discord/854071236363550763?color=%237289DA&logo=discord&logoColor=white)](https://vrcx.pypy.moe/discord) 5 | 6 | VRCX is an assistant application for VRChat that provides information about and managing friendship. This application uses the unofficial VRChat API (VRCSDK). 7 | 8 | VRCX isn't endorsed by VRChat and doesn't reflect the views or opinions of VRChat or anyone officially involved in producing or managing VRChat. VRChat is trademark of VRChat Inc. VRChat © VRChat Inc. 9 | 10 | pypy is not responsible for any problems caused by VRCX. ***Use at your own risk!*** 11 | 12 | ![vrchat api](https://user-images.githubusercontent.com/11171153/114227156-b559c400-99c8-11eb-9df6-ee6615b8118e.png) 13 | 14 | *VRChat's official stance on usage of the API, as listed in their Discord #faq channel.* 15 | 16 | Special comments about VRChatRPC.DLL 17 | - 18 | VRChatRPC.DLL is used to login VRChat via your **Steam account**. (If you press the Steam login button on the login page) 19 | 20 | If you don't need to login via Steam, VRChatRPC.DLL will not be used. 21 | 22 | In detail, VRChatRPC.DLL accesses the VRChat Process (DLL Injection) and calls the Steam API to obtain the Login Token. This may lead to BAN from VRChat. 23 | 24 | No technical measures have been taken on security yet (Of course I can't say there's no risk, but there's no problem), but I don't know what will happen later. 25 | 26 | Since it is impossible to login to a 3rdparty account in the usual way, so this is the only way. 27 | 28 | It's source code is available at https://github.com/pypy-vrc/VRChatRPC. 29 | 30 | Screenshots 31 | = 32 | ![2fa](https://user-images.githubusercontent.com/25771678/63169786-a810f880-c072-11e9-9ede-0a3a03d5da12.png) 33 | ![01feed](https://user-images.githubusercontent.com/25771678/63169780-a6dfcb80-c072-11e9-85f9-2e7c816633a2.png) 34 | ![02gamelog](https://user-images.githubusercontent.com/25771678/63169782-a7786200-c072-11e9-9221-bdc13ddbec5b.png) 35 | ![03search](https://user-images.githubusercontent.com/25771678/63169787-a810f880-c072-11e9-94fb-af3ed02fa5da.png) 36 | ![note](https://user-images.githubusercontent.com/25771678/63212073-77949180-c13a-11e9-9d8e-a3db64f55b47.png) 37 | ![09w1](https://user-images.githubusercontent.com/25771678/63170557-8153c180-c074-11e9-8f89-9b1a61b7912f.png) 38 | ![09w2](https://user-images.githubusercontent.com/25771678/63170559-81ec5800-c074-11e9-8549-efd2d7843ca1.png) 39 | ![04fav](https://user-images.githubusercontent.com/25771678/63169788-a8a98f00-c072-11e9-9257-8d910880b4a3.png) 40 | ![05fr](https://user-images.githubusercontent.com/25771678/63169789-a9422580-c072-11e9-8ccd-e2ef45dc8842.png) 41 | ![06mo](https://user-images.githubusercontent.com/25771678/63169791-a9dabc00-c072-11e9-9e12-04ab009939b2.png) 42 | ![07no](https://user-images.githubusercontent.com/25771678/63169792-aa735280-c072-11e9-92fc-f210de74865d.png) 43 | ![08op1](https://user-images.githubusercontent.com/25771678/63169793-ab0be900-c072-11e9-9d57-23bff5b44f86.png) 44 | ![08op2](https://user-images.githubusercontent.com/25771678/63169797-aba47f80-c072-11e9-8672-f055fa4bdc0f.png) 45 | ![08op3](https://user-images.githubusercontent.com/25771678/63169798-aba47f80-c072-11e9-82ac-41c58af74946.png) 46 | ![invite](https://user-images.githubusercontent.com/25771678/63169801-ac3d1600-c072-11e9-9350-3f244eba52eb.png) 47 | ![join](https://user-images.githubusercontent.com/25771678/63169804-acd5ac80-c072-11e9-8006-f49c41869156.png) 48 | ![newin](https://user-images.githubusercontent.com/25771678/63169806-ad6e4300-c072-11e9-96a4-89677141abfb.png) 49 | ![dis](https://user-images.githubusercontent.com/25771678/63170206-c62b2880-c073-11e9-836c-482f8a0935a0.png) 50 | ![noty1](https://user-images.githubusercontent.com/25771678/63169808-ae06d980-c072-11e9-93e9-fcc13312872b.PNG) 51 | ![noty2](https://user-images.githubusercontent.com/25771678/63169810-ae9f7000-c072-11e9-818b-dd419213420b.PNG) 52 | 53 | # How to install VRCX 54 | 55 | * Download latest release [zip](https://github.com/pypy-vrc/VRCX/releases/latest). 56 | * Extract entire zip archive. 57 | * Run `VRCX.exe`. 58 | 59 | # How to build VRCX from source 60 | 61 | * Get source code 62 | * Download latest source code [zip](https://github.com/pypy-vrc/VRCX/archive/master.zip) or clone repo with `git clone`. 63 | 64 | * Build .NET 65 | * Install [Visual Studio](https://visualstudio.microsoft.com/) if it's not already installed. 66 | * In Visual Studio "Open Project/Solution" and browse to the [Solution file](https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file) provided inside the downloaded source code. 67 | * Set [Configuration](https://docs.microsoft.com/en-us/visualstudio/ide/understanding-build-configurations?view=vs-2019) to `Release` and Platform to `x64` 68 | * Restore [NuGet](https://docs.microsoft.com/en-us/nuget/consume-packages/package-restore#restore-packages-automatically-using-visual-studio) packages. 69 | * [Build](https://docs.microsoft.com/en-us/visualstudio/ide/building-and-cleaning-projects-and-solutions-in-visual-studio) Solution. 70 | 71 | * Build Node.js 72 | * Download and install [Node.js](https://nodejs.org/en/download/). 73 | * Run `build-node.js.cmd`. 74 | * Run `make-junction.cmd`. 75 | 76 | * Create release zip 77 | * Run `make-zip.cmd` for [Bandizip](https://www.bandisoft.com/bandizip) or `make-zip-7z.cmd` for [7-Zip](https://www.7-zip.org). 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | 263 | # Visual Studio Code config directory 264 | .vscode/ -------------------------------------------------------------------------------- /Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /MainForm.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2019 pypy. All rights reserved. 2 | // 3 | // This work is licensed under the terms of the MIT license. 4 | // For a copy, see . 5 | 6 | using System; 7 | using System.Drawing; 8 | using System.IO; 9 | using System.Reflection; 10 | using System.Windows.Forms; 11 | using CefSharp; 12 | using CefSharp.WinForms; 13 | 14 | namespace VRCX 15 | { 16 | public partial class MainForm : Form 17 | { 18 | public static MainForm Instance; 19 | public ChromiumWebBrowser Browser; 20 | private int LastLocationX; 21 | private int LastLocationY; 22 | private int LastSizeWidth; 23 | private int LastSizeHeight; 24 | 25 | public MainForm() 26 | { 27 | Instance = this; 28 | InitializeComponent(); 29 | 30 | try 31 | { 32 | var location = Assembly.GetExecutingAssembly().Location; 33 | var icon = Icon.ExtractAssociatedIcon(location); 34 | Icon = icon; 35 | TrayIcon.Icon = icon; 36 | } 37 | catch 38 | { 39 | } 40 | 41 | Browser = new ChromiumWebBrowser( 42 | Path.Combine(Program.BaseDirectory, "html/index.html") 43 | ) 44 | { 45 | DragHandler = new NoopDragHandler(), 46 | BrowserSettings = 47 | { 48 | DefaultEncoding = "UTF-8", 49 | }, 50 | Dock = DockStyle.Fill 51 | }; 52 | 53 | Browser.IsBrowserInitializedChanged += (A, B) => 54 | { 55 | // Browser.ShowDevTools(); 56 | }; 57 | 58 | Util.ApplyJavascriptBindings(Browser.JavascriptObjectRepository); 59 | 60 | Controls.Add(Browser); 61 | } 62 | 63 | private void MainForm_Load(object sender, System.EventArgs e) 64 | { 65 | try 66 | { 67 | int.TryParse(VRCXStorage.Instance.Get("VRCX_LocationX"), out LastLocationX); 68 | int.TryParse(VRCXStorage.Instance.Get("VRCX_LocationY"), out LastLocationY); 69 | int.TryParse(VRCXStorage.Instance.Get("VRCX_SizeWidth"), out LastSizeWidth); 70 | int.TryParse(VRCXStorage.Instance.Get("VRCX_SizeHeight"), out LastSizeHeight); 71 | var location = new Point(LastLocationX, LastLocationY); 72 | var size = new Size(LastSizeWidth, LastSizeHeight); 73 | var screen = Screen.FromPoint(location); 74 | if (screen.Bounds.Contains(location.X, location.Y)) 75 | { 76 | Location = location; 77 | } 78 | if (size.Width > 0 && size.Height > 0) 79 | { 80 | Size = size; 81 | } 82 | } 83 | catch 84 | { 85 | } 86 | 87 | try 88 | { 89 | var state = WindowState; 90 | if (int.TryParse(VRCXStorage.Instance.Get("VRCX_WindowState"), out int v)) 91 | { 92 | state = (FormWindowState)v; 93 | } 94 | if (state == FormWindowState.Minimized) 95 | { 96 | state = FormWindowState.Normal; 97 | } 98 | if ("true".Equals(VRCXStorage.Instance.Get("VRCX_StartAsMinimizedState"))) 99 | { 100 | state = FormWindowState.Minimized; 101 | } 102 | WindowState = state; 103 | } 104 | catch 105 | { 106 | } 107 | 108 | // 가끔 화면 위치가 안맞음.. 이걸로 해결 될지는 모르겠음 109 | Browser.Invalidate(); 110 | } 111 | 112 | private void MainForm_Resize(object sender, System.EventArgs e) 113 | { 114 | if (WindowState != FormWindowState.Normal) 115 | { 116 | return; 117 | } 118 | LastSizeWidth = Size.Width; 119 | LastSizeHeight = Size.Height; 120 | } 121 | 122 | private void MainForm_Move(object sender, System.EventArgs e) 123 | { 124 | if (WindowState != FormWindowState.Normal) 125 | { 126 | return; 127 | } 128 | LastLocationX = Location.X; 129 | LastLocationY = Location.Y; 130 | } 131 | 132 | private void MainForm_FormClosing(object sender, FormClosingEventArgs e) 133 | { 134 | if (e.CloseReason == CloseReason.UserClosing && 135 | "true".Equals(SharedVariable.Instance.Get("config:vrcx_closetotray"))) 136 | { 137 | e.Cancel = true; 138 | Hide(); 139 | } 140 | } 141 | 142 | private void MainForm_FormClosed(object sender, FormClosedEventArgs e) 143 | { 144 | VRCXStorage.Instance.Set("VRCX_LocationX", LastLocationX.ToString()); 145 | VRCXStorage.Instance.Set("VRCX_LocationY", LastLocationY.ToString()); 146 | VRCXStorage.Instance.Set("VRCX_SizeWidth", LastSizeWidth.ToString()); 147 | VRCXStorage.Instance.Set("VRCX_SizeHeight", LastSizeHeight.ToString()); 148 | VRCXStorage.Instance.Set("VRCX_WindowState", ((int)WindowState).ToString()); 149 | } 150 | 151 | private void TrayIcon_MouseDoubleClick(object sender, MouseEventArgs e) 152 | { 153 | TrayMenu_Open_Click(sender, e); 154 | } 155 | 156 | private void TrayMenu_Open_Click(object sender, System.EventArgs e) 157 | { 158 | if (WindowState == FormWindowState.Minimized) 159 | { 160 | WindowState = FormWindowState.Normal; 161 | } 162 | Show(); 163 | Focus(); 164 | } 165 | 166 | private void TrayMenu_Quit_Click(object sender, System.EventArgs e) 167 | { 168 | Application.Exit(); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /VRForm.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | -------------------------------------------------------------------------------- /Discord.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2019 pypy. All rights reserved. 2 | // 3 | // This work is licensed under the terms of the MIT license. 4 | // For a copy, see . 5 | 6 | using DiscordRPC; 7 | using System.Text; 8 | using System.Threading; 9 | 10 | namespace VRCX 11 | { 12 | public class Discord 13 | { 14 | public static readonly Discord Instance; 15 | private readonly ReaderWriterLockSlim m_Lock; 16 | private readonly RichPresence m_Presence; 17 | private DiscordRpcClient m_Client; 18 | private Timer m_Timer; 19 | private bool m_Active; 20 | 21 | static Discord() 22 | { 23 | Instance = new Discord(); 24 | } 25 | 26 | public Discord() 27 | { 28 | m_Lock = new ReaderWriterLockSlim(); 29 | m_Presence = new RichPresence(); 30 | m_Timer = new Timer(TimerCallback, null, -1, -1); 31 | } 32 | 33 | internal void Init() 34 | { 35 | m_Timer.Change(0, 1000); 36 | } 37 | 38 | internal void Exit() 39 | { 40 | lock (this) 41 | { 42 | m_Timer.Change(-1, -1); 43 | m_Client?.Dispose(); 44 | } 45 | } 46 | 47 | private void TimerCallback(object state) 48 | { 49 | lock (this) 50 | { 51 | try 52 | { 53 | Update(); 54 | } 55 | catch 56 | { 57 | } 58 | } 59 | } 60 | 61 | private void Update() 62 | { 63 | if (m_Client != null) 64 | { 65 | m_Lock.EnterReadLock(); 66 | try 67 | { 68 | m_Client.SetPresence(m_Presence); 69 | } 70 | finally 71 | { 72 | m_Lock.ExitReadLock(); 73 | } 74 | m_Client.Invoke(); 75 | } 76 | 77 | if (m_Active == true) 78 | { 79 | if (m_Client == null) 80 | { 81 | m_Client = new DiscordRpcClient("525953831020920832"); 82 | if (m_Client.Initialize() == false) 83 | { 84 | m_Client.Dispose(); 85 | m_Client = null; 86 | } 87 | } 88 | } 89 | else if (m_Client != null) 90 | { 91 | m_Client.Dispose(); 92 | m_Client = null; 93 | } 94 | } 95 | 96 | public void SetActive(bool active) 97 | { 98 | m_Active = active; 99 | } 100 | 101 | // https://stackoverflow.com/questions/1225052/best-way-to-shorten-utf8-string-based-on-byte-length 102 | private static string LimitByteLength(string str, int maxBytesLength) 103 | { 104 | var bytesArr = Encoding.UTF8.GetBytes(str); 105 | var bytesToRemove = 0; 106 | var lastIndexInString = str.Length - 1; 107 | while (bytesArr.Length - bytesToRemove > maxBytesLength) 108 | { 109 | bytesToRemove += Encoding.UTF8.GetByteCount(new char[] { str[lastIndexInString] }); 110 | --lastIndexInString; 111 | } 112 | return Encoding.UTF8.GetString(bytesArr, 0, bytesArr.Length - bytesToRemove); 113 | } 114 | 115 | public void SetText(string details, string state) 116 | { 117 | m_Lock.EnterWriteLock(); 118 | try 119 | { 120 | m_Presence.Details = LimitByteLength(details, 127); 121 | m_Presence.State = LimitByteLength(state, 127); 122 | } 123 | finally 124 | { 125 | m_Lock.ExitWriteLock(); 126 | } 127 | } 128 | 129 | public void SetAssets(string largeKey, string largeText, string smallKey, string smallText) 130 | { 131 | m_Lock.EnterWriteLock(); 132 | try 133 | { 134 | if (string.IsNullOrEmpty(largeKey) == true && 135 | string.IsNullOrEmpty(smallKey) == true) 136 | { 137 | m_Presence.Assets = null; 138 | } 139 | else 140 | { 141 | if (m_Presence.Assets == null) 142 | { 143 | m_Presence.Assets = new Assets(); 144 | } 145 | m_Presence.Assets.LargeImageKey = largeKey; 146 | m_Presence.Assets.LargeImageText = largeText; 147 | m_Presence.Assets.SmallImageKey = smallKey; 148 | m_Presence.Assets.SmallImageText = smallText; 149 | } 150 | } 151 | finally 152 | { 153 | m_Lock.ExitWriteLock(); 154 | } 155 | } 156 | 157 | public void SetTimestamps(double startUnixMilliseconds, double endUnixMilliseconds) 158 | { 159 | var _startUnixMilliseconds = (ulong)startUnixMilliseconds; 160 | var _endUnixMilliseconds = (ulong)endUnixMilliseconds; 161 | 162 | m_Lock.EnterWriteLock(); 163 | try 164 | { 165 | if (_startUnixMilliseconds == 0) 166 | { 167 | m_Presence.Timestamps = null; 168 | } 169 | else 170 | { 171 | if (m_Presence.Timestamps == null) 172 | { 173 | m_Presence.Timestamps = new Timestamps(); 174 | } 175 | 176 | m_Presence.Timestamps.StartUnixMilliseconds = _startUnixMilliseconds; 177 | 178 | if (_endUnixMilliseconds == 0) 179 | { 180 | m_Presence.Timestamps.End = null; 181 | } 182 | else 183 | { 184 | m_Presence.Timestamps.EndUnixMilliseconds = _endUnixMilliseconds; 185 | } 186 | } 187 | } 188 | finally 189 | { 190 | m_Lock.ExitWriteLock(); 191 | } 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /MainForm.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | 124 | 124, 17 125 | 126 | -------------------------------------------------------------------------------- /OffScreenBrowser.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2019 pypy. All rights reserved. 2 | // 3 | // This work is licensed under the terms of the MIT license. 4 | // For a copy, see . 5 | 6 | using CefSharp; 7 | using CefSharp.Enums; 8 | using CefSharp.OffScreen; 9 | using CefSharp.Structs; 10 | using SharpDX.Direct3D11; 11 | using System; 12 | using System.Runtime.InteropServices; 13 | using System.Threading; 14 | 15 | namespace VRCX 16 | { 17 | public class OffScreenBrowser : ChromiumWebBrowser, IRenderHandler 18 | { 19 | private ReaderWriterLockSlim _paintBufferLock; 20 | private GCHandle _paintBuffer; 21 | private int _width; 22 | private int _height; 23 | 24 | public OffScreenBrowser(string address, int width, int height) 25 | : base( 26 | address, 27 | new BrowserSettings() 28 | { 29 | DefaultEncoding = "UTF-8" 30 | } 31 | ) 32 | { 33 | _paintBufferLock = new ReaderWriterLockSlim(); 34 | 35 | Size = new System.Drawing.Size(width, height); 36 | RenderHandler = this; 37 | 38 | Util.ApplyJavascriptBindings(JavascriptObjectRepository); 39 | } 40 | 41 | public new void Dispose() 42 | { 43 | RenderHandler = null; 44 | base.Dispose(); 45 | 46 | _paintBufferLock.EnterWriteLock(); 47 | try 48 | { 49 | if (_paintBuffer.IsAllocated == true) 50 | { 51 | _paintBuffer.Free(); 52 | } 53 | } 54 | finally 55 | { 56 | _paintBufferLock.ExitWriteLock(); 57 | } 58 | 59 | _paintBufferLock.Dispose(); 60 | } 61 | 62 | public void RenderToTexture(Texture2D texture) 63 | { 64 | _paintBufferLock.EnterReadLock(); 65 | try 66 | { 67 | if (_width > 0 && 68 | _height > 0) 69 | { 70 | var context = texture.Device.ImmediateContext; 71 | var dataBox = context.MapSubresource( 72 | texture, 73 | 0, 74 | MapMode.WriteDiscard, 75 | MapFlags.None 76 | ); 77 | if (dataBox.IsEmpty == false) 78 | { 79 | var sourcePtr = _paintBuffer.AddrOfPinnedObject(); 80 | var destinationPtr = dataBox.DataPointer; 81 | var pitch = _width * 4; 82 | var rowPitch = dataBox.RowPitch; 83 | if (pitch == rowPitch) 84 | { 85 | WinApi.CopyMemory( 86 | destinationPtr, 87 | sourcePtr, 88 | (uint)(_width * _height * 4) 89 | ); 90 | } 91 | else 92 | { 93 | for (var y = _height; y > 0; --y) 94 | { 95 | WinApi.CopyMemory( 96 | destinationPtr, 97 | sourcePtr, 98 | (uint)pitch 99 | ); 100 | sourcePtr += pitch; 101 | destinationPtr += rowPitch; 102 | } 103 | } 104 | } 105 | context.UnmapSubresource(texture, 0); 106 | } 107 | } 108 | finally 109 | { 110 | _paintBufferLock.ExitReadLock(); 111 | } 112 | } 113 | 114 | ScreenInfo? IRenderHandler.GetScreenInfo() 115 | { 116 | return null; 117 | } 118 | 119 | bool IRenderHandler.GetScreenPoint(int viewX, int viewY, out int screenX, out int screenY) 120 | { 121 | screenX = viewX; 122 | screenY = viewY; 123 | return false; 124 | } 125 | 126 | Rect IRenderHandler.GetViewRect() 127 | { 128 | return new Rect(0, 0, Size.Width, Size.Height); 129 | } 130 | 131 | void IRenderHandler.OnAcceleratedPaint(PaintElementType type, Rect dirtyRect, IntPtr sharedHandle) 132 | { 133 | // NOT USED 134 | } 135 | 136 | void IRenderHandler.OnCursorChange(IntPtr cursor, CursorType type, CursorInfo customCursorInfo) 137 | { 138 | } 139 | 140 | void IRenderHandler.OnImeCompositionRangeChanged(Range selectedRange, Rect[] characterBounds) 141 | { 142 | } 143 | 144 | void IRenderHandler.OnPaint(PaintElementType type, Rect dirtyRect, IntPtr buffer, int width, int height) 145 | { 146 | if (type == PaintElementType.View) 147 | { 148 | _paintBufferLock.EnterWriteLock(); 149 | try 150 | { 151 | if (_width != width || 152 | _height != height) 153 | { 154 | _width = width; 155 | _height = height; 156 | if (_paintBuffer.IsAllocated == true) 157 | { 158 | _paintBuffer.Free(); 159 | } 160 | _paintBuffer = GCHandle.Alloc( 161 | new byte[_width * _height * 4], 162 | GCHandleType.Pinned 163 | ); 164 | } 165 | 166 | WinApi.CopyMemory( 167 | _paintBuffer.AddrOfPinnedObject(), 168 | buffer, 169 | (uint)(width * height * 4) 170 | ); 171 | } 172 | finally 173 | { 174 | _paintBufferLock.ExitWriteLock(); 175 | } 176 | } 177 | } 178 | 179 | void IRenderHandler.OnPopupShow(bool show) 180 | { 181 | } 182 | 183 | void IRenderHandler.OnPopupSize(Rect rect) 184 | { 185 | } 186 | 187 | void IRenderHandler.OnVirtualKeyboardRequested(IBrowser browser, TextInputMode inputMode) 188 | { 189 | } 190 | 191 | bool IRenderHandler.StartDragging(IDragData dragData, DragOperationsMask mask, int x, int y) 192 | { 193 | return false; 194 | } 195 | 196 | void IRenderHandler.UpdateDragCursor(DragOperationsMask operation) 197 | { 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /html/src/vr.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | // 4 | // Copyright(c) 2019-2021 pypy and individual contributors. 5 | // All rights reserved. 6 | // 7 | // This work is licensed under the terms of the MIT license. 8 | // For a copy, see . 9 | // 10 | 11 | @import "~normalize.css/normalize.css"; 12 | @import "~animate.css/animate.min.css"; 13 | @import "~noty/lib/noty.css"; 14 | @import "~element-ui/lib/theme-chalk/index.css"; 15 | @import "~famfamfam-flags/dist/sprite/famfamfam-flags.min.css"; 16 | 17 | /* 18 | 마지노선인듯 19 | 화면 24px -> 나나 32 20 | 손등 18px -> 나나 24 21 | */ 22 | 23 | .noty_body { 24 | display: block; 25 | overflow: hidden; 26 | text-overflow: ellipsis; 27 | white-space: nowrap; 28 | } 29 | 30 | .noty_layout { 31 | width: 80% !important; 32 | max-width: none; 33 | } 34 | 35 | .noty_theme__relax.noty_bar, 36 | .noty_theme__sunset.noty_bar { 37 | position: relative; 38 | margin: 4px 0; 39 | overflow: hidden; 40 | border-radius: 2px; 41 | } 42 | 43 | .noty_theme__relax.noty_bar .noty_body, 44 | .noty_theme__sunset.noty_bar .noty_body { 45 | padding: 5px 10px 10px; 46 | font-size: 15px; 47 | text-align: center; 48 | text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1); 49 | } 50 | 51 | .noty_theme__relax.noty_bar .noty_buttons, 52 | .noty_theme__sunset.noty_bar .noty_buttons { 53 | padding: 5px 10px; 54 | } 55 | 56 | .noty_theme__relax.noty_type__alert, 57 | .noty_theme__relax.noty_type__notification { 58 | color: #444; 59 | background-color: #fff; 60 | border: 1px solid #dedede; 61 | } 62 | 63 | .noty_theme__relax.noty_type__warning { 64 | color: #826200; 65 | background-color: #ffeaa8; 66 | border: 1px solid #ffc237; 67 | } 68 | 69 | .noty_theme__relax.noty_type__warning .noty_buttons { 70 | border-color: #dfaa30; 71 | } 72 | 73 | .noty_theme__relax.noty_type__error { 74 | color: #fff; 75 | background-color: #ff8181; 76 | border: 1px solid #e25353; 77 | } 78 | 79 | .noty_theme__relax.noty_type__error .noty_buttons { 80 | border-color: #8b0000; 81 | } 82 | 83 | .noty_theme__relax.noty_type__info, 84 | .noty_theme__relax.noty_type__information { 85 | color: #fff; 86 | background-color: #78c5e7; 87 | border: 1px solid #3badd6; 88 | } 89 | 90 | .noty_theme__relax.noty_type__info .noty_buttons, 91 | .noty_theme__relax.noty_type__information .noty_buttons { 92 | border-color: #0b90c4; 93 | } 94 | 95 | .noty_theme__relax.noty_type__success { 96 | color: #006400; 97 | background-color: #bcf5bc; 98 | border: 1px solid #7cdd77; 99 | } 100 | 101 | .noty_theme__relax.noty_type__success .noty_buttons { 102 | border-color: #50c24e; 103 | } 104 | 105 | .noty_theme__sunset.noty_type__alert, 106 | .noty_theme__sunset.noty_type__notification { 107 | color: #fff; 108 | background-color: #073b4c; 109 | } 110 | 111 | .noty_theme__sunset.noty_type__alert .noty_progressbar, 112 | .noty_theme__sunset.noty_type__notification .noty_progressbar { 113 | background-color: #fff; 114 | } 115 | 116 | .noty_theme__sunset.noty_type__warning { 117 | color: #fff; 118 | background-color: #ffd166; 119 | } 120 | 121 | .noty_theme__sunset.noty_type__error { 122 | color: #fff; 123 | background-color: #ef476f; 124 | } 125 | 126 | .noty_theme__sunset.noty_type__info, 127 | .noty_theme__sunset.noty_type__information { 128 | color: #fff; 129 | background-color: #118ab2; 130 | } 131 | 132 | .noty_theme__sunset.noty_type__success { 133 | color: #fff; 134 | background-color: #06d6a0; 135 | } 136 | 137 | .noty_theme__sunset.noty_type__error .noty_progressbar { 138 | opacity: 0.4; 139 | } 140 | 141 | .noty_theme__sunset.noty_type__info .noty_progressbar, 142 | .noty_theme__sunset.noty_type__information .noty_progressbar { 143 | opacity: 0.6; 144 | } 145 | 146 | ::-webkit-scrollbar { 147 | width: 8px; 148 | height: 8px; 149 | } 150 | 151 | ::-webkit-scrollbar-track { 152 | background: rgba(0, 0, 0, 0.1); 153 | border-radius: 16px; 154 | } 155 | 156 | ::-webkit-scrollbar-thumb { 157 | background: rgba(0, 0, 0, 0.25); 158 | border-radius: 16px; 159 | } 160 | 161 | body, 162 | input, 163 | textarea, 164 | select, 165 | button { 166 | font-family: "Noto Sans JP", "Noto Sans KR", "Meiryo UI", "Malgun Gothic", "Segoe UI", sans-serif; 167 | line-height: normal; 168 | } 169 | 170 | .x-app { 171 | position: absolute; 172 | display: flex; 173 | flex-direction: column; 174 | width: 100%; 175 | height: 100%; 176 | overflow: hidden; 177 | } 178 | 179 | .x-app-type { 180 | color: #fff; 181 | background: #1f1f1f; 182 | } 183 | 184 | .x-container { 185 | position: relative; 186 | flex: none; 187 | padding: 10px; 188 | overflow: hidden auto; 189 | } 190 | 191 | .x-containerbottom { 192 | padding: 0px 10px; 193 | overflow: hidden; 194 | font-size: 20px; 195 | white-space: nowrap; 196 | } 197 | 198 | .x-containerbottom span { 199 | padding: 0px; 200 | display: block; 201 | overflow: hidden; 202 | } 203 | 204 | .x-friend-item { 205 | box-sizing: border-box; 206 | display: flex; 207 | align-items: center; 208 | font-size: 18px; 209 | } 210 | 211 | .x-friend-item .time { 212 | margin-right: 5px; 213 | } 214 | 215 | .x-friend-item .name { 216 | font-weight: bold; 217 | } 218 | 219 | .x-friend-item.friend .name { 220 | color: #fff; 221 | } 222 | 223 | .x-friend-item.favorite .name { 224 | color: #ff0; 225 | } 226 | 227 | .x-friend-item > .avatar { 228 | position: relative; 229 | display: inline-block; 230 | flex: none; 231 | width: 40px; 232 | height: 40px; 233 | margin-right: 8px; 234 | } 235 | 236 | .x-friend-item > img.avatar { 237 | width: 50px; 238 | height: 37.5px; 239 | margin-right: 0; 240 | margin-left: 5px; 241 | border-radius: 2px; 242 | } 243 | 244 | .x-friend-item > .avatar > img { 245 | width: 100%; 246 | height: 100%; 247 | border-radius: 40%; 248 | object-fit: cover; 249 | } 250 | 251 | .x-friend-item > .detail { 252 | flex: 1; 253 | overflow: hidden; 254 | } 255 | 256 | .x-friend-item > .detail > .name, 257 | .x-friend-item > .detail > .extra { 258 | display: block; 259 | overflow: hidden; 260 | text-overflow: ellipsis; 261 | white-space: nowrap; 262 | } 263 | 264 | .x-friend-item > .detail > .name { 265 | font-weight: bold; 266 | } 267 | 268 | .x-friend-item > .detail > .extra { 269 | font-weight: normal; 270 | } 271 | 272 | i.x-user-status { 273 | display: inline-block; 274 | width: 14px; 275 | height: 14px; 276 | background: #808080; 277 | border-radius: 50%; 278 | } 279 | 280 | i.x-user-status.active { 281 | background: #dfca43; 282 | } 283 | 284 | i.x-user-status.online { 285 | background: #67c23a; 286 | } 287 | 288 | i.x-user-status.joinme { 289 | background: #409eff; 290 | } 291 | 292 | i.x-user-status.askme { 293 | background: #fd9200; 294 | } 295 | 296 | i.x-user-status.busy { 297 | background: #f56c6c; 298 | } 299 | 300 | .spin { 301 | animation: rotation 2.5s infinite linear; 302 | position: absolute; 303 | width: 14px; 304 | height: 28px; 305 | } 306 | 307 | @keyframes rotation { 308 | from { 309 | transform: rotate(0deg); 310 | } 311 | to { 312 | transform: rotate(359deg); 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /html/src/app.dark.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | // 4 | // Copyright(c) 2019-2021 pypy and individual contributors. 5 | // All rights reserved. 6 | // 7 | // This work is licensed under the terms of the MIT license. 8 | // For a copy, see . 9 | // 10 | 11 | ::-webkit-scrollbar-track { 12 | background: rgba(255, 255, 255, 0.05); 13 | border-radius: 16px; 14 | } 15 | 16 | ::-webkit-scrollbar-thumb { 17 | background: rgba(255, 255, 255, 0.2); 18 | border-radius: 16px; 19 | } 20 | 21 | html, 22 | body { 23 | background-color: #101010; 24 | } 25 | 26 | body, 27 | input, 28 | textarea, 29 | select, 30 | button { 31 | color: #fff; 32 | } 33 | 34 | .el-loading-mask { 35 | background-color: rgba(0, 0, 0, 0.6); 36 | } 37 | 38 | .el-input__inner, 39 | .el-textarea__inner, 40 | .el-textarea .el-input__count, 41 | .el-input .el-input__count .el-input__count-inner { 42 | color: #fff; 43 | background-color: #444; 44 | border: #333; 45 | } 46 | 47 | .el-input-group__append, .el-input-group__prepend { 48 | color: #fff; 49 | background-color: #666; 50 | border: #555; 51 | } 52 | 53 | .el-table td, 54 | .el-table th.is-leaf { 55 | background-color: #292929; 56 | border-bottom: 1px solid #5f5f5f; 57 | } 58 | 59 | .el-table--border::after, 60 | .el-table--group::after, 61 | .el-table::before { 62 | background-color: #5f5f5f; 63 | } 64 | 65 | .el-table--striped .el-table__body tr.el-table__row--striped td { 66 | background-color: #202020; 67 | } 68 | 69 | .el-table--enable-row-hover .el-table__body tr:hover > td { 70 | background-color: #323232; 71 | } 72 | 73 | .el-pagination .btn-next, 74 | .el-pagination .btn-prev { 75 | color: #bbb; 76 | background-color: #333; 77 | } 78 | 79 | .el-pagination button:disabled { 80 | color: #101010; 81 | background-color: #333; 82 | } 83 | 84 | .el-dialog, 85 | .el-pager li { 86 | background-color: #333; 87 | } 88 | 89 | .el-pager li { 90 | color: #bbb; 91 | } 92 | 93 | .el-table { 94 | color: #fff; 95 | } 96 | 97 | .el-pagination__total { 98 | color: #bbb; 99 | } 100 | 101 | .el-tag--plain.el-tag--info { 102 | background-color: #333; 103 | } 104 | 105 | .el-tag--plain.el-tag--success { 106 | background-color: #333; 107 | } 108 | 109 | .el-button { 110 | color: #c5cad6; 111 | } 112 | 113 | .el-button:not(.el-button--text, .el-button--primary, .is-disabled) { 114 | background-color: #353535; 115 | border-color: #404040; 116 | } 117 | 118 | .el-button:not(.el-button--text, .el-button--primary, .is-disabled):focus, 119 | .el-button:not(.el-button--text, .el-button--primary, .is-disabled):hover { 120 | color: #000; 121 | background-color: #737373; 122 | border-color: #656565; 123 | } 124 | 125 | .el-button.is-disabled, 126 | .el-button.is-disabled:focus, 127 | .el-button.is-disabled:hover { 128 | background-color: #292929; 129 | border-color: #3d3d3d; 130 | } 131 | 132 | .el-tabs__item { 133 | color: #c2c4ca; 134 | } 135 | 136 | .el-tabs--card > .el-tabs__header { 137 | border-bottom-color: #5f5f5f; 138 | } 139 | 140 | .el-dropdown-menu { 141 | background-color: #353535; 142 | border-color: #404040; 143 | } 144 | 145 | .el-dropdown-menu__item--divided::before { 146 | background-color: #404040; 147 | } 148 | 149 | .el-dropdown-menu__item { 150 | color: #d4d4d4; 151 | } 152 | 153 | .el-dropdown-menu__item:focus, 154 | .el-dropdown-menu__item:not(.is-disabled):hover { 155 | color: #66b1ff; 156 | background-color: #444; 157 | } 158 | 159 | .el-popper[x-placement^="bottom"] .popper__arrow::after { 160 | border-bottom-color: #333; 161 | } 162 | 163 | .el-popper[x-placement^="bottom"] .popper__arrow { 164 | border-bottom-color: #404040; 165 | } 166 | 167 | .el-message-box { 168 | background-color: #333; 169 | border-color: #5f5f5f; 170 | } 171 | 172 | .el-tree { 173 | color: #bbb; 174 | background: #202020; 175 | } 176 | 177 | .el-menu-item:focus, 178 | .el-menu-item:hover { 179 | background-color: #505050; 180 | } 181 | 182 | .el-tabs--card > .el-tabs__header .el-tabs__item { 183 | border-left-color: #5f5f5f; 184 | } 185 | 186 | .el-tabs--card > .el-tabs__header .el-tabs__item.is-active { 187 | border-bottom-color: #9c9c9c; 188 | border-left-color: #5f5f5f; 189 | } 190 | 191 | .el-tabs--card > .el-tabs__header .el-tabs__nav { 192 | border-color: #5f5f5f; 193 | } 194 | 195 | .el-collapse-item__header { 196 | color: #d0d0d0; 197 | background-color: inherit; 198 | border-bottom-color: #5f5f5f; 199 | } 200 | 201 | .el-collapse-item__wrap { 202 | background-color: #333; 203 | border-bottom-color: #5f5f5f; 204 | } 205 | 206 | .el-message-box__title { 207 | color: #c8c8c8; 208 | } 209 | 210 | .el-dialog__title { 211 | color: #c8c8c8; 212 | } 213 | 214 | .el-message-box__content { 215 | color: #c8c8c8; 216 | } 217 | 218 | .el-collapse-item__content { 219 | color: #848484; 220 | } 221 | 222 | .el-switch__core { 223 | background-color: #212121; 224 | border-color: #5f5f5f; 225 | } 226 | 227 | .el-popover { 228 | background-color: #333; 229 | border-color: #5f5f5f; 230 | } 231 | 232 | .el-popper[x-placement^="right"] .popper__arrow::after { 233 | border-right-color: #5f5f5f; 234 | } 235 | 236 | .el-popper[x-placement^="right"] .popper__arrow { 237 | border-right-color: #5f5f5f; 238 | } 239 | 240 | .el-switch__label { 241 | color: #a0a0a0; 242 | } 243 | 244 | .el-table, 245 | .el-table__expanded-cell { 246 | background-color: inherit; 247 | } 248 | 249 | .el-tree-node__content:hover { 250 | background-color: #272727; 251 | } 252 | 253 | .el-tree-node:focus > .el-tree-node__content { 254 | background-color: #333; 255 | } 256 | 257 | .el-select-dropdown { 258 | background-color: #353535; 259 | } 260 | 261 | .el-select-dropdown__item { 262 | color: #c8c8c8; 263 | } 264 | 265 | .el-select-dropdown.is-multiple .el-select-dropdown__item.selected { 266 | background-color: #404040; 267 | } 268 | 269 | .el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover { 270 | background-color: #404040; 271 | } 272 | 273 | .el-select-dropdown__item.hover, 274 | .el-select-dropdown__item:hover { 275 | background-color: #3e3e3e; 276 | } 277 | 278 | .el-tag.el-tag--info { 279 | background-color: #404040; 280 | border-color: #252525; 281 | } 282 | 283 | .el-table__expanded-cell:hover { 284 | background-color: #323232 !important; 285 | } 286 | 287 | .el-dialog__body { 288 | color: #fff; 289 | } 290 | 291 | .el-radio { 292 | color: #fff; 293 | } 294 | 295 | .el-button { 296 | color: #fff; 297 | } 298 | 299 | .el-form-item__label { 300 | color: #c8c8c8; 301 | } 302 | 303 | .el-checkbox { 304 | color: #c8c8c8; 305 | } 306 | 307 | .x-app { 308 | background-color: #101010; 309 | } 310 | 311 | .x-container { 312 | background: #222; 313 | } 314 | 315 | .x-login-container { 316 | background-color: #101010; 317 | } 318 | 319 | .x-aside-container { 320 | background-color: #171717; 321 | } 322 | 323 | .x-friend-list > .x-friend-group { 324 | color: #fff; 325 | } 326 | 327 | .x-friend-item:hover, 328 | .x-change-image-item:hover { 329 | background: #3e3e3e; 330 | } 331 | 332 | .x-friend-item > .avatar.active::after, 333 | .x-friend-item > .avatar.online::after, 334 | .x-friend-item > .avatar.joinme::after, 335 | .x-friend-item > .avatar.askme::after, 336 | .x-friend-item > .avatar.busy::after { 337 | border: 2px solid #000; 338 | } 339 | 340 | .x-friend-item > .detail > .name { 341 | color: #fff; 342 | } 343 | 344 | .x-friend-item > .detail > .extra { 345 | color: #c7c7c7; 346 | } 347 | 348 | .x-login-container p { 349 | color: #ddd; 350 | } 351 | -------------------------------------------------------------------------------- /VRCX.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A} 8 | WinExe 9 | VRCX 10 | VRCX 11 | v4.7.2 12 | 512 13 | true 14 | true 15 | 16 | 17 | false 18 | 19 | publish\ 20 | true 21 | Disk 22 | false 23 | Foreground 24 | 7 25 | Days 26 | false 27 | false 28 | true 29 | 0 30 | 1.0.0.%2a 31 | false 32 | true 33 | 34 | 35 | true 36 | bin\x64\Debug\ 37 | DEBUG;TRACE 38 | full 39 | x64 40 | prompt 41 | MinimumRecommendedRules.ruleset 42 | true 43 | 44 | 45 | bin\x64\Release\ 46 | TRACE 47 | true 48 | pdbonly 49 | x64 50 | prompt 51 | MinimumRecommendedRules.ruleset 52 | true 53 | 54 | 55 | VRCX.ico 56 | 57 | 58 | 59 | 60 | 61 | 62 | librsync.net\Blake2Sharp.dll 63 | 64 | 65 | librsync.net\librsync.net.dll 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | Form 94 | 95 | 96 | VRForm.cs 97 | 98 | 99 | Form 100 | 101 | 102 | MainForm.cs 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | VRForm.cs 115 | 116 | 117 | MainForm.cs 118 | 119 | 120 | ResXFileCodeGenerator 121 | Resources.Designer.cs 122 | Designer 123 | 124 | 125 | True 126 | Resources.resx 127 | True 128 | 129 | 130 | SettingsSingleFileGenerator 131 | Settings.Designer.cs 132 | 133 | 134 | True 135 | Settings.settings 136 | True 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | PreserveNewest 145 | 146 | 147 | 148 | 149 | False 150 | Microsoft .NET Framework 4.5.2 %28x86 and x64%29 151 | true 152 | 153 | 154 | False 155 | .NET Framework 3.5 SP1 156 | false 157 | 158 | 159 | 160 | 161 | 90.6.50 162 | 163 | 164 | 90.6.50 165 | 166 | 167 | 90.6.50 168 | 169 | 170 | 1.0.175 171 | 172 | 173 | 7.0.2 174 | 175 | 176 | 13.0.1 177 | 178 | 179 | 4.2.0 180 | 181 | 182 | 4.2.0 183 | 184 | 185 | 4.2.0 186 | 187 | 188 | 4.2.0 189 | 190 | 191 | 4.2.0 192 | 193 | 194 | 1.0.113.7 195 | 196 | 197 | 5.0.2 198 | 199 | 200 | 201 | 202 | xcopy /y "$(ProjectDir)OpenVR\win64\openvr_api.dll" . 203 | xcopy /y /E "$(ProjectDir)AssetBundleCacher" ".\AssetBundleCacher\" 204 | 205 | -------------------------------------------------------------------------------- /AssetBundleCacher/AssetBundleCacher_Data/il2cpp_data/etc/mono/2.0/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 64 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 108 | 110 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /WebApi.cs: -------------------------------------------------------------------------------- 1 | using CefSharp; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Net; 6 | using System.Runtime.Serialization.Formatters.Binary; 7 | using System.Text; 8 | using System.Threading; 9 | 10 | namespace VRCX 11 | { 12 | public class WebApi 13 | { 14 | public static readonly WebApi Instance; 15 | private CookieContainer _cookieContainer; 16 | private bool _cookieDirty; 17 | private Timer _timer; 18 | 19 | static WebApi() 20 | { 21 | Instance = new WebApi(); 22 | ServicePointManager.DefaultConnectionLimit = 10; 23 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; 24 | } 25 | 26 | public WebApi() 27 | { 28 | _cookieContainer = new CookieContainer(); 29 | _timer = new Timer(TimerCallback, null, -1, -1); 30 | } 31 | 32 | private void TimerCallback(object state) 33 | { 34 | try 35 | { 36 | SaveCookies(); 37 | } 38 | catch 39 | { 40 | } 41 | } 42 | 43 | internal void Init() 44 | { 45 | LoadCookies(); 46 | _timer.Change(1000, 1000); 47 | } 48 | 49 | internal void Exit() 50 | { 51 | _timer.Change(-1, -1); 52 | SaveCookies(); 53 | } 54 | 55 | public void ClearCookies() 56 | { 57 | _cookieContainer = new CookieContainer(); 58 | } 59 | 60 | internal void LoadCookies() 61 | { 62 | SQLite.Instance.ExecuteNonQuery("CREATE TABLE IF NOT EXISTS `cookies` (`key` TEXT PRIMARY KEY, `value` TEXT)"); 63 | SQLite.Instance.Execute((values) => 64 | { 65 | try 66 | { 67 | using (var stream = new MemoryStream(Convert.FromBase64String((string)values[0]))) 68 | { 69 | _cookieContainer = (CookieContainer)new BinaryFormatter().Deserialize(stream); 70 | } 71 | } 72 | catch 73 | { 74 | } 75 | }, 76 | "SELECT `value` FROM `cookies` WHERE `key` = @key", 77 | new Dictionary() { 78 | {"@key", "default"} 79 | } 80 | ); 81 | } 82 | 83 | internal void SaveCookies() 84 | { 85 | if (_cookieDirty == false) 86 | { 87 | return; 88 | } 89 | try 90 | { 91 | using (var memoryStream = new MemoryStream()) 92 | { 93 | new BinaryFormatter().Serialize(memoryStream, _cookieContainer); 94 | SQLite.Instance.ExecuteNonQuery( 95 | "INSERT OR REPLACE INTO `cookies` (`key`, `value`) VALUES (@key, @value)", 96 | new Dictionary() { 97 | {"@key", "default"}, 98 | {"@value", Convert.ToBase64String(memoryStream.ToArray())} 99 | } 100 | ); 101 | } 102 | _cookieDirty = false; 103 | } 104 | catch 105 | { 106 | } 107 | } 108 | 109 | #pragma warning disable CS4014 110 | public async void Execute(IDictionary options, IJavascriptCallback callback) 111 | { 112 | try 113 | { 114 | var request = WebRequest.CreateHttp((string)options["url"]); 115 | request.CookieContainer = _cookieContainer; 116 | request.KeepAlive = true; 117 | 118 | if (options.TryGetValue("headers", out object headers) == true) 119 | { 120 | foreach (var header in (IEnumerable>)headers) 121 | { 122 | var key = header.Key; 123 | var value = header.Value.ToString(); 124 | 125 | if (string.Compare(key, "Content-Type", StringComparison.OrdinalIgnoreCase) == 0) 126 | { 127 | request.ContentType = value; 128 | } 129 | else if (string.Compare(key, "User-Agent", StringComparison.OrdinalIgnoreCase) == 0) 130 | { 131 | request.UserAgent = value; 132 | } 133 | else 134 | { 135 | request.Headers.Add(key, value); 136 | } 137 | } 138 | } 139 | 140 | if (options.TryGetValue("method", out object method) == true) 141 | { 142 | var _method = (string)method; 143 | request.Method = _method; 144 | 145 | if (string.Compare(_method, "GET", StringComparison.OrdinalIgnoreCase) != 0 && 146 | options.TryGetValue("body", out object body) == true) 147 | { 148 | using (var stream = await request.GetRequestStreamAsync()) 149 | using (var streamWriter = new StreamWriter(stream)) 150 | { 151 | await streamWriter.WriteAsync((string)body); 152 | } 153 | } 154 | } 155 | 156 | if (options.TryGetValue("uploadFilePUT", out object uploadImagePUT) == true) 157 | { 158 | request.Method = "PUT"; 159 | request.ContentType = options["fileMIME"] as string; 160 | var imageData = options["fileData"] as string; 161 | byte[] sentData = Convert.FromBase64CharArray(imageData.ToCharArray(), 0, imageData.Length); 162 | request.ContentLength = sentData.Length; 163 | using (System.IO.Stream sendStream = request.GetRequestStream()) 164 | { 165 | sendStream.Write(sentData, 0, sentData.Length); 166 | sendStream.Close(); 167 | } 168 | } 169 | 170 | if (options.TryGetValue("uploadImage", out object uploadImage) == true) 171 | { 172 | request.Method = "POST"; 173 | string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); 174 | request.ContentType = "multipart/form-data; boundary=" + boundary; 175 | Stream requestStream = request.GetRequestStream(); 176 | if (options.TryGetValue("postData", out object postDataObject) == true) 177 | { 178 | Dictionary postData = new Dictionary(); 179 | postData.Add("data", (string)postDataObject); 180 | string FormDataTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n"; 181 | foreach (string key in postData.Keys) 182 | { 183 | string item = String.Format(FormDataTemplate, boundary, key, postData[key]); 184 | byte[] itemBytes = System.Text.Encoding.UTF8.GetBytes(item); 185 | requestStream.Write(itemBytes, 0, itemBytes.Length); 186 | } 187 | } 188 | var imageData = options["imageData"] as string; 189 | byte[] fileToUpload = Convert.FromBase64CharArray(imageData.ToCharArray(), 0, imageData.Length); 190 | string fileFormKey = "image"; 191 | string fileName = "image.png"; 192 | string fileMimeType = "image/png"; 193 | string HeaderTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n"; 194 | string header = String.Format(HeaderTemplate, boundary, fileFormKey, fileName, fileMimeType); 195 | byte[] headerbytes = Encoding.UTF8.GetBytes(header); 196 | requestStream.Write(headerbytes, 0, headerbytes.Length); 197 | using (MemoryStream fileStream = new MemoryStream(fileToUpload)) 198 | { 199 | byte[] buffer = new byte[1024]; 200 | int bytesRead = 0; 201 | while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0) 202 | { 203 | requestStream.Write(buffer, 0, bytesRead); 204 | } 205 | fileStream.Close(); 206 | } 207 | byte[] newlineBytes = Encoding.UTF8.GetBytes("\r\n"); 208 | requestStream.Write(newlineBytes, 0, newlineBytes.Length); 209 | byte[] endBytes = System.Text.Encoding.UTF8.GetBytes("--" + boundary + "--"); 210 | requestStream.Write(endBytes, 0, endBytes.Length); 211 | requestStream.Close(); 212 | } 213 | 214 | try 215 | { 216 | using (var response = await request.GetResponseAsync() as HttpWebResponse) 217 | { 218 | if (response.Headers["Set-Cookie"] != null) 219 | { 220 | _cookieDirty = true; 221 | } 222 | using (var stream = response.GetResponseStream()) 223 | using (var streamReader = new StreamReader(stream)) 224 | { 225 | if (callback.CanExecute == true) 226 | { 227 | callback.ExecuteAsync(null, new 228 | { 229 | data = await streamReader.ReadToEndAsync(), 230 | status = response.StatusCode 231 | }); 232 | } 233 | } 234 | } 235 | } 236 | catch (WebException webException) 237 | { 238 | if (webException.Response is HttpWebResponse response) 239 | { 240 | if (response.Headers["Set-Cookie"] != null) 241 | { 242 | _cookieDirty = true; 243 | } 244 | using (var stream = response.GetResponseStream()) 245 | using (var streamReader = new StreamReader(stream)) 246 | { 247 | if (callback.CanExecute == true) 248 | { 249 | callback.ExecuteAsync(null, new 250 | { 251 | data = await streamReader.ReadToEndAsync(), 252 | status = response.StatusCode 253 | }); 254 | } 255 | } 256 | } 257 | else if (callback.CanExecute == true) 258 | { 259 | callback.ExecuteAsync(webException.Message, null); 260 | } 261 | } 262 | } 263 | catch (Exception e) 264 | { 265 | if (callback.CanExecute == true) 266 | { 267 | // FIXME: 브라우저는 종료되었는데 얘는 이후에 실행되면 터짐 268 | callback.ExecuteAsync(e.Message, null); 269 | } 270 | } 271 | 272 | callback.Dispose(); 273 | } 274 | #pragma warning restore CS4014 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /html/src/app.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | // 4 | // Copyright(c) 2019-2021 pypy and individual contributors. 5 | // All rights reserved. 6 | // 7 | // This work is licensed under the terms of the MIT license. 8 | // For a copy, see . 9 | // 10 | 11 | @import "~normalize.css/normalize.css"; 12 | @import "~animate.css/animate.min.css"; 13 | @import "~noty/lib/noty.css"; 14 | @import "~element-ui/lib/theme-chalk/index.css"; 15 | @import "~famfamfam-flags/dist/sprite/famfamfam-flags.min.css"; 16 | 17 | .color-palettes { 18 | background: #409eff; 19 | background: #67c23a; 20 | background: #e6a23c; 21 | background: #f56c6c; 22 | background: #909399; 23 | background: #fd9200; 24 | background: #e6e6e6; 25 | background: #c0c4cc; 26 | } 27 | 28 | .noty_layout { 29 | word-break: break-all; 30 | } 31 | 32 | .noty_theme__mint.noty_bar { 33 | position: relative; 34 | margin: 4px 0; 35 | overflow: hidden; 36 | border-radius: 2px; 37 | } 38 | 39 | .noty_theme__mint.noty_bar .noty_body { 40 | padding: 10px; 41 | font-size: 14px; 42 | } 43 | 44 | .noty_theme__mint.noty_bar .noty_buttons { 45 | padding: 10px; 46 | } 47 | 48 | .noty_theme__mint.noty_type__alert, 49 | .noty_theme__mint.noty_type__notification { 50 | color: #2f2f2f; 51 | background-color: #fff; 52 | border-bottom: 1px solid #d1d1d1; 53 | } 54 | 55 | .noty_theme__mint.noty_type__warning { 56 | color: #fff; 57 | background-color: #ffae42; 58 | border-bottom: 1px solid #e89f3c; 59 | } 60 | 61 | .noty_theme__mint.noty_type__error { 62 | color: #fff; 63 | background-color: #de636f; 64 | border-bottom: 1px solid #ca5a65; 65 | } 66 | 67 | .noty_theme__mint.noty_type__info, 68 | .noty_theme__mint.noty_type__information { 69 | color: #fff; 70 | background-color: #7f7eff; 71 | border-bottom: 1px solid #7473e8; 72 | } 73 | 74 | .noty_theme__mint.noty_type__success { 75 | color: #fff; 76 | background-color: #afc765; 77 | border-bottom: 1px solid #a0b55c; 78 | } 79 | 80 | .el-table + .pagination-bar { 81 | margin-top: 15px; 82 | } 83 | 84 | .el-dialog__body { 85 | padding: 20px; 86 | } 87 | 88 | .el-dialog__footer > .el-button + .el-button { 89 | margin-left: 5px; 90 | } 91 | 92 | .el-message-box__message p { 93 | word-break: break-all; 94 | } 95 | 96 | ::-webkit-scrollbar { 97 | width: 8px; 98 | height: 8px; 99 | } 100 | 101 | ::-webkit-scrollbar-track { 102 | background: rgba(0, 0, 0, 0.05); 103 | border-radius: 16px; 104 | } 105 | 106 | ::-webkit-scrollbar-thumb { 107 | background: rgba(0, 0, 0, 0.2); 108 | border-radius: 16px; 109 | } 110 | 111 | body, 112 | input, 113 | textarea, 114 | select, 115 | button { 116 | font-family: "Noto Sans JP", "Noto Sans KR", "Meiryo UI", "Malgun Gothic", "Segoe UI", sans-serif; 117 | line-height: normal; 118 | } 119 | 120 | a { 121 | color: #409eff; 122 | } 123 | 124 | .x-link { 125 | cursor: pointer; 126 | } 127 | 128 | .x-link:hover { 129 | text-decoration: underline; 130 | } 131 | 132 | .x-ellipsis { 133 | overflow: hidden; 134 | text-overflow: ellipsis; 135 | white-space: nowrap; 136 | } 137 | 138 | .x-app { 139 | position: absolute; 140 | display: flex; 141 | width: 100%; 142 | height: 100%; 143 | overflow: hidden auto; 144 | cursor: default; 145 | } 146 | 147 | .x-container { 148 | position: relative; 149 | flex: 1; 150 | padding: 10px; 151 | overflow: hidden auto; 152 | background: #fff; 153 | } 154 | 155 | .x-login-container { 156 | position: absolute; 157 | // modal 시작이 2000이라서 158 | z-index: 1999; 159 | display: flex; 160 | width: 100%; 161 | height: 100%; 162 | background: #fff; 163 | } 164 | 165 | .x-menu-container { 166 | flex: none; 167 | overflow: hidden auto; 168 | background: #383838; 169 | } 170 | 171 | .x-menu-container > .el-menu { 172 | background: 0; 173 | border: 0; 174 | } 175 | 176 | .el-menu-item.is-active::before { 177 | position: absolute; 178 | top: 4px; 179 | left: 1px; 180 | width: 2px; 181 | height: 48px; 182 | content: ""; 183 | background: #dcdfe6; 184 | } 185 | 186 | .el-menu-item.notify::after { 187 | position: absolute; 188 | top: 4px; 189 | right: 4px; 190 | width: 4px; 191 | height: 4px; 192 | content: ""; 193 | background: #ebeef5; 194 | border-radius: 50%; 195 | } 196 | 197 | .x-aside-container { 198 | display: flex; 199 | flex: none; 200 | flex-direction: column; 201 | width: 236px; 202 | background: #f8f8f8; 203 | } 204 | 205 | .el-popper.x-quick-search { 206 | width: 225px; 207 | min-width: 0 !important; 208 | } 209 | 210 | .el-popper.x-quick-search .el-select-dropdown__item { 211 | width: 100%; 212 | height: auto; 213 | padding: 0 10px; 214 | font-size: 12px; 215 | line-height: normal; 216 | } 217 | 218 | .x-friend-list { 219 | padding: 0 10px; 220 | overflow: hidden auto; 221 | } 222 | 223 | .x-friend-group > .el-icon-arrow-right { 224 | transition: transform 0.3s; 225 | } 226 | 227 | .x-friend-group > .el-icon-arrow-right.rotate { 228 | transform: rotate(90deg); 229 | } 230 | 231 | .x-aside-container > .x-friend-list { 232 | flex: 1; 233 | } 234 | 235 | .x-dialog .x-friend-list { 236 | display: flex; 237 | flex-wrap: wrap; 238 | align-items: flex-start; 239 | max-height: 150px; 240 | } 241 | 242 | .x-friend-list > .x-friend-group { 243 | padding: 20px 0 5px; 244 | font-size: 12px; 245 | font-weight: bold; 246 | } 247 | 248 | .x-friend-item { 249 | box-sizing: border-box; 250 | display: flex; 251 | align-items: center; 252 | padding: 5px; 253 | font-size: 12px; 254 | cursor: pointer; 255 | } 256 | 257 | .x-friend-item:hover { 258 | background: #f0f0f0; 259 | border-radius: 2px; 260 | } 261 | 262 | .x-aside-container > .x-friend-list > .x-friend-item:hover { 263 | background: #fff; 264 | border-radius: 2px; 265 | } 266 | 267 | .el-select-dropdown__item .x-friend-item:hover { 268 | background: none; 269 | border-radius: 0; 270 | } 271 | 272 | .x-dialog .x-friend-item { 273 | width: 175px; 274 | } 275 | 276 | .x-friend-item > .avatar { 277 | position: relative; 278 | display: inline-block; 279 | flex: none; 280 | width: 40px; 281 | height: 40px; 282 | margin-right: 8px; 283 | } 284 | 285 | .x-friend-item > img.avatar, 286 | img.friends-list-avatar { 287 | width: unset; 288 | height: 22.5px; 289 | margin-right: 0; 290 | margin-left: 5px; 291 | border-radius: 2px; 292 | } 293 | 294 | .x-friend-item > .avatar > img { 295 | width: 100%; 296 | height: 100%; 297 | border-radius: 50%; 298 | object-fit: cover; 299 | } 300 | 301 | .x-friend-item > .avatar.offline > img, 302 | .x-friend-item > .avatar.active > img { 303 | filter: grayscale(1); 304 | } 305 | 306 | .x-friend-item:hover > .avatar.offline > img, 307 | .x-friend-item:hover > .avatar.active > img { 308 | filter: none; 309 | } 310 | 311 | .x-friend-item > .avatar.active::after, 312 | .x-friend-item > .avatar.online::after, 313 | .x-friend-item > .avatar.joinme::after, 314 | .x-friend-item > .avatar.askme::after, 315 | .x-friend-item > .avatar.busy::after { 316 | position: absolute; 317 | right: 0; 318 | bottom: 0; 319 | width: 8px; 320 | height: 8px; 321 | content: ""; 322 | background: #909399; 323 | border: 2px solid #fff; 324 | border-radius: 50%; 325 | } 326 | 327 | .x-friend-item > .avatar.active::after { 328 | background: #dfca43; 329 | } 330 | 331 | .x-friend-item > .avatar.online::after { 332 | background: #67c23a; 333 | } 334 | 335 | .x-friend-item > .avatar.joinme::after { 336 | background: #409eff; 337 | } 338 | 339 | .x-friend-item > .avatar.askme::after { 340 | background: #fd9200; 341 | } 342 | 343 | .x-friend-item > .avatar.busy::after { 344 | background: #f56c6c; 345 | } 346 | 347 | .x-friend-item.offline > .avatar::after { 348 | display: none; 349 | } 350 | 351 | .x-friend-item > .detail { 352 | flex: 1; 353 | overflow: hidden; 354 | } 355 | 356 | .x-friend-item > .detail > .name, 357 | .x-friend-item > .detail > .extra { 358 | display: block; 359 | overflow: hidden; 360 | text-overflow: ellipsis; 361 | white-space: nowrap; 362 | } 363 | 364 | .x-friend-item > .detail > .name { 365 | font-weight: bold; 366 | color: #303133; 367 | } 368 | 369 | .x-friend-item > .detail > .extra { 370 | font-weight: normal; 371 | color: #606266; 372 | } 373 | 374 | .x-friend-item > .vrcplus-icon { 375 | border: 4px solid #dcdfe6; 376 | border-radius: 20px; 377 | width: 200px; 378 | height: 200px; 379 | cursor: pointer; 380 | } 381 | 382 | .x-friend-item > .current-vrcplus-icon { 383 | border: 4px solid #67c23a; 384 | cursor: default; 385 | } 386 | 387 | .x-friend-item > .vrcplus-icon > img { 388 | width: 100%; 389 | height: 100%; 390 | border-radius: 15px; 391 | object-fit: cover; 392 | } 393 | 394 | .x-change-image-item { 395 | display: inline-block; 396 | padding: 4px 4px 0 4px; 397 | } 398 | 399 | .x-change-image-item:hover { 400 | background: #f0f0f0; 401 | border-radius: 2px; 402 | } 403 | 404 | .x-change-image-item > img, 405 | .x-change-image-item > .el-popover__reference-wrapper > img { 406 | width: 240px; 407 | height: 180px; 408 | } 409 | 410 | .current-image { 411 | border: 2px solid #67c23a; 412 | padding: 2px 2px 0 2px; 413 | } 414 | 415 | .x-dialog > .el-dialog { 416 | max-width: 100%; 417 | margin-bottom: 10px; 418 | } 419 | 420 | .x-user-dialog > .el-dialog > .el-dialog__header, 421 | .x-world-dialog > .el-dialog > .el-dialog__header, 422 | .x-avatar-dialog > .el-dialog > .el-dialog__header { 423 | display: none; 424 | padding: 0; 425 | } 426 | 427 | .x-user-dialog > .el-dialog > .el-dialog__body, 428 | .x-world-dialog > .el-dialog > .el-dialog__body, 429 | .x-avatar-dialog > .el-dialog > .el-dialog__body { 430 | padding: 20px; 431 | } 432 | 433 | .el-popper.hex { 434 | min-width: auto; 435 | padding: 10px; 436 | font-family: monospace; 437 | text-align: center; 438 | } 439 | 440 | i.x-user-status { 441 | display: inline-block; 442 | width: 10px; 443 | height: 10px; 444 | background: #808080; 445 | border-radius: 50%; 446 | } 447 | 448 | i.x-user-status.active { 449 | background: #dfca43; 450 | } 451 | 452 | i.x-user-status.online { 453 | background: #67c23a; 454 | } 455 | 456 | i.x-user-status.joinme { 457 | background: #409eff; 458 | } 459 | 460 | i.x-user-status.askme { 461 | background: #fd9200; 462 | } 463 | 464 | i.x-user-status.busy { 465 | background: #f56c6c; 466 | } 467 | 468 | .x-tag-friend { 469 | color: rgb(255, 208, 0) !important; 470 | border-color: rgb(255, 208, 0) !important; 471 | } 472 | 473 | .x-tag-vrcplus { 474 | color: rgb(255, 208, 0) !important; 475 | border-color: rgb(255, 208, 0) !important; 476 | } 477 | 478 | .x-tag-platform-pc { 479 | color: #409eff !important; 480 | border-color: #409eff !important; 481 | } 482 | 483 | .x-tag-platform-quest { 484 | color: #67c23a !important; 485 | border-color: #67c23a !important; 486 | } 487 | 488 | .el-tree-node { 489 | white-space: normal; 490 | } 491 | 492 | .el-tree-node__content { 493 | height: auto; 494 | } 495 | 496 | .el-progress-bar { 497 | padding-right: 80px; 498 | margin-right: -85px; 499 | } 500 | 501 | .el-progress__text{ 502 | color: #c8c8c8; 503 | } 504 | 505 | .x-user-dialog .el-textarea__inner { 506 | padding: 0; 507 | background: none; 508 | border: 0; 509 | border-radius: 2px; 510 | } 511 | 512 | .options-container { 513 | margin-top: 30px; 514 | } 515 | 516 | .options-container .header { 517 | font-weight: bold; 518 | font-size: 20px; 519 | } 520 | 521 | .options-container .sub-header { 522 | font-weight: bold; 523 | font-size: 15px; 524 | } 525 | 526 | .options-container-item { 527 | font-size: 12px; 528 | margin-top: 5px; 529 | } 530 | 531 | .options-container-item .name { 532 | display: inline-block; 533 | min-width: 175px; 534 | } 535 | 536 | .toggle-switch { 537 | display: inline-block; 538 | } 539 | 540 | .toggle-list { 541 | font-size: 12px; 542 | } 543 | 544 | .toggle-list .toggle-name { 545 | display: inline-block; 546 | min-width: 140px; 547 | padding: 2px 5px 2px 0; 548 | vertical-align: top; 549 | text-align: right; 550 | } 551 | 552 | .color-picker { 553 | font-size: 18px; 554 | } 555 | 556 | .disableToggleSwitch ul li label { 557 | background-color: #fff !important; 558 | color: #8cc4ff !important; 559 | border-color: #8cc4ff !important; 560 | cursor: not-allowed !important; 561 | } 562 | 563 | .el-button--success { 564 | background-color: #67c23a !important; 565 | border-color: #67c23a !important; 566 | } 567 | 568 | .el-button--danger { 569 | background-color: #f56c6c !important; 570 | border-color: #f56c6c !important; 571 | } 572 | 573 | .el-button--warning { 574 | background-color: #e6a23c !important; 575 | border-color: #e6a23c !important; 576 | } 577 | --------------------------------------------------------------------------------