It's an enigma that this can project exist.
Enigma is a Roblox library and desktop application for providing OpenVR (SteamVR) tracker information to the Roblox client. Unlike other approaches, such as tigerVR, there is no server component required, but the approach to transfer data has some limitations.
In order to run Enigma, the application needs to be downloaded from GitHub releases needs to be downloaded and ran. The first run sets up a Roblox Studio companion plugin. It is required to restart open Studio windows after the first run.
Once Enigma is running, it wait for OpenVR (SteamVR) to be detected, and start reading and sending tracker data using the methods below. Data will only be sent when a Roblox window is focused and when the application is running.
A couple of command line arguments can be added to change how Enigma runs.
--debug
: Enables debug logging (such as the rate for when data is sent).--trace
: Enables trace + debug logging (such as average timings for sending data).--debug-http
: Enables logging for the ASP.NET server used by the comapnion plugin.
At the moment, no user interface is provided. A SteamVR overlay for statistics and temporarily pausing/resuming would be ideal, but help is wanted to achieve this (OpenGL or Vulkan knowledge potentially required). A desktop application will most likely be made in the near future using Avalonia due to it being cross-platform.
The Roblox library has a few functions in the root module.
Enigma:Enable()
- Enables reading inputs from the desktop application.
Enigma:GetUserCFrameEnabled(UserCFrame: TrackerRole, Index: number?): boolean
-
Returns if the tracker for a role is active. In most cases, the index (defaults
to 1) is not needed, but since SteamVR allows for multiple trackers with the
same role, multiple can be read. For a list of TrackerRoles
, see TrackerRole.luau.Enigma:GetUserCFrame(UserCFrame: TrackerRole, Index: number?): CFrame?
-
Returns the CFrame for a tracker if it is active, or nil
otherwise. In most cases,
the index (defaults to 1) is not needed, but since SteamVR allows for multiple
trackers with the same role, multiple can be read. For a list of TrackerRoles
,
see TrackerRole.luau.Below is a very simple example:
local Enigma = require(game:GetService("ReplicatedStorage"):WaitForChild("Enigma"))
Enigma:Enable()
while true do
print(`Left foot CFrame: {Enigma:GetUserCFrame("LeftFoot")}`) --Might be nil at any time!
task.wait()
end
The UserCFrames returned are meant to be mixed with the results of
UserInputService::GetUserCFrame
.
Enigma sends data using a combination of the system clipboard and a TextBox
in
Roblox. When an active Roblox window is detected, Enigma will continuously overwrite
the keyboard, then run Ctrl + A and Ctrl + V to override the data in the TextBox.
Due to blindly pasting with Roblox Studio being destructive, a companion plugin is automatically set up when Enigma is run. The plugin will always attempt to send heartbeat requests every 3 seconds, and will poll the latest data that would have been pushed about 60 times a second only while in run mode. The clipboard is not used to it being overwriting scripts.
Note: The plugin might be limited to replicating data at 8hz due to a Roblox Studio bug.
TextBox
inputs will be non-functional.
Gamepad or mouse/touch inputs are required.TextBox
instead
of replacing. For custom messages, a schema that is tolerant of extra characters
(JSON is not, for example) is strongly recommended.list-devices --masked
.At the moment, macOS and Linux probably do not work. It is unclear if they can be supported.
Enigma.Core.Shim.Window.WindowsWindowState
is Windows-specific. macOS and Linux
versions would need to be created.InputSimulatorStandard
is Windows-specific.OVRSharp
supports macOS or Linux.TextCopy
and ASP.NET Core are cross-platform, though.
Enigma uses .NET 8 for the
desktop application and Rojo for the companion
plugin and Roblox library. However, using Python with the
publish.py
script is the recommended way to create new releases.
# Windows
python ./publish.py
# macOS/Linux
python3 ./publish.py
When creating releases, .NET will use Native AOT for building to create smaller binaries with better performance and less memory. For macOS and Linux, see the prerequisites section on Microsoft's documentation.
While Enigma's desktop application and intended use is for SteamVR trackers, all components are set up for Enigma to be re-used for generic messages.
Below is an example of how to send a generic message using Enigma.Core
:
await RobloxPlugins.CopyPluginAsync(); // Optional to set up the plugin, but only call once!
var appInstances = new AppInstances(); // Only call this once!
await appInstances.RobloxOutput.PushDataAsync("MyData");
And to read data on the client:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Enigma = ReplicatedStorage:WaitForChild("Enigma")
local CombinedInput = require(Enigma:WaitForChild("Input"):WaitForChild("CombinedInput"))
local CompanionPluginInput = require(Enigma:WaitForChild("Input"):WaitForChild("CompanionPluginInput"))
local TextBoxInput = require(Enigma:WaitForChild("Input"):WaitForChild("TextBoxInput"))
local Input = CombinedInput.new(CombinedInput.new(), CompanionPluginInput.new())
print(Input:GetCurrentText()) --Remember that this can be anything (including empty strings) or 2 messages back-to-back!
You can replicate the main parts of the application by providing the following:
GET http://localhost:52821/enigma/data
, which returns the current data
for the client.
b. POST http://localhost:52821/enigma/heartbeat
, which is for the studio to
say it is active and to disable clipboard pasting. This endpoint is required
for the data fetching in the companion plugin.Enigma is available under the terms of the MIT License. See LICENSE for details.