Files
VRCX/Dotnet/AppApi/Electron/RegistryPlayerPrefs.cs
Natsumi 938fff63d0 Electron support for Linux (#1074)
* init

* SQLite changes

* Move html folder, edit build scripts

* AppApi interface

* Build flags

* AppApi inheritance

* Finishing touches

* Merge upstream changes

* Test CI

* Fix class inits

* Rename AppApi

* Merge upstream changes

* Fix SQLiteLegacy on Linux, Add Linux interop, build tools

* Linux specific localisation strings

* Make it run

* Bring back most of Linux functionality

* Clean up

* Fix TTS voices

* Fix UI var

* Changes

* Electron minimise to tray

* Remove separate toggle for WlxOverlay

* Fixes

* Touchups

* Move csproj

* Window zoom, Desktop Notifications, VR check on Linux

* Fix desktop notifications, VR check spam

* Fix building on Linux

* Clean up

* Fix WebApi headers

* Rewrite VRCX updater

* Clean up

* Linux updater

* Add Linux to build action

* init

* SQLite changes

* Move html folder, edit build scripts

* AppApi interface

* Build flags

* AppApi inheritance

* Finishing touches

* Merge upstream changes

* Test CI

* Fix class inits

* Rename AppApi

* Merge upstream changes

* Fix SQLiteLegacy on Linux, Add Linux interop, build tools

* Linux specific localisation strings

* Make it run

* Bring back most of Linux functionality

* Clean up

* Fix TTS voices

* Changes

* Electron minimise to tray

* Remove separate toggle for WlxOverlay

* Fixes

* Touchups

* Move csproj

* Window zoom, Desktop Notifications, VR check on Linux

* Fix desktop notifications, VR check spam

* Fix building on Linux

* Clean up

* Fix WebApi headers

* Rewrite VRCX updater

* Clean up

* Linux updater

* Add Linux to build action

* Test updater

* Rebase and handle merge conflicts

* Fix Linux updater

* Fix Linux app restart

* Fix friend order

* Handle AppImageInstaller, show an install message on Linux

* Updates to the AppImage installer

* Fix Linux updater, fix set version, check for .NET, copy wine prefix

* Handle random errors

* Rotate tall prints

* try fix Linux restart bug

* Final

---------

Co-authored-by: rs189 <35667100+rs189@users.noreply.github.com>
2025-01-11 13:09:44 +13:00

787 lines
33 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Win32;
using NLog;
namespace VRCX
{
public partial class AppApiElectron
{
private string AddHashToKeyName(string key)
{
// https://discussions.unity.com/t/playerprefs-changing-the-name-of-keys/30332/4
// VRC_GROUP_ORDER_usr_032383a7-748c-4fb2-94e4-bcb928e5de6b_h2810492971
uint hash = 5381;
foreach (var c in key)
hash = (hash * 33) ^ c;
return key + "_h" + hash;
}
private static int FindMatchingBracket(string content, int openBracketIndex)
{
int depth = 0;
for (int i = openBracketIndex; i < content.Length; i++)
{
if (content[i] == '{')
depth++;
else if (content[i] == '}')
{
depth--;
if (depth == 0)
return i;
}
}
return -1;
}
private static Dictionary<string, string> ExtractCompatToolMapping(string vdfContent)
{
var compatToolMapping = new Dictionary<string, string>();
const string sectionHeader = "\"CompatToolMapping\"";
int sectionStart = vdfContent.IndexOf(sectionHeader);
if (sectionStart == -1)
{
logger.Error("CompatToolMapping not found");
return compatToolMapping;
}
int blockStart = vdfContent.IndexOf("{", sectionStart) + 1;
int blockEnd = FindMatchingBracket(vdfContent, blockStart - 1);
if (blockStart == -1 || blockEnd == -1)
{
logger.Error("CompatToolMapping block not found");
return compatToolMapping;
}
string blockContent = vdfContent.Substring(blockStart, blockEnd - blockStart);
var keyValuePattern = new Regex("\"(\\d+)\"\\s*\\{[^}]*\"name\"\\s*\"([^\"]+)\"",
RegexOptions.Multiline);
var matches = keyValuePattern.Matches(blockContent);
foreach (Match match in matches)
{
string key = match.Groups[1].Value;
string name = match.Groups[2].Value;
if (key != "0")
{
compatToolMapping[key] = name;
}
}
return compatToolMapping;
}
private static string GetSteamVdfCompatTool()
{
string steamPath = _steamPath;
string configVdfPath = Path.Combine(steamPath, "config", "config.vdf");
if (!File.Exists(configVdfPath))
{
logger.Error("config.vdf not found");
return null;
}
string vdfContent = File.ReadAllText(configVdfPath);
var compatToolMapping = ExtractCompatToolMapping(vdfContent);
if (compatToolMapping.TryGetValue("438100", out string name))
{
return name;
}
return null;
}
private string ParseWineRegOutput(string output, string keyName)
{
if (string.IsNullOrEmpty(output))
return null;
var lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Where(line =>
!string.IsNullOrWhiteSpace(line) &&
!line.Contains("fixme:") &&
!line.Contains("wine:"))
.ToArray();
foreach (var line in lines)
{
var parts = line.Split(new[] { '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(p => p.Trim())
.ToArray();
if (parts.Length >= 3 && parts[0].Contains(keyName))
{
var valueType = parts[parts.Length - 2];
var value = parts[parts.Length - 1];
switch (valueType)
{
case "REG_BINARY":
try
{
// Treat the value as a plain hex string and decode it to ASCII
var hexValues = Enumerable.Range(0, value.Length / 2)
.Select(i => value.Substring(i * 2, 2)) // Break string into chunks of 2
.Select(hex => Convert.ToByte(hex, 16)) // Convert each chunk to a byte
.ToArray();
return Encoding.ASCII.GetString(hexValues).TrimEnd('\0');
}
catch (Exception ex)
{
logger.Error($"Error parsing REG_BINARY as plain hex string: {ex.Message}");
return null;
}
case "REG_DWORD":
return "REG_DWORD";
default:
logger.Error($"Unsupported parsed registry value type: {valueType}");
return null;
}
}
}
return null;
}
private string ParseWineRegOutputEx(string output, string keyName)
{
var lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
string currentKey = null;
string currentValue = null;
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
if (line.Contains("="))
{
var parts = line.Split(new[] { '=' }, 2);
currentKey = parts[0].Trim();
currentValue = parts[1].Trim();
string escapedString = @$"{currentValue}";
escapedString = escapedString.Replace("\\", "");
currentValue = escapedString;
if (currentKey.Contains(keyName))
{
if (currentValue.EndsWith(",\\"))
{
var multiLineValue = new StringBuilder(currentValue.TrimEnd('\\'));
while (currentValue.EndsWith(",\\"))
{
currentValue = lines[++i].Trim();
multiLineValue.Append(currentValue.TrimEnd('\\'));
}
currentValue = multiLineValue.ToString();
}
if (currentValue.StartsWith("dword:"))
{
return int.Parse(currentValue.Substring(6), System.Globalization.NumberStyles.HexNumber).ToString();
}
else if (currentValue.StartsWith("hex:"))
{
var hexValues = currentValue.Substring(4).Replace("\\", "").Split(',');
var bytes = hexValues.Select(hex => Convert.ToByte(hex, 16)).ToArray();
var decodedString = Encoding.UTF8.GetString(bytes);
if (decodedString.StartsWith("[") && decodedString.EndsWith("]"))
{
try
{
var jsonObject = Newtonsoft.Json.JsonConvert.DeserializeObject(decodedString);
return Newtonsoft.Json.JsonConvert.SerializeObject(jsonObject, Newtonsoft.Json.Formatting.Indented);
}
catch (Exception ex)
{
logger.Error($"Error parsing JSON: {ex.Message}");
return decodedString;
}
}
else
{
return currentValue;
}
}
else
{
return currentValue;
}
}
}
}
logger.Error($"Key not found: {keyName}");
return null;
}
public static string GetVRChatWinePath()
{
string compatTool = GetSteamVdfCompatTool();
if (compatTool == null)
{
logger.Error("CompatTool not found");
return null;
}
string steamPath = _steamPath;
string steamAppsCommonPath = Path.Combine(steamPath, "steamapps", "common");
string compatabilityToolsPath = Path.Combine(steamPath, "compatibilitytools.d");
string protonPath = Path.Combine(steamAppsCommonPath, compatTool);
string compatToolPath = Path.Combine(compatabilityToolsPath, compatTool);
string winePath = "";
if (Directory.Exists(compatToolPath))
{
winePath = Path.Combine(compatToolPath, "files", "bin", "wine");
if (!File.Exists(winePath))
{
Console.WriteLine("Wine not found in CompatTool path");
return null;
}
}
else if (Directory.Exists(protonPath))
{
winePath = Path.Combine(protonPath, "dist", "bin", "wine");
if (!File.Exists(winePath))
{
logger.Error("Wine not found in Proton path");
return null;
}
}
else if (Directory.Exists(compatabilityToolsPath))
{
var dirs = Directory.GetDirectories(compatabilityToolsPath);
foreach (var dir in dirs)
{
if (dir.Contains(compatTool))
{
winePath = Path.Combine(dir, "files", "bin", "wine");
if (File.Exists(winePath))
{
break;
}
}
}
if (!File.Exists(winePath))
{
Console.WriteLine("Wine not found in CompatTool path");
return null;
}
}
if (winePath == "")
{
logger.Error("CompatTool and Proton not found");
return null;
}
return winePath;
}
private ProcessStartInfo GetWineProcessStartInfo(string winePath, string winePrefix, string wineCommand)
{
var processStartInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = $"-c \"{wineCommand.Replace("\"", "\\\"")}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
};
processStartInfo.Environment["WINEFSYNC"] = "1";
processStartInfo.Environment["WINEPREFIX"] = winePrefix;
//processStartInfo.Environment["WINEDEBUG"] = "-all";
return processStartInfo;
}
private string GetWineRegCommand(string command)
{
string winePath = GetVRChatWinePath();
string winePrefix = _vrcPrefixPath;
string wineRegCommand = $"\"{winePath}\" reg {command}";
ProcessStartInfo processStartInfo = GetWineProcessStartInfo(winePath, winePrefix, wineRegCommand);
using var process = Process.Start(processStartInfo);
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
process.WaitForExit();
if (!string.IsNullOrEmpty(error) &&
!error.Contains("wineserver: using server-side synchronization.") &&
!error.Contains("fixme:wineusb:query_id"))
{
logger.Error($"Wine reg command error: {error}");
return null;
}
return output;
}
private string GetWineRegCommandEx(string regCommand)
{
string winePrefix = _vrcPrefixPath;
string filePath = Path.Combine(winePrefix, "user.reg");
if (!File.Exists(filePath))
throw new FileNotFoundException($"Registry file not found at {filePath}");
var match = Regex.Match(regCommand, @"^(add|query|delete)\s+""([^""]+)""(?:\s+/v\s+""([^""]+)"")?(?:\s+/t\s+(\w+))?(?:\s+/d\s+([^\s]+))?.*$");
if (!match.Success)
throw new ArgumentException("Invalid command format.");
string action = match.Groups[1].Value.ToLower();
string valueName = match.Groups[3].Success ? match.Groups[3].Value : null;
string valueType = match.Groups[4].Success ? match.Groups[4].Value : null;
string valueData = match.Groups[5].Success ? match.Groups[5].Value : null;
var lines = File.ReadAllLines(filePath).ToList();
var updatedLines = new List<string>();
bool keyFound = false;
bool valueFound = false;
bool inVRChatSection = false;
int headerEndIndex = -1;
string keyHeader = "[Software\\\\VRChat\\\\VRChat]";
if (action == "add")
{
for (int i = 0; i < lines.Count; i++)
{
string line = lines[i].Trim();
if (line.StartsWith(keyHeader))
{
inVRChatSection = true;
keyFound = true;
headerEndIndex = i;
// Add header and metadata lines
while (i < lines.Count && (lines[i].StartsWith("#") || lines[i].StartsWith("@") || lines[i].Trim().StartsWith(keyHeader)))
{
updatedLines.Add(lines[i]);
i++;
}
i--;
continue;
}
else if (inVRChatSection && line.StartsWith("["))
{
inVRChatSection = false;
}
if (inVRChatSection && valueName != null)
{
if (line.TrimStart().StartsWith($"\"{valueName}\"="))
{
valueFound = true;
updatedLines.Add($"\"{valueName}\"={GetRegistryValueFormat(valueType, valueData)}");
continue;
}
}
updatedLines.Add(lines[i]);
}
// Add new value if not found but section exists
if (keyFound && !valueFound && valueName != null)
{
var insertIndex = headerEndIndex + 2;
while (insertIndex < updatedLines.Count &&
(updatedLines[insertIndex].StartsWith("#") || updatedLines[insertIndex].StartsWith("@")))
{
insertIndex++;
}
updatedLines.Insert(insertIndex, $"\"{valueName}\"={GetRegistryValueFormat(valueType, valueData)}");
}
File.WriteAllLines(filePath, updatedLines);
return $"Command '{regCommand}' executed successfully.";
}
else if (action == "query")
{
if (!valueName.Contains("_h"))
{
valueName = AddHashToKeyName(valueName);
}
foreach (var line in lines)
{
if (line.Contains(valueName))
{
return line;
}
}
return $"Value \"{valueName}\" not found.";
}
logger.Error($"Unsupported registry command: {regCommand}");
return $"Command '{regCommand}' executed successfully.";
}
private static string GetRegistryValueFormat(string valueType, string valueData)
{
if (valueType?.ToUpper() == "REG_DWORD100")
{
double inputValue = double.Parse(valueData);
Span<byte> dataBytes = stackalloc byte[sizeof(double)];
BitConverter.TryWriteBytes(dataBytes, inputValue);
var hexValues = dataBytes.ToArray().Select(b => b.ToString("X2")).ToArray();
var byteString = string.Join(",", hexValues).ToLower();
var result = $"hex(4):{byteString}";
return result;
}
return valueType?.ToUpper() switch
{
"REG_DWORD" => $"dword:{int.Parse(valueData):X8}",
_ => throw new ArgumentException($"Unsupported registry value type: {valueType}"),
};
}
public override object GetVRChatRegistryKey(string key)
{
try
{
key = AddHashToKeyName(key);
string regCommand = $"query \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\" /v \"{key}\"";
var queryResult = GetWineRegCommand(regCommand);
if (queryResult == null)
return null;
var result = ParseWineRegOutput(queryResult, key);
if (result == "REG_DWORD")
{
queryResult = GetWineRegCommandEx(regCommand);
result = ParseWineRegOutputEx(queryResult, key);
}
return result;
}
catch (Exception ex)
{
logger.Error($"Exception in GetRegistryValueFromWine: {ex.Message}");
return null;
}
}
public override string GetVRChatRegistryKeyString(string key)
{
// for electron
return GetVRChatRegistryKey(key)?.ToString();
}
// TODO: check this
public async Task SetVRChatRegistryKeyAsync(string key, object value, int typeInt)
{
await Task.Run(() =>
{
SetVRChatRegistryKey(key, value, typeInt);
});
}
public override bool SetVRChatRegistryKey(string key, object value, int typeInt)
{
var type = (RegistryValueKind)typeInt;
var keyName = AddHashToKeyName(key);
switch (type)
{
case RegistryValueKind.Binary:
if (value is JsonElement jsonElement)
{
if (jsonElement.ValueKind == JsonValueKind.String)
{
byte[] byteArray = Encoding.UTF8.GetBytes(jsonElement.GetString());
var data = BitConverter.ToString(byteArray).Replace("-", "");
if (data.Length == 0)
data = "\"\"";
string regCommand = "add \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\" /v \"" + keyName + "\" /t REG_BINARY /d " + data + " /f";
var addResult = GetWineRegCommand(regCommand);
if (addResult == null)
return false;
}
else if (jsonElement.ValueKind == JsonValueKind.Array)
{
byte[] byteArray = jsonElement.EnumerateArray()
.Select(e => (byte)e.GetInt32()) // Convert each element to byte
.ToArray();
string regCommand = "add \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\" /v \"" + keyName + "\" /t REG_BINARY /d " + BitConverter.ToString(byteArray).Replace("-", "") + " /f";
var addResult = GetWineRegCommand(regCommand);
if (addResult == null)
return false;
}
else
{
logger.Error($"Invalid value for REG_BINARY: {value}. It must be a JSON string or array.");
return false;
}
}
else if (value is string jsonArray)
{
byte[] byteArray = Encoding.UTF8.GetBytes(jsonArray);
string regCommand = "add \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\" /v \"" + keyName + "\" /t REG_BINARY /d " + BitConverter.ToString(byteArray).Replace("-", "") + " /f";
var addResult = GetWineRegCommand(regCommand);
if (addResult == null)
return false;
}
else
{
logger.Error($"Invalid value for REG_BINARY: {value}. It must be a JsonElement.");
return false;
}
break;
case RegistryValueKind.DWord:
if (value is int intValue)
{
string regCommand = "add \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\" /v \"" + keyName + "\" /t REG_DWORD /d " + intValue + " /f";
var addResult = GetWineRegCommandEx(regCommand);
if (addResult == null)
return false;
}
else if (value is string stringValue && int.TryParse(stringValue, out int parsedIntValue))
{
string regCommand = "add \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\" /v \"" + keyName + "\" /t REG_DWORD /d " + parsedIntValue + " /f";
var addResult = GetWineRegCommandEx(regCommand);
if (addResult == null)
return false;
}
else if (value is JsonElement jsonElementValue && jsonElementValue.ValueKind == JsonValueKind.Number)
{
int parsedInt32Value = jsonElementValue.GetInt32();
string regCommand = "add \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\" /v \"" + keyName + "\" /t REG_DWORD /d " + parsedInt32Value + " /f";
var addResult = GetWineRegCommandEx(regCommand);
if (addResult == null)
return false;
}
else
{
logger.Error($"Invalid value for REG_DWORD: {value}. It must be a valid integer.");
return false;
}
break;
default:
logger.Error($"Unsupported set registry value type: {typeInt}");
return false;
}
return true;
}
public override void SetVRChatRegistryKey(string key, byte[] value)
{
var keyName = AddHashToKeyName(key);
var data = BitConverter.ToString(value).Replace("-", "");
if (data.Length == 0)
data = "\"\"";
var regCommand = "add \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\" /v \"" + keyName + "\" /t REG_BINARY /d " + data + " /f";
GetWineRegCommand(regCommand);
}
public override Dictionary<string, Dictionary<string, object>> GetVRChatRegistry()
{
return null;
}
// TODO: no object type
// public Dictionary<string, Dictionary<string, object>> GetVRChatRegistry()
public string GetVRChatRegistryJson()
{
var registry = new Dictionary<string, Dictionary<string, object>>();
string regCommand = "query \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\"";
var queryResult = GetWineRegCommand(regCommand);
if (queryResult == null)
return null;
var lines = queryResult.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Where(line =>
!string.IsNullOrWhiteSpace(line) &&
!line.Contains("fixme:") &&
!line.Contains("wine:"))
.ToArray();
foreach (var line in lines)
{
var parts = line.Split(new[] { '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(p => p.Trim())
.ToArray();
if (parts.Length >= 3)
{
var keyName = parts[0];
var index = keyName.LastIndexOf("_h", StringComparison.Ordinal);
if (index > 0)
keyName = keyName.Substring(0, index);
var valueType = parts[parts.Length - 2];
var value = parts[parts.Length - 1];
switch (valueType)
{
case "REG_BINARY":
try
{
// Treat the value as a plain hex string and decode it to ASCII
var hexValues = Enumerable.Range(0, value.Length / 2)
.Select(i => value.Substring(i * 2, 2)) // Break string into chunks of 2
.Select(hex => Convert.ToByte(hex, 16)) // Convert each chunk to a byte
.ToArray();
var binDict = new Dictionary<string, object>
{
{ "data", Encoding.ASCII.GetString(hexValues).TrimEnd('\0') },
{ "type", 3 }
};
registry.Add(keyName, binDict);
}
catch (Exception ex)
{
logger.Error($"Error parsing REG_BINARY as plain hex string: {ex.Message}");
}
break;
case "REG_DWORD":
string regCommandExDword = $"query \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\" /v \"{keyName}\"";
var queryResultExDword = GetWineRegCommandEx(regCommandExDword);
if (queryResultExDword == null)
break;
var resultExDword = ParseWineRegOutputEx(queryResultExDword, keyName);
if (resultExDword == null)
break;
try
{
if (resultExDword.StartsWith("hex(4)"))
{
string hexString = resultExDword;
string[] hexValues = hexString.Split(':')[1].Split(',');
byte[] byteValues = hexValues.Select(h => Convert.ToByte(h, 16)).ToArray();
if (byteValues.Length != 8)
{
throw new ArgumentException("Input does not represent a valid 8-byte double-precision float.");
}
double parsedDouble = BitConverter.ToDouble(byteValues, 0);
var doubleDict = new Dictionary<string, object>
{
{ "data", parsedDouble },
{ "type", 100 } // it's special
};
registry.Add(keyName, doubleDict);
}
else
{
// Convert dword value to integer
int parsedInt = int.Parse(resultExDword);
var dwordDict = new Dictionary<string, object>
{
{ "data", parsedInt },
{ "type", 4 }
};
registry.Add(keyName, dwordDict);
}
}
catch (Exception ex)
{
logger.Error($"Error parsing REG_DWORD: {ex.Message}");
}
break;
}
}
}
return Newtonsoft.Json.JsonConvert.SerializeObject(registry, Newtonsoft.Json.Formatting.Indented);
}
public override void SetVRChatRegistry(string json)
{
var dict = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, object>>>(json);
foreach (var item in dict)
{
var data = (JsonElement)item.Value["data"];
if (!int.TryParse(item.Value["type"].ToString(), out var type))
throw new Exception("Unknown type: " + item.Value["type"]);
string keyName = AddHashToKeyName(item.Key);
if (type == 4)
{
int intValue = data.GetInt32();
string regCommand = "add \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\" /v \"" + keyName + "\" /t REG_DWORD /d " + intValue + " /f";
var addResult = GetWineRegCommandEx(regCommand);
if (addResult == null)
continue;
}
else if (type == 100)
{
var valueLong = data.GetDouble();
string regCommand = "add \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\" /v \"" + keyName + "\" /t REG_DWORD100 /d " + valueLong + " /f";
var addResult = GetWineRegCommandEx(regCommand);
if (addResult == null)
continue;
}
else
{
// This slows down the recovery process but using async can be problematic
if (data.ValueKind == JsonValueKind.Number)
{
if (int.TryParse(data.ToString(), out var intValue))
{
SetVRChatRegistryKey(item.Key, intValue, type);
continue;
}
throw new Exception("Unknown number type: " + item.Key);
}
SetVRChatRegistryKey(item.Key, data, type);
}
}
}
public override bool HasVRChatRegistryFolder()
{
string regCommand = "query \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\"";
var queryResult = GetWineRegCommand(regCommand);
if (queryResult == null)
return false;
return !string.IsNullOrEmpty(queryResult);
}
private void CreateVRChatRegistryFolder()
{
string regCommand = "add \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\" /f";
GetWineRegCommand(regCommand);
}
public override void DeleteVRChatRegistryFolder()
{
string regCommand = "delete \"HKEY_CURRENT_USER\\SOFTWARE\\VRChat\\VRChat\" /f";
GetWineRegCommand(regCommand);
}
public override string ReadVrcRegJsonFile(string filepath)
{
if (!File.Exists(filepath))
return string.Empty;
var json = File.ReadAllText(filepath);
return json;
}
}
}