Initial commit

This commit is contained in:
pypy
2019-08-16 22:53:04 +09:00
parent 52679ba15d
commit 90f096c497
52 changed files with 27151 additions and 0 deletions

63
.gitattributes vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

BIN
OpenVR/win32/openvr_api.dll Normal file

Binary file not shown.

BIN
OpenVR/win64/openvr_api.dll Normal file

Binary file not shown.

67
Program.cs Normal file
View 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();
}
}
}
}

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

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

Binary file not shown.

104
VRForm.Designer.cs generated Normal file
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

1722
html/index.html Normal file

File diff suppressed because it is too large Load Diff

222
html/vr.css Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

17
packages.config Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB