diff --git a/README.md b/README.md index a3bb8fe8..df98be0b 100644 --- a/README.md +++ b/README.md @@ -1,270 +1,130 @@ -# VRCX +
-[![GitHub Workflow Status](https://github.com/vrcx-team/VRCX/actions/workflows/github_actions.yml/badge.svg)](https://github.com/vrcx-team/VRCX/actions/workflows/github_actions.yml) +# VRCX +[![GitHub release](https://img.shields.io/github/release/vrcx-team/VRCX.svg)](https://github.com/vrcx-team/VRCX/releases/latest) +[![GitHub Workflow Status](https://github.com/vrcx-team/VRCX/actions/workflows/github_actions.yml/badge.svg)](https://github.com/vrcx-team/VRCX/actions/workflows/github_actions.yml) [![VRCX Discord Invite](https://img.shields.io/discord/854071236363550763?color=%237289DA&logo=discord&logoColor=white)](https://vrcx.pypy.moe/discord) | **English** | [日本語](./README.jp.md) | -VRCX is an assistant application for VRChat that provides information about and managing friendship. This application makes use of the unofficial VRChat API SDK. +VRCX is an assistant/companion application for VRChat that provides information about and helps you accomplish various things related to VRChat in a more convenient fashion than relying on the plain VRChat client (desktop or VR), or website alone. It also includes some other neat features outlined below. -VRCX is not endorsed by VRChat and does not reflect the views or opinions of VRChat or anyone officially involved in producing or managing VRChat properties. VRChat and all associated properties are trademarks or registered trademarks of VRChat Inc. VRChat © VRChat Inc. +# Getting Started -pypy & Natsumi are not responsible for any problems caused by VRCX. **_Use at your own risk!_** +
-## How to install VRCX +Download and run the latest installer (`VRCX_Setup.exe`) from [here](https://github.com/vrcx-team/VRCX/releases/latest). -- Download latest release setup from [here](https://github.com/vrcx-team/VRCX/releases/latest). -- Run `VRCX_Setup.exe`. +# Features -## Is VRCX against VRChat ToS? +
-**TL;DR:** no. +- :family: Friend, world, and avatar list management + - Manage your friends list, world/group/avatar lists outside of VRChat. + - Monitor the world/avatar activity of your friends and check their online status. + - Keep track of when you first added them and when you last saw them. + - See how much time you've spent together in worlds and how many times. +- :electric_plug: Automatically launch apps when you start VRChat + - You can configure VRCX to launch other apps when you start VRChat. + - For example, you could have VRCX launch an OSC app or a voice changer app when VRChat opens up. +- :floppy_disk: World Persistence + - For worlds that support the feature, VRCX can save world settings, save states, inventories, and other data! + - **Note**: To use this feature, you must have "Allow Untrusted URLs" enabled in your VRChat settings. + - For Developers: [Wiki Page - World Persistence (PWI)](https://github.com/vrcx-team/VRCX/wiki/World-Persistence-(PWI)) +- :mag: Search for avatars, users, worlds, and groups +- :clipboard: Build a local, unrestricted world favorites list +- :camera: Store world data in the pictures you take in-game, so you can remember that one world you took those cool pictures in like... 6 months ago! +- :bell: Monitor/respond to notifications + - You can send/receive invites and friend requests from VRCX as well as see the instance info of invites that you receive. +- :scroll: See stats/players for your current instance +- :tv: See the links to videos and that are playing in the world you're in, as well as various other logged data. +- :bar_chart: Improved Discord Rich Presence + - You can optionally display more information about your current instance in Discord. + - This includes the world thumbnail, name, instance ID, and player count, depending on your settings and whether the lobby is private. You can also add a join button for public lobbies! +- :crystal_ball: VR Overlay with configurable live feed of all supported events/notifications -_VRChat's official stance on usage of the API, as listed in their Discord #faq channel._ -![vrchat api](https://user-images.githubusercontent.com/11171153/114227156-b559c400-99c8-11eb-9df6-ee6615b8118e.png) +## Miscellanous + +- Want a new look for VRCX? Check out [Themes](https://github.com/vrcx-team/VRCX/wiki/Themes) +- See [Building from source](https://github.com/vrcx-team/VRCX/wiki/Building-from-source) for instructions on how to build VRCX from source. +- For a guide on how to run VRCX on linux, see [here](https://github.com/vrcx-team/VRCX/wiki/Running-VRCX-on-Linux) # Screenshots -

Screenshot(click to open)

+
-### Login +

Login

-![login](https://user-images.githubusercontent.com/82102170/224703139-9cb24dda-3839-4f75-a665-cca69f9e08ea.png) -![2fa](https://user-images.githubusercontent.com/82102170/224703275-103e78fd-e917-428d-b901-6817d6b59b29.png) + + + + + +
login2fa
-### Feed +

Feed

-![feed](https://user-images.githubusercontent.com/82102170/224714129-772d7418-034a-4fe3-aa2e-22ea71154d9a.png) +feed -### GameLog +

GameLog

-![gamelog](https://user-images.githubusercontent.com/82102170/224714186-75cbf46d-f7b2-4a16-bcc5-2ec06d7f4b0d.png) +gamelog -### UserInfo +

UserInfo

-#### Me +

Me

-![me](https://user-images.githubusercontent.com/82102170/224704240-b10aba50-29b9-4ef4-b35a-958107a32cd6.png) +me -#### Friend +

Friend

-![friend](https://user-images.githubusercontent.com/82102170/224714608-ac49621f-c28f-4266-8af8-715f4b9f2367.png) +friend -### World +

World

-![instance](https://user-images.githubusercontent.com/82102170/224715566-67782a5e-f948-402b-b78d-1b2dd5e2382f.png) -![info](https://user-images.githubusercontent.com/82102170/224715824-c0c4220e-4f20-4799-8419-f8138de35b7a.png) + + + + + +
instanceinfo
-### Favorite +

Favorite

-#### Friend +

Friend

-![friend](https://user-images.githubusercontent.com/82102170/224716414-5c6720bd-6d38-4e2d-9353-6bfaee47700e.png) +friend -#### World +

World

-![world](https://user-images.githubusercontent.com/82102170/224716652-ca54f3d1-449b-43f9-81f3-7bd0833c7d9d.png) +world -#### Avatar +

Avatar

-![avatar](https://user-images.githubusercontent.com/82102170/224717146-37681b38-61ef-4302-8104-212c2161dc12.png) +avatar -### FriendLog +

Friend Log

-![friendlog](https://user-images.githubusercontent.com/82102170/224717793-dbbccdfd-4f89-4597-b38e-8070549b2cf8.png) +friendlog -### Moderation +

Discord Rich Presence

-![moderation](https://user-images.githubusercontent.com/82102170/224718331-87ab30b2-aae1-4bbf-9472-7279af48bd5d.png) +discord -### Notification + +
-![notification](https://user-images.githubusercontent.com/82102170/224718963-706e2d99-fc35-41a5-be2c-ff45681ab084.png) +## Is VRCX against VRChat's TOS? -### PlayerList +**No.** -![image](https://user-images.githubusercontent.com/82102170/224722231-54de7848-340c-4e9d-adce-30bccb6f2e8c.png) +VRCX is an external tool that uses the VRChat API to provide the features it does. -### Settings +It does not modify the game in any way, only using the API responsibly to provide the features it does. It is not a mod, or a cheat, or any other form of modification to the game. -#### General +To see VRChat's stance on API usage, see the #faq channel in the VRChat Discord. -![general](https://user-images.githubusercontent.com/82102170/224722538-9e320d5d-5348-4696-9a3d-720e7027b06c.png) +--- -#### Appearance +VRCX is not endorsed by VRChat and does not reflect the views or opinions of VRChat or anyone officially involved in producing or managing VRChat properties. VRChat and all associated properties are trademarks or registered trademarks of VRChat Inc. VRChat © VRChat Inc. -![appearance_1](https://user-images.githubusercontent.com/82102170/224724725-8e319924-91cb-41f9-96b4-0ec438aeaa76.png) -![appearance_2](https://user-images.githubusercontent.com/82102170/224724776-002c8ac3-69f3-4d7a-877f-54faf574a6f4.png) - -#### Notifications - -![notif](https://user-images.githubusercontent.com/82102170/224723929-edafafd1-69d1-4367-bc1c-1097f7478dba.png) - -#### Wrist Overlay - -![overlay](https://user-images.githubusercontent.com/82102170/224723886-415487fa-5c99-4b07-a6ab-2c8477aa576c.png) - -#### Discord Presence - -![rpc_setting](https://user-images.githubusercontent.com/82102170/224724105-3e97c7a3-6aa0-414b-a363-7bae56b9b3b1.png) - -#### Advanced - -![advance_1](https://user-images.githubusercontent.com/82102170/224724348-84ef6fca-5a00-45db-915d-ed42d244f0ca.png) -![advance_2](https://user-images.githubusercontent.com/82102170/224724480-07618cd1-fbf5-4f1e-a85f-f7fb94e7d70f.png) - -### Other - -#### Join - -![join](https://user-images.githubusercontent.com/82102170/224725201-8eb04169-af9c-476b-a5c5-58986d17d2e6.png) - -#### Create Instance - -![create](https://user-images.githubusercontent.com/82102170/224725526-7eab7b52-fec7-4c23-a941-7c4c29e8c944.png) - -#### Discord Rich Presence - -![rpc](https://user-images.githubusercontent.com/82102170/224725991-3fc81a3d-ca15-4dcb-a057-d713803bd666.png) - -### VR Overlay(old) - -![overlay1](https://user-images.githubusercontent.com/82102170/178281800-af4c69da-a0f5-43d8-9515-e960e1a16b39.png) -![overlay2](https://user-images.githubusercontent.com/82102170/178281884-ea1df88c-f16c-4c83-825c-c285f49b1ff1.png) - -
- -## Themes - -To install a theme place `custom.css` into your `%AppData%\VRCX` folder then press `Shift + Alt + R`. - -[Dark Vanilla](https://github.com/MintLily/Dark-Vanilla/tree/main/VRCX) by [MintLily](https://github.com/MintLily) -![dark-vanilla](https://user-images.githubusercontent.com/11171153/190136499-13ab2fe3-9fed-4b45-a764-e8665993d994.png) - -[Pink Theme](https://github.com/kamiya10/VRCX-theme/tree/main/pink) by [Kamiya](https://github.com/kamiya10) -![pink-theme](https://user-images.githubusercontent.com/11171153/190136121-4d6ba6e5-4a04-43b2-8264-0325c858f2d8.png) - -[Material 3](https://github.com/kamiya10/VRCX-theme/tree/main/m3) by [Kamiya](https://github.com/kamiya10) -![material-3](https://user-images.githubusercontent.com/11171153/190136730-65268fac-1dab-4528-813c-b4d5e5e86f7d.png) - -## VRCX launch parameters - -- `--debug` launch Chromium DevTools with VRCX. -- `--config="C:\VRCX.sqlite3"` set a custom path for VRCX database. -- `--gpufix` set VRCX overlay rendering GPU to index 1 forcing the overlay to render to a secondary dedicated GPU or if you're using an integrated GPU it will force rendering to your dedicated GPU. -- `vrcx://user/usr_id` open VRCX user dialog. -- `vrcx://avatar/avtr_id` open VRCX avatar dialog. -- `vrcx://group/grp_id` open VRCX group dialog. -- `vrcx://world/wrld_id` open VRCX world dialog. -- `vrcx://world/wrld_id:12345` open VRCX world dialog with instance. -- `vrcx://world/https://vrch.at/0gmbxjpj` open VRCX world dialog with unlocked instance. -- `vrcx://import/avatar/avtr_id,avtr_id...` open VRCX avatar favorites import dialog. -- `vrcx://import/world/wrld_id,wrld_id...` open VRCX world favorites import dialog. -- `vrcx://import/friend/usr_id,usr_id...` open VRCX friend favorites import dialog. -- `vrcx://addavatardb/https://website/vrcx_search.php` open VRCX remote avatar database provider dialog. - -## Keyboard shortcuts - -- `Ctrl + Shift + I` open Chromium DevTools. -- `Ctrl + R` restart VRCX. -- `Shift + Alt + R` reload custom css. - -## Common issues - -### **VRCX running slow** - -- Settings > Appearance > Max Table Size - - Lower this value to something like 100 this will decrease the amount of table entries stored in RAM. -- Settings > Advanced > Auto Clear Cache - - Lower this interval to something like 3 hours this will decrease the amount of unnecessary worlds, users and avatars stored in RAM. - -### **VRCX opens to a white screen** - -This can be caused by a many different things here's a list of things to try: - -- Install [Microsoft Visual C++ Redistributable 2019](https://aka.ms/vs/17/release/vc_redist.x64.exe). -- Reboot your computer, maybe another process is trying to read the database file. -- Disable antivirus software, Avast and AVG are known to delete Cef/VRCX. -- Remove AVerMedia AssistCentral this is known to cause problems with VRCX for some unknown reason. -- Unstable RAM can be a potential cause, remove any RAM overclock and run MemTest86. -- Run VRCX as administrator, this can fix it on some rare occasions. -- Test a fresh database, first close VRCX then backup your current database by opening `%AppData%\VRCX\` and renaming `VRCX.sqlite3` to something like `Backup.sqlite3`, if this solves it but you'd like to still keep your data follow the steps bellow on how to repair your database. - -### **SteamVR Overlay not working** - -- Try launch VRCX with the launch parameter `--gpufix`. -- Disable SteamVR safe mode. -- If your computer has an iGPU follow these [steps](https://www.windowsdigitals.com/force-chrome-firefox-game-to-use-nvidia-gpu-integrated-graphics/). - -### **How to repair VRCX database** - -- Close VRCX. -- Install [DB Browser for SQLite](https://sqlitebrowser.org/). -- File > Open Database... -- Browse to `%AppData%\VRCX\` then select `VRCX.sqlite3`. -- File > Export > Database to SQL file... -- Click Save and place the file somewhere. -- File > Close Database -- Rename `VRCX.sqlite3` in `%AppData%\VRCX\` to something like `Backup.sqlite3`. -- File > Import > Database from SQL file... -- Click Yes to creating a new database. -- Save it in `%AppData%\VRCX\` as `VRCX.sqlite3`. -- File > Write Changes. -- Close DB Browser and delete the no longer needed `*.sql` export file. - -## How to run VRCX on Linux - -Guide made by [RinLovesYou](https://github.com/RinLovesYou) - -- Installing Wine - - - Arch Linux: Wine can be installed by enabling the [multilib](https://wiki.archlinux.org/title/Multilib) repository and running `sudo pacman -S wine` - - - Ubuntu: Please refer to the [wiki](https://wiki.winehq.org/Ubuntu) - -- Winetricks Magic - - > It is imperative that you are using Wine 7.0rc5 or later! this has not been tested on earlier versions. Wine 7.0 Finally allows for full installation of the .net framework, which is needed for VRCX to function. - - - Start over with a clean wine prefix. Either create one for VRCX or `rm -rf ~/.wine` and use the main prefix. - - - Ensure you are NOT using DXVK either within your prefix or for your VRCX.exe. VRCX requires WineD3D to operate. - - - `winetricks --force -q dotnet472 corefonts` Silent and easy automation of the process of installing dependencies. - - - `wine winecfg -v win10` Set your prefix windows version. - - - You will require the `Linux` branch as CEF newer than 108 is currently incompatible with wine staging 8.4 as of this moment. It can be found in the Actions tab under the Linux branch. Download and extract the VRCX artifact. - - - You should just be able to run `wine VRCX.exe` now. Should you get an error about CEFSharp, you can try wine `VRCX.exe -no-cef-sandbox` - -- Notes - - - VRCX on Linux is known to have a nasty memory leak so keep an eye on memory usage or restart it frequently. - - - If you have a laptop that uses hardware muxing with an Intel iGPU you'll need to set your graphics device in `nvidia-settings` to only the NVIDIA GPU. - - ![image](https://camo.githubusercontent.com/ed672a01defae989c4bf5963c0cc9db973b42203e1e5e927f3341c6a9115beb8/68747470733a2f2f63646e2e646973636f72646170702e636f6d2f6174746163686d656e74732f3835343037313233363336333535303736362f3933353531333532313839373233343435322f756e6b6e6f776e2e706e67) - -## How to build VRCX from source - -- Get source code - - - Download latest source code [zip](https://github.com/vrcx-team/VRCX/archive/master.zip) or clone repo with `git clone`. - -- Build .NET - - - Install [Visual Studio](https://visualstudio.microsoft.com/) if it's not already installed. - - In Visual Studio "Open Project/Solution" and browse to the [Solution file](https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file) provided inside the downloaded source code. - - Set [Configuration](https://docs.microsoft.com/en-us/visualstudio/ide/understanding-build-configurations?view=vs-2019) to `Release` and Platform to `x64` - - Restore [NuGet](https://docs.microsoft.com/en-us/nuget/consume-packages/package-restore#restore-packages-automatically-using-visual-studio) packages. - - [Build](https://docs.microsoft.com/en-us/visualstudio/ide/building-and-cleaning-projects-and-solutions-in-visual-studio) Solution. - -- Build Node.js - - - Download and install [Node.js](https://nodejs.org/en/download/). - - Run `build-node.js.cmd`. - - Run `make-junction.cmd`. - -- Create release zip - - Run `make-zip.cmd` for [Bandizip](https://www.bandisoft.com/bandizip) or `make-zip-7z.cmd` for [7-Zip](https://www.7-zip.org). diff --git a/WorldDBManager.cs b/WorldDBManager.cs index cb229af5..a0bb9867 100644 --- a/WorldDBManager.cs +++ b/WorldDBManager.cs @@ -18,8 +18,8 @@ namespace VRCX private readonly WorldDatabase worldDB; private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); - private string currentWorldId = null; private string lastError = null; + private bool debugWorld = false; public WorldDBManager(string url) { @@ -50,6 +50,7 @@ namespace VRCX responseData.Error = "VRCX not yet initialized. Try again in a moment."; responseData.StatusCode = 503; + responseData.ConnectionKey = null; SendJsonResponse(context.Response, responseData); continue; }; @@ -75,6 +76,7 @@ namespace VRCX responseData.OK = lastError == null; responseData.StatusCode = 200; responseData.Data = lastError; + responseData.ConnectionKey = null; lastError = null; SendJsonResponse(context.Response, responseData); break; @@ -87,9 +89,14 @@ namespace VRCX context.Response.StatusCode = 200; context.Response.Close(); break; + case "/vrcx/data/settings": + responseData = await HandleSetSettingsRequest(context); + SendJsonResponse(context.Response, responseData); + break; default: responseData.Error = "Invalid VRCX endpoint."; responseData.StatusCode = 404; + responseData.ConnectionKey = null; SendJsonResponse(context.Response, responseData); break; } @@ -105,12 +112,53 @@ namespace VRCX responseData.Error = $"VRCX has encountered an exception while processing the url '{request.Url}': {ex.Message}"; responseData.StatusCode = 500; + responseData.ConnectionKey = null; SendJsonResponse(context.Response, responseData); } } } + private async Task HandleSetSettingsRequest(HttpListenerContext context) + { + var request = context.Request; + + string worldId = await GetCurrentWorldID(); + string set = request.QueryString["set"]; + string value = request.QueryString["value"]; + + if (!TryInitializeWorld(worldId, out string connectionKey)) + { + return ConstructErrorResponse(500, "Failed to get/verify current world ID."); + } + + if (set != null && value != null) + { + switch (set) + { + case "externalReads": + if (request.QueryString["value"] == "true") + { + worldDB.SetWorldAllowExternalRead(worldId, true); + } + else if (request.QueryString["value"] == "false") + { + worldDB.SetWorldAllowExternalRead(worldId, false); + } + else + { + return ConstructErrorResponse(400, "Invalid value for 'externalReads' setting."); + } + break; + default: + return ConstructErrorResponse(400, "Invalid setting name."); + } + } + + return ConstructSuccessResponse(null, connectionKey); + } + + /// /// Handles an HTTP listener request to initialize a connection to the world db manager. /// @@ -122,41 +170,24 @@ namespace VRCX if (request.QueryString["debug"] == "true") { - if (!worldDB.DoesWorldExist("wrld_12345")) - { - worldDB.AddWorld("wrld_12345", "12345"); - worldDB.AddDataEntry("wrld_12345", "test", "testvalue"); - } - - currentWorldId = "wrld_12345"; - - return ConstructSuccessResponse("12345"); + debugWorld = true; + } + else if (request.QueryString["debug"] == "false") + { + debugWorld = false; } string worldId = await GetCurrentWorldID(); - if (String.IsNullOrEmpty(worldId)) + if (TryInitializeWorld(worldId, out string connectionKey)) { - return ConstructErrorResponse(500, "Failed to get/verify current world ID."); - } - - currentWorldId = worldId; - - var existsInDB = worldDB.DoesWorldExist(currentWorldId); - string connectionKey; - - if (!existsInDB) - { - connectionKey = GenerateWorldConnectionKey(); - worldDB.AddWorld(currentWorldId, connectionKey); + logger.Info("Initialized a connection to the world database for world ID '{0}' with connection key {1}.", worldId, connectionKey); + return ConstructSuccessResponse(connectionKey, connectionKey); } else { - connectionKey = worldDB.GetWorldConnectionKey(currentWorldId); + return ConstructErrorResponse(500, "Failed to get/verify current world ID."); } - - logger.Info("Initialized connection to world ID '{0}' with connection key '{1}'.", currentWorldId, connectionKey); - return ConstructSuccessResponse(connectionKey); } /// @@ -174,38 +205,18 @@ namespace VRCX return ConstructErrorResponse(400, "Missing key parameter."); } - var worldIdOverride = request.QueryString["world"]; + var worldId = await GetCurrentWorldID(); - if (worldIdOverride != null) + if (!TryInitializeWorld(worldId, out string connectionKey)) { - var world = worldDB.GetWorld(worldIdOverride); - - if (world == null) - { - return ConstructErrorResponse(200, $"World ID '{worldIdOverride}' not initialized in this user's database."); - } - - if (!world.AllowExternalRead) - { - return ConstructErrorResponse(200, $"World ID '{worldIdOverride}' does not allow external reads."); - } - } - - if (currentWorldId == "wrld_12345" && worldIdOverride == null) - worldIdOverride = "wrld_12345"; - - var worldId = worldIdOverride ?? await GetCurrentWorldID(); - - if (worldIdOverride == null && (String.IsNullOrEmpty(currentWorldId) || worldId != currentWorldId)) - { - return ConstructErrorResponse(400, "World ID not initialized."); + return ConstructErrorResponse(500, "Failed to get/verify current world ID."); } var value = worldDB.GetDataEntry(worldId, key); - logger.Debug("Serving a request for data with key '{0}' from world ID '{1}'.", key, worldId); - // This is intended to be null if the key doesn't exist. - return ConstructSuccessResponse(value?.Value); + logger.Debug("Serving a request for data with key '{0}' from world ID '{1}' with connection key {2}.", key, worldId, connectionKey); + // This value is intended to be null if the key doesn't exist. + return ConstructSuccessResponse(value?.Value, connectionKey); } /// @@ -217,45 +228,24 @@ namespace VRCX { var request = context.Request; - var worldIdOverride = request.QueryString["world"]; - if (worldIdOverride != null) + var worldId = await GetCurrentWorldID(); + + if (!TryInitializeWorld(worldId, out string connectionKey)) { - var world = worldDB.GetWorld(worldIdOverride); - - if (world == null) - { - return ConstructErrorResponse(200, $"World ID '{worldIdOverride}' not initialized in this user's database."); - } - - if (!world.AllowExternalRead) - { - return ConstructErrorResponse(200, $"World ID '{worldIdOverride}' does not allow external reads."); - } - } - - if (currentWorldId == "wrld_12345" && worldIdOverride == null) - worldIdOverride = "wrld_12345"; - - var worldId = worldIdOverride ?? await GetCurrentWorldID(); - - if (worldIdOverride == null && (String.IsNullOrEmpty(currentWorldId) || worldId != currentWorldId)) - { - return ConstructErrorResponse(400, "World ID not initialized."); + return ConstructErrorResponse(500, "Failed to get/verify current world ID."); } var entries = worldDB.GetAllDataEntries(worldId); - logger.Debug("Serving a request for all data from world ID '{0}'.", worldId); - var data = new Dictionary(); foreach (var entry in entries) { data.Add(entry.Key, entry.Value); } - // This is intended to be null if the key doesn't exist. - return ConstructSuccessResponse(JsonConvert.SerializeObject(data)); + logger.Debug("Serving a request for all data ({0} entries) for world ID '{1}' with connection key {2}.", data.Count, worldId, connectionKey); + return ConstructSuccessResponse(JsonConvert.SerializeObject(data), connectionKey); } /// @@ -277,19 +267,12 @@ namespace VRCX var worldId = await GetCurrentWorldID(); - if (String.IsNullOrEmpty(currentWorldId) || (worldId != currentWorldId && currentWorldId != "wrld_12345")) + if (!TryInitializeWorld(worldId, out string connectionKey)) { - return ConstructErrorResponse(400, "World ID not initialized."); + return ConstructErrorResponse(500, "Failed to get/verify current world ID."); } - var values = worldDB.GetDataEntries(currentWorldId, keyArray).ToList(); - - /*if (values == null) - { - responseData.Error = $"No data found for keys '{keys}' under world id '{currentWorldId}'."; - responseData.StatusCode = 404; - return responseData; - }*/ + var values = worldDB.GetDataEntries(worldId, keyArray).ToList(); // Build a dictionary of key/value pairs to send back. If a key doesn't exist in the database, the key will be included in the response as requested but with a null value. var data = new Dictionary(); @@ -301,8 +284,32 @@ namespace VRCX data.Add(dataKey, dataValue); } - logger.Debug("Serving a request for bulk data with keys '{0}' from world ID '{1}'.", keys, currentWorldId); - return ConstructSuccessResponse(JsonConvert.SerializeObject(data)); + logger.Debug("Serving a request for bulk data with keys '{0}' from world ID '{1}' with connection key {2}.", keys, worldId, connectionKey); + return ConstructSuccessResponse(JsonConvert.SerializeObject(data), connectionKey); + } + + private bool TryInitializeWorld(string worldId, out string connectionKey) + { + if (String.IsNullOrEmpty(worldId)) + { + connectionKey = null; + return false; + } + + var existsInDB = worldDB.DoesWorldExist(worldId); + + if (!existsInDB) + { + connectionKey = GenerateWorldConnectionKey(); + worldDB.AddWorld(worldId, connectionKey); + logger.Info("Added new world ID '{0}' with connection key '{1}' to the database.", worldId, connectionKey); + } + else + { + connectionKey = worldDB.GetWorldConnectionKey(worldId); + } + + return true; } /// @@ -311,6 +318,8 @@ namespace VRCX /// A string representation of a GUID that can be used to identify the world on requests. private string GenerateWorldConnectionKey() { + if (debugWorld) return "12345"; + // Ditched the old method of generating a short key, since we're just going with json anyway who cares about a longer identifier // Since we can rely on this GUID being unique, we can use it to identify the world on requests instead of trying to keep track of the user's current world. // I uhh, should probably make sure this is actually unique though. Just in case. I'll do that later. @@ -323,6 +332,8 @@ namespace VRCX /// The ID of the current world as a string, or null if it could not be retrieved. private async Task GetCurrentWorldID() { + if (debugWorld) return "wrld_12345"; + JavascriptResponse funcResult; try @@ -349,7 +360,7 @@ namespace VRCX return worldId; } - private WorldDataRequestResponse ConstructSuccessResponse(string data = null) + private WorldDataRequestResponse ConstructSuccessResponse(string data = null, string connectionKey = null) { var responseData = new WorldDataRequestResponse(true, null, null); @@ -357,6 +368,7 @@ namespace VRCX responseData.Error = null; responseData.OK = true; responseData.Data = data; + responseData.ConnectionKey = connectionKey; return responseData; } @@ -368,6 +380,7 @@ namespace VRCX responseData.Error = error; responseData.OK = false; responseData.Data = null; + responseData.ConnectionKey = null; return responseData; } @@ -433,31 +446,17 @@ namespace VRCX return; } - if (String.IsNullOrEmpty(request.Key)) + if (String.IsNullOrEmpty(request.RequestType)) { - logger.Warn("World {0} tried to store data with no key provided", request.Key); - this.lastError = "`key` is missing or null"; - return; - } - - if (String.IsNullOrEmpty(request.Value)) - { - logger.Warn("World {0} tried to store data with no value provided", request.Key); - this.lastError = "`value` is missing or null"; - return; - } - - if (String.IsNullOrEmpty(request.ConnectionKey)) - { - logger.Warn("World {0} tried to store data with no connection key provided", request.Key); - this.lastError = "`connectionKey` is missing or null"; + logger.Warn("World tried to store data with no request type provided. Request: ", json); + this.lastError = "`requestType` is missing or null"; return; } // Make sure the connection key is a valid GUID. No point in doing anything else if it's not. - if (!Guid.TryParse(request.ConnectionKey, out Guid _)) + if (!debugWorld && !Guid.TryParse(request.ConnectionKey, out Guid _)) { - logger.Warn("World {0} tried to store data with an invalid GUID as a connection key {1}", request.Key, request.ConnectionKey); + logger.Warn("World tried to store data with an invalid GUID as a connection key '{0}'", request.ConnectionKey); this.lastError = "Invalid GUID provided as connection key"; // invalid guid return; @@ -465,14 +464,29 @@ namespace VRCX // Get the world ID from the connection key string worldId = worldDB.GetWorldByConnectionKey(request.ConnectionKey); + if (worldId == null) { - logger.Warn("World {0} tried to store data with invalid connection key {1}", request.Key, request.ConnectionKey); + logger.Warn("World tried to store data under {0} with an invalid connection key {1}", request.Key, request.ConnectionKey); this.lastError = "Invalid connection key"; // invalid connection key return; } + if (String.IsNullOrEmpty(request.Key)) + { + logger.Warn("World {0} tried to store data with no key provided", worldId); + this.lastError = "`key` is missing or null"; + return; + } + + if (String.IsNullOrEmpty(request.Value)) + { + logger.Warn("World {0} tried to store data under key {1} with no value provided", worldId, request.Key); + this.lastError = "`value` is missing or null"; + return; + } + // Get/calculate the old and new data sizes for this key/the world int oldTotalDataSize = worldDB.GetWorldDataSize(worldId); int oldDataSize = worldDB.GetDataEntrySize(worldId, request.Key); diff --git a/WorldDataRequestResponse.cs b/WorldDataRequestResponse.cs index 0523e3f9..a8e27afa 100644 --- a/WorldDataRequestResponse.cs +++ b/WorldDataRequestResponse.cs @@ -30,6 +30,8 @@ namespace VRCX /// [JsonProperty("statusCode")] public int StatusCode { get; set; } + [JsonProperty("connectionKey")] + public string ConnectionKey { get; set; } public WorldDataRequestResponse(bool ok, string error, string data) { diff --git a/WorldDatabase.cs b/WorldDatabase.cs index 993e4d00..2ac950bf 100644 --- a/WorldDatabase.cs +++ b/WorldDatabase.cs @@ -154,6 +154,16 @@ END;"; return connectionKey; } + /// + /// Sets the value of the allow_external_read field for the world with the specified ID in the database. + /// + /// The ID of the world to set the allow_external_read field for. + /// The value to set for the allow_external_read field. + public void SetWorldAllowExternalRead(string worldId, bool allowExternalRead) + { + sqlite.Execute("UPDATE worlds SET allow_external_read = ? WHERE world_id = ?", allowExternalRead, worldId); + } + /// /// Adds a new world to the database. ///