From 15fd97060e4707f8bf5bd72f2978c56f248a3634 Mon Sep 17 00:00:00 2001 From: loukylor Date: Fri, 13 Aug 2021 16:26:55 -0700 Subject: [PATCH] New Mod: PrivateInstanceIcon! --- .../PrivateInstanceIcon.csproj | 36 ++++++ PrivateInstanceIcon/PrivateInstanceIconMod.cs | 114 ++++++++++++++++++ PrivateInstanceIcon/icon.png | Bin 0 -> 2560 bytes README.md | 11 ++ VRC-Mods.sln | 16 ++- 5 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 PrivateInstanceIcon/PrivateInstanceIcon.csproj create mode 100644 PrivateInstanceIcon/PrivateInstanceIconMod.cs create mode 100644 PrivateInstanceIcon/icon.png diff --git a/PrivateInstanceIcon/PrivateInstanceIcon.csproj b/PrivateInstanceIcon/PrivateInstanceIcon.csproj new file mode 100644 index 0000000..3eeeb13 --- /dev/null +++ b/PrivateInstanceIcon/PrivateInstanceIcon.csproj @@ -0,0 +1,36 @@ + + + + PrivateInstanceIcon + PrivateInstanceIcon + + + + + + + + + + + + + + + + + $(VRChatPath)MelonLoader\Managed\UnityEngine.ImageConversionModule.dll + + + $(VRChatPath)MelonLoader\Managed\UnityEngine.UI.dll + + + $(VRChatPath)MelonLoader\Managed\VRCCore-Standalone.dll + + + + + + + + diff --git a/PrivateInstanceIcon/PrivateInstanceIconMod.cs b/PrivateInstanceIcon/PrivateInstanceIconMod.cs new file mode 100644 index 0000000..7f793cb --- /dev/null +++ b/PrivateInstanceIcon/PrivateInstanceIconMod.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections; +using System.IO; +using System.Linq; +using System.Reflection; +using MelonLoader; +using UnityEngine; +using UnityEngine.UI; +using VRC.Core; + +[assembly: MelonInfo(typeof(PrivateInstanceIcon.PrivateInstanceIconMod), "PrivateInstanceIcon", "1.0.0", "loukylor", "https://github.com/loukylor/VRC-Mods")] +[assembly: MelonGame("VRChat", "VRChat")] + +namespace PrivateInstanceIcon +{ + public class PrivateInstanceIconMod : MelonMod + { + private static PropertyInfo listEnum; + private static PropertyInfo pickerPrefabProp; + private static Sprite iconSprite; + + public static MelonPreferences_Entry excludeJoinMe; + public static MelonPreferences_Entry hidePrivateInstances; + public override void OnApplicationStart() + { + listEnum = typeof(UiUserList).GetProperties().First(pi => pi.Name.StartsWith("field_Public_Enum")); + pickerPrefabProp = typeof(UiUserList).GetProperties().First(pi => pi.PropertyType == typeof(GameObject)); + + Texture2D iconTex = new Texture2D(2, 2); + using (Stream iconStream = Assembly.GetManifestResourceStream("PrivateInstanceIcon.icon.png")) + { + var buffer = new byte[iconStream.Length]; + iconStream.Read(buffer, 0, buffer.Length); + ImageConversion.LoadImage(iconTex, buffer); + } + + Rect rect = new Rect(0, 0, iconTex.width, iconTex.height); + Vector2 pivot = new Vector2(0.5f, 0.5f); + Vector4 border = Vector4.zero; + iconSprite = Sprite.CreateSprite_Injected(iconTex, ref rect, ref pivot, 50, 0, SpriteMeshType.Tight, ref border, false); + iconSprite.hideFlags = HideFlags.DontUnloadUnusedAsset; + + foreach (MethodInfo method in typeof(UiUserList).GetMethods().Where(mi => mi.Name.StartsWith("Method_Protected_Virtual_Void_VRCUiContentButton_Object_"))) + HarmonyInstance.Patch(method, null, typeof(PrivateInstanceIconMod).GetMethod(nameof(OnSetPickerContentFromApiModel), BindingFlags.NonPublic | BindingFlags.Static).ToNewHarmonyMethod()); + HarmonyInstance.Patch(typeof(UiUserList).GetMethod("Awake"), postfix: typeof(PrivateInstanceIconMod).GetMethod(nameof(OnUiUserListAwake), BindingFlags.NonPublic | BindingFlags.Static).ToNewHarmonyMethod(), finalizer: typeof(PrivateInstanceIconMod).GetMethod(nameof(OnSetPickerContentFromApiModelErrored), BindingFlags.NonPublic | BindingFlags.Static).ToNewHarmonyMethod()); + + MelonPreferences_Category category = MelonPreferences.CreateCategory("PrivateInstanceIcon Config"); + excludeJoinMe = category.CreateEntry(nameof(excludeJoinMe), true, "Whether to hide the icon when people are on join me, and in private instances."); + hidePrivateInstances = category.CreateEntry(nameof(hidePrivateInstances), false, "Whether to just not show people who are in private instances."); + } + + private static void OnUiUserListAwake(UiUserList __instance) + { + if ((int)listEnum.GetValue(__instance) != 3) + return; + + GameObject pickerPrefab = (GameObject)pickerPrefabProp.GetValue(__instance); + VRCUiContentButton picker = pickerPrefab.GetComponent(); + + GameObject[] newArr = new GameObject[picker.field_Public_VRCUiDynamicOverlayIcons_0.field_Public_ArrayOf_GameObject_0.Length + 1]; + picker.field_Public_VRCUiDynamicOverlayIcons_0.field_Public_ArrayOf_GameObject_0.CopyTo(newArr, 0); + + GameObject icon = GameObject.Instantiate(picker.transform.Find("Icons/OverlayIcons/iconUserOnPC").gameObject); + icon.name = "PrivateInstanceIcon"; + icon.transform.SetParent(picker.transform.Find("Icons/OverlayIcons")); + icon.GetComponent().sprite = iconSprite; + icon.SetActive(false); + + newArr[newArr.Length - 1] = icon; + + picker.field_Public_VRCUiDynamicOverlayIcons_0.field_Public_ArrayOf_GameObject_0 = newArr; + } + + private static void OnSetPickerContentFromApiModel(UiUserList __instance, VRCUiContentButton __0, Il2CppSystem.Object __1) + { + if ((int)listEnum.GetValue(__instance) != 3) + return; + + APIUser user = __1.TryCast(); + if (user == null) + return; + + GameObject icon = __0.field_Public_VRCUiDynamicOverlayIcons_0.field_Public_ArrayOf_GameObject_0.First(gameObject => gameObject.name == "PrivateInstanceIcon"); + if (user.location == "private" && !(excludeJoinMe.Value && __0.field_Public_UiStatusIcon_0.field_Public_UserStatus_0 == APIUser.UserStatus.JoinMe)) + { + if (hidePrivateInstances.Value) + MelonCoroutines.Start(SetInactiveCoroutine(__0.gameObject)); + else + icon.SetActive(true); + } + else + { + icon.SetActive(false); + } + } + + private static IEnumerator SetInactiveCoroutine(GameObject go) + { + // This was necessary. fuck you unity + go.SetActive(false); + yield return null; + go.SetActive(false); + } + + private static Exception OnSetPickerContentFromApiModelErrored(Exception __exception) + { + // There's actually no better way to do this https://github.com/knah/Il2CppAssemblyUnhollower/blob/master/UnhollowerBaseLib/Il2CppException.cs + if (__exception is NullReferenceException || __exception.Message.Contains("System.NullReferenceException")) + return null; + else + return __exception; + } + } +} diff --git a/PrivateInstanceIcon/icon.png b/PrivateInstanceIcon/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..872bfac13e4634f6d37565d8bc32c6fbeab1236d GIT binary patch literal 2560 zcmb_ecTm%b5>6ulG?aiMRivEKL_s2m2qGN>DMuG6Cj>7v5eS6xi{Vs21Tn~gNIAd) z(xghuPeAaVf<921NeDG~5CTUGY9Kkhxi>fO&HLxgyxrN|@7tOEXJ@~e%{b?5ixyW9 zhd>}`dpj$)oeF*nQRGf`qv26I1sUvUYXuR0^Rn)TcOVe47JIAH9)w~x-R~yDT7Gan ziq7V>o8fP_X3C1|`RY>!CJ5@2!HDWgth6$Js9`|oCcc_l3D+Ysa! z!qxsZx9UU&R2DDNAl)uONMTVgpmaY{_JD3orD)902xq7vXbA3$e&jv?%!T*WlQF6# zL4mv0ZaGgn(RlYEDr|}wxFIc^R!i{_SwZmN`oYvit$$>J9Kai`J0oX@aCp{bgjkUi+S^ z{8Qpva>CO~=Z9gX;&{jKLYGJdiU;2UkkXh zYpU_FiHYg$UT_XP)vid9H@74m9spZ5uLz)Kjpqmwc3Kl5Jwcap{VGa?Qw$BBMNrMI2x-K`vz597bDfNMh2nu zC?{XI7}kpRtTPQ3aIYB|GzLl22-qI9RRL0S%Sq?5QMJM^HvGOT<)4DJE7uDAnO_OzFRz@nIv)i)^Tg?x>+N2+z zm8f{p_Oz93tlX^=#}rG>GLC36i(`&-Ul-*t({jfh>%6ZuwvCswH}1S6IVRZLX!2z7 z-BBfPmDAha==$a!cQh5_(7J!EaQ;!HwfYLqG+g}RHJ%Kk(MVVQ^WokPAJ^wyUs-2M z^_&u(-lDEJX>#?}eel=sjS=$`Ah&52g!4UD<08>LDxWvuxU=O?lCv8wG`mj}(|mQ+ zlam$-?%3+d$)2FEKj$9k3l9e+UTsMJq3syy#(zNl)=s9qfQ?QX19VL6*(6VovKo4Wv45 zjFumAZ65Xhi(Yz8pd6ag;$oIE54jp^AAnkV1=pBBEteVd+64PUQ2e4-t>tnZ;-v-d z;A{V{rLsEBtFaO!a$rnBDuoXvAAnKt*@pE_a^@7QUR(giq9ynYm|>rPo80^@|9WToc8z zyw$u>$>dPpSGVv7PC+^@{$IqmwazObRz;lWrpvXr^G88e_;|j}UK_x6onw)2d{MvE z=lYc(IM%hVbJSz@?ok6;v__j`p$#w{Vz0VjBHp!!Fl?G@Vq%6=?&p1a&(t4(KcV)+ zBu!?%nLcC&4tA4s2WnBym>xB2*=PQ++P*QC&yw;DjToiQ{RD<-uK!`ustuX?oeuCO z5d}>whTaXVp|qr?KIZhqy*KGfO|!tUt`ga`+t_mJd0HyQzvKy)uJu^xPZ#KD4*{9+$iyPhO9Z z7}U$es1~zM4Nirabt$` za*2n#+Wv#}7u_5_njhCRVrn1L>KWQ*{nCu2rK8_Xkkep6t?_Lx6y<;)$nHyifa71zK5)+!_XEcjw$Vyg;3vjC8zv1g?rysYJ zh+7)K;1jv7%p~2T0X-+bLRUsJZ5!n95pRDJyb4-t?toU&w;##{{Wk^whoJvws>qdq z5J(MGLYkM{ot~j2RBoTV1(80s+`c0V4N&@c z5jR!L{??;8BFBZ#w*@S}>HUu)+k>Q-w=i_gam} +You can also just straight up hide users from the list that you cant join. Note that when the list refreshes, the hidden users might pop up for a frame. + +## Picture of the Icon +![Picture of me with the icon on it](https://i.imgur.com/bLgOC5R.png) + # ReloadAvatars Adds buttons to reload a single user's avatar or all users' avatar. diff --git a/VRC-Mods.sln b/VRC-Mods.sln index bc34da9..3b7d411 100644 --- a/VRC-Mods.sln +++ b/VRC-Mods.sln @@ -41,7 +41,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SelectYourself", "SelectYou EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChairExitController", "ChairExitController\ChairExitController.csproj", "{C44CEA6D-AAAC-4C6F-B231-C9E6040583E5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UserHistory", "UserHistory\UserHistory.csproj", "{805A672C-8C37-4240-9F03-0F5371708E04}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserHistory", "UserHistory\UserHistory.csproj", "{805A672C-8C37-4240-9F03-0F5371708E04}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrivateInstanceIcon", "PrivateInstanceIcon\PrivateInstanceIcon.csproj", "{D7A475B6-D116-4800-A433-02329F460640}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -245,6 +247,18 @@ Global {805A672C-8C37-4240-9F03-0F5371708E04}.Release|x64.Build.0 = Release|Any CPU {805A672C-8C37-4240-9F03-0F5371708E04}.Release|x86.ActiveCfg = Release|Any CPU {805A672C-8C37-4240-9F03-0F5371708E04}.Release|x86.Build.0 = Release|Any CPU + {D7A475B6-D116-4800-A433-02329F460640}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7A475B6-D116-4800-A433-02329F460640}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7A475B6-D116-4800-A433-02329F460640}.Debug|x64.ActiveCfg = Debug|Any CPU + {D7A475B6-D116-4800-A433-02329F460640}.Debug|x64.Build.0 = Debug|Any CPU + {D7A475B6-D116-4800-A433-02329F460640}.Debug|x86.ActiveCfg = Debug|Any CPU + {D7A475B6-D116-4800-A433-02329F460640}.Debug|x86.Build.0 = Debug|Any CPU + {D7A475B6-D116-4800-A433-02329F460640}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7A475B6-D116-4800-A433-02329F460640}.Release|Any CPU.Build.0 = Release|Any CPU + {D7A475B6-D116-4800-A433-02329F460640}.Release|x64.ActiveCfg = Release|Any CPU + {D7A475B6-D116-4800-A433-02329F460640}.Release|x64.Build.0 = Release|Any CPU + {D7A475B6-D116-4800-A433-02329F460640}.Release|x86.ActiveCfg = Release|Any CPU + {D7A475B6-D116-4800-A433-02329F460640}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE