Skip to content

Commit

Permalink
feat: add lazyStabalization and playerHeightIndex
Browse files Browse the repository at this point in the history
  • Loading branch information
Hackebein committed Dec 26, 2024
1 parent 773fc07 commit d2ca110
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 4 deletions.
46 changes: 42 additions & 4 deletions ObjectTracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import argparse
import zeroconf
import logging
import read_registry
from logging.handlers import RotatingFileHandler
from pythonosc import udp_client, dispatcher, osc_server
from tinyoscquery.queryservice import OSCQueryService
Expand Down Expand Up @@ -199,6 +200,25 @@ def get_parameter(parameter: str, fallback):
global parameters
return parameters.get(parameter, fallback)

def add_hash_to_key_name(key: str) -> str:
"""
Appends a hash to the given key using a hashing algorithm similar to the one in the provided C# function.
The hash is calculated by starting with 5381 and, for each character in the key, multiplying the current hash by 33
and XOR'ing it with the character's ASCII value. The result is masked to simulate a 32-bit unsigned integer.
Args:
key (str): The original key string.
Returns:
str: The key appended with "_h" followed by the computed hash.
"""
hash_val = 5381
for c in key:
hash_val = (hash_val * 33) ^ ord(c)
hash_val &= 0xFFFFFFFF # Simulate 32-bit unsigned integer overflow
return f"{key}_h{hash_val}"


def on_avatar_change(addr, value) -> None:
"""
Expand Down Expand Up @@ -232,6 +252,7 @@ def osc_message_handler(addr, value) -> None:
on_avatar_change(addr, value)
set_parameter(parameter, value)
if parameter == "ObjectTracking/config/index" and value == 0:
update_player_height()
logger.info(trackers)
if parameter == "ObjectTracking/config/index" and value != 0:
device = get_parameter("ObjectTracking/config/device", 0)
Expand Down Expand Up @@ -379,6 +400,23 @@ def get_logger(debug=False):
return logging.getLogger(__name__)


def update_player_height():
# player height setting is not available as a parameter in VRChat
# therefore we have to read it from the registry
# Feature request: https://feedback.vrchat.com/feature-requests/p/irl-to-vr-scale
player_height = read_registry.read_registry_raw_qword(
read_registry.HKEY_CURRENT_USER,
r"Software\VRChat\VRChat",
add_hash_to_key_name("PlayerHeight"),
1.7
) * 100

# 3'0" to 8'0", 92cm to 243cm
heights = [i * 2.54 for i in range(3 * 12, 8 * 12 + 1)] + [i for i in range(92, 243 + 1)]

closest_height_index = heights.index(min(heights, key=lambda x: abs(x - player_height)))
send_parameter(f"ObjectTracking/playerHeightIndex", closest_height_index)


# Argument Parser
parser = argparse.ArgumentParser(
Expand All @@ -393,7 +431,7 @@ def get_logger(debug=False):
openvr.VRApplications().addApplicationManifest(get_absolute_path("app.vrmanifest"))

# first start
if not os.path.isfile(get_absolute_data_path("config.json")):
if getattr(sys, 'frozen', False) and not os.path.isfile(get_absolute_data_path("config.json")):
try:
openvr.VRApplications().setApplicationAutoLaunch("Hackebein.ObjectTracking", True)
except Exception as e:
Expand Down Expand Up @@ -494,7 +532,7 @@ def get_logger(debug=False):

if hmd_raw is not None:
#hmd = relative_matrix(tracking_reference, hmd_raw)
if get_parameter("ObjectTracking/isStabilized", False) == False:
if get_parameter("ObjectTracking/isStabilized", False) == False and get_parameter("ObjectTracking/isLazyStabilized", False) == False:
old_pill_raw = pill_raw
pill_raw = set_y_and_xz_rotation_to_zero(hmd_raw)
if get_parameter("TrackingType", 0) > 3 and get_parameter("VelocityX", 0) == 0 and get_parameter("VelocityY", 0) == 0 and get_parameter("VelocityZ", 0) == 0:
Expand All @@ -503,8 +541,8 @@ def get_logger(debug=False):
if pill_raw is not None:
pill = relative_matrix(tracking_reference, pill_raw)

for key, object in tracking_objects_raw.items():
tracking_objects[key] = relative_matrix(tracking_reference, object)
for key, object_raw in tracking_objects_raw.items():
tracking_objects[key] = relative_matrix(tracking_reference, object_raw)

if pill is not None:
for key, tracker in trackers.items():
Expand Down
105 changes: 105 additions & 0 deletions read_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import ctypes
from ctypes import wintypes

# Registry type definitions
HKEY = wintypes.HANDLE
REGSAM = wintypes.DWORD
LPBYTE = ctypes.POINTER(ctypes.c_ubyte)

# Load advapi32.dll (Windows Registry API)
advapi32 = ctypes.WinDLL("advapi32", use_last_error=True)

# Function prototypes
RegOpenKeyExW = advapi32.RegOpenKeyExW
RegOpenKeyExW.argtypes = (
HKEY, # hKey
wintypes.LPCWSTR, # lpSubKey
wintypes.DWORD, # ulOptions
REGSAM, # samDesired
ctypes.POINTER(HKEY) # phkResult
)
RegOpenKeyExW.restype = wintypes.LONG

RegQueryValueExW = advapi32.RegQueryValueExW
RegQueryValueExW.argtypes = (
HKEY, # hKey
wintypes.LPCWSTR, # lpValueName
ctypes.POINTER(wintypes.DWORD), # lpReserved
ctypes.POINTER(wintypes.DWORD), # lpType
LPBYTE, # lpData
ctypes.POINTER(wintypes.DWORD) # lpcbData
)
RegQueryValueExW.restype = wintypes.LONG

RegCloseKey = advapi32.RegCloseKey
RegCloseKey.argtypes = (HKEY,)
RegCloseKey.restype = wintypes.LONG

# Common registry root handles
HKEY_CLASSES_ROOT = 0x80000000
HKEY_CURRENT_USER = 0x80000001
HKEY_LOCAL_MACHINE = 0x80000002
HKEY_USERS = 0x80000003
HKEY_CURRENT_CONFIG = 0x80000005

# Access mask for reading keys
KEY_READ = 0x20019

def read_registry_raw_qword(hive: int, subkey: str, value_name: str, default: float) -> float:
"""
Reads (up to) 8 bytes from a Windows registry value, even if it is
incorrectly labeled as REG_DWORD but actually contains 8 bytes.
:param hive: One of the predefined registry root constants (e.g., HKEY_LOCAL_MACHINE).
:param subkey: The path to the registry key (e.g., r'SOFTWARE\\MyKey').
:param value_name: The name of the registry value to read.
:param default: The default value to return if the registry value cannot be read.
:return: The registry value as a float, or the default value if the read fails.
"""
try:
hkey = HKEY()
# 1) Open the registry key
rc = RegOpenKeyExW(hive, subkey, 0, KEY_READ, ctypes.byref(hkey))
if rc != 0:
raise OSError(f"RegOpenKeyExW failed with error code {rc}")

# Prepare variables for the query
reg_type = wintypes.DWORD()
data_size = wintypes.DWORD(8) # We expect up to 8 bytes
raw_buffer = (ctypes.c_ubyte * 8)() # 8-byte buffer

# 2) Query the registry value
rc = RegQueryValueExW(
hkey,
value_name,
None,
ctypes.byref(reg_type),
ctypes.cast(raw_buffer, LPBYTE),
ctypes.byref(data_size)
)
if rc != 0:
raise OSError(f"RegQueryValueExW failed with error code {rc}")

# data_size.value tells us how many bytes were read
# Interpreted as 64-bit double (float in python)
return ctypes.c_double.from_buffer_copy(bytes(raw_buffer[:data_size.value])).value
except Exception as e:
print(f"An error occurred: {e}")
return default
finally:
# 3) Close the key
RegCloseKey(hkey)

def bytes_to_qword_le(raw: bytes, signed: bool = False) -> int:
"""
Convert up to 8 bytes of little-endian data into a Python int.
:param raw: Raw bytes from the registry.
:param signed: Whether to interpret the 64-bit value as signed.
:return: An integer (could be unsigned or signed, depending on `signed`).
"""
if len(raw) > 8:
raise ValueError("Data is longer than 8 bytes; unexpected for a QWORD.")
# Pad or slice to exactly 8 bytes, then interpret as little-endian
data_8 = raw.ljust(8, b'\x00')[:8]
return int.from_bytes(data_8, byteorder='little', signed=signed)

0 comments on commit d2ca110

Please sign in to comment.