mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-04 22:06:06 +02:00
Backup/restore VRC registry
This commit is contained in:
@@ -0,0 +1,312 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
|
namespace VRCX
|
||||||
|
{
|
||||||
|
public partial class AppApi
|
||||||
|
{
|
||||||
|
[DllImport("advapi32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
|
||||||
|
public static extern int RegSetValueExA(
|
||||||
|
IntPtr hKey,
|
||||||
|
string lpValueName,
|
||||||
|
int reserved,
|
||||||
|
RegistryValueKind dwType,
|
||||||
|
byte[] lpData,
|
||||||
|
int cbData
|
||||||
|
);
|
||||||
|
|
||||||
|
[DllImport("advapi32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
|
||||||
|
public static extern int RegOpenKeyExA(
|
||||||
|
IntPtr hKey,
|
||||||
|
string lpSubKey,
|
||||||
|
int ulOptions,
|
||||||
|
int samDesired,
|
||||||
|
out IntPtr phkResult
|
||||||
|
);
|
||||||
|
|
||||||
|
[DllImport("advapi32.dll")]
|
||||||
|
public static extern int RegCloseKey(IntPtr hKey);
|
||||||
|
|
||||||
|
public 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the value of the specified key from the VRChat group in the windows registry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The name of the key to retrieve.</param>
|
||||||
|
/// <returns>The value of the specified key, or null if the key does not exist.</returns>
|
||||||
|
public object GetVRChatRegistryKey(string key)
|
||||||
|
{
|
||||||
|
var keyName = AddHashToKeyName(key);
|
||||||
|
using (var regKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\VRChat\VRChat"))
|
||||||
|
{
|
||||||
|
var data = regKey?.GetValue(keyName);
|
||||||
|
if (data == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var type = regKey.GetValueKind(keyName);
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case RegistryValueKind.Binary:
|
||||||
|
return Encoding.ASCII.GetString((byte[])data);
|
||||||
|
|
||||||
|
case RegistryValueKind.DWord:
|
||||||
|
if (data.GetType() != typeof(long))
|
||||||
|
return data;
|
||||||
|
|
||||||
|
long.TryParse(data.ToString(), out var longValue);
|
||||||
|
var bytes = BitConverter.GetBytes(longValue);
|
||||||
|
var doubleValue = BitConverter.ToDouble(bytes, 0);
|
||||||
|
return doubleValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the value of the specified key in the VRChat group in the windows registry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The name of the key to set.</param>
|
||||||
|
/// <param name="value">The value to set for the specified key.</param>
|
||||||
|
/// <param name="typeInt">The RegistryValueKind type.</param>
|
||||||
|
/// <returns>True if the key was successfully set, false otherwise.</returns>
|
||||||
|
public bool SetVRChatRegistryKey(string key, object value, int typeInt)
|
||||||
|
{
|
||||||
|
var type = (RegistryValueKind)typeInt;
|
||||||
|
var keyName = AddHashToKeyName(key);
|
||||||
|
using (var regKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\VRChat\VRChat", true))
|
||||||
|
{
|
||||||
|
if (regKey == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
object setValue = null;
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case RegistryValueKind.Binary:
|
||||||
|
setValue = Encoding.ASCII.GetBytes(value.ToString());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RegistryValueKind.DWord:
|
||||||
|
setValue = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setValue == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
regKey.SetValue(keyName, setValue, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the value of the specified key in the VRChat group in the windows registry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The name of the key to set.</param>
|
||||||
|
/// <param name="value">The value to set for the specified key.</param>
|
||||||
|
public void SetVRChatRegistryKey(string key, byte[] value)
|
||||||
|
{
|
||||||
|
var keyName = AddHashToKeyName(key);
|
||||||
|
var hKey = (IntPtr)0x80000001; // HKEY_LOCAL_MACHINE
|
||||||
|
const int keyWrite = 0x20006;
|
||||||
|
const string keyFolder = @"SOFTWARE\VRChat\VRChat";
|
||||||
|
var openKeyResult = RegOpenKeyExA(hKey, keyFolder, 0, keyWrite, out var folderPointer);
|
||||||
|
if (openKeyResult != 0)
|
||||||
|
throw new Exception("Error opening registry key. Error code: " + openKeyResult);
|
||||||
|
|
||||||
|
var setKeyResult = RegSetValueExA(folderPointer, keyName, 0, RegistryValueKind.DWord, value, value.Length);
|
||||||
|
if (setKeyResult != 0)
|
||||||
|
throw new Exception("Error setting registry value. Error code: " + setKeyResult);
|
||||||
|
|
||||||
|
RegCloseKey(hKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, Dictionary<string, object>> GetVRChatRegistry()
|
||||||
|
{
|
||||||
|
var output = new Dictionary<string, Dictionary<string, object>>();
|
||||||
|
using (var regKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\VRChat\VRChat"))
|
||||||
|
{
|
||||||
|
if (regKey == null)
|
||||||
|
throw new Exception("Nothing to backup.");
|
||||||
|
|
||||||
|
var keys = regKey.GetValueNames();
|
||||||
|
foreach (var key in keys)
|
||||||
|
{
|
||||||
|
var data = regKey.GetValue(key);
|
||||||
|
var index = key.LastIndexOf("_h", StringComparison.Ordinal);
|
||||||
|
if (index <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var keyName = key.Substring(0, index);
|
||||||
|
if (data == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var type = regKey.GetValueKind(key);
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case RegistryValueKind.Binary:
|
||||||
|
var binDict = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "data", Encoding.ASCII.GetString((byte[])data) },
|
||||||
|
{ "type", type }
|
||||||
|
};
|
||||||
|
output.Add(keyName, binDict);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RegistryValueKind.DWord:
|
||||||
|
if (data.GetType() != typeof(long))
|
||||||
|
{
|
||||||
|
var dwordDict = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "data", data },
|
||||||
|
{ "type", type }
|
||||||
|
};
|
||||||
|
output.Add(keyName, dwordDict);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<long> spanLong = stackalloc long[] { (long)data };
|
||||||
|
var doubleValue = MemoryMarshal.Cast<long, double>(spanLong)[0];
|
||||||
|
var floatDict = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "data", doubleValue },
|
||||||
|
{ "type", 100 } // it's special
|
||||||
|
};
|
||||||
|
output.Add(keyName, floatDict);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Debug.WriteLine($"Unknown registry value kind: {type}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetVRChatRegistry(string json)
|
||||||
|
{
|
||||||
|
CreateVRChatRegistryFolder();
|
||||||
|
Span<double> spanDouble = stackalloc double[1];
|
||||||
|
var dict = System.Text.Json.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"]);
|
||||||
|
|
||||||
|
if (data.ValueKind == JsonValueKind.Number)
|
||||||
|
{
|
||||||
|
if (type == 100)
|
||||||
|
{
|
||||||
|
// fun handling of double to long to byte array
|
||||||
|
spanDouble[0] = data.Deserialize<double>();
|
||||||
|
var valueLong = MemoryMarshal.Cast<double, long>(spanDouble)[0];
|
||||||
|
const int dataLength = sizeof(long);
|
||||||
|
var dataBytes = new byte[dataLength];
|
||||||
|
Buffer.BlockCopy(BitConverter.GetBytes(valueLong), 0, dataBytes, 0, dataLength);
|
||||||
|
SetVRChatRegistryKey(item.Key, dataBytes);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 bool HasVRChatRegistryFolder()
|
||||||
|
{
|
||||||
|
using (var regKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\VRChat\VRChat"))
|
||||||
|
{
|
||||||
|
return regKey != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateVRChatRegistryFolder()
|
||||||
|
{
|
||||||
|
if (HasVRChatRegistryFolder())
|
||||||
|
return;
|
||||||
|
|
||||||
|
using (var key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\VRChat\VRChat"))
|
||||||
|
{
|
||||||
|
if (key == null)
|
||||||
|
throw new Exception("Error creating registry key.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteVRChatRegistryFolder()
|
||||||
|
{
|
||||||
|
using (var regKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\VRChat\VRChat"))
|
||||||
|
{
|
||||||
|
if (regKey == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Registry.CurrentUser.DeleteSubKeyTree(@"SOFTWARE\VRChat\VRChat");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens a file dialog to select a VRChat registry backup JSON file.
|
||||||
|
/// </summary>
|
||||||
|
public void OpenVrcRegJsonFileDialog()
|
||||||
|
{
|
||||||
|
if (dialogOpen) return;
|
||||||
|
dialogOpen = true;
|
||||||
|
|
||||||
|
var thread = new Thread(() =>
|
||||||
|
{
|
||||||
|
using (var openFileDialog = new OpenFileDialog())
|
||||||
|
{
|
||||||
|
openFileDialog.DefaultExt = ".json";
|
||||||
|
openFileDialog.Filter = "JSON Files (*.json)|*.json";
|
||||||
|
openFileDialog.FilterIndex = 1;
|
||||||
|
openFileDialog.RestoreDirectory = true;
|
||||||
|
|
||||||
|
if (openFileDialog.ShowDialog() != DialogResult.OK)
|
||||||
|
{
|
||||||
|
dialogOpen = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogOpen = false;
|
||||||
|
|
||||||
|
var path = openFileDialog.FileName;
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// return file contents
|
||||||
|
var json = File.ReadAllText(path);
|
||||||
|
ExecuteAppFunction("restoreVrcRegistryFromFile", json);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
thread.SetApartmentState(ApartmentState.STA);
|
||||||
|
thread.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@@ -86,6 +86,7 @@
|
|||||||
<Compile Include="Dotnet\AppApi\Folders.cs" />
|
<Compile Include="Dotnet\AppApi\Folders.cs" />
|
||||||
<Compile Include="Dotnet\AppApi\GameHandler.cs" />
|
<Compile Include="Dotnet\AppApi\GameHandler.cs" />
|
||||||
<Compile Include="Dotnet\AppApi\LocalPlayerModerations.cs" />
|
<Compile Include="Dotnet\AppApi\LocalPlayerModerations.cs" />
|
||||||
|
<Compile Include="Dotnet\AppApi\RegistryPlayerPrefs.cs" />
|
||||||
<Compile Include="Dotnet\AppApi\Screenshot.cs" />
|
<Compile Include="Dotnet\AppApi\Screenshot.cs" />
|
||||||
<Compile Include="Dotnet\AppApi\VrcConfigFile.cs" />
|
<Compile Include="Dotnet\AppApi\VrcConfigFile.cs" />
|
||||||
<Compile Include="Dotnet\AppApi\XSOverlay.cs" />
|
<Compile Include="Dotnet\AppApi\XSOverlay.cs" />
|
||||||
|
|||||||
+280
-2
@@ -5267,6 +5267,7 @@ speechSynthesis.getVoices();
|
|||||||
this.refreshCustomCss();
|
this.refreshCustomCss();
|
||||||
this.refreshCustomScript();
|
this.refreshCustomScript();
|
||||||
this.checkVRChatDebugLogging();
|
this.checkVRChatDebugLogging();
|
||||||
|
this.checkAutoBackupRestoreVrcRegistry();
|
||||||
this.migrateStoredUsers();
|
this.migrateStoredUsers();
|
||||||
this.$nextTick(function () {
|
this.$nextTick(function () {
|
||||||
this.$el.style.display = '';
|
this.$el.style.display = '';
|
||||||
@@ -14553,6 +14554,16 @@ speechSynthesis.getVoices();
|
|||||||
this.autoStateChange
|
this.autoStateChange
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
$app.data.vrcRegistryAutoBackup = configRepository.getBool(
|
||||||
|
'VRCX_vrcRegistryAutoBackup',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
$app.methods.saveVrcRegistryAutoBackup = function () {
|
||||||
|
configRepository.setBool(
|
||||||
|
'VRCX_vrcRegistryAutoBackup',
|
||||||
|
this.vrcRegistryAutoBackup
|
||||||
|
);
|
||||||
|
};
|
||||||
$app.data.orderFriendsGroup0 = configRepository.getBool(
|
$app.data.orderFriendsGroup0 = configRepository.getBool(
|
||||||
'orderFriendGroup0',
|
'orderFriendGroup0',
|
||||||
true
|
true
|
||||||
@@ -23850,11 +23861,14 @@ speechSynthesis.getVoices();
|
|||||||
var D = this.VRCXUpdateDialog;
|
var D = this.VRCXUpdateDialog;
|
||||||
var url = this.branches[this.branch].urlReleases;
|
var url = this.branches[this.branch].urlReleases;
|
||||||
this.checkingForVRCXUpdate = true;
|
this.checkingForVRCXUpdate = true;
|
||||||
|
try {
|
||||||
var response = await webApiService.execute({
|
var response = await webApiService.execute({
|
||||||
url,
|
url,
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
this.checkingForVRCXUpdate = false;
|
this.checkingForVRCXUpdate = false;
|
||||||
|
}
|
||||||
var json = JSON.parse(response.data);
|
var json = JSON.parse(response.data);
|
||||||
if (this.debugWebRequests) {
|
if (this.debugWebRequests) {
|
||||||
console.log(json, response);
|
console.log(json, response);
|
||||||
@@ -23919,12 +23933,15 @@ speechSynthesis.getVoices();
|
|||||||
}
|
}
|
||||||
var url = this.branches[this.branch].urlLatest;
|
var url = this.branches[this.branch].urlLatest;
|
||||||
this.checkingForVRCXUpdate = true;
|
this.checkingForVRCXUpdate = true;
|
||||||
|
try {
|
||||||
var response = await webApiService.execute({
|
var response = await webApiService.execute({
|
||||||
url,
|
url,
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
});
|
});
|
||||||
this.pendingVRCXUpdate = false;
|
} finally {
|
||||||
this.checkingForVRCXUpdate = false;
|
this.checkingForVRCXUpdate = false;
|
||||||
|
}
|
||||||
|
this.pendingVRCXUpdate = false;
|
||||||
var json = JSON.parse(response.data);
|
var json = JSON.parse(response.data);
|
||||||
if (this.debugWebRequests) {
|
if (this.debugWebRequests) {
|
||||||
console.log(json, response);
|
console.log(json, response);
|
||||||
@@ -27668,7 +27685,6 @@ speechSynthesis.getVoices();
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// group members
|
// group members
|
||||||
|
|
||||||
$app.data.isGroupMembersLoading = false;
|
$app.data.isGroupMembersLoading = false;
|
||||||
@@ -28441,6 +28457,268 @@ speechSynthesis.getVoices();
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
// #region | Dialog: registry backup dialog
|
||||||
|
|
||||||
|
$app.data.registryBackupDialog = {
|
||||||
|
visible: false
|
||||||
|
};
|
||||||
|
|
||||||
|
$app.data.registryBackupTable = {
|
||||||
|
data: [],
|
||||||
|
tableProps: {
|
||||||
|
stripe: true,
|
||||||
|
size: 'mini',
|
||||||
|
defaultSort: {
|
||||||
|
prop: 'date',
|
||||||
|
order: 'descending'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layout: 'table'
|
||||||
|
};
|
||||||
|
|
||||||
|
$app.methods.showRegistryBackupDialog = function () {
|
||||||
|
this.$nextTick(() =>
|
||||||
|
adjustDialogZ(this.$refs.registryBackupDialog.$el)
|
||||||
|
);
|
||||||
|
var D = this.registryBackupDialog;
|
||||||
|
D.visible = true;
|
||||||
|
this.updateRegistryBackupDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app.methods.updateRegistryBackupDialog = function () {
|
||||||
|
var D = this.registryBackupDialog;
|
||||||
|
this.registryBackupTable.data = [];
|
||||||
|
if (!D.visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var backupsJson = configRepository.getString(
|
||||||
|
'VRCX_VRChatRegistryBackups'
|
||||||
|
);
|
||||||
|
if (!backupsJson) {
|
||||||
|
backupsJson = JSON.stringify([]);
|
||||||
|
}
|
||||||
|
this.registryBackupTable.data = JSON.parse(backupsJson);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app.methods.promptVrcRegistryBackupName = async function () {
|
||||||
|
var name = await this.$prompt(
|
||||||
|
'Enter a name for the backup',
|
||||||
|
'Backup Name',
|
||||||
|
{
|
||||||
|
confirmButtonText: 'Confirm',
|
||||||
|
cancelButtonText: 'Cancel',
|
||||||
|
inputPattern: /\S+/,
|
||||||
|
inputErrorMessage: 'Name is required',
|
||||||
|
inputValue: 'Backup'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (name.action === 'confirm') {
|
||||||
|
this.backupVrcRegistry(name.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$app.methods.backupVrcRegistry = async function (name) {
|
||||||
|
var regJson = await AppApi.GetVRChatRegistry();
|
||||||
|
var newBackup = {
|
||||||
|
name,
|
||||||
|
date: new Date().toJSON(),
|
||||||
|
data: regJson
|
||||||
|
};
|
||||||
|
var backupsJson = configRepository.getString(
|
||||||
|
'VRCX_VRChatRegistryBackups'
|
||||||
|
);
|
||||||
|
if (!backupsJson) {
|
||||||
|
backupsJson = JSON.stringify([]);
|
||||||
|
}
|
||||||
|
var backups = JSON.parse(backupsJson);
|
||||||
|
backups.push(newBackup);
|
||||||
|
configRepository.setString(
|
||||||
|
'VRCX_VRChatRegistryBackups',
|
||||||
|
JSON.stringify(backups)
|
||||||
|
);
|
||||||
|
this.updateRegistryBackupDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app.methods.deleteVrcRegistryBackup = function (row) {
|
||||||
|
var backups = this.registryBackupTable.data;
|
||||||
|
removeFromArray(backups, row);
|
||||||
|
configRepository.setString(
|
||||||
|
'VRCX_VRChatRegistryBackups',
|
||||||
|
JSON.stringify(backups)
|
||||||
|
);
|
||||||
|
this.updateRegistryBackupDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app.methods.restoreVrcRegistryBackup = function (row) {
|
||||||
|
this.$confirm('Continue? Restore Backup', 'Confirm', {
|
||||||
|
confirmButtonText: 'Confirm',
|
||||||
|
cancelButtonText: 'Cancel',
|
||||||
|
type: 'warning',
|
||||||
|
callback: (action) => {
|
||||||
|
if (action !== 'confirm') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var data = JSON.stringify(row.data);
|
||||||
|
AppApi.SetVRChatRegistry(data)
|
||||||
|
.then(() => {
|
||||||
|
this.$message({
|
||||||
|
message: 'VRC registry settings restored',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
this.$message({
|
||||||
|
message: `Failed to restore VRC registry settings, check console for full error: ${e}`,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$app.methods.saveVrcRegistryBackupToFile = function (row) {
|
||||||
|
this.downloadAndSaveJson(row.name, row.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app.methods.restoreVrcRegistryFromFile = function (json) {
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(json);
|
||||||
|
if (!data || typeof data !== 'object') {
|
||||||
|
throw new Error('Invalid JSON');
|
||||||
|
}
|
||||||
|
// quick check to make sure it's a valid registry backup
|
||||||
|
for (var key in data) {
|
||||||
|
var value = data[key];
|
||||||
|
if (
|
||||||
|
typeof value !== 'object' ||
|
||||||
|
typeof value.type !== 'number' ||
|
||||||
|
typeof value.data === 'undefined'
|
||||||
|
) {
|
||||||
|
throw new Error('Invalid JSON');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AppApi.SetVRChatRegistry(json)
|
||||||
|
.then(() => {
|
||||||
|
this.$message({
|
||||||
|
message: 'VRC registry settings restored',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
this.$message({
|
||||||
|
message: `Failed to restore VRC registry settings, check console for full error: ${e}`,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
this.$message({
|
||||||
|
message: 'Invalid JSON',
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$app.methods.deleteVrcRegistry = function () {
|
||||||
|
this.$confirm('Continue? Delete VRC Registry Settings', 'Confirm', {
|
||||||
|
confirmButtonText: 'Confirm',
|
||||||
|
cancelButtonText: 'Cancel',
|
||||||
|
type: 'warning',
|
||||||
|
callback: (action) => {
|
||||||
|
if (action !== 'confirm') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AppApi.DeleteVRChatRegistryFolder().then(() => {
|
||||||
|
this.$message({
|
||||||
|
message: 'VRC registry settings deleted',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$app.methods.clearVrcRegistryDialog = function () {
|
||||||
|
this.registryBackupTable.data = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
$app.methods.checkAutoBackupRestoreVrcRegistry = async function () {
|
||||||
|
if (!this.vrcRegistryAutoBackup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for auto restore
|
||||||
|
var hasVRChatRegistryFolder = await AppApi.HasVRChatRegistryFolder();
|
||||||
|
if (!hasVRChatRegistryFolder) {
|
||||||
|
var lastBackupDate = configRepository.getString(
|
||||||
|
'VRCX_VRChatRegistryLastBackupDate'
|
||||||
|
);
|
||||||
|
var lastRestoreCheck = configRepository.getString(
|
||||||
|
'VRCX_VRChatRegistryLastRestoreCheck'
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
lastRestoreCheck &&
|
||||||
|
lastBackupDate &&
|
||||||
|
lastRestoreCheck === lastBackupDate
|
||||||
|
) {
|
||||||
|
// only ask to restore once
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// popup message about auto restore
|
||||||
|
this.$alert(
|
||||||
|
$t('dialog.registry_backup.restore_prompt'),
|
||||||
|
$t('dialog.registry_backup.header')
|
||||||
|
);
|
||||||
|
this.showRegistryBackupDialog();
|
||||||
|
AppApi.FocusWindow();
|
||||||
|
configRepository.setString(
|
||||||
|
'VRCX_VRChatRegistryLastRestoreCheck',
|
||||||
|
lastBackupDate
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.autoBackupVrcRegistry();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$app.methods.autoBackupVrcRegistry = function () {
|
||||||
|
var date = new Date();
|
||||||
|
var lastBackupDate = configRepository.getString(
|
||||||
|
'VRCX_VRChatRegistryLastBackupDate'
|
||||||
|
);
|
||||||
|
if (lastBackupDate) {
|
||||||
|
var lastBackup = new Date(lastBackupDate);
|
||||||
|
var diff = date.getTime() - lastBackup.getTime();
|
||||||
|
var diffDays = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||||
|
if (diffDays < 7) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var backupsJson = configRepository.getString(
|
||||||
|
'VRCX_VRChatRegistryBackups'
|
||||||
|
);
|
||||||
|
if (!backupsJson) {
|
||||||
|
backupsJson = JSON.stringify([]);
|
||||||
|
}
|
||||||
|
var backups = JSON.parse(backupsJson);
|
||||||
|
backups.forEach((backup) => {
|
||||||
|
if (backup.name === 'Auto Backup') {
|
||||||
|
// remove old auto backup
|
||||||
|
removeFromArray(backups, backup);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
configRepository.setString(
|
||||||
|
'VRCX_VRChatRegistryBackups',
|
||||||
|
JSON.stringify(backups)
|
||||||
|
);
|
||||||
|
this.backupVrcRegistry('Auto Backup');
|
||||||
|
configRepository.setString(
|
||||||
|
'VRCX_VRChatRegistryLastBackupDate',
|
||||||
|
date.toJSON()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
$app = new Vue($app);
|
$app = new Vue($app);
|
||||||
|
|||||||
@@ -2761,6 +2761,29 @@ html
|
|||||||
el-button(type="default" size="mini" icon="el-icon-download" circle @click="downloadAndSaveImage(fullscreenImageDialog.imageUrl)" style="margin-left:5px")
|
el-button(type="default" size="mini" icon="el-icon-download" circle @click="downloadAndSaveImage(fullscreenImageDialog.imageUrl)" style="margin-left:5px")
|
||||||
img(v-lazy="fullscreenImageDialog.imageUrl" style="width:100%;height:100vh;object-fit:contain")
|
img(v-lazy="fullscreenImageDialog.imageUrl" style="width:100%;height:100vh;object-fit:contain")
|
||||||
|
|
||||||
|
el-dialog.x-dialog(:before-close="beforeDialogClose" @closed="clearVrcRegistryDialog" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="registryBackupDialog" :visible.sync="registryBackupDialog.visible" :title="$t('dialog.registry_backup.header')" width="600px")
|
||||||
|
div(v-if="registryBackupDialog.visible" style="margin-top:10px")
|
||||||
|
div.options-container
|
||||||
|
div.options-container-item
|
||||||
|
span.name {{ $t('dialog.registry_backup.auto_backup') }}
|
||||||
|
el-switch(v-model="vrcRegistryAutoBackup" @change="saveVrcRegistryAutoBackup")
|
||||||
|
el-button(@click="promptVrcRegistryBackupName" size="small") {{ $t('dialog.registry_backup.backup') }}
|
||||||
|
el-button(@click="AppApi.OpenVrcRegJsonFileDialog()" size="small") {{ $t('dialog.registry_backup.restore_from_file') }}
|
||||||
|
el-button(@click="deleteVrcRegistry" size="small") {{ $t('dialog.registry_backup.reset') }}
|
||||||
|
data-tables(v-bind="registryBackupTable" style="margin-top:10px")
|
||||||
|
el-table-column(:label="$t('dialog.registry_backup.name')" prop="name")
|
||||||
|
el-table-column(:label="$t('dialog.registry_backup.date')" prop="date")
|
||||||
|
template(v-once #default="scope")
|
||||||
|
span {{ scope.row.date | formatDate('long') }}
|
||||||
|
el-table-column(:label="$t('dialog.registry_backup.action')" width="90" align="right")
|
||||||
|
template(v-once #default="scope")
|
||||||
|
el-tooltip(placement="top" :content="$t('dialog.registry_backup.restore')" :disabled="hideTooltips")
|
||||||
|
el-button(type="text" icon="el-icon-upload2" size="mini" @click="restoreVrcRegistryBackup(scope.row)")
|
||||||
|
el-tooltip(placement="top" :content="$t('dialog.registry_backup.save_to_file')" :disabled="hideTooltips")
|
||||||
|
el-button(type="text" icon="el-icon-download" size="mini" @click="saveVrcRegistryBackupToFile(scope.row)")
|
||||||
|
el-tooltip(placement="top" :content="$t('dialog.registry_backup.delete')" :disabled="hideTooltips")
|
||||||
|
el-button(type="text" icon="el-icon-delete" size="mini" @click="deleteVrcRegistryBackup(scope.row)")
|
||||||
|
|
||||||
//- dialog: open source software notice
|
//- dialog: open source software notice
|
||||||
el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" :visible.sync="ossDialog" :title="$t('dialog.open_source.header')" width="650px")
|
el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" :visible.sync="ossDialog" :title="$t('dialog.open_source.header')" width="650px")
|
||||||
div(v-if="ossDialog" style="height:350px;overflow:hidden scroll;word-break:break-all")
|
div(v-if="ossDialog" style="height:350px;overflow:hidden scroll;word-break:break-all")
|
||||||
|
|||||||
@@ -379,6 +379,7 @@
|
|||||||
"header": "Advanced",
|
"header": "Advanced",
|
||||||
"launch_options": "Launch Options",
|
"launch_options": "Launch Options",
|
||||||
"screenshot_metadata": "Screenshot Metadata",
|
"screenshot_metadata": "Screenshot Metadata",
|
||||||
|
"vrc_registry_backup": "VRC Registry Backup",
|
||||||
"common_folders": "Common Folders",
|
"common_folders": "Common Folders",
|
||||||
"pending_offline": {
|
"pending_offline": {
|
||||||
"header": "Pending Offline",
|
"header": "Pending Offline",
|
||||||
@@ -1230,6 +1231,20 @@
|
|||||||
"copy_image": "Copy Image",
|
"copy_image": "Copy Image",
|
||||||
"open_folder": "Open Folder",
|
"open_folder": "Open Folder",
|
||||||
"upload": "Upload"
|
"upload": "Upload"
|
||||||
|
},
|
||||||
|
"registry_backup": {
|
||||||
|
"header": "VRC Registry Settings Backup",
|
||||||
|
"backup": "Backup",
|
||||||
|
"restore": "Restore",
|
||||||
|
"save_to_file": "Save to file",
|
||||||
|
"delete": "Delete",
|
||||||
|
"restore_from_file": "Restore from file",
|
||||||
|
"reset": "Reset",
|
||||||
|
"name": "Name",
|
||||||
|
"date": "Date",
|
||||||
|
"action": "Action",
|
||||||
|
"auto_backup": "Weekly Auto Backup",
|
||||||
|
"restore_prompt": "VRCX has noticed auto backup of VRC registry settings is enabled but this computer dosn't have any, if you'd like to restore from backup you can do so from here."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prompt": {
|
"prompt": {
|
||||||
|
|||||||
@@ -379,6 +379,7 @@ mixin settingsTab()
|
|||||||
el-button(size="small" icon="el-icon-s-operation" @click="showVRChatConfig()") VRChat config.json
|
el-button(size="small" icon="el-icon-s-operation" @click="showVRChatConfig()") VRChat config.json
|
||||||
el-button(size="small" icon="el-icon-s-operation" @click="showLaunchOptions()") {{ $t('view.settings.advanced.advanced.launch_options') }}
|
el-button(size="small" icon="el-icon-s-operation" @click="showLaunchOptions()") {{ $t('view.settings.advanced.advanced.launch_options') }}
|
||||||
el-button(size="small" icon="el-icon-picture" @click="showScreenshotMetadataDialog()") {{ $t('view.settings.advanced.advanced.screenshot_metadata') }}
|
el-button(size="small" icon="el-icon-picture" @click="showScreenshotMetadataDialog()") {{ $t('view.settings.advanced.advanced.screenshot_metadata') }}
|
||||||
|
el-button(size="small" icon="el-icon-goods" @click="showRegistryBackupDialog()") {{ $t('view.settings.advanced.advanced.vrc_registry_backup') }}
|
||||||
//- Advanced | Common Folders
|
//- Advanced | Common Folders
|
||||||
div.options-container
|
div.options-container
|
||||||
span.header {{ $t('view.settings.advanced.advanced.common_folders') }}
|
span.header {{ $t('view.settings.advanced.advanced.common_folders') }}
|
||||||
|
|||||||
Reference in New Issue
Block a user