diff --git a/AppApi.cs b/AppApi.cs index 0f5a8e2d..29afabcc 100644 --- a/AppApi.cs +++ b/AppApi.cs @@ -465,7 +465,7 @@ namespace VRCX return clipboard; } - public string GetVRChatRegistryKey(string key) + public object GetVRChatRegistryKey(string key) { // https://answers.unity.com/questions/177945/playerprefs-changing-the-name-of-keys.html?childToView=208076#answer-208076 // VRC_GROUP_ORDER_usr_032383a7-748c-4fb2-94e4-bcb928e5de6b_h2810492971 @@ -476,13 +476,54 @@ namespace VRCX using (var regKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\VRChat\VRChat")) { - var bytes = (byte[])regKey?.GetValue(keyName); - if (bytes == null) + var data = regKey?.GetValue(keyName); + if (data == null) return null; - var value = Encoding.ASCII.GetString(bytes); - return value; + var type = regKey.GetValueKind(keyName); + switch (type) + { + case RegistryValueKind.Binary: + return Encoding.ASCII.GetString((byte[])data); + + case RegistryValueKind.DWord: + return data; + } } + + return null; + } + + public bool SetVRChatRegistryKey(string key, string value) + { + uint hash = 5381; + foreach (char c in key) + hash = hash * 33 ^ c; + var keyName = key + "_h" + hash; + + using (var regKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\VRChat\VRChat", true)) + { + if (regKey?.GetValue(keyName) == null) + return false; + + var type = regKey.GetValueKind(keyName); + object setValue = null; + switch (type) + { + case RegistryValueKind.Binary: + setValue = Encoding.ASCII.GetBytes(value); + break; + + case RegistryValueKind.DWord: + setValue = value; + break; + } + if (setValue == null) + return false; + + regKey.SetValue(keyName, setValue, type); + } + return true; } public Dictionary GetVRChatModerations(string currentUserId) @@ -512,6 +553,61 @@ namespace VRCX return output; } + public short GetVRChatUserModeration(string currentUserId, string userId) + { + var filePath = Path.Combine(GetVRChatAppDataLocation(), "LocalPlayerModerations", $"{currentUserId}-show-hide-user.vrcset"); + if (!File.Exists(filePath)) + return 0; + + using (var reader = new StreamReader(filePath)) + { + string line; + while ((line = reader.ReadLine()) != null) + { + var index = line.IndexOf(' '); + if (index <= 0) + continue; + + if (userId == line.Substring(0, index)) + { + return short.Parse(line.Substring(line.Length - 3)); + } + } + } + return 0; + } + + public bool SetVRChatUserModeration(string currentUserId, string userId, int type) + { + var filePath = Path.Combine(GetVRChatAppDataLocation(), "LocalPlayerModerations", $"{currentUserId}-show-hide-user.vrcset"); + if (!File.Exists(filePath)) + return false; + + var lines = File.ReadAllLines(filePath).ToList(); + var index = lines.FindIndex(x => x.StartsWith(userId)); + if (index >= 0) + lines.RemoveAt(index); + + if (type != 0) + { + var sb = new StringBuilder(userId); + while (sb.Length < 64) + sb.Append(' '); + + sb.Append(type.ToString("000")); + lines.Add(sb.ToString()); + } + try + { + File.WriteAllLines(filePath, lines); + } + catch (Exception) + { + return false; + } + return true; + } + public void SetStartup(bool enabled) { try diff --git a/LogWatcher.cs b/LogWatcher.cs index cafb09b9..daca175a 100644 --- a/LogWatcher.cs +++ b/LogWatcher.cs @@ -37,7 +37,6 @@ namespace VRCX private Thread m_Thread; private bool m_ResetLog; private bool m_FirstRun = true; - private bool m_NullLogMsg; private static DateTime tillDate = DateTime.Now; // NOTE @@ -129,10 +128,8 @@ namespace VRCX // sort by creation time Array.Sort(fileInfos, (a, b) => a.CreationTimeUtc.CompareTo(b.CreationTimeUtc)); - var index = 0; foreach (var fileInfo in fileInfos) { - index++; fileInfo.Refresh(); if (fileInfo.Exists == false) { @@ -154,17 +151,6 @@ namespace VRCX m_LogContextMap.Add(fileInfo.Name, logContext); } - if (!m_NullLogMsg && fileInfo.Length == 0 && index == fileInfos.Length) - { - // check if last file is empty - Console.WriteLine($"{fileInfo.Name}"); - if (MainForm.Instance != null && MainForm.Instance.Browser != null) - { - MainForm.Instance.Browser.ExecuteScriptAsync("$app.showNullLogWarning()"); - m_NullLogMsg = true; - } - } - if (logContext.Length == fileInfo.Length) { continue; diff --git a/html/src/app.js b/html/src/app.js index f890b4eb..eff8ce29 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -4649,6 +4649,7 @@ speechSynthesis.getVoices(); this.getGameLogTable(); this.refreshCustomCss(); this.refreshCustomScript(); + this.checkVRChatDebugLogging(); this.$nextTick(function () { this.$el.style.display = ''; if (!this.enablePrimaryPassword) { @@ -4827,6 +4828,10 @@ speechSynthesis.getVoices(); this.lastLocationReset(); this.clearNowPlaying(); this.updateVRLastLocation(); + workerTimers.setTimeout( + () => this.checkVRChatDebugLogging(), + 60000 + ); this.nextDiscordUpdate = 0; } if (isSteamVRRunning !== this.isSteamVRRunning) { @@ -14000,6 +14005,7 @@ speechSynthesis.getVoices(); isBlock: false, isMute: false, isHideAvatar: false, + isShowAvatar: false, isInteractOff: false, isFavorite: false, @@ -14043,6 +14049,7 @@ speechSynthesis.getVoices(); joinCount: 0, timeSpent: 0, lastSeen: '', + avatarModeration: 0, previousDisplayNames: [], dateFriended: '', unFriended: false @@ -14285,6 +14292,9 @@ speechSynthesis.getVoices(); D.lastSeen = ''; D.joinCount = 0; D.timeSpent = 0; + D.avatarModeration = 0; + D.isHideAvatar = false; + D.isShowAvatar = false; D.previousDisplayNames = []; D.dateFriended = ''; D.unFriended = false; @@ -14318,7 +14328,6 @@ speechSynthesis.getVoices(); D.outgoingRequest = false; D.isBlock = false; D.isMute = false; - D.isHideAvatar = false; D.isInteractOff = false; for (var ref of API.cachedPlayerModerations.values()) { if ( @@ -14428,6 +14437,17 @@ speechSynthesis.getVoices(); displayNameMapSorted.keys() ); }); + AppApi.GetVRChatUserModeration( + API.currentUser.id, + userId + ).then((result) => { + D.avatarModeration = result; + if (result === 4) { + D.isHideAvatar = true; + } else if (result === 5) { + D.isShowAvatar = true; + } + }); } API.getRepresentedGroup({userId}).then((args1) => { D.representedGroup = args1.json; @@ -15187,18 +15207,6 @@ speechSynthesis.getVoices(); type: 'mute' }); break; - case 'Show Avatar': - API.deletePlayerModeration({ - moderated: userId, - type: 'hideAvatar' - }); - break; - case 'Hide Avatar': - API.sendPlayerModeration({ - moderated: userId, - type: 'hideAvatar' - }); - break; case 'Enable Avatar Interaction': API.deletePlayerModeration({ moderated: userId, @@ -15315,6 +15323,18 @@ speechSynthesis.getVoices(); this.copyUser(D.id); } else if (command === 'Invite To Group') { this.showInviteGroupDialog('', D.id); + } else if (command === 'Hide Avatar') { + if (D.isHideAvatar) { + this.setPlayerModeration(D.id, 0); + } else { + this.setPlayerModeration(D.id, 4); + } + } else if (command === 'Show Avatar') { + if (D.isShowAvatar) { + this.setPlayerModeration(D.id, 0); + } else { + this.setPlayerModeration(D.id, 5); + } } else { this.$confirm(`Continue? ${command}`, 'Confirm', { confirmButtonText: 'Confirm', @@ -23287,10 +23307,6 @@ speechSynthesis.getVoices(); }); }; - API.$on('GROUP:ROLES', function (args) { - console.log(args); - }); - /* params: { groupId: string @@ -23908,13 +23924,40 @@ speechSynthesis.getVoices(); } }; - $app.methods.showNullLogWarning = function () { - return; - // eslint-disable-next-line no-unreachable - this.$alert( - 'VRCX noticed your last log file is empty this is normally caused by disabling debug logging. VRCX requires debug logging to be enabled to function correctly. Please enable debug logging in VRChat quick menu settings > debug > enable debug logging, then rejoin the instance or restart VRChat.', - 'Enable debug logging' - ); + $app.methods.checkVRChatDebugLogging = async function () { + try { + var loggingEnabled = await AppApi.GetVRChatRegistryKey( + 'LOGGING_ENABLED' + ); + if (loggingEnabled === null) { + // key not found + return; + } + if (loggingEnabled === 1) { + // already enabled + return; + } + var result = await AppApi.SetVRChatRegistryKey( + 'LOGGING_ENABLED', + '1' + ); + if (!result) { + // failed to set key + this.$alert( + 'VRCX has noticed VRChat debug logging is disabled. VRCX requires debug logging in order to function correctly. Please enable debug logging in VRChat quick menu settings > debug > enable debug logging, then rejoin the instance or restart VRChat.', + 'Enable debug logging' + ); + console.error('Failed to enable debug logging', result); + return; + } + this.$alert( + 'VRCX has noticed VRChat debug logging is disabled and automatically re-enabled it. VRCX requires debug logging in order to function correctly.', + 'Enabled debug logging' + ); + console.log('Enabled debug logging'); + } catch (e) { + console.error(e); + } }; $app.methods.downloadAndSaveImage = async function (url) { @@ -23953,6 +23996,31 @@ speechSynthesis.getVoices(); } }; + $app.methods.setPlayerModeration = function (userId, type) { + var D = this.userDialog; + AppApi.SetVRChatUserModeration(API.currentUser.id, userId, type).then( + (result) => { + if (result) { + if (type === 4) { + D.isShowAvatar = false; + D.isHideAvatar = true; + } else if (type === 5) { + D.isShowAvatar = true; + D.isHideAvatar = false; + } else { + D.isShowAvatar = false; + D.isHideAvatar = false; + } + } else { + $app.$message({ + message: 'Failed to change avatar moderation', + type: 'error' + }); + } + } + ); + }; + $app = new Vue($app); window.$app = $app; })(); diff --git a/html/src/index.pug b/html/src/index.pug index 81af3aa7..8d1c9244 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -1636,7 +1636,7 @@ html el-tooltip(v-else placement="top" content="Add to favorites" :disabled="hideTooltips") el-button(type="default" @click="userDialogCommand('Add Favorite')" icon="el-icon-star-off" circle) el-dropdown(trigger="click" @command="userDialogCommand" size="small") - el-button(:type="(userDialog.incomingRequest || userDialog.outgoingRequest) ? 'success' : (userDialog.isBlock || userDialog.isMute || userDialog.isHideAvatar) ? 'danger' : 'default'" icon="el-icon-more" circle style="margin-left:5px") + el-button(:type="(userDialog.incomingRequest || userDialog.outgoingRequest || userDialog.isShowAvatar) ? 'success' : (userDialog.isBlock || userDialog.isMute || userDialog.isHideAvatar) ? 'danger' : 'default'" icon="el-icon-more" circle style="margin-left:5px") el-dropdown-menu(#default="dropdown") el-dropdown-item(icon="el-icon-refresh" command="Refresh") Refresh el-dropdown-item(icon="el-icon-s-order" command="Copy User") Copy User URL @@ -1669,8 +1669,8 @@ html el-dropdown-item(v-else icon="el-icon-circle-close" command="Block" divided :disabled="userDialog.ref.$isModerator") Block el-dropdown-item(v-if="userDialog.isMute" icon="el-icon-microphone" command="Unmute" style="color:#F56C6C") Unmute el-dropdown-item(v-else icon="el-icon-turn-off-microphone" command="Mute" :disabled="userDialog.ref.$isModerator") Mute - //- el-dropdown-item(v-if="userDialog.isHideAvatar" icon="el-icon-user-solid" command="Show Avatar" style="color:#F56C6C") Show Avatar - //- el-dropdown-item(v-else icon="el-icon-user" command="Hide Avatar") Hide Avatar + el-dropdown-item(icon="el-icon-user-solid" command="Show Avatar") #[i.el-icon-check.el-icon--left(v-if="userDialog.isShowAvatar")]Show Avatar + el-dropdown-item(icon="el-icon-user" command="Hide Avatar") #[i.el-icon-check.el-icon--left(v-if="userDialog.isHideAvatar")]Hide Avatar el-dropdown-item(v-if="userDialog.isInteractOff" icon="el-icon-thumb" command="Enable Avatar Interaction" style="color:#F56C6C") Enable Avatar Interaction el-dropdown-item(v-else icon="el-icon-circle-close" command="Disable Avatar Interaction") Disable Avatar Interaction template(v-if="userDialog.isFriend") @@ -3661,7 +3661,7 @@ html //- dialog: invite group el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="inviteGroupDialog" :visible.sync="inviteGroupDialog.visible" title="Invite To Group" width="450px") div(v-if="inviteGroupDialog.visible" v-loading="inviteGroupDialog.loading") - span The limits for this is unknown be careful using it + span The limits for this are unknown be careful when using it, inviting too many users to a group is known to cause a ban. el-select(v-model="inviteGroupDialog.groupId" clearable placeholder="Choose Group" filterable :disabled="inviteGroupDialog.loading" @change="isAllowedToInviteToGroup" style="margin-top:15px") el-option-group(v-if="inviteGroupDialog.groups.length" label="Groups" style="width:410px") el-option.x-friend-item(v-for="group in inviteGroupDialog.groups" :key="group.id" :label="group.name" :value="group.id" style="height:auto")