├── .gitignore ├── .gitmodules ├── ActionMenuUtils ├── ActionMenuAPI.cs ├── ActionMenuUtils.csproj ├── Main.cs ├── ModSettings.cs └── icons ├── CHANGELOG.md ├── Common ├── _dummy2_.dll └── _dummy_.dll ├── Directory.Build.props ├── ILRepack ├── ILRepack.Lib.MSBuild.Task.dll ├── ILRepack.Lib.MSBuild.Task.targets └── ILRepack.dll ├── IntegrityCheckGenerator ├── IntegrityCheckGenerator.cs └── IntegrityCheckGenerator.csproj ├── IntegrityCheckWeaver ├── DummyThree.cs ├── IntegrityCheckWeaver.csproj ├── Program.cs └── Utils.cs ├── LICENSE ├── Libs └── .here ├── README.md ├── StandaloneThirdPerson ├── CameraBehindMode.cs ├── CameraMode.cs ├── Main.cs ├── ModSettings.cs ├── QMEnableDisableListener.cs ├── StandaloneThirdPerson.csproj └── Utils.cs ├── UpdateChecker ├── Main.cs ├── Mod.cs ├── ModVersion.cs └── UpdateChecker.csproj ├── VRChatMods.sln └── WorldPredownload ├── Cache └── CacheManager.cs ├── Constants.cs ├── DownloadManager ├── DownloadComplete.cs ├── DownloadInfo.cs ├── DownloadProgress.cs ├── DownloadType.cs └── WorldDownloadManager.cs ├── Helpers ├── Delegates.cs ├── ExtensionMethods.cs ├── UserAgent.cs ├── Utilities.cs └── XRefUtils.cs ├── Main.cs ├── ModSettings.cs ├── Patches.cs ├── UI ├── FriendButton.cs ├── HudIcon.cs ├── InviteButton.cs ├── LoadingScreen.cs ├── WorldButton.cs └── WorldDownloadStatus.cs ├── WorldPredownload.csproj └── gompowpd /.gitignore: -------------------------------------------------------------------------------- 1 | /Output 2 | obj/ 3 | .idea 4 | Libs/*dll 5 | Libs/*db 6 | packages 7 | BadAppleFramesProcessor 8 | *.user 9 | Benchmarks 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ActionMenuApi"] 2 | path = ActionMenuApi 3 | url = https://github.com/gompocp/ActionMenuApi/ 4 | branch = main 5 | -------------------------------------------------------------------------------- /ActionMenuUtils/ActionMenuAPI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | //using ActionMenu = ActionMenu; 5 | //using PedalOption = PedalOption; 6 | //using ActionMenuOpener = ActionMenuOpener; 7 | //using ActionMenuDriver = ActionMenuDriver; 8 | using PedalOptionTriggerEvent = PedalOption.MulticastDelegateNPublicSealedBoUnique; //Will this change?, ¯\_(ツ)_/¯ 9 | using ActionMenuPage = ActionMenu.ObjectNPublicAcTeAcStGaUnique; //Will this change?, ¯\_(ツ)_/¯x2 10 | using UnityEngine; 11 | using HarmonyLib; 12 | using System.Reflection; 13 | using UnhollowerRuntimeLib.XrefScans; 14 | using MelonLoader; 15 | using UnhollowerRuntimeLib; 16 | 17 | //Made by gompo#6956 you are free to use this provided credit is given and 18 | //your mod is licensed under the same license as the one in this repository 19 | //for more complicated situations please dm me on discord 20 | //Credits: 21 | //-Function XRefCheck here adapted to use string lists rather than just strings from Ben's 22 | // (Ben 🐾#3621) Dynamic Bone Safety Mod, link: https://github.com/BenjaminZehowlt/DynamicBonesSafety/blob/master/DynamicBonesSafetyMod.cs 23 | 24 | namespace ActionMenuUtils 25 | { 26 | internal class ActionMenuAPI 27 | { 28 | public static List configPagePre = new List(); //I'm lazy so I made these two public for my mod 29 | public static List configPagePost = new List(); 30 | private static List emojisPagePre = new List(); 31 | private static List emojisPagePost = new List(); 32 | private static List expressionPagePre = new List(); 33 | private static List expressionPagePost = new List(); 34 | private static List sdk2ExpressionPagePre = new List(); 35 | private static List sdk2ExpressionPagePost = new List(); 36 | private static List mainPagePre = new List(); 37 | private static List mainPagePost = new List(); 38 | private static List menuOpacityPagePre = new List(); 39 | private static List menuOpacityPagePost = new List(); 40 | private static List menuSizePagePre = new List(); 41 | private static List menuSizePagePost = new List(); 42 | private static List nameplatesPagePre = new List(); 43 | private static List nameplatesPagePost = new List(); 44 | private static List nameplatesOpacityPagePre = new List(); 45 | private static List nameplatesOpacityPagePost = new List(); 46 | private static List nameplatesVisibilityPagePre = new List(); 47 | private static List nameplatesVisibilityPagePost = new List(); 48 | private static List nameplatesSizePagePre = new List(); 49 | private static List nameplatesSizePagePost = new List(); 50 | private static List optionsPagePre = new List(); 51 | private static List optionsPagePost = new List(); 52 | 53 | private static List openConfigPageKeywords = new List(new string[] { "Menu Size", "Menu Opacity" }); 54 | private static List openMainPageKeyWords = new List(new string[] { "Options", "Emojis" }); 55 | private static List openMenuOpacityPageKeyWords = new List(new string[] { "{0}%" }); 56 | private static List openEmojisPageKeyWords = new List(new string[] { " ", "_" }); 57 | private static List openExpressionMenuKeyWords = new List(new string[] { "Reset Avatar" }); 58 | private static List openOptionsPageKeyWords = new List(new string[] { "Config" }); //"Nameplates" and "Close Menu" are ones as well but that update hasnt dropped yet 59 | private static List openSDK2ExpressionPageKeyWords = new List(new string[] { "EMOTE{0}" }); 60 | 61 | // This isnt out yet on the stable branch sooo no idea if these will work properly when it drops 62 | private static List openNameplatesOpacityPageKeyWords = new List(new string[] { "100%", "80%", "60%", "40%", "20%", "0%" }); 63 | private static List openNameplatesPageKeyWords = new List(new string[] { "Visibility", "Size", "Opacity" }); 64 | private static List openNameplatesVisibilityPageKeyWords = new List(new string[] { "Nameplates Shown", "Icons Only", "Nameplates Hidden" }); 65 | private static List openNameplatesSizePageKeyWords = new List(new string[] { "Large", "Medium", "Normal", "Small", "Tiny" }); 66 | 67 | 68 | private static List openMenuSizePageKeyWords = new List(new string[] { "XXXXXXXXX" }); // No strings found :( Unusable for now. Scanning for methods doesnt help either as there are other functions that yield similar results 69 | 70 | 71 | 72 | 73 | 74 | 75 | /// 76 | /// Creates a new instance of ActionMenuApi and patches all required methods if found 77 | /// 78 | public ActionMenuAPI() 79 | { 80 | /* 81 | foreach (MethodBase methodBase in typeof(ActionMenu).GetMethods().Where(m => m.Name.StartsWith("Method") && checkXref(m, openMainPageKeyWords))) 82 | { 83 | MelonLogger.Log("Found Main Page: " + methodBase.Name); 84 | harmonyInstance.Patch(methodBase, new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenMainPagePre))), new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenMainPagePost)))); 85 | break; 86 | } 87 | 88 | foreach (MethodBase methodBase in typeof(ActionMenu).GetMethods().Where(m => m.Name.StartsWith("Method") && checkXref(m, openConfigPageKeywords))) 89 | { 90 | MelonLogger.Log("Found Config Page:" + methodBase.Name); 91 | harmonyInstance.Patch(methodBase, new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenConfigPagePre))), new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenConfigPagePost)))); 92 | break; 93 | } 94 | foreach (MethodBase methodBase in typeof(ActionMenu).GetMethods().Where(m => m.Name.StartsWith("Method") && checkXref(m, openMenuOpacityPageKeyWords))) 95 | { 96 | MelonLogger.Log("Found Menu Opacity Page:" + methodBase.Name); 97 | harmonyInstance.Patch(methodBase, new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenMenuOpacityPagePre))), new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenMenuOpacityPagePost)))); 98 | break; 99 | } 100 | foreach (MethodBase methodBase in typeof(ActionMenu).GetMethods().Where(m => m.Name.StartsWith("Method") && checkXref(m, openEmojisPageKeyWords))) 101 | { 102 | MelonLogger.Log("Found Emojis Page:" + methodBase.Name); 103 | harmonyInstance.Patch(methodBase, new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenEmojisPagePre))), new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenEmojisPagePost)))); 104 | break; 105 | } 106 | foreach (MethodBase methodBase in typeof(ActionMenu).GetMethods().Where(m => m.Name.StartsWith("Method") && checkXref(m, openExpressionMenuKeyWords))) 107 | { 108 | MelonLogger.Log("Found Expression Menu Page:" + methodBase.Name); 109 | harmonyInstance.Patch(methodBase, new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenExpressionMenuPre))), new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenExpressionMenuPost)))); 110 | break; 111 | } 112 | foreach (MethodBase methodBase in typeof(ActionMenu).GetMethods().Where(m => m.Name.StartsWith("Method") && checkXref(m, openNameplatesOpacityPageKeyWords))) 113 | { 114 | MelonLogger.Log("Found Nameplates Opacity Page:" + methodBase.Name); 115 | harmonyInstance.Patch(methodBase, new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenNameplatesOpacityPre))), new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenNameplatesOpacityPost)))); 116 | break; 117 | } 118 | foreach (MethodBase methodBase in typeof(ActionMenu).GetMethods().Where(m => m.Name.StartsWith("Method") && checkXref(m, openNameplatesPageKeyWords))) 119 | { 120 | MelonLogger.Log("Found Namesplates Page:" + methodBase.Name); 121 | harmonyInstance.Patch(methodBase, new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenNameplatesPagePre))), new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenNameplatesPagePost)))); 122 | break; 123 | } 124 | foreach (MethodBase methodBase in typeof(ActionMenu).GetMethods().Where(m => m.Name.StartsWith("Method") && checkXref(m, openNameplatesVisibilityPageKeyWords))) 125 | { 126 | MelonLogger.Log("Found Namesplates Visibility Page:" + methodBase.Name); 127 | harmonyInstance.Patch(methodBase, new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenNameplatesVisibilityPre))), new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenNameplatesVisibilityPost)))); 128 | break; 129 | } 130 | foreach (MethodBase methodBase in typeof(ActionMenu).GetMethods().Where(m => m.Name.StartsWith("Method") && checkXref(m, openNameplatesSizePageKeyWords))) 131 | { 132 | MelonLogger.Log("Found Namesplates Size Page:" + methodBase.Name); 133 | harmonyInstance.Patch(methodBase, new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenNameplatesSizePre))), new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenNameplatesSizePost)))); 134 | break; 135 | } 136 | */ 137 | foreach (MethodBase methodBase in typeof(ActionMenu).GetMethods().Where(m => m.Name.StartsWith("Method") && checkXref(m, openOptionsPageKeyWords))) 138 | { 139 | 140 | MelonLogger.Msg("Found Options Page:" + methodBase.Name); 141 | Main.HarmonyInstance.Patch(methodBase, new HarmonyMethod(typeof(ActionMenuAPI).GetMethod(nameof(OpenOptionsPre))), new HarmonyMethod(typeof(ActionMenuAPI).GetMethod(nameof(OpenOptionsPost)))); 142 | break; 143 | } 144 | /* 145 | foreach (MethodBase methodBase in typeof(ActionMenu).GetMethods().Where(m => m.Name.StartsWith("Method") && checkXref(m, openSDK2ExpressionPageKeyWords))) 146 | { 147 | MelonLogger.Log("Found SDK2 Expression Page:" + methodBase.Name); 148 | harmonyInstance.Patch(methodBase, new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenSDK2ExpressionPre))), new HarmonyMethod(typeof(ActionMenuApi).GetMethod(nameof(OpenSDK2ExpressionPost)))); 149 | break; 150 | }*/ 151 | 152 | 153 | } 154 | 155 | 156 | 157 | /// 158 | /// Adds a custom Pedal to an already existing menu in vrchat. Options are in enum ActionMenuPageType 159 | /// 160 | /// The page type you want to add your pedal to 161 | /// Called on pedal click 162 | /// Pedal Text 163 | /// Pedal Icon 164 | /// Whether to place your pedal before or after vrchat places its own 165 | public PedalStruct AddPedalToExistingMenu(ActionMenuPageType pageType, System.Action triggerEvent, string text = "Button Text", Texture2D icon = null, Insertion insertion = Insertion.Post) 166 | { 167 | PedalStruct customPedal = new PedalStruct(text, icon, triggerEvent); 168 | switch (pageType) 169 | { 170 | case ActionMenuPageType.Config: 171 | if (insertion == Insertion.Pre) configPagePre.Add(customPedal); 172 | else if (insertion == Insertion.Post) configPagePost.Add(customPedal); 173 | break; 174 | case ActionMenuPageType.Emojis: 175 | if (insertion == Insertion.Pre) emojisPagePre.Add(customPedal); 176 | else if (insertion == Insertion.Post) emojisPagePost.Add(customPedal); 177 | break; 178 | case ActionMenuPageType.Expression: 179 | if (insertion == Insertion.Pre) expressionPagePre.Add(customPedal); 180 | else if (insertion == Insertion.Post) expressionPagePost.Add(customPedal); 181 | break; 182 | case ActionMenuPageType.Main: 183 | if (insertion == Insertion.Pre) mainPagePre.Add(customPedal); 184 | else if (insertion == Insertion.Post) mainPagePost.Add(customPedal); 185 | break; 186 | case ActionMenuPageType.MenuOpacity: 187 | if (insertion == Insertion.Pre) menuOpacityPagePre.Add(customPedal); 188 | else if (insertion == Insertion.Post) menuOpacityPagePost.Add(customPedal); 189 | break; 190 | case ActionMenuPageType.MenuSize: 191 | if (insertion == Insertion.Pre) menuSizePagePre.Add(customPedal); 192 | else if (insertion == Insertion.Post) menuSizePagePost.Add(customPedal); 193 | break; 194 | case ActionMenuPageType.Nameplates: 195 | if (insertion == Insertion.Pre) nameplatesPagePre.Add(customPedal); 196 | else if (insertion == Insertion.Post) nameplatesPagePost.Add(customPedal); 197 | break; 198 | case ActionMenuPageType.NameplatesOpacity: 199 | if (insertion == Insertion.Pre) nameplatesOpacityPagePre.Add(customPedal); 200 | else if (insertion == Insertion.Post) nameplatesOpacityPagePost.Add(customPedal); 201 | break; 202 | case ActionMenuPageType.NameplatesSize: 203 | if (insertion == Insertion.Pre) nameplatesSizePagePre.Add(customPedal); 204 | else if (insertion == Insertion.Post) nameplatesSizePagePost.Add(customPedal); 205 | break; 206 | case ActionMenuPageType.NameplatesVisibilty: 207 | if (insertion == Insertion.Pre) nameplatesVisibilityPagePre.Add(customPedal); 208 | else if (insertion == Insertion.Post) nameplatesVisibilityPagePost.Add(customPedal); 209 | break; 210 | case ActionMenuPageType.Options: 211 | if (insertion == Insertion.Pre) optionsPagePre.Add(customPedal); 212 | else if (insertion == Insertion.Post) optionsPagePost.Add(customPedal); 213 | break; 214 | } 215 | return customPedal; 216 | } 217 | 218 | /// 219 | /// call this in a triggerevent for an already existing pedal to open a new submenu 220 | /// Add pedals to this in the onStart param using AddPedalToCustomMenu() 221 | /// 222 | /// Called on submenu open whats the difference... I dunno it exists tho 223 | /// Called on submenu close 224 | /// Pedal Icon 225 | /// title 226 | public ActionMenuPage CreateSubMenu(System.Action openFunc, System.Action closeFunc = null, Texture2D icon = null, string text = null) 227 | { 228 | if (GetActionMenuOpener() == null) return null; 229 | 230 | return GetActionMenuOpener().field_Public_ActionMenu_0.PushPage(openFunc, closeFunc, icon, text); 231 | } 232 | 233 | /// 234 | /// If you create a custom Submenu you can use this to add your pedals to it 235 | /// 236 | /// Called on pedal click 237 | /// Pedal Text 238 | /// Pedal Icon 239 | public PedalOption AddPedalToCustomMenu(System.Action triggerEvent, string text = "Button Text", Texture2D icon = null) 240 | { 241 | ActionMenuOpener actionMenuOpener = GetActionMenuOpener(); 242 | if (actionMenuOpener == null) return null; 243 | PedalOption pedalOption = actionMenuOpener.field_Public_ActionMenu_0.AddOption(); 244 | pedalOption.setText(text); // VVV 245 | pedalOption.setIcon(icon); //Pretty self explanatory 246 | pedalOption.field_Public_MulticastDelegateNPublicSealedBoUnique_0 = DelegateSupport.ConvertDelegate(triggerEvent); 247 | 248 | return pedalOption; 249 | } 250 | 251 | private static ActionMenuOpener GetActionMenuOpener() 252 | { 253 | //MonoBehaviourPublicObBoSiObObObUnique 254 | if (!ActionMenuDriver.field_Public_Static_ActionMenuDriver_0.field_Public_ActionMenuOpener_0.isOpen() && ActionMenuDriver.field_Public_Static_ActionMenuDriver_0.field_Public_ActionMenuOpener_1.isOpen()) 255 | { 256 | return ActionMenuDriver.field_Public_Static_ActionMenuDriver_0.field_Public_ActionMenuOpener_1; 257 | } 258 | else if (ActionMenuDriver.field_Public_Static_ActionMenuDriver_0.field_Public_ActionMenuOpener_0.isOpen() && !ActionMenuDriver.field_Public_Static_ActionMenuDriver_0.field_Public_ActionMenuOpener_1.isOpen()) 259 | { 260 | return ActionMenuDriver.field_Public_Static_ActionMenuDriver_0.field_Public_ActionMenuOpener_0; 261 | } 262 | else return null; 263 | /* 264 | else if (ActionMenuDriver._instance.openerL.isOpen() && ActionMenuDriver._instance.openerR.isOpen()) 265 | { 266 | return null; //Which one to return ¯\_(ツ)_/¯ Mystery till I figure something smart out 267 | } 268 | */ 269 | } 270 | 271 | public static void OpenConfigPagePre(ActionMenu __instance) 272 | { 273 | AddPedalsInList(configPagePre, __instance); 274 | } 275 | public static void OpenConfigPagePost(ActionMenu __instance) 276 | { 277 | AddPedalsInList(configPagePost, __instance); 278 | } 279 | public static void OpenMainPagePre(ActionMenu __instance) 280 | { 281 | AddPedalsInList(mainPagePre, __instance); 282 | } 283 | public static void OpenMainPagePost(ActionMenu __instance) 284 | { 285 | AddPedalsInList(mainPagePost, __instance); 286 | } 287 | public static void OpenMenuOpacityPagePre(ActionMenu __instance) 288 | { 289 | AddPedalsInList(menuOpacityPagePre, __instance); 290 | } 291 | public static void OpenMenuOpacityPagePost(ActionMenu __instance) 292 | { 293 | AddPedalsInList(menuOpacityPagePost, __instance); 294 | } 295 | public static void OpenEmojisPagePre(ActionMenu __instance) 296 | { 297 | AddPedalsInList(emojisPagePre, __instance); 298 | } 299 | public static void OpenEmojisPagePost(ActionMenu __instance) 300 | { 301 | AddPedalsInList(emojisPagePost, __instance); 302 | } 303 | public static void OpenExpressionMenuPre(ActionMenu __instance) 304 | { 305 | AddPedalsInList(expressionPagePre, __instance); 306 | } 307 | public static void OpenExpressionMenuPost(ActionMenu __instance) 308 | { 309 | AddPedalsInList(expressionPagePost, __instance); 310 | } 311 | public static void OpenNameplatesOpacityPre(ActionMenu __instance) 312 | { 313 | AddPedalsInList(nameplatesOpacityPagePre, __instance); 314 | } 315 | public static void OpenNameplatesOpacityPost(ActionMenu __instance) 316 | { 317 | AddPedalsInList(nameplatesOpacityPagePost, __instance); 318 | } 319 | public static void OpenNameplatesPagePre(ActionMenu __instance) 320 | { 321 | AddPedalsInList(nameplatesPagePre, __instance); 322 | } 323 | public static void OpenNameplatesPagePost(ActionMenu __instance) 324 | { 325 | AddPedalsInList(nameplatesPagePost, __instance); 326 | } 327 | public static void OpenNameplatesVisibilityPre(ActionMenu __instance) 328 | { 329 | AddPedalsInList(nameplatesVisibilityPagePre, __instance); 330 | } 331 | public static void OpenNameplatesVisibilityPost(ActionMenu __instance) 332 | { 333 | AddPedalsInList(nameplatesVisibilityPagePost, __instance); 334 | } 335 | public static void OpenNameplatesSizePre(ActionMenu __instance) 336 | { 337 | AddPedalsInList(nameplatesSizePagePre, __instance); 338 | } 339 | public static void OpenNameplatesSizePost(ActionMenu __instance) 340 | { 341 | AddPedalsInList(nameplatesSizePagePost, __instance); 342 | } 343 | public static void OpenOptionsPre(ActionMenu __instance) 344 | { 345 | AddPedalsInList(optionsPagePre, __instance); 346 | } 347 | public static void OpenOptionsPost(ActionMenu __instance) 348 | { 349 | AddPedalsInList(optionsPagePost, __instance); 350 | } 351 | public static void OpenSDK2ExpressionPre(ActionMenu __instance) 352 | { 353 | AddPedalsInList(sdk2ExpressionPagePre, __instance); 354 | } 355 | public static void OpenSDK2ExpressionPost(ActionMenu __instance) 356 | { 357 | AddPedalsInList(sdk2ExpressionPagePost, __instance); 358 | } 359 | public static void OpenMenuSizePre(ActionMenu __instance) 360 | { 361 | AddPedalsInList(menuSizePagePre, __instance); 362 | } 363 | public static void OpenMenuSizePost(ActionMenu __instance) 364 | { 365 | AddPedalsInList(menuSizePagePost, __instance); 366 | } 367 | 368 | private static void AddPedalsInList(List pedalStructs, ActionMenu instance) 369 | { 370 | foreach (PedalStruct pedalStruct in pedalStructs) 371 | { 372 | PedalOption pedalOption = instance.AddOption(); 373 | pedalOption.setText(pedalStruct.text); // VVV 374 | pedalOption.setIcon(pedalStruct.icon); //Pretty self explanatory 375 | pedalOption.field_Public_MulticastDelegateNPublicSealedBoUnique_0 = DelegateSupport.ConvertDelegate(pedalStruct.triggerEvent); 376 | } 377 | } 378 | 379 | // Function here adapted to use string lists rather than just strings from Ben's (Ben 🐾#3621) Dynamic Bone Safety Mod, link: https://github.com/BenjaminZehowlt/DynamicBonesSafety/blob/master/DynamicBonesSafetyMod.cs 380 | private bool checkXref(MethodBase m, List keywords) 381 | { 382 | try 383 | { 384 | foreach (string keyword in keywords) 385 | { 386 | 387 | if (!XrefScanner.XrefScan(m).Any( 388 | instance => instance.Type == XrefType.Global && instance.ReadAsObject() != null && instance.ReadAsObject().ToString() 389 | .Equals(keyword, StringComparison.OrdinalIgnoreCase))) 390 | { 391 | return false; 392 | } 393 | } 394 | return true; 395 | } 396 | catch{ } 397 | return false; 398 | } 399 | 400 | internal class PedalStruct 401 | { 402 | public string text { get; set; } 403 | public Texture2D icon { get; set; } 404 | public System.Action triggerEvent { get; set; } 405 | public PedalStruct(string text, Texture2D icon, System.Action triggerEvent) 406 | { 407 | this.text = text; 408 | this.icon = icon; 409 | this.triggerEvent = triggerEvent; 410 | } 411 | } 412 | 413 | 414 | internal enum Insertion 415 | { 416 | Pre, 417 | Post 418 | } 419 | 420 | 421 | // Note: anything to do with nameplates and menu size wont work till the new update 422 | internal enum ActionMenuPageType 423 | { 424 | Config, 425 | Emojis, 426 | Expression, 427 | SDK2Expression, 428 | Main, 429 | MenuOpacity, 430 | MenuSize, 431 | Nameplates, 432 | NameplatesOpacity, 433 | NameplatesVisibilty, 434 | NameplatesSize, 435 | Options 436 | } 437 | 438 | } 439 | internal static class ExtensionMethods 440 | { 441 | 442 | public static PedalOption AddOption(this ActionMenu menu) 443 | { 444 | return menu.Method_Private_PedalOption_0(); 445 | //return menu.Method_Private_MonoBehaviourPublicObSiObAcSiBoObStSiBoUnique_0(); //This should be safe for a while unless they add another similar method 446 | } 447 | public static string setText(this PedalOption pedal, string text) 448 | { 449 | return pedal.prop_String_0 = text; //Likewise... should be safe 450 | } 451 | public static string getText(this PedalOption pedal) 452 | { 453 | return pedal.prop_String_0; //Likewise... should be safe 454 | } 455 | public static ActionMenuPage PushPage(this ActionMenu menu, Il2CppSystem.Action openFunc, Il2CppSystem.Action closeFunc = null, Texture2D icon = null, string text = null) 456 | { 457 | return menu.Method_Public_ObjectNPublicAcTeAcStGaUnique_Action_Action_Texture2D_String_0(openFunc, closeFunc, icon, text); //Likewise... should be safe but reflection is pretty easy for it 458 | } 459 | public static Texture2D setIcon(this PedalOption pedal, Texture2D icon) 460 | { 461 | //PropertyInfo texture = typeof(PedalOption).GetProperties().Where(p => p.PropertyType == typeof(Texture2D)).First(); meh 462 | return pedal.prop_Texture2D_0 = icon; 463 | } 464 | public static Texture2D getIcon(this PedalOption pedal) 465 | { 466 | return pedal.prop_Texture2D_0; 467 | } 468 | public static bool isOpen(this ActionMenuOpener actionMenuOpener) 469 | { 470 | return actionMenuOpener.field_Private_Boolean_0; 471 | } 472 | } 473 | 474 | } 475 | -------------------------------------------------------------------------------- /ActionMenuUtils/ActionMenuUtils.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472 5 | true 6 | 1.3.11.0 7 | 9 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ActionMenuUtils/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reflection; 5 | using ActionMenuApi.Api; 6 | using MelonLoader; 7 | using UnhollowerRuntimeLib; 8 | using UnhollowerRuntimeLib.XrefScans; 9 | using UnityEngine; 10 | using UnityEngine.UI; 11 | using VRC.Animation; 12 | using VRC.Core; 13 | using VRC.SDKBase; 14 | using Main = ActionMenuUtils.Main; 15 | 16 | [assembly: MelonGame("VRChat", "VRChat")] 17 | [assembly: MelonOptionalDependencies("ActionMenuApi")] 18 | [assembly: MelonInfo(typeof(Main), "ActionMenuUtils", "1.3.11", "gompo", "https://github.com/gompocp/VRChatMods/releases/")] 19 | [assembly: VerifyLoaderVersion(0, 4, 2, true)] 20 | 21 | namespace ActionMenuUtils 22 | { 23 | public partial class Main : MelonMod 24 | { 25 | private static AssetBundle iconsAssetBundle; 26 | private static Texture2D respawnIcon; 27 | private static Texture2D helpIcon; 28 | private static Texture2D goHomeIcon; 29 | private static Texture2D resetAvatarIcon; 30 | private static Texture2D rejoinInstanceIcon; 31 | private static ActionMenuAPI actionMenuApi; 32 | private static MelonMod Instance; 33 | public new static HarmonyLib.Harmony HarmonyInstance => Instance.HarmonyInstance; 34 | 35 | 36 | public override void OnApplicationStart() 37 | { 38 | Instance = this; 39 | try 40 | { 41 | if (string.IsNullOrEmpty(ID)) return; 42 | //Adapted from knah's JoinNotifier mod found here: https://github.com/knah/VRCMods/blob/master/JoinNotifier/JoinNotifierMod.cs 43 | using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ActionMenuUtils.icons")) 44 | using (var tempStream = new MemoryStream((int)stream.Length)) 45 | { 46 | stream.CopyTo(tempStream); 47 | 48 | iconsAssetBundle = AssetBundle.LoadFromMemory_Internal(tempStream.ToArray(), 0); 49 | iconsAssetBundle.hideFlags |= HideFlags.DontUnloadUnusedAsset; 50 | } 51 | respawnIcon = iconsAssetBundle.LoadAsset_Internal("Assets/Resources/Refresh.png", Il2CppType.Of()).Cast(); 52 | respawnIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 53 | helpIcon = iconsAssetBundle.LoadAsset_Internal("Assets/Resources/Help.png", Il2CppType.Of()).Cast(); 54 | helpIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 55 | goHomeIcon = iconsAssetBundle.LoadAsset_Internal("Assets/Resources/Home.png", Il2CppType.Of()).Cast(); 56 | goHomeIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 57 | resetAvatarIcon = iconsAssetBundle.LoadAsset_Internal("Assets/Resources/Avatar.png", Il2CppType.Of()).Cast(); 58 | resetAvatarIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 59 | rejoinInstanceIcon = iconsAssetBundle.LoadAsset_Internal("Assets/Resources/Pin.png", Il2CppType.Of()).Cast(); 60 | rejoinInstanceIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 61 | } 62 | catch (Exception e) { 63 | MelonLogger.Warning("Consider checking for newer version as mod possibly no longer working, Exception occured OnAppStart(): " + e.Message); 64 | } 65 | ModSettings.RegisterSettings(); 66 | ModSettings.Apply(); 67 | if(MelonHandler.Mods.Any(m => m.Info.Name.Equals("ActionMenuApi"))) { 68 | SetupButtonsForAMAPI(); 69 | } 70 | else 71 | { 72 | actionMenuApi = new ActionMenuAPI(); 73 | SetupButtons(); 74 | } 75 | 76 | } 77 | 78 | 79 | 80 | 81 | private static void SetupButtonsForAMAPI() 82 | { 83 | VRCActionMenuPage.AddSubMenu(ActionMenuPage.Options, "SOS", 84 | () => 85 | { 86 | //Respawn 87 | if (ModSettings.confirmRespawn) 88 | CustomSubMenu.AddSubMenu("Respawn", 89 | () => CustomSubMenu.AddButton("Confirm Respawn", Utils.Respawn, respawnIcon), 90 | respawnIcon 91 | ); 92 | else 93 | CustomSubMenu.AddButton("Respawn", Utils.Respawn, respawnIcon); 94 | 95 | //Reset Avatar 96 | if (ModSettings.confirmAvatarReset) 97 | CustomSubMenu.AddSubMenu("Reset Avatar", 98 | () => CustomSubMenu.AddButton("Confirm Reset Avatar", Utils.ResetAvatar, resetAvatarIcon), 99 | resetAvatarIcon 100 | ); 101 | else 102 | CustomSubMenu.AddButton("Reset Avatar", Utils.ResetAvatar, resetAvatarIcon); 103 | 104 | //Instance Rejoin 105 | if (ModSettings.confirmInstanceRejoin) 106 | CustomSubMenu.AddSubMenu("Rejoin Instance", 107 | () => CustomSubMenu.AddButton("Confirm Instance Rejoin", Utils.RejoinInstance, rejoinInstanceIcon), 108 | rejoinInstanceIcon 109 | ); 110 | else 111 | CustomSubMenu.AddButton("Rejoin Instance", Utils.RejoinInstance, rejoinInstanceIcon); 112 | 113 | //Go Home 114 | if (ModSettings.confirmGoHome) 115 | CustomSubMenu.AddSubMenu("Go Home", 116 | () => CustomSubMenu.AddButton("Confirm Go Home",Utils.Home, goHomeIcon), 117 | goHomeIcon 118 | ); 119 | else 120 | CustomSubMenu.AddButton("Go Home",Utils.Home, goHomeIcon); 121 | 122 | }, helpIcon 123 | ); 124 | } 125 | 126 | public override void OnPreferencesLoaded() => ModSettings.Apply(); 127 | public override void OnPreferencesSaved() => ModSettings.Apply(); 128 | 129 | 130 | private static void SetupButtons() 131 | { 132 | 133 | actionMenuApi.AddPedalToExistingMenu(ActionMenuAPI.ActionMenuPageType.Options, delegate 134 | { 135 | actionMenuApi.CreateSubMenu(() => { 136 | 137 | if (ModSettings.confirmRespawn) 138 | actionMenuApi.AddPedalToCustomMenu(() => 139 | 140 | actionMenuApi.CreateSubMenu(() => 141 | actionMenuApi.AddPedalToCustomMenu(Utils.Respawn, "Confirm Respawn", respawnIcon) 142 | ), "Respawn", respawnIcon 143 | ); 144 | else 145 | actionMenuApi.AddPedalToCustomMenu(Utils.Respawn, "Respawn", respawnIcon); 146 | 147 | if (ModSettings.confirmGoHome) 148 | actionMenuApi.AddPedalToCustomMenu(() => 149 | actionMenuApi.CreateSubMenu( () => 150 | actionMenuApi.AddPedalToCustomMenu(Utils.Home, "Confirm Go Home", goHomeIcon) 151 | ), "Go Home", goHomeIcon 152 | ); 153 | else 154 | actionMenuApi.AddPedalToCustomMenu(Utils.Home, "Go Home", goHomeIcon); 155 | 156 | if (ModSettings.confirmAvatarReset) 157 | actionMenuApi.AddPedalToCustomMenu(() => 158 | actionMenuApi.CreateSubMenu(() => 159 | actionMenuApi.AddPedalToCustomMenu(Utils.ResetAvatar, "Confirm Reset Avatar", resetAvatarIcon) 160 | ), "Reset Avatar", resetAvatarIcon 161 | ); 162 | else 163 | actionMenuApi.AddPedalToCustomMenu(Utils.ResetAvatar, "Reset Avatar", resetAvatarIcon); 164 | 165 | if (ModSettings.confirmInstanceRejoin) 166 | { 167 | actionMenuApi.AddPedalToCustomMenu(() => 168 | 169 | actionMenuApi.CreateSubMenu(() => 170 | actionMenuApi.AddPedalToCustomMenu(Utils.RejoinInstance, "Confirm Instance Rejoin", rejoinInstanceIcon) 171 | ), "Rejoin Instance", rejoinInstanceIcon 172 | ); 173 | } 174 | else 175 | actionMenuApi.AddPedalToCustomMenu(Utils.RejoinInstance, "Rejoin Instance", rejoinInstanceIcon); 176 | 177 | }); 178 | }, "Help", helpIcon); 179 | } 180 | private static string ID = "gompo"; 181 | } 182 | 183 | static class Utils 184 | { 185 | //Gracefully taken from Advanced Invites https://github.com/Psychloor/AdvancedInvites/blob/master/AdvancedInvites/Utilities.cs#L356 186 | public static bool XRefScanFor(this MethodBase methodBase, string searchTerm) 187 | { 188 | return XrefScanner.XrefScan(methodBase).Any( 189 | xref => xref.Type == XrefType.Global && xref.ReadAsObject()?.ToString().IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) >= 0); 190 | } 191 | public static GoHomeDelegate GetGoHomeDelegate 192 | { 193 | get 194 | { 195 | if (goHomeDelegate != null) return goHomeDelegate; 196 | MethodInfo goHomeMethod = typeof(VRCFlowManager).GetMethods(BindingFlags.Public | BindingFlags.Instance).First( 197 | m => m.GetParameters().Length == 0 && m.ReturnType == typeof(void) && m.XRefScanFor("Going to Home Location: ")); 198 | 199 | goHomeDelegate = (GoHomeDelegate)Delegate.CreateDelegate( 200 | typeof(GoHomeDelegate), 201 | VRCFlowManager.prop_VRCFlowManager_0, 202 | goHomeMethod); 203 | return goHomeDelegate; 204 | } 205 | } 206 | public static void GoHome() => GetGoHomeDelegate(); 207 | private static GoHomeDelegate goHomeDelegate; 208 | public delegate void GoHomeDelegate(); 209 | 210 | public static void Respawn() 211 | { 212 | GameObject.Find("UserInterface/QuickMenu/ShortcutMenu/RespawnButton").GetComponent