mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
VRChat Config Warnings (#1358)
Co-authored-by: Erimel <marioluigivideo@gmail.com>
This commit is contained in:
@@ -7,6 +7,9 @@ import dev.slimevr.bridge.ISteamVRBridge
|
||||
import dev.slimevr.config.ConfigManager
|
||||
import dev.slimevr.firmware.FirmwareUpdateHandler
|
||||
import dev.slimevr.firmware.SerialFlashingHandler
|
||||
import dev.slimevr.games.vrchat.VRCConfigHandler
|
||||
import dev.slimevr.games.vrchat.VRCConfigHandlerStub
|
||||
import dev.slimevr.games.vrchat.VRChatConfigManager
|
||||
import dev.slimevr.osc.OSCHandler
|
||||
import dev.slimevr.osc.OSCRouter
|
||||
import dev.slimevr.osc.VMCHandler
|
||||
@@ -50,6 +53,7 @@ class VRServer @JvmOverloads constructor(
|
||||
bridgeProvider: BridgeProvider = { _, _ -> sequence {} },
|
||||
serialHandlerProvider: (VRServer) -> SerialHandler = { _ -> SerialHandlerStub() },
|
||||
flashingHandlerProvider: (VRServer) -> SerialFlashingHandler? = { _ -> null },
|
||||
vrcConfigHandlerProvider: (VRServer) -> VRCConfigHandler = { _ -> VRCConfigHandlerStub() },
|
||||
acquireMulticastLock: () -> Any? = { null },
|
||||
// configPath is used by VRWorkout, do not remove!
|
||||
configPath: String,
|
||||
@@ -87,6 +91,8 @@ class VRServer @JvmOverloads constructor(
|
||||
|
||||
val firmwareUpdateHandler: FirmwareUpdateHandler
|
||||
|
||||
val vrcConfigManager: VRChatConfigManager
|
||||
|
||||
@JvmField
|
||||
val autoBoneHandler: AutoBoneHandler
|
||||
|
||||
@@ -124,6 +130,7 @@ class VRServer @JvmOverloads constructor(
|
||||
// AutoBone requires HumanPoseManager first
|
||||
autoBoneHandler = AutoBoneHandler(this)
|
||||
firmwareUpdateHandler = FirmwareUpdateHandler(this)
|
||||
vrcConfigManager = VRChatConfigManager(this, vrcConfigHandlerProvider(this))
|
||||
protocolAPI = ProtocolAPI(this)
|
||||
val computedTrackers = humanPoseManager.computedTrackers
|
||||
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
package dev.slimevr.games.vrchat
|
||||
|
||||
import dev.slimevr.VRServer
|
||||
import dev.slimevr.tracking.processor.config.SkeletonConfigToggles
|
||||
import dev.slimevr.tracking.trackers.TrackerPosition
|
||||
import dev.slimevr.tracking.trackers.TrackerUtils
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import kotlin.math.*
|
||||
|
||||
enum class VRCTrackerModel(val value: Int, val id: Int) {
|
||||
UNKNOWN(-1, solarxr_protocol.rpc.VRCTrackerModel.UNKNOWN),
|
||||
SPHERE(0, solarxr_protocol.rpc.VRCTrackerModel.SPHERE),
|
||||
SYSTEM(1, solarxr_protocol.rpc.VRCTrackerModel.SYSTEM),
|
||||
BOX(2, solarxr_protocol.rpc.VRCTrackerModel.BOX),
|
||||
AXIS(3, solarxr_protocol.rpc.VRCTrackerModel.AXIS),
|
||||
;
|
||||
|
||||
companion object {
|
||||
private val byValue = VRCTrackerModel.entries.associateBy { it.value }
|
||||
|
||||
fun getByValue(value: Int): VRCTrackerModel? = byValue[value]
|
||||
}
|
||||
}
|
||||
|
||||
enum class VRCSpineMode(val value: Int, val id: Int) {
|
||||
UNKNOWN(-1, solarxr_protocol.rpc.VRCSpineMode.UNKNOWN),
|
||||
LOCK_HIP(0, solarxr_protocol.rpc.VRCSpineMode.LOCK_HIP),
|
||||
LOCK_HEAD(1, solarxr_protocol.rpc.VRCSpineMode.LOCK_HEAD),
|
||||
LOCK_BOTH(2, solarxr_protocol.rpc.VRCSpineMode.LOCK_BOTH),
|
||||
;
|
||||
|
||||
companion object {
|
||||
private val byValue = VRCSpineMode.entries.associateBy { it.value }
|
||||
|
||||
fun getByValue(value: Int): VRCSpineMode? = byValue[value]
|
||||
}
|
||||
}
|
||||
|
||||
enum class VRCAvatarMeasurementType(val value: Int, val id: Int) {
|
||||
UNKNOWN(-1, solarxr_protocol.rpc.VRCAvatarMeasurementType.UNKNOWN),
|
||||
ARM_SPAN(0, solarxr_protocol.rpc.VRCAvatarMeasurementType.ARM_SPAN),
|
||||
HEIGHT(1, solarxr_protocol.rpc.VRCAvatarMeasurementType.HEIGHT),
|
||||
;
|
||||
|
||||
companion object {
|
||||
private val byValue = VRCAvatarMeasurementType.entries.associateBy { it.value }
|
||||
|
||||
fun getByValue(value: Int): VRCAvatarMeasurementType? = byValue[value]
|
||||
}
|
||||
}
|
||||
|
||||
data class VRCConfigValues(
|
||||
val legacyMode: Boolean,
|
||||
val shoulderTrackingDisabled: Boolean,
|
||||
val shoulderWidthCompensation: Boolean,
|
||||
val userHeight: Double,
|
||||
val calibrationRange: Double,
|
||||
val calibrationVisuals: Boolean,
|
||||
val trackerModel: VRCTrackerModel,
|
||||
val spineMode: VRCSpineMode,
|
||||
val avatarMeasurementType: VRCAvatarMeasurementType,
|
||||
)
|
||||
|
||||
data class VRCConfigRecommendedValues(
|
||||
val legacyMode: Boolean,
|
||||
val shoulderTrackingDisabled: Boolean,
|
||||
val shoulderWidthCompensation: Boolean,
|
||||
val userHeight: Double,
|
||||
val calibrationRange: Double,
|
||||
val calibrationVisuals: Boolean,
|
||||
val trackerModel: VRCTrackerModel,
|
||||
val spineMode: Array<VRCSpineMode>,
|
||||
val avatarMeasurementType: VRCAvatarMeasurementType,
|
||||
)
|
||||
|
||||
data class VRCConfigValidity(
|
||||
val legacyModeOk: Boolean,
|
||||
val shoulderTrackingOk: Boolean,
|
||||
val shoulderWidthCompensationOk: Boolean,
|
||||
val userHeightOk: Boolean,
|
||||
val calibrationOk: Boolean,
|
||||
val calibrationVisualsOk: Boolean,
|
||||
val tackerModelOk: Boolean,
|
||||
val spineModeOk: Boolean,
|
||||
val avatarMeasurementOk: Boolean,
|
||||
)
|
||||
|
||||
abstract class VRCConfigHandler {
|
||||
abstract val isSupported: Boolean
|
||||
abstract fun initHandler(onChange: (config: VRCConfigValues) -> Unit)
|
||||
}
|
||||
|
||||
class VRCConfigHandlerStub : VRCConfigHandler() {
|
||||
override val isSupported: Boolean
|
||||
get() = false
|
||||
|
||||
override fun initHandler(onChange: (config: VRCConfigValues) -> Unit) {}
|
||||
}
|
||||
|
||||
interface VRCConfigListener {
|
||||
fun onChange(validity: VRCConfigValidity, values: VRCConfigValues, recommended: VRCConfigRecommendedValues)
|
||||
}
|
||||
|
||||
class VRChatConfigManager(val server: VRServer, private val handler: VRCConfigHandler) {
|
||||
|
||||
private val listeners: MutableList<VRCConfigListener> = CopyOnWriteArrayList()
|
||||
var currentValues: VRCConfigValues? = null
|
||||
|
||||
val isSupported: Boolean
|
||||
get() = handler.isSupported
|
||||
|
||||
init {
|
||||
handler.initHandler(::onChange)
|
||||
}
|
||||
|
||||
/**
|
||||
* shoulderTrackingDisabled should be true if:
|
||||
* The user isn't tracking their whole arms from their controllers:
|
||||
* forceArmsFromHMD is enabled || the user doesn't have hand trackers with position || the user doesn't have lower arms trackers || the user doesn't have upper arm trackers
|
||||
* And the user isn't tracking their arms from their HMD or doesn't have both shoulders:
|
||||
* (forceArmsFromHMD is disabled && user has hand trackers with position) || user is missing a shoulder tracker
|
||||
*/
|
||||
fun recommendedValues(): VRCConfigRecommendedValues {
|
||||
val forceArmsFromHMD = server.humanPoseManager.getToggle(SkeletonConfigToggles.FORCE_ARMS_FROM_HMD)
|
||||
|
||||
val hasLeftHandWithPosition = TrackerUtils.getTrackerForSkeleton(server.allTrackers, TrackerPosition.LEFT_HAND)?.hasPosition ?: false
|
||||
val hasRightHandWithPosition = TrackerUtils.getTrackerForSkeleton(server.allTrackers, TrackerPosition.RIGHT_HAND)?.hasPosition ?: false
|
||||
|
||||
val isMissingAnArmTracker = TrackerUtils.getTrackerForSkeleton(server.allTrackers, TrackerPosition.LEFT_LOWER_ARM) == null ||
|
||||
TrackerUtils.getTrackerForSkeleton(server.allTrackers, TrackerPosition.RIGHT_LOWER_ARM) == null ||
|
||||
TrackerUtils.getTrackerForSkeleton(server.allTrackers, TrackerPosition.LEFT_UPPER_ARM) == null ||
|
||||
TrackerUtils.getTrackerForSkeleton(server.allTrackers, TrackerPosition.RIGHT_UPPER_ARM) == null
|
||||
val isMissingAShoulderTracker = TrackerUtils.getTrackerForSkeleton(server.allTrackers, TrackerPosition.LEFT_SHOULDER) == null ||
|
||||
TrackerUtils.getTrackerForSkeleton(server.allTrackers, TrackerPosition.RIGHT_SHOULDER) == null
|
||||
|
||||
return VRCConfigRecommendedValues(
|
||||
legacyMode = false,
|
||||
shoulderTrackingDisabled =
|
||||
((forceArmsFromHMD || !hasLeftHandWithPosition || !hasRightHandWithPosition) || isMissingAnArmTracker) && // Not tracking shoulders from hands
|
||||
((!forceArmsFromHMD && hasLeftHandWithPosition && hasRightHandWithPosition) || isMissingAShoulderTracker), // Not tracking shoulders from HMD
|
||||
userHeight = server.humanPoseManager.realUserHeight.toDouble(),
|
||||
calibrationRange = 0.2,
|
||||
trackerModel = VRCTrackerModel.AXIS,
|
||||
spineMode = arrayOf(VRCSpineMode.LOCK_HIP, VRCSpineMode.LOCK_HEAD),
|
||||
calibrationVisuals = true,
|
||||
avatarMeasurementType = VRCAvatarMeasurementType.HEIGHT,
|
||||
shoulderWidthCompensation = true,
|
||||
)
|
||||
}
|
||||
|
||||
fun addListener(listener: VRCConfigListener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
fun removeListener(listener: VRCConfigListener) {
|
||||
listeners.removeIf { l -> l === listener }
|
||||
}
|
||||
|
||||
fun checkValidity(values: VRCConfigValues, recommended: VRCConfigRecommendedValues): VRCConfigValidity = VRCConfigValidity(
|
||||
legacyModeOk = values.legacyMode == recommended.legacyMode,
|
||||
shoulderTrackingOk = values.shoulderTrackingDisabled == recommended.shoulderTrackingDisabled,
|
||||
spineModeOk = recommended.spineMode.contains(values.spineMode),
|
||||
tackerModelOk = values.trackerModel == recommended.trackerModel,
|
||||
calibrationOk = abs(values.calibrationRange - recommended.calibrationRange) < 0.1,
|
||||
userHeightOk = abs(server.humanPoseManager.realUserHeight - values.userHeight) < 0.1,
|
||||
calibrationVisualsOk = values.calibrationVisuals == recommended.calibrationVisuals,
|
||||
avatarMeasurementOk = values.avatarMeasurementType == recommended.avatarMeasurementType,
|
||||
shoulderWidthCompensationOk = values.shoulderWidthCompensation == recommended.shoulderWidthCompensation,
|
||||
)
|
||||
|
||||
fun onChange(values: VRCConfigValues) {
|
||||
val recommended = recommendedValues()
|
||||
val validity = checkValidity(values, recommended)
|
||||
currentValues = values
|
||||
listeners.forEach {
|
||||
it.onChange(validity, values, recommended)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package dev.slimevr.protocol.rpc;
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder;
|
||||
import dev.slimevr.tracking.processor.HumanPoseManager;
|
||||
import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets;
|
||||
import solarxr_protocol.rpc.SkeletonConfigResponse;
|
||||
import solarxr_protocol.rpc.SkeletonPart;
|
||||
|
||||
|
||||
public class RPCBuilder {
|
||||
|
||||
public static int createSkeletonConfig(
|
||||
FlatBufferBuilder fbb,
|
||||
HumanPoseManager humanPoseManager
|
||||
) {
|
||||
int[] partsOffsets = new int[SkeletonConfigOffsets.values().length];
|
||||
|
||||
for (int index = 0; index < SkeletonConfigOffsets.values().length; index++) {
|
||||
SkeletonConfigOffsets val = SkeletonConfigOffsets.values[index];
|
||||
int part = SkeletonPart
|
||||
.createSkeletonPart(fbb, val.id, humanPoseManager.getOffset(val));
|
||||
partsOffsets[index] = part;
|
||||
}
|
||||
|
||||
int parts = SkeletonConfigResponse.createSkeletonPartsVector(fbb, partsOffsets);
|
||||
return SkeletonConfigResponse.createSkeletonConfigResponse(fbb, parts, 0);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import dev.slimevr.protocol.ProtocolHandler
|
||||
import dev.slimevr.protocol.datafeed.DataFeedBuilder
|
||||
import dev.slimevr.protocol.rpc.autobone.RPCAutoBoneHandler
|
||||
import dev.slimevr.protocol.rpc.firmware.RPCFirmwareUpdateHandler
|
||||
import dev.slimevr.protocol.rpc.games.vrchat.RPCVRChatHandler
|
||||
import dev.slimevr.protocol.rpc.reset.RPCResetHandler
|
||||
import dev.slimevr.protocol.rpc.serial.RPCProvisioningHandler
|
||||
import dev.slimevr.protocol.rpc.serial.RPCSerialHandler
|
||||
@@ -44,6 +45,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
RPCHandshakeHandler(this, api)
|
||||
RPCTrackingPause(this, api)
|
||||
RPCFirmwareUpdateHandler(this, api)
|
||||
RPCVRChatHandler(this, api)
|
||||
|
||||
registerPacketListener(
|
||||
RpcMessage.ResetRequest,
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package dev.slimevr.protocol.rpc.games.vrchat
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder
|
||||
import solarxr_protocol.rpc.*
|
||||
|
||||
fun buildVRCConfigValues(fbb: FlatBufferBuilder, values: dev.slimevr.games.vrchat.VRCConfigValues): Int {
|
||||
VRCConfigValues.startVRCConfigValues(fbb)
|
||||
VRCConfigValues.addCalibrationRange(fbb, values.calibrationRange.toFloat())
|
||||
VRCConfigValues.addCalibrationVisuals(fbb, values.calibrationVisuals)
|
||||
VRCConfigValues.addSpineMode(fbb, values.spineMode.id)
|
||||
VRCConfigValues.addLegacyMode(fbb, values.legacyMode)
|
||||
VRCConfigValues.addShoulderTrackingDisabled(fbb, values.shoulderTrackingDisabled)
|
||||
VRCConfigValues.addTrackerModel(fbb, values.trackerModel.id)
|
||||
VRCConfigValues.addAvatarMeasurementType(fbb, values.avatarMeasurementType.id)
|
||||
VRCConfigValues.addUserHeight(fbb, values.userHeight.toFloat())
|
||||
VRCConfigValues.addShoulderWidthCompensation(fbb, values.shoulderWidthCompensation)
|
||||
return VRCConfigValues.endVRCConfigValues(fbb)
|
||||
}
|
||||
|
||||
fun buildVRCConfigValidity(fbb: FlatBufferBuilder, validity: dev.slimevr.games.vrchat.VRCConfigValidity): Int {
|
||||
VRCConfigValidity.startVRCConfigValidity(fbb)
|
||||
VRCConfigValidity.addCalibrationRangeOk(fbb, validity.calibrationOk)
|
||||
VRCConfigValidity.addCalibrationVisualsOk(fbb, validity.calibrationVisualsOk)
|
||||
VRCConfigValidity.addSpineModeOk(fbb, validity.spineModeOk)
|
||||
VRCConfigValidity.addLegacyModeOk(fbb, validity.legacyModeOk)
|
||||
VRCConfigValidity.addShoulderTrackingOk(fbb, validity.shoulderTrackingOk)
|
||||
VRCConfigValidity.addTrackerModelOk(fbb, validity.tackerModelOk)
|
||||
VRCConfigValidity.addUserHeightOk(fbb, validity.userHeightOk)
|
||||
VRCConfigValidity.addAvatarMeasurementTypeOk(fbb, validity.avatarMeasurementOk)
|
||||
VRCConfigValidity.addShoulderWidthCompensationOk(fbb, validity.shoulderWidthCompensationOk)
|
||||
return VRCConfigValidity.endVRCConfigValidity(fbb)
|
||||
}
|
||||
|
||||
fun buildVRCConfigRecommendedValues(fbb: FlatBufferBuilder, values: dev.slimevr.games.vrchat.VRCConfigRecommendedValues): Int {
|
||||
val spineModeOffset = VRCConfigRecommendedValues
|
||||
.createSpineModeVector(
|
||||
fbb,
|
||||
values.spineMode.map { it.id.toByte() }.toByteArray(),
|
||||
)
|
||||
|
||||
VRCConfigRecommendedValues.startVRCConfigRecommendedValues(fbb)
|
||||
VRCConfigRecommendedValues.addCalibrationRange(fbb, values.calibrationRange.toFloat())
|
||||
VRCConfigRecommendedValues.addCalibrationVisuals(fbb, values.calibrationVisuals)
|
||||
VRCConfigRecommendedValues.addSpineMode(fbb, spineModeOffset)
|
||||
VRCConfigRecommendedValues.addLegacyMode(fbb, values.legacyMode)
|
||||
VRCConfigRecommendedValues.addShoulderTrackingDisabled(fbb, values.shoulderTrackingDisabled)
|
||||
VRCConfigRecommendedValues.addTrackerModel(fbb, values.trackerModel.id)
|
||||
VRCConfigRecommendedValues.addAvatarMeasurementType(fbb, values.avatarMeasurementType.id)
|
||||
VRCConfigRecommendedValues.addUserHeight(fbb, values.userHeight.toFloat())
|
||||
VRCConfigRecommendedValues.addShoulderWidthCompensation(fbb, values.shoulderWidthCompensation)
|
||||
return VRCConfigRecommendedValues.endVRCConfigRecommendedValues(fbb)
|
||||
}
|
||||
|
||||
fun buildVRCConfigStateResponse(
|
||||
fbb: FlatBufferBuilder,
|
||||
isSupported: Boolean,
|
||||
validity: dev.slimevr.games.vrchat.VRCConfigValidity?,
|
||||
values: dev.slimevr.games.vrchat.VRCConfigValues?,
|
||||
recommended: dev.slimevr.games.vrchat.VRCConfigRecommendedValues?,
|
||||
): Int {
|
||||
if (!isSupported) {
|
||||
VRCConfigStateChangeResponse.startVRCConfigStateChangeResponse(fbb)
|
||||
VRCConfigStateChangeResponse.addIsSupported(fbb, false)
|
||||
return VRCConfigStateChangeResponse.endVRCConfigStateChangeResponse(fbb)
|
||||
}
|
||||
|
||||
if (validity == null || values == null || recommended == null) {
|
||||
error("invalid state - all should be set")
|
||||
}
|
||||
|
||||
val validityOffset = buildVRCConfigValidity(fbb, validity)
|
||||
val valuesOffset = buildVRCConfigValues(fbb, values)
|
||||
val recommendedOffset = buildVRCConfigRecommendedValues(fbb, recommended)
|
||||
|
||||
VRCConfigStateChangeResponse.startVRCConfigStateChangeResponse(fbb)
|
||||
VRCConfigStateChangeResponse.addIsSupported(fbb, true)
|
||||
VRCConfigStateChangeResponse.addValidity(fbb, validityOffset)
|
||||
VRCConfigStateChangeResponse.addState(fbb, valuesOffset)
|
||||
VRCConfigStateChangeResponse.addRecommended(fbb, recommendedOffset)
|
||||
return VRCConfigStateChangeResponse.endVRCConfigStateChangeResponse(fbb)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package dev.slimevr.protocol.rpc.games.vrchat
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder
|
||||
import dev.slimevr.games.vrchat.VRCConfigListener
|
||||
import dev.slimevr.games.vrchat.VRCConfigRecommendedValues
|
||||
import dev.slimevr.games.vrchat.VRCConfigValidity
|
||||
import dev.slimevr.games.vrchat.VRCConfigValues
|
||||
import dev.slimevr.protocol.GenericConnection
|
||||
import dev.slimevr.protocol.ProtocolAPI
|
||||
import dev.slimevr.protocol.rpc.RPCHandler
|
||||
import solarxr_protocol.rpc.*
|
||||
|
||||
class RPCVRChatHandler(
|
||||
private val rpcHandler: RPCHandler,
|
||||
var api: ProtocolAPI,
|
||||
) : VRCConfigListener {
|
||||
|
||||
init {
|
||||
api.server.vrcConfigManager.addListener(this)
|
||||
|
||||
rpcHandler.registerPacketListener(RpcMessage.VRCConfigStateRequest) { conn: GenericConnection, messageHeader: RpcMessageHeader ->
|
||||
this.onConfigStateRequest(
|
||||
conn,
|
||||
messageHeader,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onConfigStateRequest(conn: GenericConnection, messageHeader: RpcMessageHeader) {
|
||||
val fbb = FlatBufferBuilder(32)
|
||||
|
||||
val configManager = api.server.vrcConfigManager
|
||||
val values = configManager.currentValues
|
||||
val recommended = configManager.recommendedValues()
|
||||
// FUCKING KOTLIN BRING ME BACK MY FUCKING TERNARY OPERATORS!!!!!!!!!!!!!!!!! - With love <3 Futura
|
||||
val validity = if (values !== null) configManager.checkValidity(values, recommended) else null
|
||||
|
||||
val response = buildVRCConfigStateResponse(
|
||||
fbb,
|
||||
isSupported = api.server.vrcConfigManager.isSupported,
|
||||
validity = validity,
|
||||
values = values,
|
||||
recommended = api.server.vrcConfigManager.recommendedValues(),
|
||||
)
|
||||
|
||||
val outbound = rpcHandler.createRPCMessage(
|
||||
fbb,
|
||||
RpcMessage.VRCConfigStateChangeResponse,
|
||||
response,
|
||||
)
|
||||
fbb.finish(outbound)
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
|
||||
override fun onChange(validity: VRCConfigValidity, values: VRCConfigValues, recommended: VRCConfigRecommendedValues) {
|
||||
val fbb = FlatBufferBuilder(32)
|
||||
|
||||
val response = buildVRCConfigStateResponse(
|
||||
fbb,
|
||||
isSupported = api.server.vrcConfigManager.isSupported,
|
||||
validity = validity,
|
||||
values = values,
|
||||
recommended = recommended,
|
||||
)
|
||||
|
||||
val outbound = rpcHandler.createRPCMessage(
|
||||
fbb,
|
||||
RpcMessage.VRCConfigStateChangeResponse,
|
||||
response,
|
||||
)
|
||||
fbb.finish(outbound)
|
||||
|
||||
this.api.apiServers.forEach { apiServer ->
|
||||
apiServer.apiConnections.forEach { it.send(fbb.dataBuffer()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package dev.slimevr.tracking.processor
|
||||
import com.jme3.math.FastMath
|
||||
import dev.slimevr.VRServer
|
||||
import dev.slimevr.VRServer.Companion.getNextLocalTrackerId
|
||||
import dev.slimevr.autobone.errors.BodyProportionError
|
||||
import dev.slimevr.config.ConfigManager
|
||||
import dev.slimevr.tracking.processor.config.SkeletonConfigManager
|
||||
import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets
|
||||
@@ -641,6 +642,10 @@ class HumanPoseManager(val server: VRServer?) {
|
||||
val userHeightFromConfig: Float
|
||||
get() = skeletonConfigManager.userHeightFromOffsets
|
||||
|
||||
@get:ThreadSafe
|
||||
val realUserHeight: Float
|
||||
get() = skeletonConfigManager.userHeightFromOffsets / BodyProportionError.eyeHeightToHeightRatio
|
||||
|
||||
// #endregion
|
||||
fun getPauseTracking(): Boolean = skeleton.getPauseTracking()
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import dev.slimevr.SLIMEVR_IDENTIFIER
|
||||
import dev.slimevr.VRServer
|
||||
import dev.slimevr.bridge.Bridge
|
||||
import dev.slimevr.desktop.firmware.DesktopSerialFlashingHandler
|
||||
import dev.slimevr.desktop.games.vrchat.DesktopVRCConfigHandler
|
||||
import dev.slimevr.desktop.platform.SteamVRBridge
|
||||
import dev.slimevr.desktop.platform.linux.UnixSocketBridge
|
||||
import dev.slimevr.desktop.platform.linux.UnixSocketRpcBridge
|
||||
@@ -123,6 +124,7 @@ fun main(args: Array<String>) {
|
||||
::provideBridges,
|
||||
{ _ -> DesktopSerialHandler() },
|
||||
{ _ -> DesktopSerialFlashingHandler() },
|
||||
{ _ -> DesktopVRCConfigHandler() },
|
||||
configPath = configDir,
|
||||
)
|
||||
vrServer.start()
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package dev.slimevr.desktop.games.vrchat
|
||||
|
||||
import com.sun.jna.Memory
|
||||
import com.sun.jna.platform.win32.Advapi32
|
||||
import com.sun.jna.platform.win32.Advapi32Util
|
||||
import com.sun.jna.platform.win32.WinNT
|
||||
import com.sun.jna.platform.win32.WinReg
|
||||
import com.sun.jna.ptr.IntByReference
|
||||
import dev.slimevr.games.vrchat.VRCAvatarMeasurementType
|
||||
import dev.slimevr.games.vrchat.VRCConfigHandler
|
||||
import dev.slimevr.games.vrchat.VRCConfigValues
|
||||
import dev.slimevr.games.vrchat.VRCSpineMode
|
||||
import dev.slimevr.games.vrchat.VRCTrackerModel
|
||||
import io.eiren.util.OperatingSystem
|
||||
import java.util.Timer
|
||||
import kotlin.concurrent.timerTask
|
||||
|
||||
// Vrchat is dumb and write 64 bit doubles in the registry as DWORD instead of QWORD.
|
||||
// so we have to be creative
|
||||
fun getQwordValue(path: String, key: String): Double? {
|
||||
val hKey = WinReg.HKEY_CURRENT_USER
|
||||
val phkResult = WinReg.HKEYByReference()
|
||||
|
||||
// Open the registry key
|
||||
if (Advapi32.INSTANCE.RegOpenKeyEx(hKey, path, 0, WinNT.KEY_READ, phkResult) != 0) {
|
||||
println("Error: Cannot open registry key")
|
||||
return null
|
||||
}
|
||||
|
||||
val lpData = Memory(8)
|
||||
val lpcbData = IntByReference(8)
|
||||
|
||||
val result = Advapi32.INSTANCE.RegQueryValueEx(
|
||||
phkResult.value,
|
||||
key,
|
||||
0,
|
||||
null,
|
||||
lpData,
|
||||
lpcbData,
|
||||
)
|
||||
Advapi32.INSTANCE.RegCloseKey(phkResult.value)
|
||||
|
||||
if (result != 0) {
|
||||
println("Error: Cannot read registry key")
|
||||
return null
|
||||
}
|
||||
return lpData.getDouble(0)
|
||||
}
|
||||
|
||||
fun getDwordValue(path: String, key: String): Int? = try {
|
||||
val data = Advapi32Util.registryGetIntValue(WinReg.HKEY_CURRENT_USER, path, key)
|
||||
data
|
||||
} catch (e: Exception) {
|
||||
println("Error reading DWORD: ${e.message}")
|
||||
null
|
||||
}
|
||||
|
||||
fun getVRChatKeys(path: String): Map<String, String> {
|
||||
val keysMap = mutableMapOf<String, String>()
|
||||
|
||||
try {
|
||||
Advapi32Util.registryGetValues(WinReg.HKEY_CURRENT_USER, path).forEach {
|
||||
keysMap[it.key.replace("""_h\d+$""".toRegex(), "")] = it.key
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("Error reading Values from VRC registry: ${e.message}")
|
||||
}
|
||||
return keysMap
|
||||
}
|
||||
|
||||
const val VRC_REG_PATH = "Software\\VRChat\\VRChat"
|
||||
|
||||
class DesktopVRCConfigHandler : VRCConfigHandler() {
|
||||
|
||||
private val getDevicesTimer = Timer("FetchVRCConfigTimer")
|
||||
|
||||
private var configState: VRCConfigValues? = null
|
||||
private var vrcConfigKeys = getVRChatKeys(VRC_REG_PATH)
|
||||
lateinit var onChange: (config: VRCConfigValues) -> Unit
|
||||
|
||||
private fun intValue(key: String): Int? {
|
||||
val realKey = vrcConfigKeys[key] ?: return null
|
||||
return getDwordValue(VRC_REG_PATH, realKey)
|
||||
}
|
||||
|
||||
private fun doubleValue(key: String): Double? {
|
||||
val realKey = vrcConfigKeys[key] ?: return null
|
||||
return getQwordValue(VRC_REG_PATH, realKey)
|
||||
}
|
||||
|
||||
private fun updateCurrentState() {
|
||||
vrcConfigKeys = getVRChatKeys(VRC_REG_PATH)
|
||||
val newConfig = VRCConfigValues(
|
||||
legacyMode = intValue("VRC_IK_LEGACY") == 1,
|
||||
shoulderTrackingDisabled = intValue("VRC_IK_DISABLE_SHOULDER_TRACKING") == 1,
|
||||
userHeight = doubleValue("PlayerHeight") ?: -1.0,
|
||||
calibrationRange = doubleValue("VRC_IK_CALIBRATION_RANGE") ?: -1.0,
|
||||
trackerModel = VRCTrackerModel.getByValue(intValue("VRC_IK_TRACKER_MODEL") ?: -1) ?: VRCTrackerModel.UNKNOWN,
|
||||
spineMode = VRCSpineMode.getByValue(intValue("VRC_IK_FBT_SPINE_MODE") ?: -1) ?: VRCSpineMode.UNKNOWN,
|
||||
calibrationVisuals = intValue("VRC_IK_CALIBRATION_VIS") == 1,
|
||||
avatarMeasurementType = VRCAvatarMeasurementType.getByValue(intValue("VRC_IK_AVATAR_MEASUREMENT_TYPE") ?: -1) ?: VRCAvatarMeasurementType.UNKNOWN,
|
||||
shoulderWidthCompensation = intValue("VRC_IK_SHOULDER_WIDTH_COMPENSATION") == 1,
|
||||
)
|
||||
if (newConfig != configState) {
|
||||
configState = newConfig
|
||||
onChange(newConfig)
|
||||
}
|
||||
}
|
||||
|
||||
override val isSupported: Boolean
|
||||
get() = OperatingSystem.currentPlatform === OperatingSystem.WINDOWS && vrcConfigKeys.isNotEmpty()
|
||||
|
||||
override fun initHandler(onChange: (config: VRCConfigValues) -> Unit) {
|
||||
this.onChange = onChange
|
||||
if (isSupported) {
|
||||
updateCurrentState()
|
||||
getDevicesTimer.scheduleAtFixedRate(
|
||||
timerTask {
|
||||
updateCurrentState()
|
||||
},
|
||||
0,
|
||||
3000,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user