Initial commit
63
.gitattributes
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
261
.gitignore
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# DNX
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
#*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
6
App.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
|
||||
</startup>
|
||||
</configuration>
|
||||
72
Browser.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using CefSharp;
|
||||
using CefSharp.OffScreen;
|
||||
using SharpDX.Direct3D11;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public class Browser : ChromiumWebBrowser
|
||||
{
|
||||
private Texture2D m_Texture;
|
||||
|
||||
public Browser(Texture2D texture, string address)
|
||||
: base(address, new BrowserSettings()
|
||||
{
|
||||
DefaultEncoding = "UTF-8"
|
||||
})
|
||||
{
|
||||
m_Texture = texture;
|
||||
Size = new Size(texture.Description.Width, texture.Description.Height);
|
||||
RenderHandler.Dispose();
|
||||
RenderHandler = new RenderHandler(this);
|
||||
var options = new BindingOptions()
|
||||
{
|
||||
CamelCaseJavascriptNames = false
|
||||
};
|
||||
JavascriptObjectRepository.Register("VRCX", new VRCX(), true, options);
|
||||
JavascriptObjectRepository.Register("VRCXStorage", new VRCXStorage(), false, options);
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
var handler = (RenderHandler)RenderHandler;
|
||||
lock (handler.BufferLock)
|
||||
{
|
||||
if (handler.Buffer.IsAllocated)
|
||||
{
|
||||
var context = m_Texture.Device.ImmediateContext;
|
||||
var box = context.MapSubresource(m_Texture, 0, MapMode.WriteDiscard, MapFlags.None);
|
||||
if (box.DataPointer != IntPtr.Zero)
|
||||
{
|
||||
var width = handler.Width;
|
||||
var height = handler.Height;
|
||||
if (box.RowPitch == width * 4)
|
||||
{
|
||||
WinApi.CopyMemory(box.DataPointer, handler.Buffer.AddrOfPinnedObject(), (uint)handler.BufferSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dest = box.DataPointer;
|
||||
var src = handler.Buffer.AddrOfPinnedObject();
|
||||
var pitch = box.RowPitch;
|
||||
var length = width * 4;
|
||||
for (var i = 0; i < height; ++i)
|
||||
{
|
||||
WinApi.CopyMemory(dest, src, (uint)length);
|
||||
dest += pitch;
|
||||
src += length;
|
||||
}
|
||||
}
|
||||
}
|
||||
context.UnmapSubresource(m_Texture, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
82
CpuMonitor.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public static class CpuMonitor
|
||||
{
|
||||
public static float CpuUsage { get; private set; }
|
||||
private static Thread m_Thread;
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
if (m_Thread == null)
|
||||
{
|
||||
m_Thread = new Thread(() =>
|
||||
{
|
||||
PerformanceCounter cpuCounter = null;
|
||||
try
|
||||
{
|
||||
cpuCounter = new PerformanceCounter("Processor Information", "% Processor Utility", "_Total", true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
try
|
||||
{
|
||||
if (cpuCounter == null)
|
||||
{
|
||||
cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total", true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
while (m_Thread != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (cpuCounter != null)
|
||||
{
|
||||
CpuUsage = cpuCounter.NextValue();
|
||||
}
|
||||
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ThreadInterruptedException
|
||||
}
|
||||
}
|
||||
if (cpuCounter != null)
|
||||
{
|
||||
cpuCounter.Dispose();
|
||||
}
|
||||
});
|
||||
m_Thread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
var thread = m_Thread;
|
||||
if (thread != null)
|
||||
{
|
||||
m_Thread = null;
|
||||
try
|
||||
{
|
||||
thread.Interrupt();
|
||||
thread.Join();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
111
Discord.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using DiscordRPC;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public class Discord
|
||||
{
|
||||
private static RichPresence m_Presence = new RichPresence();
|
||||
private static DiscordRpcClient m_Client;
|
||||
private static bool m_Active;
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
if (m_Client != null)
|
||||
{
|
||||
m_Client.Invoke();
|
||||
}
|
||||
if (m_Active)
|
||||
{
|
||||
if (m_Client == null)
|
||||
{
|
||||
m_Client = new DiscordRpcClient("525953831020920832");
|
||||
m_Client.Initialize();
|
||||
}
|
||||
lock (m_Presence)
|
||||
{
|
||||
m_Client.SetPresence(m_Presence);
|
||||
}
|
||||
}
|
||||
else if (m_Client != null)
|
||||
{
|
||||
m_Client.Dispose();
|
||||
m_Client = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetActive(bool active)
|
||||
{
|
||||
m_Active = active;
|
||||
}
|
||||
|
||||
public void SetText(string details, string state)
|
||||
{
|
||||
lock (m_Presence)
|
||||
{
|
||||
m_Presence.Details = details;
|
||||
m_Presence.State = state;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAssets(string largeKey, string largeText, string smallKey, string smallText)
|
||||
{
|
||||
lock (m_Presence)
|
||||
{
|
||||
if (string.IsNullOrEmpty(largeKey) &&
|
||||
string.IsNullOrEmpty(smallKey))
|
||||
{
|
||||
m_Presence.Assets = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_Presence.Assets == null)
|
||||
{
|
||||
m_Presence.Assets = new Assets();
|
||||
}
|
||||
m_Presence.Assets.LargeImageKey = largeKey;
|
||||
m_Presence.Assets.LargeImageText = largeText;
|
||||
m_Presence.Assets.SmallImageKey = smallKey;
|
||||
m_Presence.Assets.SmallImageText = smallText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// JSB Sucks
|
||||
public void SetTimestamps(double startUnixMilliseconds, double endUnixMilliseconds)
|
||||
{
|
||||
SetTimestamps((ulong)startUnixMilliseconds, (ulong)endUnixMilliseconds);
|
||||
}
|
||||
|
||||
public static void SetTimestamps(ulong startUnixMilliseconds, ulong endUnixMilliseconds)
|
||||
{
|
||||
lock (m_Presence)
|
||||
{
|
||||
if (startUnixMilliseconds == 0)
|
||||
{
|
||||
m_Presence.Timestamps = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_Presence.Timestamps == null)
|
||||
{
|
||||
m_Presence.Timestamps = new Timestamps();
|
||||
}
|
||||
m_Presence.Timestamps.StartUnixMilliseconds = startUnixMilliseconds;
|
||||
if (endUnixMilliseconds == 0)
|
||||
{
|
||||
m_Presence.Timestamps.End = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Presence.Timestamps.EndUnixMilliseconds = endUnixMilliseconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
JsonSerializer.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public static class JsonSerializer
|
||||
{
|
||||
public static void Serialize<T>(string path, T obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var file = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
|
||||
using (var stream = new StreamWriter(file, Encoding.UTF8))
|
||||
using (var writer = new JsonTextWriter(stream))
|
||||
{
|
||||
var serializer = Newtonsoft.Json.JsonSerializer.CreateDefault();
|
||||
serializer.Formatting = Formatting.Indented;
|
||||
serializer.Serialize(writer, obj, typeof(T));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Deserialize<T>(string path, ref T obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
using (var stream = new StreamReader(file, Encoding.UTF8))
|
||||
using (var reader = new JsonTextReader(stream))
|
||||
{
|
||||
obj = Newtonsoft.Json.JsonSerializer.CreateDefault().Deserialize<T>(reader);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
213
LogWatcher.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public class VRCX_LogWatcher
|
||||
{
|
||||
private static List<string[]> m_GameLog = new List<string[]>();
|
||||
private static Thread m_Thread;
|
||||
private static bool m_Reset;
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
if (m_Thread == null)
|
||||
{
|
||||
m_Thread = new Thread(() =>
|
||||
{
|
||||
var lastPosition = 0L;
|
||||
var firstLine = string.Empty;
|
||||
while (m_Thread != null)
|
||||
{
|
||||
if (m_Reset)
|
||||
{
|
||||
m_Reset = false;
|
||||
firstLine = string.Empty;
|
||||
lastPosition = 0;
|
||||
}
|
||||
var info = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"Low\VRChat\VRChat");
|
||||
if (info != null &&
|
||||
info.Exists)
|
||||
{
|
||||
var files = info.GetFiles("output_log_*.txt", SearchOption.TopDirectoryOnly);
|
||||
if (files != null &&
|
||||
files.Length >= 1)
|
||||
{
|
||||
Array.Sort(files, (A, B) => B.LastWriteTime.CompareTo(A.LastWriteTime));
|
||||
if (firstLine == string.Empty)
|
||||
{
|
||||
for (var i = files.Length - 1; i >= 1; --i)
|
||||
{
|
||||
using (var stream = files[i].Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
var line = string.Empty;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
if (line.Length > 32 &&
|
||||
line[31] == '-')
|
||||
{
|
||||
ParseLine(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using (var stream = files[0].Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
var line = reader.ReadLine();
|
||||
if (line != null)
|
||||
{
|
||||
if (string.Equals(firstLine, line))
|
||||
{
|
||||
stream.Position = lastPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
firstLine = line;
|
||||
}
|
||||
do
|
||||
{
|
||||
lastPosition = stream.Position;
|
||||
ParseLine(line);
|
||||
}
|
||||
while ((line = reader.ReadLine()) != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
Thread.Sleep(3000);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ThreadInterruptedException
|
||||
}
|
||||
}
|
||||
});
|
||||
m_Thread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
var thread = m_Thread;
|
||||
if (thread != null)
|
||||
{
|
||||
m_Thread = null;
|
||||
try
|
||||
{
|
||||
thread.Interrupt();
|
||||
thread.Join();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ConvertLogTimeToISO8601(string s)
|
||||
{
|
||||
// 2019.07.31 22:26:24
|
||||
var dt = new DateTime(
|
||||
int.Parse(s.Substring(0, 4)),
|
||||
int.Parse(s.Substring(5, 2)),
|
||||
int.Parse(s.Substring(8, 2)),
|
||||
int.Parse(s.Substring(11, 2)),
|
||||
int.Parse(s.Substring(14, 2)),
|
||||
int.Parse(s.Substring(17, 2)),
|
||||
DateTimeKind.Local
|
||||
).ToUniversalTime();
|
||||
return $"{dt:yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'}";
|
||||
}
|
||||
|
||||
private static void ParseLine(string line)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 2019.07.31 22:26:24 Log - [RoomManager] Joining wrld_4432ea9b-729c-46e3-8eaf-846aa0a37fdd:6974~private(usr_4f76a584-9d4b-46f6-8209-8305eb683661)~nonce(0000000000000000000000000000000000000000000000000000000000000000)
|
||||
// 2019.07.31 22:26:24 Log - [RoomManager] Joining or Creating Room: VRChat Home
|
||||
if (string.Compare(line, 34, "[RoomManager] Joining ", 0, "[RoomManager] Joining ".Length) == 0 &&
|
||||
string.Compare(line, 56, "or ", 0, "or ".Length) != 0)
|
||||
{
|
||||
lock (m_GameLog)
|
||||
{
|
||||
m_GameLog.Add(new[]
|
||||
{
|
||||
ConvertLogTimeToISO8601(line),
|
||||
"Location",
|
||||
line.Substring(56)
|
||||
});
|
||||
}
|
||||
}
|
||||
// 2019.07.31 22:41:18 Log - [NetworkManager] OnPlayerJoined pypy
|
||||
else if (string.Compare(line, 34, "[NetworkManager] OnPlayerJoined ", 0, "[NetworkManager] OnPlayerJoined ".Length) == 0)
|
||||
{
|
||||
lock (m_GameLog)
|
||||
{
|
||||
m_GameLog.Add(new[]
|
||||
{
|
||||
ConvertLogTimeToISO8601(line),
|
||||
"OnPlayerJoined",
|
||||
line.Substring(66)
|
||||
});
|
||||
}
|
||||
}
|
||||
// 2019.07.31 22:29:31 Log - [NetworkManager] OnPlayerLeft pypy
|
||||
else if (string.Compare(line, 34, "[NetworkManager] OnPlayerLeft ", 0, "[NetworkManager] OnPlayerLeft ".Length) == 0)
|
||||
{
|
||||
lock (m_GameLog)
|
||||
{
|
||||
m_GameLog.Add(new[]
|
||||
{
|
||||
ConvertLogTimeToISO8601(line),
|
||||
"OnPlayerLeft",
|
||||
line.Substring(64)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
lock (m_GameLog)
|
||||
{
|
||||
m_Reset = true;
|
||||
m_GameLog.Clear();
|
||||
}
|
||||
m_Thread.Interrupt();
|
||||
}
|
||||
|
||||
public string[][] GetLogs()
|
||||
{
|
||||
lock (m_GameLog)
|
||||
{
|
||||
var array = m_GameLog.ToArray();
|
||||
m_GameLog.Clear();
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasLog()
|
||||
{
|
||||
lock (m_GameLog)
|
||||
{
|
||||
return m_GameLog.Count > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
MainForm.Designer.cs
generated
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
partial class MainForm
|
||||
{
|
||||
/// <summary>
|
||||
/// 필수 디자이너 변수입니다.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// 사용 중인 모든 리소스를 정리합니다.
|
||||
/// </summary>
|
||||
/// <param name="disposing">관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form 디자이너에서 생성한 코드
|
||||
|
||||
/// <summary>
|
||||
/// 디자이너 지원에 필요한 메서드입니다.
|
||||
/// 이 메서드의 내용을 코드 편집기로 수정하지 마세요.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.timer = new System.Windows.Forms.Timer(this.components);
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// timer
|
||||
//
|
||||
this.timer.Enabled = true;
|
||||
this.timer.Interval = 1000;
|
||||
this.timer.Tick += new System.EventHandler(this.timer_Tick);
|
||||
//
|
||||
// MainForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(884, 561);
|
||||
this.Name = "MainForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "VRCX";
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Timer timer;
|
||||
}
|
||||
}
|
||||
60
MainForm.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using System.Drawing;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public partial class MainForm : Form
|
||||
{
|
||||
public static MainForm Instance { get; private set; }
|
||||
public static ChromiumWebBrowser Browser { get; private set; }
|
||||
|
||||
public MainForm()
|
||||
{
|
||||
Instance = this;
|
||||
InitializeComponent();
|
||||
try
|
||||
{
|
||||
Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
// Application.StartupPath + "/html/index.html"
|
||||
Browser = new ChromiumWebBrowser(Application.StartupPath + "/html/index.html")
|
||||
{
|
||||
BrowserSettings =
|
||||
{
|
||||
// UniversalAccessFromFileUrls = CefState.Enabled,
|
||||
DefaultEncoding = "UTF-8",
|
||||
},
|
||||
Dock = DockStyle.Fill,
|
||||
};
|
||||
var options = new BindingOptions()
|
||||
{
|
||||
CamelCaseJavascriptNames = false
|
||||
};
|
||||
Browser.JavascriptObjectRepository.Register("VRCX", new VRCX(), true, options);
|
||||
Browser.JavascriptObjectRepository.Register("VRCXStorage", new VRCXStorage(), false, options);
|
||||
Browser.JavascriptObjectRepository.Register("LogWatcher", new VRCX_LogWatcher(), true, options);
|
||||
Browser.JavascriptObjectRepository.Register("Discord", new Discord(), true, options);
|
||||
Browser.IsBrowserInitializedChanged += (A, B) =>
|
||||
{
|
||||
// Browser.ShowDevTools();
|
||||
};
|
||||
Controls.Add(Browser);
|
||||
}
|
||||
|
||||
private void timer_Tick(object sender, System.EventArgs e)
|
||||
{
|
||||
Discord.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
123
MainForm.resx
Normal file
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="timer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
7141
OpenVR/openvr_api.cs
Normal file
BIN
OpenVR/win32/openvr_api.dll
Normal file
BIN
OpenVR/win64/openvr_api.dll
Normal file
67
Program.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
[STAThread]
|
||||
public static void Main()
|
||||
{
|
||||
var settings = new CefSettings
|
||||
{
|
||||
IgnoreCertificateErrors = true,
|
||||
CachePath = "cache",
|
||||
PersistUserPreferences = true,
|
||||
PersistSessionCookies = true,
|
||||
WindowlessRenderingEnabled = true
|
||||
};
|
||||
settings.CefCommandLineArgs.Add("disable-web-security", "1");
|
||||
settings.CefCommandLineArgs.Add("no-proxy-server", "1");
|
||||
settings.CefCommandLineArgs.Add("disable-plugins-discovery", "1");
|
||||
settings.CefCommandLineArgs.Add("disable-extensions", "1");
|
||||
settings.CefCommandLineArgs.Add("disable-pdf-extension", "1");
|
||||
// settings.CefCommandLineArgs.Add("disable-gpu", "1");
|
||||
settings.CefCommandLineArgs.Add("disable-direct-write", "1");
|
||||
settings.LogSeverity = LogSeverity.Disable;
|
||||
settings.DisableGpuAcceleration();
|
||||
/*settings.RegisterScheme(new CefCustomScheme
|
||||
{
|
||||
SchemeName = "vrcx",
|
||||
DomainName = "app",
|
||||
SchemeHandlerFactory = new FolderSchemeHandlerFactory(Application.StartupPath + "/../../../html")
|
||||
});*/
|
||||
|
||||
// MUST TURN ON (Error when creating a browser on certain systems.)
|
||||
CefSharpSettings.WcfEnabled = true;
|
||||
|
||||
CefSharpSettings.ShutdownOnExit = false;
|
||||
CefSharpSettings.SubprocessExitIfParentProcessClosed = true;
|
||||
|
||||
// Cef.EnableHighDPISupport();
|
||||
|
||||
if (Cef.Initialize(settings, true, browserProcessHandler: null))
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
CpuMonitor.Start();
|
||||
VRCXStorage.Load();
|
||||
VRCXVR.Setup();
|
||||
VRCX_LogWatcher.Start();
|
||||
Application.Run(new MainForm());
|
||||
VRCX_LogWatcher.Stop();
|
||||
VRCXVR.Stop();
|
||||
VRCXStorage.Save();
|
||||
CpuMonitor.Stop();
|
||||
Cef.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해
|
||||
// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면
|
||||
// 이러한 특성 값을 변경하세요.
|
||||
[assembly: AssemblyTitle("VRCX")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("VRCX")]
|
||||
[assembly: AssemblyCopyright("pypy")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에
|
||||
// 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면
|
||||
// 해당 형식에 대해 ComVisible 특성을 true로 설정하세요.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다.
|
||||
[assembly: Guid("d9f66f2e-3ed9-4d53-a6ac-adcc1513562a")]
|
||||
|
||||
// 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다.
|
||||
//
|
||||
// 주 버전
|
||||
// 부 버전
|
||||
// 빌드 번호
|
||||
// 수정 버전
|
||||
//
|
||||
// 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호가 자동으로
|
||||
// 지정되도록 할 수 있습니다.
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
71
Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,71 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 이 코드는 도구를 사용하여 생성되었습니다.
|
||||
// 런타임 버전:4.0.30319.42000
|
||||
//
|
||||
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
|
||||
// 이러한 변경 내용이 손실됩니다.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace VRCX.Properties
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 지역화된 문자열 등을 찾기 위한 강력한 형식의 리소스 클래스입니다.
|
||||
/// </summary>
|
||||
// 이 클래스는 ResGen 또는 Visual Studio와 같은 도구를 통해 StronglyTypedResourceBuilder
|
||||
// 클래스에서 자동으로 생성되었습니다.
|
||||
// 멤버를 추가하거나 제거하려면 .ResX 파일을 편집한 다음 /str 옵션을 사용하여
|
||||
// ResGen을 다시 실행하거나 VS 프로젝트를 다시 빌드하십시오.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources
|
||||
{
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 클래스에서 사용하는 캐시된 ResourceManager 인스턴스를 반환합니다.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((resourceMan == null))
|
||||
{
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("VRCX.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 강력한 형식의 리소스 클래스를 사용하여 모든 리소스 조회에 대해 현재 스레드의 CurrentUICulture 속성을
|
||||
/// 재정의합니다.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceCulture;
|
||||
}
|
||||
set
|
||||
{
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
117
Properties/Resources.resx
Normal file
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
30
Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,30 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace VRCX.Properties
|
||||
{
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
|
||||
{
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default
|
||||
{
|
||||
get
|
||||
{
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Properties/Settings.settings
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
116
RenderHandler.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using CefSharp;
|
||||
using CefSharp.Enums;
|
||||
using CefSharp.OffScreen;
|
||||
using CefSharp.Structs;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public class RenderHandler : IRenderHandler
|
||||
{
|
||||
private ChromiumWebBrowser m_Browser;
|
||||
public readonly object BufferLock = new object();
|
||||
public int BufferSize { get; private set; }
|
||||
public GCHandle Buffer { get; private set; }
|
||||
public int Width { get; private set; }
|
||||
public int Height { get; private set; }
|
||||
|
||||
public RenderHandler(ChromiumWebBrowser browser)
|
||||
{
|
||||
m_Browser = browser;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (BufferLock)
|
||||
{
|
||||
if (Buffer.IsAllocated)
|
||||
{
|
||||
Buffer.Free();
|
||||
}
|
||||
}
|
||||
m_Browser = null;
|
||||
}
|
||||
|
||||
public virtual ScreenInfo? GetScreenInfo()
|
||||
{
|
||||
return new ScreenInfo { DeviceScaleFactor = 1f };
|
||||
}
|
||||
|
||||
public virtual Rect GetViewRect()
|
||||
{
|
||||
return new Rect(0, 0, m_Browser.Size.Width, m_Browser.Size.Height);
|
||||
}
|
||||
|
||||
public virtual bool GetScreenPoint(int viewX, int viewY, out int screenX, out int screenY)
|
||||
{
|
||||
screenX = viewX;
|
||||
screenY = viewY;
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void OnAcceleratedPaint(PaintElementType type, Rect dirtyRect, IntPtr sharedHandle)
|
||||
{
|
||||
// NOT USED
|
||||
}
|
||||
|
||||
public virtual void OnPaint(PaintElementType type, Rect dirtyRect, IntPtr buffer, int width, int height)
|
||||
{
|
||||
if (type == PaintElementType.View)
|
||||
{
|
||||
lock (BufferLock)
|
||||
{
|
||||
if (!Buffer.IsAllocated ||
|
||||
width != Width ||
|
||||
height != Height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
BufferSize = width * height * 4;
|
||||
if (Buffer.IsAllocated)
|
||||
{
|
||||
Buffer.Free();
|
||||
}
|
||||
Buffer = GCHandle.Alloc(new byte[BufferSize], GCHandleType.Pinned);
|
||||
}
|
||||
WinApi.CopyMemory(Buffer.AddrOfPinnedObject(), buffer, (uint)BufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnCursorChange(IntPtr cursor, CursorType type, CursorInfo customCursorInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual bool StartDragging(IDragData dragData, DragOperationsMask mask, int x, int y)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void UpdateDragCursor(DragOperationsMask operation)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnPopupShow(bool show)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnPopupSize(Rect rect)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnImeCompositionRangeChanged(Range selectedRange, Rect[] characterBounds)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnVirtualKeyboardRequested(IBrowser browser, TextInputMode inputMode)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
95
VRCX.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using CefSharp;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public class VRCX
|
||||
{
|
||||
public void ShowDevTools()
|
||||
{
|
||||
try
|
||||
{
|
||||
MainForm.Browser.ShowDevTools();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteAllCookies()
|
||||
{
|
||||
Cef.GetGlobalCookieManager().DeleteCookies();
|
||||
}
|
||||
|
||||
public string LoginWithSteam()
|
||||
{
|
||||
return VRChatRPC.Update()
|
||||
? VRChatRPC.GetAuthSessionTicket()
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
public bool IsGameRunning()
|
||||
{
|
||||
return WinApi.FindWindow("UnityWndClass", "VRChat") != IntPtr.Zero;
|
||||
}
|
||||
|
||||
public void StartGame(string location)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start("vrchat://launch?id=" + location);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowVRForm()
|
||||
{
|
||||
try
|
||||
{
|
||||
MainForm.Instance.BeginInvoke(new MethodInvoker(() =>
|
||||
{
|
||||
if (VRForm.Instance==null)
|
||||
{
|
||||
new VRForm().Show();
|
||||
}
|
||||
}));
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void StartVR()
|
||||
{
|
||||
VRCXVR.Start();
|
||||
}
|
||||
|
||||
public void StopVR()
|
||||
{
|
||||
VRCXVR.Stop();
|
||||
}
|
||||
|
||||
public void RefreshVR()
|
||||
{
|
||||
VRCXVR.Refresh();
|
||||
}
|
||||
|
||||
public string[][] GetVRDevices()
|
||||
{
|
||||
return VRCXVR.GetDevices();
|
||||
}
|
||||
|
||||
public float CpuUsage()
|
||||
{
|
||||
return CpuMonitor.CpuUsage;
|
||||
}
|
||||
}
|
||||
}
|
||||
211
VRCX.csproj
Normal file
@@ -0,0 +1,211 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="packages\CefSharp.OffScreen.73.1.130\build\CefSharp.OffScreen.props" Condition="Exists('packages\CefSharp.OffScreen.73.1.130\build\CefSharp.OffScreen.props')" />
|
||||
<Import Project="packages\CefSharp.WinForms.73.1.130\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.73.1.130\build\CefSharp.WinForms.props')" />
|
||||
<Import Project="packages\CefSharp.Common.73.1.130\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.73.1.130\build\CefSharp.Common.props')" />
|
||||
<Import Project="packages\cef.redist.x64.73.1.13\build\cef.redist.x64.props" Condition="Exists('packages\cef.redist.x64.73.1.13\build\cef.redist.x64.props')" />
|
||||
<Import Project="packages\cef.redist.x86.73.1.13\build\cef.redist.x86.props" Condition="Exists('packages\cef.redist.x86.73.1.13\build\cef.redist.x86.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>VRCX</RootNamespace>
|
||||
<AssemblyName>VRCX</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
<UpdateEnabled>false</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>vrchat.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="DiscordRPC, Version=1.0.121.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>packages\DiscordRichPresence.1.0.121\lib\net35\DiscordRPC.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SharpDX, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
|
||||
<HintPath>packages\SharpDX.4.2.0\lib\net45\SharpDX.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SharpDX.D3DCompiler, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
|
||||
<HintPath>packages\SharpDX.D3DCompiler.4.2.0\lib\net45\SharpDX.D3DCompiler.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SharpDX.Desktop, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
|
||||
<HintPath>packages\SharpDX.Desktop.4.2.0\lib\net45\SharpDX.Desktop.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SharpDX.Direct2D1, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
|
||||
<HintPath>packages\SharpDX.Direct2D1.4.2.0\lib\net45\SharpDX.Direct2D1.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SharpDX.Direct3D11, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
|
||||
<HintPath>packages\SharpDX.Direct3D11.4.2.0\lib\net45\SharpDX.Direct3D11.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SharpDX.DXGI, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
|
||||
<HintPath>packages\SharpDX.DXGI.4.2.0\lib\net45\SharpDX.DXGI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SharpDX.Mathematics, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
|
||||
<HintPath>packages\SharpDX.Mathematics.4.2.0\lib\net45\SharpDX.Mathematics.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Deployment" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Discord.cs" />
|
||||
<Compile Include="CpuMonitor.cs" />
|
||||
<Compile Include="Browser.cs" />
|
||||
<Compile Include="RenderHandler.cs" />
|
||||
<Compile Include="VRForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="VRForm.Designer.cs">
|
||||
<DependentUpon>VRForm.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="MainForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="MainForm.Designer.cs">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="VRCXVR.cs" />
|
||||
<Compile Include="openvr_api.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="LogWatcher.cs" />
|
||||
<Compile Include="VRChatRPC.cs" />
|
||||
<Compile Include="VRCX.cs" />
|
||||
<Compile Include="VRCXStorage.cs" />
|
||||
<Compile Include="JsonSerializer.cs" />
|
||||
<Compile Include="WinApi.cs" />
|
||||
<EmbeddedResource Include="VRForm.resx">
|
||||
<DependentUpon>VRForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="MainForm.resx">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<None Include="packages.config" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="vrchat.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include=".NETFramework,Version=v4.5.2">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Microsoft .NET Framework 4.5.2%28x86 및 x64%29</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>이 프로젝트는 이 컴퓨터에 없는 NuGet 패키지를 참조합니다. 해당 패키지를 다운로드하려면 NuGet 패키지 복원을 사용하십시오. 자세한 내용은 http://go.microsoft.com/fwlink/?LinkID=322105를 참조하십시오. 누락된 파일은 {0}입니다.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('packages\cef.redist.x86.73.1.13\build\cef.redist.x86.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x86.73.1.13\build\cef.redist.x86.props'))" />
|
||||
<Error Condition="!Exists('packages\cef.redist.x64.73.1.13\build\cef.redist.x64.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x64.73.1.13\build\cef.redist.x64.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.Common.73.1.130\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.73.1.130\build\CefSharp.Common.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.Common.73.1.130\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.73.1.130\build\CefSharp.Common.targets'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.WinForms.73.1.130\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.73.1.130\build\CefSharp.WinForms.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.WinForms.73.1.130\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.73.1.130\build\CefSharp.WinForms.targets'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.OffScreen.73.1.130\build\CefSharp.OffScreen.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.OffScreen.73.1.130\build\CefSharp.OffScreen.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.OffScreen.73.1.130\build\CefSharp.OffScreen.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.OffScreen.73.1.130\build\CefSharp.OffScreen.targets'))" />
|
||||
</Target>
|
||||
<Import Project="packages\CefSharp.Common.73.1.130\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.73.1.130\build\CefSharp.Common.targets')" />
|
||||
<Import Project="packages\CefSharp.WinForms.73.1.130\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.73.1.130\build\CefSharp.WinForms.targets')" />
|
||||
<Import Project="packages\CefSharp.OffScreen.73.1.130\build\CefSharp.OffScreen.targets" Condition="Exists('packages\CefSharp.OffScreen.73.1.130\build\CefSharp.OffScreen.targets')" />
|
||||
</Project>
|
||||
31
VRCX.sln
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.28307.168
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRCX", "VRCX.csproj", "{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Debug|x64.Build.0 = Debug|x64
|
||||
{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Release|x64.ActiveCfg = Release|x64
|
||||
{D9F66F2E-3ED9-4D53-A6AC-ADCC1513562A}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {797D384F-D118-4CBB-9450-17949F9EFCA4}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
67
VRCXStorage.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public class VRCXStorage
|
||||
{
|
||||
private static Dictionary<string, string> m_Storage = new Dictionary<string, string>();
|
||||
|
||||
public static void Load()
|
||||
{
|
||||
JsonSerializer.Deserialize(Application.StartupPath + "/VRCX.json", ref m_Storage);
|
||||
}
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
JsonSerializer.Serialize(Application.StartupPath + "/VRCX.json", m_Storage);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (m_Storage)
|
||||
{
|
||||
m_Storage.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
lock (m_Storage)
|
||||
{
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
lock (m_Storage)
|
||||
{
|
||||
return m_Storage.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public string Get(string key)
|
||||
{
|
||||
lock (m_Storage)
|
||||
{
|
||||
return m_Storage.TryGetValue(key, out string value)
|
||||
? value
|
||||
: string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public void Set(string key, string value)
|
||||
{
|
||||
lock (m_Storage)
|
||||
{
|
||||
m_Storage[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
584
VRCXVR.cs
Normal file
@@ -0,0 +1,584 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using CefSharp;
|
||||
using SharpDX;
|
||||
using SharpDX.Direct3D;
|
||||
using SharpDX.Direct3D11;
|
||||
using SharpDX.DXGI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using Valve.VR;
|
||||
using Device = SharpDX.Direct3D11.Device;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public static class VRCXVR
|
||||
{
|
||||
private static readonly object m_LockObject = new object();
|
||||
private static List<string[]> m_Devices = new List<string[]>();
|
||||
private static Thread m_Thread;
|
||||
private static Device m_Device;
|
||||
private static Texture2D m_Texture1;
|
||||
private static Texture2D m_Texture2;
|
||||
private static Browser m_Browser1;
|
||||
private static Browser m_Browser2;
|
||||
private static float[] m_Rotation = { 0f, 0f, 0f };
|
||||
private static float[] m_Translation = { 0f, 0f, 0f };
|
||||
private static float[] m_L_Translation = { -7f / 100f, -5f / 100f, 6f / 100f };
|
||||
private static float[] m_R_Translation = { 7f / 100f, -5f / 100f, 6f / 100f };
|
||||
private static float[] m_L_Rotation = { 90f * (float)(Math.PI / 180f), 90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f) };
|
||||
private static float[] m_R_Rotation = { -90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f) };
|
||||
|
||||
// NOTE
|
||||
// 메모리 릭 때문에 미리 생성해놓고 계속 사용함
|
||||
public static void Setup()
|
||||
{
|
||||
m_Device = new Device(DriverType.Hardware, DeviceCreationFlags.SingleThreaded | DeviceCreationFlags.BgraSupport);
|
||||
m_Texture1 = new Texture2D(m_Device, new Texture2DDescription()
|
||||
{
|
||||
Width = 512,
|
||||
Height = 512,
|
||||
MipLevels = 1,
|
||||
ArraySize = 1,
|
||||
Format = Format.B8G8R8A8_UNorm,
|
||||
SampleDescription = new SampleDescription(1, 0),
|
||||
Usage = ResourceUsage.Dynamic,
|
||||
BindFlags = BindFlags.ShaderResource,
|
||||
CpuAccessFlags = CpuAccessFlags.Write
|
||||
});
|
||||
m_Texture2 = new Texture2D(m_Device, new Texture2DDescription()
|
||||
{
|
||||
Width = 512,
|
||||
Height = 512,
|
||||
MipLevels = 1,
|
||||
ArraySize = 1,
|
||||
Format = Format.B8G8R8A8_UNorm,
|
||||
SampleDescription = new SampleDescription(1, 0),
|
||||
Usage = ResourceUsage.Dynamic,
|
||||
BindFlags = BindFlags.ShaderResource,
|
||||
CpuAccessFlags = CpuAccessFlags.Write
|
||||
});
|
||||
m_Browser1 = new Browser(m_Texture1, Application.StartupPath + "/html/vr.html?1");
|
||||
m_Browser2 = new Browser(m_Texture2, Application.StartupPath + "/html/vr.html?2");
|
||||
}
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
lock (m_LockObject)
|
||||
{
|
||||
if (m_Thread == null)
|
||||
{
|
||||
m_Thread = new Thread(ThreadProc);
|
||||
m_Thread.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
lock (m_LockObject)
|
||||
{
|
||||
var thread = m_Thread;
|
||||
if (thread != null)
|
||||
{
|
||||
m_Thread = null;
|
||||
try
|
||||
{
|
||||
thread.Interrupt();
|
||||
thread.Join();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Refresh()
|
||||
{
|
||||
m_Browser1.ExecuteScriptAsync("location.reload()");
|
||||
m_Browser2.ExecuteScriptAsync("location.reload()");
|
||||
}
|
||||
|
||||
public static string[][] GetDevices()
|
||||
{
|
||||
lock (m_Devices)
|
||||
{
|
||||
return m_Devices.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateDevices(CVRSystem system, ref uint trackingIndex)
|
||||
{
|
||||
lock (m_Devices)
|
||||
{
|
||||
m_Devices.Clear();
|
||||
var sb = new StringBuilder(256);
|
||||
var state = new VRControllerState_t();
|
||||
for (var i = 0u; i < OpenVR.k_unMaxTrackedDeviceCount; ++i)
|
||||
{
|
||||
var devClass = system.GetTrackedDeviceClass(i);
|
||||
if (devClass == ETrackedDeviceClass.Controller ||
|
||||
devClass == ETrackedDeviceClass.GenericTracker)
|
||||
{
|
||||
var err = ETrackedPropertyError.TrackedProp_Success;
|
||||
var batteryPercentage = system.GetFloatTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float, ref err);
|
||||
if (err != ETrackedPropertyError.TrackedProp_Success)
|
||||
{
|
||||
batteryPercentage = 1f;
|
||||
}
|
||||
sb.Clear();
|
||||
system.GetStringTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_TrackingSystemName_String, sb, (uint)sb.Capacity, ref err);
|
||||
var isOculus = sb.ToString().IndexOf("oculus", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
// Oculus : B/Y, Bit 1, Mask 2
|
||||
// Oculus : A/X, Bit 7, Mask 128
|
||||
// Vive : Menu, Bit 1, Mask 2,
|
||||
// Vive : Grip, Bit 2, Mask 4
|
||||
var role = system.GetControllerRoleForTrackedDeviceIndex(i);
|
||||
if (role == ETrackedControllerRole.LeftHand ||
|
||||
role == ETrackedControllerRole.RightHand)
|
||||
{
|
||||
if (system.GetControllerState(i, ref state, (uint)Marshal.SizeOf(state)) &&
|
||||
(state.ulButtonPressed & (isOculus ? 2u : 4u)) != 0)
|
||||
{
|
||||
if (role == ETrackedControllerRole.LeftHand)
|
||||
{
|
||||
Array.Copy(m_L_Translation, m_Translation, 3);
|
||||
Array.Copy(m_L_Rotation, m_Rotation, 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(m_R_Translation, m_Translation, 3);
|
||||
Array.Copy(m_R_Rotation, m_Rotation, 3);
|
||||
}
|
||||
trackingIndex = i;
|
||||
}
|
||||
}
|
||||
var type = string.Empty;
|
||||
if (devClass == ETrackedDeviceClass.Controller)
|
||||
{
|
||||
if (role == ETrackedControllerRole.LeftHand)
|
||||
{
|
||||
type = "leftController";
|
||||
}
|
||||
else if (role == ETrackedControllerRole.RightHand)
|
||||
{
|
||||
type = "rightController";
|
||||
}
|
||||
else
|
||||
{
|
||||
type = "controller";
|
||||
}
|
||||
}
|
||||
else if (devClass == ETrackedDeviceClass.GenericTracker)
|
||||
{
|
||||
type = "tracker";
|
||||
}
|
||||
m_Devices.Add(new[]
|
||||
{
|
||||
type,
|
||||
system.IsTrackedDeviceConnected(i)
|
||||
? "connected"
|
||||
: "disconnected",
|
||||
(batteryPercentage * 100).ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static EVROverlayError ProcessDashboard(CVROverlay overlay, ref ulong dashboardHandle, bool dashboardVisible)
|
||||
{
|
||||
var err = EVROverlayError.None;
|
||||
|
||||
if (dashboardHandle == 0)
|
||||
{
|
||||
err = overlay.FindOverlay("VRCX", ref dashboardHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
if (err != EVROverlayError.UnknownOverlay)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
ulong handle = 0;
|
||||
err = overlay.CreateDashboardOverlay("VRCX", "VRCX", ref dashboardHandle, ref handle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
err = overlay.SetOverlayWidthInMeters(dashboardHandle, 1.5f);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
err = overlay.SetOverlayInputMethod(dashboardHandle, VROverlayInputMethod.Mouse);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var e = new VREvent_t();
|
||||
|
||||
while (overlay.PollNextOverlayEvent(dashboardHandle, ref e, (uint)Marshal.SizeOf(e)))
|
||||
{
|
||||
var type = (EVREventType)e.eventType;
|
||||
if (type == EVREventType.VREvent_MouseMove)
|
||||
{
|
||||
var m = e.data.mouse;
|
||||
var s = m_Browser1.Size;
|
||||
m_Browser1.GetBrowserHost().SendMouseMoveEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), false, CefEventFlags.None);
|
||||
}
|
||||
else if (type == EVREventType.VREvent_MouseButtonDown)
|
||||
{
|
||||
var m = e.data.mouse;
|
||||
var s = m_Browser1.Size;
|
||||
m_Browser1.GetBrowserHost().SendMouseClickEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), MouseButtonType.Left, false, 1, CefEventFlags.LeftMouseButton);
|
||||
}
|
||||
else if (type == EVREventType.VREvent_MouseButtonUp)
|
||||
{
|
||||
var m = e.data.mouse;
|
||||
var s = m_Browser1.Size;
|
||||
m_Browser1.GetBrowserHost().SendMouseClickEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), MouseButtonType.Left, true, 1, CefEventFlags.None);
|
||||
}
|
||||
}
|
||||
|
||||
if (dashboardVisible)
|
||||
{
|
||||
var texture = new Texture_t
|
||||
{
|
||||
handle = m_Texture1.NativePointer
|
||||
};
|
||||
err = overlay.SetOverlayTexture(dashboardHandle, ref texture);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
private static EVROverlayError ProcessOverlay1(CVROverlay overlay, ref ulong overlayHandle, ref bool overlayVisible, bool dashboardVisible, uint trackingIndex, DateTime nextRender)
|
||||
{
|
||||
var err = EVROverlayError.None;
|
||||
|
||||
if (overlayHandle == 0)
|
||||
{
|
||||
err = overlay.FindOverlay("VRCX1", ref overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
if (err != EVROverlayError.UnknownOverlay)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
overlayVisible = false;
|
||||
err = overlay.CreateOverlay("VRCX1", "VRCX1", ref overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
err = overlay.SetOverlayAlpha(overlayHandle, 0.9f);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
err = overlay.SetOverlayWidthInMeters(overlayHandle, 1f);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
err = overlay.SetOverlayInputMethod(overlayHandle, VROverlayInputMethod.None);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trackingIndex != OpenVR.k_unTrackedDeviceIndexInvalid)
|
||||
{
|
||||
// http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices
|
||||
// Scaling-Rotation-Translation
|
||||
var m = Matrix.Scaling(0.25f);
|
||||
m *= Matrix.RotationX(m_Rotation[0]);
|
||||
m *= Matrix.RotationY(m_Rotation[1]);
|
||||
m *= Matrix.RotationZ(m_Rotation[2]);
|
||||
m *= Matrix.Translation(m_Translation[0], m_Translation[1], m_Translation[2]);
|
||||
var hm34 = new HmdMatrix34_t
|
||||
{
|
||||
m0 = m.M11,
|
||||
m1 = m.M21,
|
||||
m2 = m.M31,
|
||||
m3 = m.M41,
|
||||
m4 = m.M12,
|
||||
m5 = m.M22,
|
||||
m6 = m.M32,
|
||||
m7 = m.M42,
|
||||
m8 = m.M13,
|
||||
m9 = m.M23,
|
||||
m10 = m.M33,
|
||||
m11 = m.M43,
|
||||
};
|
||||
err = overlay.SetOverlayTransformTrackedDeviceRelative(overlayHandle, trackingIndex, ref hm34);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dashboardVisible &&
|
||||
DateTime.Now.CompareTo(nextRender) <= 0)
|
||||
{
|
||||
var texture = new Texture_t
|
||||
{
|
||||
handle = m_Texture1.NativePointer
|
||||
};
|
||||
err = overlay.SetOverlayTexture(overlayHandle, ref texture);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
if (!overlayVisible)
|
||||
{
|
||||
err = overlay.ShowOverlay(overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
overlayVisible = true;
|
||||
}
|
||||
}
|
||||
else if (overlayVisible)
|
||||
{
|
||||
err = overlay.HideOverlay(overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
overlayVisible = false;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
private static EVROverlayError ProcessOverlay2(CVROverlay overlay, ref ulong overlayHandle, ref bool overlayVisible, bool dashboardVisible)
|
||||
{
|
||||
var err = EVROverlayError.None;
|
||||
|
||||
if (overlayHandle == 0)
|
||||
{
|
||||
err = overlay.FindOverlay("VRCX2", ref overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
if (err != EVROverlayError.UnknownOverlay)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
overlayVisible = false;
|
||||
err = overlay.CreateOverlay("VRCX2", "VRCX2", ref overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
err = overlay.SetOverlayAlpha(overlayHandle, 0.9f);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
err = overlay.SetOverlayWidthInMeters(overlayHandle, 1f);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
err = overlay.SetOverlayInputMethod(overlayHandle, VROverlayInputMethod.None);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
var m = Matrix.Scaling(1f);
|
||||
m *= Matrix.Translation(0, -0.3f, -1.5f);
|
||||
var hm34 = new HmdMatrix34_t
|
||||
{
|
||||
m0 = m.M11,
|
||||
m1 = m.M21,
|
||||
m2 = m.M31,
|
||||
m3 = m.M41,
|
||||
m4 = m.M12,
|
||||
m5 = m.M22,
|
||||
m6 = m.M32,
|
||||
m7 = m.M42,
|
||||
m8 = m.M13,
|
||||
m9 = m.M23,
|
||||
m10 = m.M33,
|
||||
m11 = m.M43,
|
||||
};
|
||||
err = overlay.SetOverlayTransformTrackedDeviceRelative(overlayHandle, OpenVR.k_unTrackedDeviceIndex_Hmd, ref hm34);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dashboardVisible)
|
||||
{
|
||||
var texture = new Texture_t
|
||||
{
|
||||
handle = m_Texture2.NativePointer
|
||||
};
|
||||
err = overlay.SetOverlayTexture(overlayHandle, ref texture);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
if (!overlayVisible)
|
||||
{
|
||||
err = overlay.ShowOverlay(overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
overlayVisible = true;
|
||||
}
|
||||
}
|
||||
else if (overlayVisible)
|
||||
{
|
||||
err = overlay.HideOverlay(overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
overlayVisible = false;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
private static void ThreadProc()
|
||||
{
|
||||
var e = new VREvent_t();
|
||||
var nextOpenVRInit = DateTime.MinValue;
|
||||
var nextDeviceInfoUpdate = DateTime.MinValue;
|
||||
var nextRender = DateTime.MinValue;
|
||||
var trackingIndex = OpenVR.k_unTrackedDeviceIndexInvalid;
|
||||
var overlayVisible1 = false;
|
||||
var overlayVisible2 = false;
|
||||
var dashboardHandle = 0UL;
|
||||
var overlayHandle1 = 0UL;
|
||||
var overlayHandle2 = 0UL;
|
||||
|
||||
while (m_Thread != null)
|
||||
{
|
||||
m_Browser1.Render();
|
||||
m_Browser2.Render();
|
||||
|
||||
try
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ThreadInterruptedException
|
||||
}
|
||||
|
||||
var system = OpenVR.System;
|
||||
|
||||
if (system == null)
|
||||
{
|
||||
if (DateTime.Now.CompareTo(nextOpenVRInit) < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var _err = EVRInitError.None;
|
||||
system = OpenVR.Init(ref _err, EVRApplicationType.VRApplication_Overlay);
|
||||
nextOpenVRInit = DateTime.Now.AddSeconds(5);
|
||||
if (system == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
while (system.PollNextEvent(ref e, (uint)Marshal.SizeOf(e)))
|
||||
{
|
||||
var type = (EVREventType)e.eventType;
|
||||
if (type == EVREventType.VREvent_Quit)
|
||||
{
|
||||
OpenVR.Shutdown();
|
||||
// VRChat이 실행 중일 때만 켜는 옵션이 생겨서 시간을 줄임
|
||||
nextOpenVRInit = DateTime.Now.AddSeconds(10);
|
||||
system = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (system == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (DateTime.Now.CompareTo(nextDeviceInfoUpdate) >= 0)
|
||||
{
|
||||
trackingIndex = OpenVR.k_unTrackedDeviceIndexInvalid;
|
||||
UpdateDevices(system, ref trackingIndex);
|
||||
if (trackingIndex != OpenVR.k_unTrackedDeviceIndexInvalid)
|
||||
{
|
||||
nextRender = DateTime.Now.AddSeconds(10);
|
||||
}
|
||||
nextDeviceInfoUpdate = DateTime.Now.AddSeconds(0.1);
|
||||
}
|
||||
|
||||
var overlay = OpenVR.Overlay;
|
||||
|
||||
if (overlay == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var dashboardVisible = overlay.IsDashboardVisible();
|
||||
|
||||
var err = ProcessDashboard(overlay, ref dashboardHandle, dashboardVisible);
|
||||
|
||||
if (err != EVROverlayError.None &&
|
||||
dashboardHandle != 0)
|
||||
{
|
||||
overlay.DestroyOverlay(dashboardHandle);
|
||||
dashboardHandle = 0;
|
||||
}
|
||||
|
||||
err = ProcessOverlay1(overlay, ref overlayHandle1, ref overlayVisible1, dashboardVisible, trackingIndex, nextRender);
|
||||
|
||||
if (err != EVROverlayError.None &&
|
||||
overlayHandle1 != 0)
|
||||
{
|
||||
overlay.DestroyOverlay(overlayHandle1);
|
||||
overlayHandle1 = 0;
|
||||
}
|
||||
|
||||
err = ProcessOverlay2(overlay, ref overlayHandle2, ref overlayVisible2, dashboardVisible);
|
||||
|
||||
if (err != EVROverlayError.None &&
|
||||
overlayHandle2 != 0)
|
||||
{
|
||||
overlay.DestroyOverlay(overlayHandle2);
|
||||
overlayHandle2 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
lock (m_Devices)
|
||||
{
|
||||
m_Devices.Clear();
|
||||
}
|
||||
|
||||
OpenVR.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
55
VRChatRPC.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public static class VRChatRPC
|
||||
{
|
||||
[DllImport("VRChatRPC", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool VRChatRPC_000();
|
||||
|
||||
[DllImport("VRChatRPC", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern int VRChatRPC_001([Out] byte[] data, int size);
|
||||
|
||||
[DllImport("VRChatRPC", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern IntPtr VRChatRPC_002();
|
||||
|
||||
public static bool Update()
|
||||
{
|
||||
return VRChatRPC_000();
|
||||
}
|
||||
|
||||
public static string GetAuthSessionTicket()
|
||||
{
|
||||
var a = new byte[1024];
|
||||
var n = VRChatRPC_001(a, 1024);
|
||||
return BitConverter.ToString(a, 0, n).Replace("-", string.Empty);
|
||||
}
|
||||
|
||||
public static string GetPersonaName()
|
||||
{
|
||||
var ptr = VRChatRPC_002();
|
||||
if (ptr != IntPtr.Zero)
|
||||
{
|
||||
int n = 0;
|
||||
while (Marshal.ReadByte(ptr, n) != 0)
|
||||
{
|
||||
++n;
|
||||
}
|
||||
if (n > 0)
|
||||
{
|
||||
var a = new byte[n];
|
||||
Marshal.Copy(ptr, a, 0, a.Length);
|
||||
return Encoding.UTF8.GetString(a);
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
VRChatRPC/README.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
it grabs some data from VRChat process.. use at own risk.
|
||||
|
||||
by pypy (mina#5656)
|
||||
BIN
VRChatRPC/VRChatRPC.dll
Normal file
104
VRForm.Designer.cs
generated
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
partial class VRForm
|
||||
{
|
||||
/// <summary>
|
||||
/// 필수 디자이너 변수입니다.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// 사용 중인 모든 리소스를 정리합니다.
|
||||
/// </summary>
|
||||
/// <param name="disposing">관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form 디자이너에서 생성한 코드
|
||||
|
||||
/// <summary>
|
||||
/// 디자이너 지원에 필요한 메서드입니다.
|
||||
/// 이 메서드의 내용을 코드 편집기로 수정하지 마세요.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.timer = new System.Windows.Forms.Timer(this.components);
|
||||
this.panel1 = new System.Windows.Forms.Panel();
|
||||
this.panel2 = new System.Windows.Forms.Panel();
|
||||
this.button_refresh = new System.Windows.Forms.Button();
|
||||
this.button_devtools = new System.Windows.Forms.Button();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// panel1
|
||||
//
|
||||
this.panel1.Location = new System.Drawing.Point(0, 0);
|
||||
this.panel1.Name = "panel1";
|
||||
this.panel1.Size = new System.Drawing.Size(512, 512);
|
||||
this.panel1.TabIndex = 0;
|
||||
//
|
||||
// panel2
|
||||
//
|
||||
this.panel2.Location = new System.Drawing.Point(518, 0);
|
||||
this.panel2.Name = "panel2";
|
||||
this.panel2.Size = new System.Drawing.Size(512, 512);
|
||||
this.panel2.TabIndex = 1;
|
||||
//
|
||||
// button_refresh
|
||||
//
|
||||
this.button_refresh.Location = new System.Drawing.Point(12, 518);
|
||||
this.button_refresh.Name = "button_refresh";
|
||||
this.button_refresh.Size = new System.Drawing.Size(75, 23);
|
||||
this.button_refresh.TabIndex = 27;
|
||||
this.button_refresh.Text = "Refresh";
|
||||
this.button_refresh.UseVisualStyleBackColor = true;
|
||||
this.button_refresh.Click += new System.EventHandler(this.button_refresh_Click);
|
||||
//
|
||||
// button_devtools
|
||||
//
|
||||
this.button_devtools.Location = new System.Drawing.Point(93, 518);
|
||||
this.button_devtools.Name = "button_devtools";
|
||||
this.button_devtools.Size = new System.Drawing.Size(75, 23);
|
||||
this.button_devtools.TabIndex = 27;
|
||||
this.button_devtools.Text = "DevTools";
|
||||
this.button_devtools.UseVisualStyleBackColor = true;
|
||||
this.button_devtools.Click += new System.EventHandler(this.button_devtools_Click);
|
||||
//
|
||||
// VRForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(1038, 553);
|
||||
this.Controls.Add(this.button_devtools);
|
||||
this.Controls.Add(this.button_refresh);
|
||||
this.Controls.Add(this.panel2);
|
||||
this.Controls.Add(this.panel1);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.MaximizeBox = false;
|
||||
this.Name = "VRForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "VR";
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Timer timer;
|
||||
private System.Windows.Forms.Panel panel1;
|
||||
private System.Windows.Forms.Panel panel2;
|
||||
private System.Windows.Forms.Button button_refresh;
|
||||
private System.Windows.Forms.Button button_devtools;
|
||||
}
|
||||
}
|
||||
74
VRForm.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public partial class VRForm : Form
|
||||
{
|
||||
public static VRForm Instance { get; private set; }
|
||||
public static ChromiumWebBrowser Browser1 { get; private set; }
|
||||
public static ChromiumWebBrowser Browser2 { get; private set; }
|
||||
|
||||
public VRForm()
|
||||
{
|
||||
Instance = this;
|
||||
InitializeComponent();
|
||||
//
|
||||
Browser1 = new ChromiumWebBrowser(Application.StartupPath + "/html/vr.html?1")
|
||||
{
|
||||
BrowserSettings =
|
||||
{
|
||||
// UniversalAccessFromFileUrls = CefState.Enabled,
|
||||
DefaultEncoding = "UTF-8",
|
||||
},
|
||||
Dock = DockStyle.Fill,
|
||||
};
|
||||
Browser2 = new ChromiumWebBrowser(Application.StartupPath + "/html/vr.html?2")
|
||||
{
|
||||
BrowserSettings =
|
||||
{
|
||||
// UniversalAccessFromFileUrls = CefState.Enabled,
|
||||
DefaultEncoding = "UTF-8",
|
||||
},
|
||||
Dock = DockStyle.Fill,
|
||||
};
|
||||
var options = new BindingOptions()
|
||||
{
|
||||
CamelCaseJavascriptNames = false
|
||||
};
|
||||
Browser1.JavascriptObjectRepository.Register("VRCX", new VRCX(), true, options);
|
||||
Browser1.JavascriptObjectRepository.Register("VRCXStorage", new VRCXStorage(), false, options);
|
||||
Browser2.JavascriptObjectRepository.Register("VRCX", new VRCX(), true, options);
|
||||
Browser2.JavascriptObjectRepository.Register("VRCXStorage", new VRCXStorage(), false, options);
|
||||
Browser1.IsBrowserInitializedChanged += (A, B) =>
|
||||
{
|
||||
// Browser1.ShowDevTools();
|
||||
};
|
||||
Browser2.IsBrowserInitializedChanged += (A, B) =>
|
||||
{
|
||||
// Browser2.ShowDevTools();
|
||||
};
|
||||
panel1.Controls.Add(Browser1);
|
||||
panel2.Controls.Add(Browser2);
|
||||
}
|
||||
|
||||
private void button_refresh_Click(object sender, System.EventArgs e)
|
||||
{
|
||||
VRCXVR.Refresh();
|
||||
Browser1.ExecuteScriptAsync("location.reload()");
|
||||
Browser2.ExecuteScriptAsync("location.reload()");
|
||||
}
|
||||
|
||||
private void button_devtools_Click(object sender, System.EventArgs e)
|
||||
{
|
||||
Browser1.ShowDevTools();
|
||||
Browser2.ShowDevTools();
|
||||
}
|
||||
}
|
||||
}
|
||||
123
VRForm.resx
Normal file
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="timer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
19
WinApi.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public static class WinApi
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError = false)]
|
||||
public static extern void CopyMemory(IntPtr destination, IntPtr source, uint length);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
||||
}
|
||||
}
|
||||
67
html/.eslintrc.js
Normal file
@@ -0,0 +1,67 @@
|
||||
module.exports = {
|
||||
'env': {
|
||||
'browser': true,
|
||||
'es6': true,
|
||||
'jquery': true
|
||||
},
|
||||
'extends': 'eslint:all',
|
||||
'globals': {
|
||||
'CefSharp': 'readonly',
|
||||
'VRCX': 'readonly',
|
||||
'VRCXStorage': 'readonly',
|
||||
'LogWatcher': 'readonly',
|
||||
'Discord': 'readonly',
|
||||
'Noty': 'readonly',
|
||||
'Vue': 'readonly',
|
||||
'VueLazyload': 'readonly',
|
||||
'DataTables': 'readonly',
|
||||
'ELEMENT': 'readonly'
|
||||
},
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 9
|
||||
},
|
||||
'root': true,
|
||||
'rules': {
|
||||
'camelcase': 0,
|
||||
'capitalized-comments': 0,
|
||||
'complexity': 0,
|
||||
'default-case': 0,
|
||||
'func-names': 0,
|
||||
'guard-for-in': 0,
|
||||
'id-length': 0,
|
||||
'indent': 0,
|
||||
'init-declarations': 0,
|
||||
'linebreak-style': 0,
|
||||
'max-depth': 0,
|
||||
'max-len': 0,
|
||||
'max-lines': 0,
|
||||
'max-lines-per-function': 0,
|
||||
'max-statements': 0,
|
||||
'multiline-comment-style': 0,
|
||||
'newline-per-chained-call': 0,
|
||||
'new-cap': 0,
|
||||
'no-console': 0,
|
||||
'no-empty': ['error', { 'allowEmptyCatch': true }],
|
||||
'no-magic-numbers': 0,
|
||||
'no-mixed-operators': 0,
|
||||
'no-nested-ternary': 0,
|
||||
'no-plusplus': 0,
|
||||
'no-tabs': 0,
|
||||
'no-ternary': 0,
|
||||
'no-throw-literal': 0,
|
||||
'no-undefined': 0,
|
||||
'no-underscore-dangle': 0,
|
||||
'no-var': 0,
|
||||
'no-warning-comments': 0,
|
||||
'object-curly-spacing': ['error', 'always'],
|
||||
'one-var': 0,
|
||||
'padded-blocks': 0,
|
||||
'prefer-named-capture-group': 0,
|
||||
'quotes': ['error', 'single', { 'avoidEscape': true }],
|
||||
'quote-props': 0,
|
||||
'sort-keys': 0,
|
||||
'space-before-function-paren': ['error', { 'named': 'never' }],
|
||||
'strict': 0,
|
||||
'vars-on-top': 0
|
||||
}
|
||||
};
|
||||
413
html/app.css
Normal file
@@ -0,0 +1,413 @@
|
||||
@charset "utf-8";
|
||||
/*
|
||||
Copyright(c) 2019 pypy. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
*/
|
||||
|
||||
.noty_layout {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.noty_theme__mint.noty_bar {
|
||||
margin: 4px 0;
|
||||
overflow: hidden;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.noty_theme__mint.noty_bar .noty_body {
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.noty_theme__mint.noty_bar .noty_buttons {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.noty_theme__mint.noty_type__alert, .noty_theme__mint.noty_type__notification {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #D1D1D1;
|
||||
color: #2F2F2F;
|
||||
}
|
||||
|
||||
.noty_theme__mint.noty_type__warning {
|
||||
background-color: #FFAE42;
|
||||
border-bottom: 1px solid #E89F3C;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.noty_theme__mint.noty_type__error {
|
||||
background-color: #DE636F;
|
||||
border-bottom: 1px solid #CA5A65;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.noty_theme__mint.noty_type__info, .noty_theme__mint.noty_type__information {
|
||||
background-color: #7F7EFF;
|
||||
border-bottom: 1px solid #7473E8;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.noty_theme__mint.noty_type__success {
|
||||
background-color: #AFC765;
|
||||
border-bottom: 1px solid #A0B55C;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.el-table+.pagination-bar {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
body, input, textarea, select, button {
|
||||
font-family: 'Noto Sans JP', 'Noto Sans KR', 'Meiryo UI', 'Malgun Gothic', 'Segoe UI', sans-serif;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.x-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.x-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.x-ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.x-app {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden auto;
|
||||
}
|
||||
|
||||
.x-container {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
overflow: hidden auto;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.x-login-container {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
/* modal 시작이 2000이라서 */
|
||||
z-index: 1999;
|
||||
}
|
||||
|
||||
.x-menu-container {
|
||||
flex: none;
|
||||
overflow: hidden auto;
|
||||
background: #383838;
|
||||
}
|
||||
|
||||
.x-menu-container>.el-menu {
|
||||
background: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.el-menu-item.notify::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: #909399;
|
||||
border-radius: 50%
|
||||
}
|
||||
|
||||
.x-aside-container {
|
||||
flex: none;
|
||||
width: 236px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.el-popper.x-quick-search {
|
||||
min-width: 0 !important;
|
||||
width: 225px;
|
||||
}
|
||||
|
||||
.el-popper.x-quick-search .el-select-dropdown__item {
|
||||
padding: 0 10px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
font-size: 12px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.x-friend-list {
|
||||
overflow: hidden auto;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.x-friend-group>.el-icon-arrow-right {
|
||||
transition: transform .3s;
|
||||
}
|
||||
|
||||
.x-friend-group>.el-icon-arrow-right.rotate {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.x-aside-container .x-friend-list {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.x-dialog .x-friend-list {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
max-height: 150px;
|
||||
}
|
||||
|
||||
.x-friend-list>.x-friend-group {
|
||||
padding: 20px 0 5px;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.x-friend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.x-friend-item:hover {
|
||||
background: #eee;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.x-aside-container .x-friend-item:hover {
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item .x-friend-item:hover {
|
||||
background: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.x-dialog .x-friend-item {
|
||||
width: 175px;
|
||||
}
|
||||
|
||||
.x-friend-item>.avatar {
|
||||
flex: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 8px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.x-friend-item>img.avatar {
|
||||
width: 50px;
|
||||
height: 37.5px;
|
||||
margin-left: 5px;
|
||||
margin-right: 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.x-friend-item>.avatar>img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 40%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.x-friend-item>.avatar.offline>img {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.x-friend-item:hover>.avatar.offline>img {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.x-friend-item>.avatar.active::after, .x-friend-item>.avatar.joinme::after, .x-friend-item>.avatar.busy::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #fff;
|
||||
background: #909399;
|
||||
}
|
||||
|
||||
.x-friend-item>.avatar.active::after {
|
||||
background: #67C23A;
|
||||
}
|
||||
|
||||
.x-friend-item>.avatar.joinme::after {
|
||||
background: #409EFF;
|
||||
}
|
||||
|
||||
.x-friend-item>.avatar.busy::after {
|
||||
background: #F56C6C;
|
||||
}
|
||||
|
||||
.x-friend-item.offline>.avatar::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.x-friend-item>.detail {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.x-friend-item>.detail>.name, .x-friend-item>.detail>.extra {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.x-friend-item>.detail>.name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.x-friend-item>.detail>.extra {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.x-dialog .el-dialog {
|
||||
margin-bottom: 10px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.x-user-dialog .el-dialog__header, .x-world-dialog .el-dialog__header, .x-avatar-dialog .el-dialog__header {
|
||||
padding: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.x-user-dialog .el-dialog__body, .x-world-dialog .el-dialog__body, .x-avatar-dialog .el-dialog__body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.el-popper.hex {
|
||||
font-family: monospace;
|
||||
text-align: center;
|
||||
min-width: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
i.x-user-status {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: gray;
|
||||
}
|
||||
|
||||
i.x-user-status.active {
|
||||
background: #67C23A;
|
||||
}
|
||||
|
||||
i.x-user-status.joinme {
|
||||
background: #409EFF;
|
||||
}
|
||||
|
||||
i.x-user-status.busy {
|
||||
background: #F56C6C;
|
||||
}
|
||||
|
||||
.el-tag.x-tag-vip {
|
||||
border-color: rgb(181, 38, 38);
|
||||
color: rgb(181, 38, 38);
|
||||
}
|
||||
|
||||
.el-tag.x-tag-friend {
|
||||
/*border-color: rgb(255, 255, 0);
|
||||
color: rgb(255, 255, 0);*/
|
||||
border-color: rgb(255, 208, 0);
|
||||
color: rgb(255, 208, 0);
|
||||
}
|
||||
|
||||
.el-tag.x-tag-untrusted {
|
||||
border-color: rgb(204, 204, 204);
|
||||
color: rgb(204, 204, 204);
|
||||
}
|
||||
|
||||
.el-tag.x-tag-basic {
|
||||
border-color: rgb(23, 120, 255);
|
||||
color: rgb(23, 120, 255);
|
||||
}
|
||||
|
||||
.el-tag.x-tag-known {
|
||||
border-color: rgb(43, 207, 92);
|
||||
color: rgb(43, 207, 92);
|
||||
}
|
||||
|
||||
.el-tag.x-tag-trusted {
|
||||
border-color: rgb(255, 123, 66);
|
||||
color: rgb(255, 123, 66);
|
||||
}
|
||||
|
||||
.el-tag.x-tag-veteran {
|
||||
border-color: rgb(129, 67, 230);
|
||||
color: rgb(129, 67, 230);
|
||||
}
|
||||
|
||||
.el-tag.x-tag-legend {
|
||||
/*border-color: rgb(255, 255, 0);
|
||||
color: rgb(255, 255, 0);*/
|
||||
border-color: rgb(255, 208, 0);
|
||||
color: rgb(255, 208, 0);
|
||||
}
|
||||
|
||||
.el-tag.x-tag-troll {
|
||||
border-color: rgb(120, 47, 47);
|
||||
color: rgb(120, 47, 47);
|
||||
}
|
||||
|
||||
.el-tag.x-tag-legendary {
|
||||
border-color: rgb(0, 0, 0);
|
||||
color: rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
.x-dialog .el-tree {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.x-dialog .el-tree-node {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.x-dialog .el-tree-node__content {
|
||||
height: auto;
|
||||
}
|
||||
6645
html/app.js
Normal file
BIN
html/images/controller_status_off.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
html/images/controller_status_ready.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
html/images/controller_status_ready_low.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
html/images/other_status_off.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
html/images/other_status_ready.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
html/images/other_status_standby.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
html/images/tracker_status_off.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
html/images/tracker_status_ready.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
html/images/tracker_status_ready_low.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
1722
html/index.html
Normal file
222
html/vr.css
Normal file
@@ -0,0 +1,222 @@
|
||||
@charset "utf-8";
|
||||
/*
|
||||
Copyright(c) 2019 pypy. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
*/
|
||||
|
||||
/*
|
||||
마지노선인듯
|
||||
화면 24px -> 나나 32
|
||||
손등 18px -> 나나 24
|
||||
*/
|
||||
|
||||
.noty_body {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.noty_layout {
|
||||
max-width: none;
|
||||
width: 80% !important;
|
||||
}
|
||||
|
||||
.noty_theme__relax.noty_bar {
|
||||
margin: 4px 0;
|
||||
overflow: hidden;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.noty_theme__relax.noty_bar .noty_body {
|
||||
padding: 5px 10px 10px;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.noty_theme__relax.noty_bar .noty_buttons {
|
||||
border-top: 1px solid #e7e7e7;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.noty_theme__relax.noty_type__alert, .noty_theme__relax.noty_type__notification {
|
||||
background-color: #fff;
|
||||
border: 1px solid #dedede;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.noty_theme__relax.noty_type__warning {
|
||||
background-color: #FFEAA8;
|
||||
border: 1px solid #FFC237;
|
||||
color: #826200;
|
||||
}
|
||||
|
||||
.noty_theme__relax.noty_type__warning .noty_buttons {
|
||||
border-color: #dfaa30;
|
||||
}
|
||||
|
||||
.noty_theme__relax.noty_type__error {
|
||||
background-color: #FF8181;
|
||||
border: 1px solid #e25353;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.noty_theme__relax.noty_type__error .noty_buttons {
|
||||
border-color: darkred;
|
||||
}
|
||||
|
||||
.noty_theme__relax.noty_type__info, .noty_theme__relax.noty_type__information {
|
||||
background-color: #78C5E7;
|
||||
border: 1px solid #3badd6;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.noty_theme__relax.noty_type__info .noty_buttons, .noty_theme__relax.noty_type__information .noty_buttons {
|
||||
border-color: #0B90C4;
|
||||
}
|
||||
|
||||
.noty_theme__relax.noty_type__success {
|
||||
background-color: #BCF5BC;
|
||||
border: 1px solid #7cdd77;
|
||||
color: darkgreen;
|
||||
}
|
||||
|
||||
.noty_theme__relax.noty_type__success .noty_buttons {
|
||||
border-color: #50C24E;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
body, input, textarea, select, button {
|
||||
font-family: 'Noto Sans JP', 'Noto Sans KR', 'Meiryo UI', 'Malgun Gothic', 'Segoe UI', sans-serif;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.x-app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.x-app-type {
|
||||
background: #1f1f1f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.x-container {
|
||||
flex: none;
|
||||
padding: 10px;
|
||||
overflow: hidden auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.x-friend-list {
|
||||
overflow: hidden auto;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.x-friend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.x-friend-item .time {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.x-friend-item .name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.x-friend-item.friend .name {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.x-friend-item.favorite .name {
|
||||
color: #ff0;
|
||||
}
|
||||
|
||||
.x-friend-item>.avatar {
|
||||
flex: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 8px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.x-friend-item>img.avatar {
|
||||
width: 50px;
|
||||
height: 37.5px;
|
||||
margin-left: 5px;
|
||||
margin-right: 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.x-friend-item>.avatar>img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 40%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.x-friend-item>.detail {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.x-friend-item>.detail>.name, .x-friend-item>.detail>.extra {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.x-friend-item>.detail>.name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.x-friend-item>.detail>.extra {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
i.x-user-status {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
background: gray;
|
||||
}
|
||||
|
||||
i.x-user-status.active {
|
||||
background: #67C23A;
|
||||
}
|
||||
|
||||
i.x-user-status.joinme {
|
||||
background: #409EFF;
|
||||
}
|
||||
|
||||
i.x-user-status.busy {
|
||||
background: #F56C6C;
|
||||
}
|
||||
144
html/vr.html
Normal file
@@ -0,0 +1,144 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||||
<title>VRCXVR</title>
|
||||
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
|
||||
<link rel="preconnect" href="https://api.vrchat.cloud">
|
||||
<link rel="preconnect" href="https://d348imysud55la.cloudfront.net">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/noty/3.2.0-beta/noty.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.11.1/theme-chalk/index.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans+JP|Noto+Sans+KR&display=swap">
|
||||
</head>
|
||||
<body>
|
||||
<div id="x-app" class="x-app" :class="{ 'x-app-type': appType === '1' }" style="display:none">
|
||||
|
||||
<div class="x-container" style="flex:1">
|
||||
<div ref="list" class="x-friend-list" style="color:#aaa">
|
||||
<template v-for="feed in feeds">
|
||||
<div v-if="feed.type === 'GPS'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }">
|
||||
<div class="detail">
|
||||
<span class="extra">
|
||||
<span class="time">{{ feed.created_at | formatDate('HH:MI') }}</span>
|
||||
<span class="name" v-text="feed.displayName"></span> is in <location :location="feed.location[0]"></location>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="feed.type === 'Offline'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }">
|
||||
<div class="detail">
|
||||
<span class="extra">
|
||||
<span class="time">{{ feed.created_at | formatDate('HH:MI') }}</span>
|
||||
<span class="name" v-text="feed.displayName"></span> has logged out
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="feed.type === 'Online'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }">
|
||||
<div class="detail">
|
||||
<span class="extra">
|
||||
<span class="time">{{ feed.created_at | formatDate('HH:MI') }}</span>
|
||||
<span class="name" v-text="feed.displayName"></span> has logged in
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="feed.type === 'Status'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }">
|
||||
<div class="detail">
|
||||
<span class="extra">
|
||||
<span class="time">{{ feed.created_at | formatDate('HH:MI') }}</span>
|
||||
<span class="name" v-text="feed.displayName"></span> is <i class="x-user-status" :class="userStatusClass(feed.status[0])"></i> {{feed.status[0].statusDescription}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="feed.type === 'OnPlayerJoined'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }">
|
||||
<div class="detail">
|
||||
<span class="extra">
|
||||
<span class="time">{{ feed.created_at | formatDate('HH:MI') }}</span>
|
||||
<span class="name" v-text="feed.data"></span> has joined
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="feed.type === 'OnPlayerLeft'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }">
|
||||
<div class="detail">
|
||||
<span class="extra">
|
||||
<span class="time">{{ feed.created_at | formatDate('HH:MI') }}</span>
|
||||
<span class="name" v-text="feed.data"></span> has left
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="feed.type === 'Location'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }">
|
||||
<div class="detail">
|
||||
<span class="extra">
|
||||
<span class="time">{{ feed.created_at | formatDate('HH:MI') }}</span>
|
||||
<location :location="feed.data"></location>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="x-container">
|
||||
<div style="display:flex;flex-direction:row">
|
||||
<template v-if="devices.length">
|
||||
<div v-for="device in devices" style="flex:none;text-align:center;width:64px">
|
||||
<template v-if="device[0] === 'tracker'">
|
||||
<img v-if="device[1] !== 'connected'" src="images/tracker_status_off.png" style="width:32px;height:32px">
|
||||
<img v-else-if="device[2] < 20" src="images/tracker_status_ready_low.png" style="width:32px;height:32px">
|
||||
<img v-else src="images/tracker_status_ready.png" style="width:32px;height:32px">
|
||||
</template>
|
||||
<template v-else-if="device[0] === 'leftController'">
|
||||
<img v-if="device[1] !== 'connected'" src="images/controller_status_off.png" style="width:32px;height:32px">
|
||||
<img v-else-if="device[2] < 20" src="images/controller_status_ready_low.png" style="width:32px;height:32px">
|
||||
<img v-else src="images/controller_status_ready.png" style="width:32px;height:32px">
|
||||
</template>
|
||||
<template v-else-if="device[0] === 'rightController'">
|
||||
<img v-if="device[1] !== 'connected'" src="images/controller_status_off.png" style="width:32px;height:32px">
|
||||
<img v-else-if="device[2] < 20" src="images/controller_status_ready_low.png" style="width:32px;height:32px">
|
||||
<img v-else src="images/controller_status_ready.png" style="width:32px;height:32px">
|
||||
</template>
|
||||
<template v-else-if="device[0] === 'controller'">
|
||||
<img v-if="device[1] !== 'connected'" src="images/controller_status_off.png" style="width:32px;height:32px">
|
||||
<img v-else-if="device[2] < 20" src="images/controller_status_ready_low.png" style="width:32px;height:32px">
|
||||
<img v-else src="images/controller_status_ready.png" style="width:32px;height:32px">
|
||||
</template>
|
||||
<template v-else>
|
||||
<img v-if="device[1] !== 'connected'" src="images/other_status_off.png" style="width:32px;height:32px">
|
||||
<img v-else-if="device[2] < 20" src="images/other_status_ready_low.png" style="width:32px;height:32px">
|
||||
<img v-else src="images/other_status_ready.png" style="width:32px;height:32px">
|
||||
</template>
|
||||
<br><span>{{ device[2] }}%</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else>
|
||||
<span>No SteamVR Devices</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="x-container">
|
||||
<span style="float:right">{{ currentTime | formatDate('YYYY-MM-DD HH:MI:SS AMPM') }}</span>
|
||||
<span>CPU {{ cpuUsage }}%</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/noty/3.2.0-beta/noty.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.11.1/index.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.11.1/locale/en.min.js"></script>
|
||||
<script>
|
||||
(() => {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = `vr.css?_=${Date.now()}`;
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
var script = document.createElement('script');
|
||||
script.src = `vr.js?_=${Date.now()}`;
|
||||
document.getElementsByTagName('body')[0].appendChild(script);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
726
html/vr.js
Normal file
@@ -0,0 +1,726 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
if (window.CefSharp) {
|
||||
Promise.all([
|
||||
CefSharp.BindObjectAsync('VRCX'),
|
||||
CefSharp.BindObjectAsync('VRCXStorage')
|
||||
]).catch(() => {
|
||||
location = 'https://github.com/pypy-vrc/vrcx';
|
||||
}).then(() => {
|
||||
|
||||
VRCXStorage.GetBool = function (key) {
|
||||
return this.Get(key) === 'true';
|
||||
};
|
||||
|
||||
VRCXStorage.SetBool = function (key, value) {
|
||||
this.Set(key, value
|
||||
? 'true'
|
||||
: 'false');
|
||||
};
|
||||
|
||||
VRCXStorage.GetInt = function (key) {
|
||||
return parseInt(this.Get(key), 10) || 0;
|
||||
};
|
||||
|
||||
VRCXStorage.SetInt = function (key, value) {
|
||||
this.Set(key, String(value));
|
||||
};
|
||||
|
||||
VRCXStorage.GetFloat = function (key) {
|
||||
return parseFloat(this.Get(key), 10) || 0.0;
|
||||
};
|
||||
|
||||
VRCXStorage.SetFloat = function (key, value) {
|
||||
this.Set(key, String(value));
|
||||
};
|
||||
|
||||
VRCXStorage.GetArray = function (key) {
|
||||
try {
|
||||
var json = this.Get(key);
|
||||
if (json) {
|
||||
var array = JSON.parse(json);
|
||||
if (Array.isArray(array)) {
|
||||
return array;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
VRCXStorage.SetArray = function (key, value) {
|
||||
this.Set(key, JSON.stringify(value));
|
||||
};
|
||||
|
||||
VRCXStorage.GetObject = function (key) {
|
||||
try {
|
||||
var json = this.Get(key);
|
||||
if (json) {
|
||||
return JSON.parse(json);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
VRCXStorage.SetObject = function (key, value) {
|
||||
this.Set(key, JSON.stringify(value));
|
||||
};
|
||||
|
||||
Noty.overrideDefaults({
|
||||
animation: {
|
||||
open: 'animated fadeIn',
|
||||
close: 'animated zoomOut'
|
||||
},
|
||||
layout: 'topCenter',
|
||||
theme: 'relax',
|
||||
timeout: 6000
|
||||
});
|
||||
|
||||
var escapeTag = (s) => String(s).replace(/["&'<>]/gu, (c) => `&#${c.charCodeAt(0)};`);
|
||||
Vue.filter('escapeTag', escapeTag);
|
||||
|
||||
var commaNumber = (n) => String(Number(n) || 0).replace(/(\d)(?=(\d{3})+(?!\d))/gu, '$1,');
|
||||
Vue.filter('commaNumber', commaNumber);
|
||||
|
||||
var formatDate = (s, format) => {
|
||||
var ctx = new Date(s);
|
||||
if (isNaN(ctx)) {
|
||||
return escapeTag(s);
|
||||
}
|
||||
var hours = ctx.getHours();
|
||||
var map = {
|
||||
'YYYY': String(10000 + ctx.getFullYear()).substr(-4),
|
||||
'MM': String(101 + ctx.getMonth()).substr(-2),
|
||||
'DD': String(100 + ctx.getDate()).substr(-2),
|
||||
'HH24': String(100 + hours).substr(-2),
|
||||
'HH': String(100 + (hours > 12
|
||||
? hours - 12
|
||||
: hours)).substr(-2),
|
||||
'MI': String(100 + ctx.getMinutes()).substr(-2),
|
||||
'SS': String(100 + ctx.getSeconds()).substr(-2),
|
||||
'AMPM': hours >= 12
|
||||
? 'PM'
|
||||
: 'AM'
|
||||
};
|
||||
return format.replace(/YYYY|MM|DD|HH24|HH|MI|SS|AMPM/gu, (c) => map[c] || c);
|
||||
};
|
||||
Vue.filter('formatDate', formatDate);
|
||||
|
||||
var textToHex = (s) => String(s).split('').map((c) => c.charCodeAt(0).toString(16)).join(' ');
|
||||
Vue.filter('textToHex', textToHex);
|
||||
|
||||
var timeToText = (t) => {
|
||||
var sec = Number(t);
|
||||
if (isNaN(sec)) {
|
||||
return escapeTag(t);
|
||||
}
|
||||
sec = Math.floor(sec / 1000);
|
||||
var arr = [];
|
||||
if (sec < 0) {
|
||||
sec = -sec;
|
||||
}
|
||||
if (sec >= 86400) {
|
||||
arr.push(`${Math.floor(sec / 86400)}d`);
|
||||
sec %= 86400;
|
||||
}
|
||||
if (sec >= 3600) {
|
||||
arr.push(`${Math.floor(sec / 3600)}h`);
|
||||
sec %= 3600;
|
||||
}
|
||||
if (sec >= 60) {
|
||||
arr.push(`${Math.floor(sec / 60)}m`);
|
||||
sec %= 60;
|
||||
}
|
||||
if (sec ||
|
||||
!arr.length) {
|
||||
arr.push(`${sec}s`);
|
||||
}
|
||||
return arr.join(' ');
|
||||
};
|
||||
Vue.filter('timeToText', timeToText);
|
||||
|
||||
ELEMENT.locale(ELEMENT.lang.en);
|
||||
|
||||
//
|
||||
// API
|
||||
//
|
||||
|
||||
var API = {};
|
||||
|
||||
API.$handler = {};
|
||||
|
||||
API.$emit = function (event, ...args) {
|
||||
try {
|
||||
// console.log(event, ...args);
|
||||
var h = this.$handler[event];
|
||||
if (h) {
|
||||
h.forEach((f) => f(...args));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
API.$on = function (event, callback) {
|
||||
var h = this.$handler[event];
|
||||
if (h) {
|
||||
h.push(callback);
|
||||
} else {
|
||||
this.$handler[event] = [callback];
|
||||
}
|
||||
};
|
||||
|
||||
API.$off = function (event, callback) {
|
||||
var h = this.$handler[event];
|
||||
if (h) {
|
||||
h.find((val, idx, arr) => {
|
||||
if (val !== callback) {
|
||||
return false;
|
||||
}
|
||||
if (arr.length > 1) {
|
||||
arr.splice(idx, 1);
|
||||
} else {
|
||||
delete this.$handler[event];
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
API.$fetch = {};
|
||||
|
||||
API.call = function (endpoint, options) {
|
||||
var input = `https://api.vrchat.cloud/api/1/${endpoint}`;
|
||||
var init = {
|
||||
method: 'GET',
|
||||
mode: 'cors',
|
||||
credentials: 'include',
|
||||
cache: 'no-cache',
|
||||
referrerPolicy: 'no-referrer',
|
||||
...options
|
||||
};
|
||||
if (init.method === 'GET') {
|
||||
if (init.body) {
|
||||
var url = new URL(input);
|
||||
for (var key in init.body) {
|
||||
url.searchParams.set(key, init.body[key]);
|
||||
}
|
||||
input = url.toString();
|
||||
init.body = null;
|
||||
}
|
||||
// merge requests
|
||||
if (this.$fetch[input]) {
|
||||
return this.$fetch[input];
|
||||
}
|
||||
} else {
|
||||
init.headers = {
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
...init.headers
|
||||
};
|
||||
init.body = init.body
|
||||
? JSON.stringify(init.body)
|
||||
: '{}';
|
||||
}
|
||||
var req = fetch(input, init).catch((err) => {
|
||||
this.$throw(0, err);
|
||||
}).then((res) => res.json().catch(() => {
|
||||
if (!res.ok) {
|
||||
this.$throw(res.status);
|
||||
}
|
||||
this.$throw(0, 'Invalid JSON');
|
||||
}).then((json) => {
|
||||
if (!res.ok) {
|
||||
if (typeof json.error === 'object') {
|
||||
this.$throw(
|
||||
json.error.status_code || res.status,
|
||||
json.error.message,
|
||||
json.error.data
|
||||
);
|
||||
} else if (typeof json.error === 'string') {
|
||||
this.$throw(
|
||||
json.status_code || res.status,
|
||||
json.error
|
||||
);
|
||||
} else {
|
||||
this.$throw(res.status, json);
|
||||
}
|
||||
}
|
||||
return json;
|
||||
}));
|
||||
if (init.method === 'GET') {
|
||||
this.$fetch[input] = req.finally(() => {
|
||||
delete this.$fetch[input];
|
||||
});
|
||||
}
|
||||
return req;
|
||||
};
|
||||
|
||||
API.$status = {
|
||||
100: 'Continue',
|
||||
101: 'Switching Protocols',
|
||||
102: 'Processing',
|
||||
103: 'Early Hints',
|
||||
200: 'OK',
|
||||
201: 'Created',
|
||||
202: 'Accepted',
|
||||
203: 'Non-Authoritative Information',
|
||||
204: 'No Content',
|
||||
205: 'Reset Content',
|
||||
206: 'Partial Content',
|
||||
207: 'Multi-Status',
|
||||
208: 'Already Reported',
|
||||
226: 'IM Used',
|
||||
300: 'Multiple Choices',
|
||||
301: 'Moved Permanently',
|
||||
302: 'Found',
|
||||
303: 'See Other',
|
||||
304: 'Not Modified',
|
||||
305: 'Use Proxy',
|
||||
306: 'Switch Proxy',
|
||||
307: 'Temporary Redirect',
|
||||
308: 'Permanent Redirect',
|
||||
400: 'Bad Request',
|
||||
401: 'Unauthorized',
|
||||
402: 'Payment Required',
|
||||
403: 'Forbidden',
|
||||
404: 'Not Found',
|
||||
405: 'Method Not Allowed',
|
||||
406: 'Not Acceptable',
|
||||
407: 'Proxy Authentication Required',
|
||||
408: 'Request Timeout',
|
||||
409: 'Conflict',
|
||||
410: 'Gone',
|
||||
411: 'Length Required',
|
||||
412: 'Precondition Failed',
|
||||
413: 'Payload Too Large',
|
||||
414: 'URI Too Long',
|
||||
415: 'Unsupported Media Type',
|
||||
416: 'Range Not Satisfiable',
|
||||
417: 'Expectation Failed',
|
||||
418: "I'm a teapot",
|
||||
421: 'Misdirected Request',
|
||||
422: 'Unprocessable Entity',
|
||||
423: 'Locked',
|
||||
424: 'Failed Dependency',
|
||||
425: 'Too Early',
|
||||
426: 'Upgrade Required',
|
||||
428: 'Precondition Required',
|
||||
429: 'Too Many Requests',
|
||||
431: 'Request Header Fields Too Large',
|
||||
451: 'Unavailable For Legal Reasons',
|
||||
500: 'Internal Server Error',
|
||||
501: 'Not Implemented',
|
||||
502: 'Bad Gateway',
|
||||
503: 'Service Unavailable',
|
||||
504: 'Gateway Timeout',
|
||||
505: 'HTTP Version Not Supported',
|
||||
506: 'Variant Also Negotiates',
|
||||
507: 'Insufficient Storage',
|
||||
508: 'Loop Detected',
|
||||
510: 'Not Extended',
|
||||
511: 'Network Authentication Required',
|
||||
// CloudFlare Error
|
||||
520: 'Web server returns an unknown error',
|
||||
521: 'Web server is down',
|
||||
522: 'Connection timed out',
|
||||
523: 'Origin is unreachable',
|
||||
524: 'A timeout occurred',
|
||||
525: 'SSL handshake failed',
|
||||
526: 'Invalid SSL certificate',
|
||||
527: 'Railgun Listener to origin error'
|
||||
};
|
||||
|
||||
API.$throw = function (code, error) {
|
||||
throw {
|
||||
'status_code': code,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
// API: Config
|
||||
|
||||
API.config = {};
|
||||
|
||||
API.$on('CONFIG', (args) => {
|
||||
args.ref = API.updateConfig(args.json);
|
||||
});
|
||||
|
||||
API.getConfig = function () {
|
||||
return this.call('config', {
|
||||
method: 'GET'
|
||||
}).then((json) => {
|
||||
var args = {
|
||||
json
|
||||
};
|
||||
this.$emit('CONFIG', args);
|
||||
return args;
|
||||
});
|
||||
};
|
||||
|
||||
API.updateConfig = function (ref) {
|
||||
var ctx = {
|
||||
clientApiKey: '',
|
||||
...ref
|
||||
};
|
||||
this.config = ctx;
|
||||
return ctx;
|
||||
};
|
||||
|
||||
// API: Location
|
||||
|
||||
API.parseLocation = function (tag) {
|
||||
var L = {
|
||||
tag: String(tag || ''),
|
||||
isOffline: false,
|
||||
isPrivate: false,
|
||||
worldId: '',
|
||||
instanceId: '',
|
||||
instanceName: '',
|
||||
accessType: '',
|
||||
userId: null,
|
||||
hiddenId: null,
|
||||
privateId: null,
|
||||
friendsId: null,
|
||||
canRequestInvite: false
|
||||
};
|
||||
if (L.tag === 'offline') {
|
||||
L.isOffline = true;
|
||||
} else if (L.tag === 'private') {
|
||||
L.isPrivate = true;
|
||||
} else if (!L.tag.startsWith('local')) {
|
||||
var sep = L.tag.indexOf(':');
|
||||
if (sep >= 0) {
|
||||
L.worldId = L.tag.substr(0, sep);
|
||||
L.instanceId = L.tag.substr(sep + 1);
|
||||
L.instanceId.split('~').forEach((s, i) => {
|
||||
if (i) {
|
||||
var A = s.indexOf('(');
|
||||
var Z = A >= 0
|
||||
? s.lastIndexOf(')')
|
||||
: -1;
|
||||
var key = Z >= 0
|
||||
? s.substr(0, A)
|
||||
: s;
|
||||
var value = A < Z
|
||||
? s.substr(A + 1, Z - A - 1)
|
||||
: '';
|
||||
if (key === 'hidden') {
|
||||
L.hiddenId = value;
|
||||
} else if (key === 'private') {
|
||||
L.privateId = value;
|
||||
} else if (key === 'friends') {
|
||||
L.friendsId = value;
|
||||
} else if (key === 'canRequestInvite') {
|
||||
L.canRequestInvite = true;
|
||||
}
|
||||
} else {
|
||||
L.instanceName = s;
|
||||
}
|
||||
});
|
||||
L.accessType = 'public';
|
||||
if (L.privateId !== null) {
|
||||
if (L.canRequestInvite) {
|
||||
// InvitePlus
|
||||
L.accessType = 'invite+';
|
||||
} else {
|
||||
// InviteOnly
|
||||
L.accessType = 'invite';
|
||||
}
|
||||
L.userId = L.privateId;
|
||||
} else if (L.friendsId !== null) {
|
||||
// FriendsOnly
|
||||
L.accessType = 'friends';
|
||||
L.userId = L.friendsId;
|
||||
} else if (L.hiddenId !== null) {
|
||||
// FriendsOfGuests
|
||||
L.accessType = 'friends+';
|
||||
L.userId = L.hiddenId;
|
||||
}
|
||||
} else {
|
||||
L.worldId = L.tag;
|
||||
}
|
||||
}
|
||||
return L;
|
||||
};
|
||||
|
||||
Vue.component('location', {
|
||||
template: '<span>{{ text }}<slot></slot></span>',
|
||||
props: {
|
||||
location: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
text: this.location
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
parse() {
|
||||
var L = API.parseLocation(this.location);
|
||||
if (L.isOffline) {
|
||||
this.text = 'Offline';
|
||||
} else if (L.isPrivate) {
|
||||
this.text = 'Private';
|
||||
} else if (L.worldId) {
|
||||
var ref = API.world[L.worldId];
|
||||
if (ref) {
|
||||
this.text = `${ref.name} #${L.instanceName} ${L.accessType}`;
|
||||
} else {
|
||||
API.getWorld({
|
||||
worldId: L.worldId
|
||||
}).then((args) => {
|
||||
if (L.tag === this.location) {
|
||||
this.text = `${args.ref.name} #${L.instanceName} ${L.accessType}`;
|
||||
}
|
||||
return args;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
location() {
|
||||
this.parse();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parse();
|
||||
}
|
||||
});
|
||||
|
||||
// API: World
|
||||
|
||||
API.world = {};
|
||||
|
||||
API.$on('WORLD', (args) => {
|
||||
args.ref = API.updateWorld(args.json);
|
||||
});
|
||||
|
||||
/*
|
||||
param: {
|
||||
worldId: string
|
||||
}
|
||||
*/
|
||||
API.getWorld = function (param) {
|
||||
return this.call(`worlds/${param.worldId}?apiKey=${this.config.clientApiKey}`, {
|
||||
method: 'GET'
|
||||
}).then((json) => {
|
||||
var args = {
|
||||
param,
|
||||
json
|
||||
};
|
||||
this.$emit('WORLD', args);
|
||||
return args;
|
||||
});
|
||||
};
|
||||
|
||||
API.updateWorld = function (ref) {
|
||||
var ctx = this.world[ref.id];
|
||||
if (ctx) {
|
||||
Object.assign(ctx, ref);
|
||||
} else {
|
||||
ctx = {
|
||||
id: ref.id,
|
||||
name: '',
|
||||
description: '',
|
||||
authorId: '',
|
||||
authorName: '',
|
||||
capacity: 0,
|
||||
tags: [],
|
||||
releaseStatus: '',
|
||||
imageUrl: '',
|
||||
thumbnailImageUrl: '',
|
||||
assetUrl: '',
|
||||
assetUrlObject: {},
|
||||
pluginUrl: '',
|
||||
pluginUrlObject: {},
|
||||
unityPackageUrl: '',
|
||||
unityPackageUrlObject: {},
|
||||
unityPackages: [],
|
||||
version: 0,
|
||||
previewYoutubeId: '',
|
||||
favorites: 0,
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
publicationDate: '',
|
||||
labsPublicationDate: '',
|
||||
visits: 0,
|
||||
popularity: 0,
|
||||
heat: 0,
|
||||
publicOccupants: 0,
|
||||
privateOccupants: 0,
|
||||
occupants: 0,
|
||||
instances: [],
|
||||
// custom
|
||||
labs_: false,
|
||||
//
|
||||
...ref
|
||||
};
|
||||
this.world[ctx.id] = ctx;
|
||||
}
|
||||
if (ctx.tags) {
|
||||
ctx.labs_ = ctx.tags.includes('system_labs');
|
||||
}
|
||||
return ctx;
|
||||
};
|
||||
|
||||
var $app = {
|
||||
data: {
|
||||
API,
|
||||
VRCX,
|
||||
// 1 = 대시보드랑 손목에 보이는거
|
||||
// 2 = 항상 화면에 보이는 거
|
||||
appType: location.href.substr(-1),
|
||||
currentTime: new Date().toJSON(),
|
||||
cpuUsage: 0,
|
||||
feeds: [],
|
||||
devices: []
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
watch: {},
|
||||
el: '#x-app',
|
||||
mounted() {
|
||||
// https://media.discordapp.net/attachments/581757976625283083/611170278218924033/unknown.png
|
||||
// 현재 날짜 시간
|
||||
// 컨트롤러 배터리 상황
|
||||
// --
|
||||
// OO is Let's Just H!!!!! [GPS]
|
||||
// OO has logged in [Online]
|
||||
// OO has logged out [Offline]
|
||||
// OO has joined [OnPlayerJoined]
|
||||
// OO has left [OnPlayerLeft]
|
||||
// [Moderation]
|
||||
// OO has blocked you
|
||||
// OO has muted you
|
||||
// OO has hidden you
|
||||
// --
|
||||
API.getConfig().catch((err) => {
|
||||
// FIXME: 어케 복구하냐 이건
|
||||
throw err;
|
||||
}).then((args) => {
|
||||
setInterval(() => this.update(), 1000);
|
||||
this.update();
|
||||
this.$nextTick(() => {
|
||||
if (this.appType === '1') {
|
||||
this.$el.style.display = '';
|
||||
}
|
||||
});
|
||||
return args;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$app.methods.update = function () {
|
||||
this.currentTime = new Date().toJSON();
|
||||
VRCX.CpuUsage().then((cpuUsage) => {
|
||||
this.cpuUsage = cpuUsage.toFixed(2);
|
||||
});
|
||||
VRCX.GetVRDevices().then((devices) => {
|
||||
devices.forEach((device) => {
|
||||
device[2] = parseInt(device[2], 10);
|
||||
});
|
||||
this.devices = devices;
|
||||
});
|
||||
this.updateSharedFeed();
|
||||
};
|
||||
|
||||
$app.methods.updateSharedFeed = function () {
|
||||
// TODO: block mute hideAvatar unfriend
|
||||
var _feeds = this.feeds;
|
||||
this.feeds = VRCXStorage.GetArray('sharedFeeds');
|
||||
if (this.appType === '2') {
|
||||
var map = {};
|
||||
_feeds.forEach((feed) => {
|
||||
if (feed.isFavorite) {
|
||||
if (feed.type === 'OnPlayerJoined' ||
|
||||
feed.type === 'OnPlayerLeft') {
|
||||
if (!map[feed.data] ||
|
||||
map[feed.data] < feed.created_at) {
|
||||
map[feed.data] = feed.created_at;
|
||||
}
|
||||
} else if (feed.type === 'Online' ||
|
||||
feed.type === 'Offline') {
|
||||
if (!map[feed.displayName] ||
|
||||
map[feed.displayName] < feed.created_at) {
|
||||
map[feed.displayName] = feed.created_at;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
var notys = [];
|
||||
this.feeds.forEach((feed) => {
|
||||
if (feed.isFavorite) {
|
||||
if (feed.type === 'Online' ||
|
||||
feed.type === 'Offline') {
|
||||
if (!map[feed.displayName] ||
|
||||
map[feed.displayName] < feed.created_at) {
|
||||
map[feed.displayName] = feed.created_at;
|
||||
notys.push(feed);
|
||||
}
|
||||
} else if (feed.type === 'OnPlayerJoined' ||
|
||||
feed.type === 'OnPlayerLeft') {
|
||||
if (!map[feed.data] ||
|
||||
map[feed.data] < feed.created_at) {
|
||||
map[feed.data] = feed.created_at;
|
||||
notys.push(feed);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
var bias = new Date(Date.now() - 60000).toJSON();
|
||||
notys.forEach((noty) => {
|
||||
if (noty.created_at > bias) {
|
||||
if (noty.type === 'OnPlayerJoined') {
|
||||
new Noty({
|
||||
type: 'alert',
|
||||
text: `<strong>${noty.data}</strong> has joined`
|
||||
}).show();
|
||||
} else if (noty.type === 'OnPlayerLeft') {
|
||||
new Noty({
|
||||
type: 'alert',
|
||||
text: `<strong>${noty.data}</strong> has left`
|
||||
}).show();
|
||||
} else if (noty.type === 'Online') {
|
||||
new Noty({
|
||||
type: 'alert',
|
||||
text: `<strong>${noty.displayName}</strong> has logged in`
|
||||
}).show();
|
||||
} else if (noty.type === 'Offline') {
|
||||
new Noty({
|
||||
type: 'alert',
|
||||
text: `<strong>${noty.displayName}</strong> has logged out`
|
||||
}).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$app.methods.userStatusClass = function (user) {
|
||||
var style = {};
|
||||
if (user) {
|
||||
if (user.location === 'offline') {
|
||||
style.offline = true;
|
||||
} else if (user.status === 'active') {
|
||||
style.active = true;
|
||||
} else if (user.status === 'join me') {
|
||||
style.joinme = true;
|
||||
} else if (user.status === 'busy') {
|
||||
style.busy = true;
|
||||
}
|
||||
}
|
||||
return style;
|
||||
};
|
||||
|
||||
$app = new Vue($app);
|
||||
window.$app = $app;
|
||||
});
|
||||
} else {
|
||||
location = 'https://github.com/pypy-vrc/vrcx';
|
||||
}
|
||||
7141
openvr_api.cs
Normal file
17
packages.config
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="cef.redist.x64" version="73.1.13" targetFramework="net452" />
|
||||
<package id="cef.redist.x86" version="73.1.13" targetFramework="net452" />
|
||||
<package id="CefSharp.Common" version="73.1.130" targetFramework="net452" />
|
||||
<package id="CefSharp.OffScreen" version="73.1.130" targetFramework="net452" />
|
||||
<package id="CefSharp.WinForms" version="73.1.130" targetFramework="net452" />
|
||||
<package id="DiscordRichPresence" version="1.0.121" targetFramework="net452" />
|
||||
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net452" />
|
||||
<package id="SharpDX" version="4.2.0" targetFramework="net452" />
|
||||
<package id="SharpDX.D3DCompiler" version="4.2.0" targetFramework="net452" />
|
||||
<package id="SharpDX.Desktop" version="4.2.0" targetFramework="net452" />
|
||||
<package id="SharpDX.Direct2D1" version="4.2.0" targetFramework="net452" />
|
||||
<package id="SharpDX.Direct3D11" version="4.2.0" targetFramework="net452" />
|
||||
<package id="SharpDX.DXGI" version="4.2.0" targetFramework="net452" />
|
||||
<package id="SharpDX.Mathematics" version="4.2.0" targetFramework="net452" />
|
||||
</packages>
|
||||
BIN
vrchat.ico
Normal file
|
After Width: | Height: | Size: 364 KiB |