├── .github └── workflows │ └── dotnet.yml ├── .gitignore ├── .vs ├── ProjectSettings.json ├── VRCMods │ ├── DesignTimeBuild │ │ └── .dtbcache.v2 │ └── v16 │ │ └── .suo ├── VSWorkspaceState.json └── slnx.sqlite ├── AdvancedSafety ├── AdvancedSafety.csproj ├── AdvancedSafetyMod.cs ├── AdvancedSafetySettings.cs ├── AvatarHiding.cs ├── BundleVerifier │ ├── BundleDlContext.cs │ ├── BundleDlInterceptor.cs │ ├── BundleDownloadMethods.cs │ ├── BundleHashCache.cs │ ├── BundleVerifier.zip │ ├── BundleVerifierMod.cs │ ├── RestrictedProcessRunner │ │ ├── BundleVerifierProcessHandle.cs │ │ ├── Interop │ │ │ ├── InteropMethods.cs │ │ │ ├── JobHandle.cs │ │ │ └── ProcessHandle.cs │ │ ├── MemoryMapWriterStream.cs │ │ └── RestrictedProcessHandle.cs │ └── VerifierExitCodes.cs ├── ComponentAdjustment.cs ├── FinalIkPatches.cs ├── NativeStructs.cs ├── PortalHiding.cs ├── PriorityQueue.cs ├── QuickMenuHideAvatarButtonHandler.cs ├── ReaderPatches.cs ├── ScanningReflectionCache.cs ├── SortingOrderHammerer.cs └── UiExpansionKitSupport.cs ├── CameraMinus ├── CameraMinus.csproj └── CameraMinusMod.cs ├── Common ├── CustomizedMelonMod.cs ├── NativePatchUtils.cs ├── _dummy2_.dll └── _dummy_.dll ├── Directory.Build.props ├── EmojiPageButtons ├── EmojiPageButtons.csproj └── EmojiPageButtonsMod.cs ├── FavCat ├── Adapters │ ├── DbAvatarAdapter.cs │ ├── DbPlayerAdapter.cs │ ├── DbWorldAdapter.cs │ └── IStoredModelAdapter.cs ├── AssetsHandler.cs ├── CustomLists │ ├── CustomPicker.cs │ ├── CustomPickerList.cs │ ├── IPickerElement.cs │ └── PickerPool.cs ├── Database │ ├── DatabaseEntity.cs │ ├── DatabaseFavoriteHandler.cs │ ├── DatabaseImageHandler.cs │ ├── LocalStoreDatabase.Avatar.cs │ ├── LocalStoreDatabase.Player.cs │ ├── LocalStoreDatabase.World.cs │ ├── LocalStoreDatabase.cs │ └── Stored │ │ ├── INamedStoredObject.cs │ │ ├── StoredAvatar.cs │ │ ├── StoredCategory.cs │ │ ├── StoredCategoryOrder.cs │ │ ├── StoredFavorite.cs │ │ ├── StoredImageInfo.cs │ │ ├── StoredPlayer.cs │ │ └── StoredWorld.cs ├── ExportProcessor.cs ├── Extensions.cs ├── FavCat.csproj ├── FavCat.csproj.DotSettings ├── FavCatMod.cs ├── FavCatSettings.cs ├── GlobalImageCache.cs ├── ImportFolderProcessor.cs ├── Modules │ ├── AvatarModule.cs │ ├── ExtendedFavoritesModuleBase.cs │ ├── PlayersModule.cs │ └── WorldsModule.cs ├── ReFetchFavoritesProcessor.cs ├── ScanningReflectionCache.cs └── extraui ├── Finitizer ├── Finitizer.csproj └── FinitizerMod.cs ├── FriendsPlusHome ├── FriendsPlusHome.csproj └── FriendsPlusHomeMod.cs ├── IKTweaks ├── AnimationsHandler.cs ├── CachedSolver.cs ├── CustomSpineSolver.cs ├── HandOffsetsManager.cs ├── IKTweaks.csproj ├── IKTweaksMod.cs ├── IkTweaksSettings.cs ├── Math.cs ├── VrIkHandling.cs ├── WallFreezeFixer.cs └── WeightAdjustmentCookie.cs ├── 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 ├── JoinNotifier ├── JoinNotifier.csproj ├── JoinNotifierMod.cs ├── JoinNotifierSettings.cs ├── NetworkManagerHooks.cs └── joinnotifier.assetbundle ├── LICENSE ├── LagFreeScreenshots ├── API │ ├── LfsApi.cs │ ├── MetadataV2.cs │ └── ScreenshotRotation.cs ├── AwaitProvider.cs ├── EventHandler.cs ├── LagFreeScreenshots.csproj ├── LagFreeScreenshotsMod.cs ├── Metadata.cs ├── PngUtils.cs └── PresetScreenshotSizes.cs ├── Libs └── .touch ├── LibsAlt └── .touch ├── Malicious-Mods.md ├── MirrorResolutionUnlimiter ├── MirrorResolutionUnlimiter.csproj ├── MirrorResolutionUnlimiterMod.cs ├── OriginalPixelLightsSettingKeeper.cs └── UiExtensionsAddon.cs ├── ParticleAndBoneLimiterSettings ├── CustomParticleSettingsUiHandler.cs ├── ParticleAndBoneLimiterSettings.cs └── ParticleAndBoneLimiterSettings.csproj ├── README.md ├── ReleaseChangelog.md ├── ScaleGoesBrr ├── ScaleGoesBrr.csproj ├── ScaleGoesBrrComponent.cs └── ScaleGoesBrrMod.cs ├── SparkleBeGone ├── SparkleBeGone.csproj ├── SparkleBeGoneMod.cs └── sparklebegone ├── Styletor ├── API │ └── StyletorApi.cs ├── BundledStyles │ ├── basic-recolorable.styletor.zip │ ├── bigger-mic-button.styletor.zip │ ├── default-fixes.styletor.zip │ └── qm-background-lemon.styletor.zip ├── ExtraHandlers │ ├── ActionMenuHandler.cs │ └── UiLasersHandler.cs ├── Jsons │ ├── SpriteJson.cs │ └── StyleMetadata.cs ├── SettingsHolder.cs ├── StyleEngineWrapper.cs ├── Styles │ ├── BuiltinStyleExporter.cs │ ├── ColorizerManager.cs │ ├── OverrideStyle.cs │ ├── OverridesStyleSheet.cs │ └── StylesLoader.cs ├── Styletor.csproj ├── StyletorMod.cs └── Utils │ ├── Extensions.cs │ ├── SpriteSnipperUtil.cs │ └── Utils.cs ├── TrueShaderAntiCrash ├── DxbcShaderFilter.dll ├── ShaderFilterApi.cs ├── TrueShaderAntiCrash.csproj └── TrueShaderAntiCrashMod.cs ├── Turbones ├── BoneDeleteHandler.cs ├── JigglyRustSolver.dll ├── JigglySolverApi.cs ├── NativeStructs.cs ├── Offsets.cs ├── Turbones.csproj └── TurbonesMod.cs ├── UIExpansionKit ├── API │ ├── BuiltinUiUtils.cs │ ├── Classes │ │ └── AwaitProvider.cs │ ├── Controls │ │ ├── FluentExtensions.cs │ │ └── Interfaces.cs │ ├── ExpandedMenu.cs │ ├── ExpansionKitApi.cs │ ├── ICustomLayoutedMenu.cs │ ├── ICustomShowableLayoutedMenu.cs │ ├── IShowableMenu.cs │ ├── LayoutDescription.cs │ └── TaskUtilities.cs ├── ButtonFactory.cs ├── COPYING.LESSER ├── Components │ ├── DestroyListener.cs │ ├── EnableDisableListener.cs │ ├── StyleElementWrapper.cs │ └── StyleEngineUpdateDriver.cs ├── ControlsImpl │ ├── BaseMenuControl.cs │ ├── Dummies.cs │ ├── MenuControlWithText.cs │ └── MenuToggle.cs ├── CustomCameraPageImpl.cs ├── CustomExpandoOverlayImpl.cs ├── CustomFullMenuPopupImpl.cs ├── CustomLayoutedPageImpl.cs ├── CustomLayoutedPageWithOwnedMenuImpl.cs ├── CustomQuickMenuPageImpl.cs ├── EnumPrefUtil.cs ├── ExpansionKitSettings.cs ├── Extensions.cs ├── FieldInject │ ├── InjectedField.cs │ └── NativeStructs.cs ├── ModSettingsHandler.cs ├── PinnedPrefUtil.cs ├── PreloadedBundleContents.cs ├── Resources │ ├── UixBase.styletor.zip │ ├── modui.assetbundle │ └── uix-style-main.vrcss ├── ScanningReflectionCache.cs ├── StylingHelper.cs ├── UIExpansionKit.csproj ├── UIExpansionKit.csproj.DotSettings ├── UiExpansionKitMod.cs └── UnityUtils.cs ├── VRCMods.sln ├── VRCMods.sln.DotSettings └── ViewPointTweaker ├── ViewPointTweaker.csproj └── ViewPointTweakerMod.cs /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | create: 7 | tags: 8 | - '*' 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: windows-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Checkout libs 19 | uses: actions/checkout@v2 20 | with: 21 | repository: ${{ secrets.LibsRepository }} 22 | token: ${{ secrets.LibsRepositoryToken }} 23 | path: Libs 24 | 25 | - name: Setup .NET 26 | uses: actions/setup-dotnet@v1 27 | with: 28 | dotnet-version: 5.0.x 29 | 30 | - name: Restore dependencies 31 | run: dotnet restore 32 | 33 | - name: Build 34 | run: dotnet build --no-restore 35 | 36 | - name: Publish a release 37 | uses: softprops/action-gh-release@v1 38 | if: startsWith(github.ref, 'refs/tags/') 39 | with: 40 | body_path: ReleaseChangelog.md 41 | files: | 42 | Output/Debug/net472/AdvancedSafety.dll 43 | Output/Debug/net472/CameraMinus.dll 44 | Output/Debug/net472/FavCat-merged.dll 45 | Output/Debug/net472/Finitizer.dll 46 | Output/Debug/net472/FriendsPlusHome.dll 47 | Output/Debug/net472/IKTweaks.dll 48 | Output/Debug/net472/JoinNotifier.dll 49 | Output/Debug/net472/LagFreeScreenshots.dll 50 | Output/Debug/net472/MirrorResolutionUnlimiter.dll 51 | Output/Debug/net472/ParticleAndBoneLimiterSettings.dll 52 | Output/Debug/net472/ScaleGoesBrr.dll 53 | Output/Debug/net472/Styletor.dll 54 | Output/Debug/net472/TrueShaderAntiCrash.dll 55 | Output/Debug/net472/Turbones.dll 56 | Output/Debug/net472/UIExpansionKit.dll 57 | Output/Debug/net472/ViewPointTweaker.dll 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | bin/ 3 | obj/ 4 | /packages 5 | Libs/*.dll 6 | Libs/*.db 7 | LibsAlt/*.dll 8 | *.user 9 | /Output 10 | IKTweaks/FinalIK 11 | .vs -------------------------------------------------------------------------------- /.vs/ProjectSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "CurrentProjectSetting": null 3 | } -------------------------------------------------------------------------------- /.vs/VRCMods/DesignTimeBuild/.dtbcache.v2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/.vs/VRCMods/DesignTimeBuild/.dtbcache.v2 -------------------------------------------------------------------------------- /.vs/VRCMods/v16/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/.vs/VRCMods/v16/.suo -------------------------------------------------------------------------------- /.vs/VSWorkspaceState.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExpandedNodes": [ 3 | "" 4 | ], 5 | "SelectedNode": "\\VRCMods.sln", 6 | "PreviewInSolutionExplorer": false 7 | } -------------------------------------------------------------------------------- /.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/.vs/slnx.sqlite -------------------------------------------------------------------------------- /AdvancedSafety/AdvancedSafety.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | true 6 | true 7 | latest 8 | 1.6.1.0 9 | true 10 | 11 | 12 | 13 | none 14 | false 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /AdvancedSafety/BundleVerifier/BundleVerifier.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/AdvancedSafety/BundleVerifier/BundleVerifier.zip -------------------------------------------------------------------------------- /AdvancedSafety/BundleVerifier/RestrictedProcessRunner/BundleVerifierProcessHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace AdvancedSafety.BundleVerifier.RestrictedProcessRunner 5 | { 6 | public sealed class BundleVerifierProcessHandle : IDisposable 7 | { 8 | private readonly RestrictedProcessHandle myProcessHandle; 9 | 10 | public BundleVerifierProcessHandle(string executablePath, string sharedMemoryName, TimeSpan maxTime, ulong maxMemory, int minFps, int maxComponents) 11 | { 12 | var pid = Process.GetCurrentProcess().Id; 13 | 14 | myProcessHandle = new RestrictedProcessHandle(executablePath, $"-batchmode -nolog -nographics {maxComponents} {pid} {sharedMemoryName} {minFps}"); 15 | 16 | myProcessHandle.SetLimits(maxTime, maxMemory, false, false, false); 17 | myProcessHandle.Start(); 18 | } 19 | 20 | public int? WaitForExit(TimeSpan timeout) => myProcessHandle.WaitForExit(timeout); 21 | 22 | public void Dispose() 23 | { 24 | myProcessHandle.Dispose(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /AdvancedSafety/BundleVerifier/RestrictedProcessRunner/Interop/JobHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace AdvancedSafety.BundleVerifier.RestrictedProcessRunner.Interop 5 | { 6 | 7 | public sealed class JobHandle : IDisposable 8 | { 9 | private IntPtr myJobHandle; 10 | 11 | public JobHandle() 12 | { 13 | InteropMethods.SECURITY_ATTRIBUTES empty = default; 14 | myJobHandle = InteropMethods.CreateJobObject(ref empty, null); 15 | if (myJobHandle == IntPtr.Zero) 16 | { 17 | throw new ApplicationException("Unable to create job object"); 18 | } 19 | } 20 | 21 | internal IntPtr Handle => myJobHandle; 22 | 23 | public unsafe void SetLimits(TimeSpan? cpuTimeLimit, ulong? memoryBytes, bool allowNetwork, bool allowDesktop, 24 | bool allowChildProcesses) 25 | { 26 | { 27 | InteropMethods.JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits = default; 28 | 29 | if (cpuTimeLimit != null) 30 | { 31 | limits.BasicLimitInformation.LimitFlags |= InteropMethods.JobBasicLimitFlags.PROCESS_TIME; 32 | limits.BasicLimitInformation.PerProcessUserTimeLimit = cpuTimeLimit.Value.Ticks; 33 | } 34 | 35 | if (memoryBytes != null) 36 | { 37 | limits.BasicLimitInformation.LimitFlags |= InteropMethods.JobBasicLimitFlags.PROCESS_MEMORY; 38 | limits.ProcessMemoryLimit = (IntPtr)memoryBytes.Value; 39 | } 40 | 41 | if (!allowChildProcesses) 42 | { 43 | limits.BasicLimitInformation.ActiveProcessLimit = 1; 44 | limits.BasicLimitInformation.LimitFlags |= InteropMethods.JobBasicLimitFlags.ACTIVE_PROCESS; 45 | } 46 | 47 | limits.BasicLimitInformation.LimitFlags |= InteropMethods.JobBasicLimitFlags.KILL_ON_JOB_CLOSE; 48 | 49 | InteropMethods.SetInformationJobObject(myJobHandle, 50 | InteropMethods.JobObjectInfoClass.JobObjectExtendedLimitInformation, (IntPtr)(&limits), 51 | Marshal.SizeOf()); 52 | } 53 | 54 | if (!allowDesktop) 55 | { 56 | var uiLimits = new InteropMethods.JOBOBJECT_BASIC_UI_RESTRICTIONS 57 | { UIRestrictionsClass = InteropMethods.UiRestrictionClass.ALL }; 58 | InteropMethods.SetInformationJobObject(myJobHandle, 59 | InteropMethods.JobObjectInfoClass.JobObjectBasicUIRestrictions, (IntPtr)(&uiLimits), 60 | Marshal.SizeOf()); 61 | } 62 | 63 | if (!allowNetwork) 64 | { 65 | var limits = new InteropMethods.JOBOBJECT_NET_RATE_CONTROL_INFORMATION 66 | { 67 | ControlFlags = InteropMethods.JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_ENABLE | 68 | InteropMethods.JOB_OBJECT_NET_RATE_CONTROL_FLAGS 69 | .JOB_OBJECT_NET_RATE_CONTROL_MAX_BANDWIDTH, 70 | MaxBandwidth = 0 71 | }; 72 | 73 | InteropMethods.SetInformationJobObject(myJobHandle, 74 | InteropMethods.JobObjectInfoClass.JobObjectNetRateControlInformation, (IntPtr)(&limits), 75 | Marshal.SizeOf()); 76 | } 77 | } 78 | 79 | private void ReleaseUnmanagedResources() 80 | { 81 | if (myJobHandle != IntPtr.Zero) InteropMethods.CloseHandle(myJobHandle); 82 | myJobHandle = IntPtr.Zero; 83 | } 84 | 85 | public void Dispose() 86 | { 87 | ReleaseUnmanagedResources(); 88 | GC.SuppressFinalize(this); 89 | } 90 | 91 | ~JobHandle() 92 | { 93 | ReleaseUnmanagedResources(); 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /AdvancedSafety/BundleVerifier/RestrictedProcessRunner/Interop/ProcessHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.InteropServices; 4 | using System.Threading; 5 | 6 | namespace AdvancedSafety.BundleVerifier.RestrictedProcessRunner.Interop 7 | { 8 | 9 | public sealed class ProcessHandle : IDisposable 10 | { 11 | private IntPtr myProcessHandle; 12 | private IntPtr myThreadHandle; 13 | 14 | private int myIsStarted; 15 | 16 | public ProcessHandle(string commandLine) 17 | { 18 | InteropMethods.SECURITY_ATTRIBUTES empty = default; 19 | InteropMethods.PROCESS_INFORMATION processInfo = default; 20 | var startupInfo = new InteropMethods.STARTUPINFO 21 | { 22 | cb = Marshal.SizeOf(), 23 | }; 24 | var created = InteropMethods.CreateProcess(null, commandLine, ref empty, ref empty, false, 25 | InteropMethods.ProcessCreationFlags.CREATE_NO_WINDOW | 26 | InteropMethods.ProcessCreationFlags.CREATE_SUSPENDED, 27 | IntPtr.Zero, null, ref startupInfo, out processInfo); 28 | if (!created) 29 | throw new ArgumentException("Can't create process"); 30 | 31 | myProcessHandle = processInfo.hProcess; 32 | myThreadHandle = processInfo.hThread; 33 | } 34 | 35 | public ProcessHandle(string commandLine, JobHandle job) : this(commandLine) 36 | { 37 | InteropMethods.AssignProcessToJobObject(job.Handle, myProcessHandle); 38 | } 39 | 40 | public void Start() 41 | { 42 | if (Interlocked.CompareExchange(ref myIsStarted, 1, 0) == 0) 43 | InteropMethods.ResumeThread(myThreadHandle); 44 | else 45 | throw new ApplicationException("Already started"); 46 | } 47 | 48 | public int? WaitForExit(TimeSpan timeout) 49 | { 50 | var waitTime = Stopwatch.StartNew(); 51 | do 52 | { 53 | InteropMethods.GetExitCodeProcess(myProcessHandle, out var exitCode); 54 | if (exitCode != InteropMethods.STILL_ACTIVE) 55 | return exitCode; 56 | Thread.Sleep(33); 57 | } while (waitTime.Elapsed < timeout); 58 | 59 | return null; 60 | } 61 | 62 | public int? GetExitCode() 63 | { 64 | InteropMethods.GetExitCodeProcess(myProcessHandle, out var exitCode); 65 | if (exitCode != InteropMethods.STILL_ACTIVE) 66 | return exitCode; 67 | return null; 68 | } 69 | 70 | private void ReleaseUnmanagedResources() 71 | { 72 | if (myProcessHandle != IntPtr.Zero) InteropMethods.CloseHandle(myProcessHandle); 73 | myProcessHandle = IntPtr.Zero; 74 | 75 | if (myThreadHandle != IntPtr.Zero) InteropMethods.CloseHandle(myThreadHandle); 76 | myThreadHandle = IntPtr.Zero; 77 | } 78 | 79 | private void Dispose(bool disposing) 80 | { 81 | ReleaseUnmanagedResources(); 82 | } 83 | 84 | public void Dispose() 85 | { 86 | Dispose(true); 87 | GC.SuppressFinalize(this); 88 | } 89 | 90 | ~ProcessHandle() 91 | { 92 | Dispose(false); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /AdvancedSafety/BundleVerifier/RestrictedProcessRunner/MemoryMapWriterStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.MemoryMappedFiles; 4 | using System.Threading; 5 | 6 | namespace AdvancedSafety.BundleVerifier.RestrictedProcessRunner 7 | { 8 | public class MemoryMapWriterStream : Stream 9 | { 10 | private readonly MemoryMappedViewAccessor myView; 11 | private int myPosition; 12 | 13 | public MemoryMapWriterStream(MemoryMappedFile mapFile) 14 | { 15 | myView = mapFile.CreateViewAccessor(); 16 | } 17 | 18 | public override void Flush() { } 19 | 20 | internal unsafe byte* GetPointer() 21 | { 22 | byte* ptr = null; 23 | myView.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); 24 | return ptr; 25 | } 26 | 27 | internal void ReleasePointer() => myView.SafeMemoryMappedViewHandle.ReleasePointer(); 28 | 29 | public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); 30 | public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); 31 | 32 | public override void SetLength(long value) => myView.Write(0, (int) value); 33 | 34 | public override void Write(byte[] buffer, int offset, int count) 35 | { 36 | if (count + myPosition > myView.Capacity - 8) 37 | throw new IOException($"Stream is full (capacity {myView.Capacity}, requires {count + myPosition}"); 38 | 39 | myView.WriteArray(myPosition + 8, buffer, offset, count); 40 | myPosition += count; 41 | Interlocked.MemoryBarrier(); 42 | myView.Write(4, myPosition); 43 | } 44 | 45 | protected override void Dispose(bool disposing) 46 | { 47 | base.Dispose(disposing); 48 | if (disposing) 49 | myView.Dispose(); 50 | } 51 | 52 | public override bool CanRead => false; 53 | public override bool CanSeek => false; 54 | public override bool CanWrite => true; 55 | public override long Length => Position; 56 | public override long Position { get => myPosition; set => throw new NotSupportedException(); } 57 | } 58 | } -------------------------------------------------------------------------------- /AdvancedSafety/BundleVerifier/RestrictedProcessRunner/RestrictedProcessHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AdvancedSafety.BundleVerifier.RestrictedProcessRunner.Interop; 3 | 4 | namespace AdvancedSafety.BundleVerifier.RestrictedProcessRunner 5 | { 6 | public sealed class RestrictedProcessHandle : IDisposable 7 | { 8 | private readonly JobHandle myJobHandle; 9 | private readonly ProcessHandle myProcessHandle; 10 | 11 | public RestrictedProcessHandle(string processPath, string commandline) 12 | { 13 | myJobHandle = new JobHandle(); 14 | 15 | try 16 | { 17 | var cli = $"\"{processPath.Replace("\"", "")}\" {commandline}"; 18 | myProcessHandle = new ProcessHandle(cli, myJobHandle); 19 | } 20 | catch 21 | { 22 | myJobHandle.Dispose(); 23 | throw; 24 | } 25 | } 26 | 27 | public int? WaitForExit(TimeSpan timeout) => myProcessHandle.WaitForExit(timeout); 28 | 29 | public void Dispose() 30 | { 31 | myJobHandle.Dispose(); 32 | myProcessHandle.Dispose(); 33 | } 34 | 35 | public void SetLimits(TimeSpan? cpuTimeLimit, ulong? memoryBytes, bool allowNetwork, bool allowDesktop, bool allowChildProcesses) => 36 | myJobHandle.SetLimits(cpuTimeLimit, memoryBytes, allowNetwork, allowDesktop, allowChildProcesses); 37 | 38 | public void Start() => myProcessHandle.Start(); 39 | } 40 | } -------------------------------------------------------------------------------- /AdvancedSafety/BundleVerifier/VerifierExitCodes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace AdvancedSafety.BundleVerifier 4 | { 5 | public static class VerifierExitCodes 6 | { 7 | private static readonly Dictionary ourCodeDescriptions = new() 8 | { 9 | { 179, "Generic exception (report a bug!)" }, 10 | { 180, "CLI argument parse error (report a bug!)" }, 11 | { 181, "VRChat process suddenly died" }, 12 | { 182, "Loaded bundle was null" }, 13 | { 183, "Loaded bundle did not contain a prefab" }, 14 | { 184, "Prefab instantiation failed" }, 15 | { 185, "Minimum framerate not achieved" }, 16 | { 186, "Over component limit" }, 17 | { -1073741819, "Generic crash (corrupted bundle)" }, // 0xc0000005 18 | { -2147483645, "Breakpoint crash (corrupted bundle)" }, // 0x80000003 19 | }; 20 | 21 | internal static string GetExitCodeDescription(int? exitCode) 22 | { 23 | if (!exitCode.HasValue) return "Timed out"; 24 | 25 | return ourCodeDescriptions.TryGetValue(exitCode.Value, out var result) ? result : "Unknown"; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /AdvancedSafety/FinalIkPatches.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using HarmonyLib; 3 | using RootMotion.FinalIK; 4 | using UnityEngine; 5 | 6 | namespace AdvancedSafety 7 | { 8 | /** 9 | * Source: FinalIkSanity (https://github.com/FenrixTheFox/FinalIKSanity, GPLv3) and Requi (contributed personally by him) 10 | */ 11 | public static class FinalIkPatches 12 | { 13 | public static void ApplyPatches(HarmonyLib.Harmony harmony) 14 | { 15 | harmony.Patch(typeof(IKSolverHeuristic).GetMethods().First(m => m.Name.Equals("IsValid") && m.GetParameters().Length == 1), 16 | new HarmonyMethod(typeof(FinalIkPatches), nameof(IkSolverIsValid))); 17 | 18 | harmony.Patch(typeof(IKSolverAim).GetMethod(nameof(IKSolverAim.GetClampedIKPosition)), 19 | new HarmonyMethod(typeof(FinalIkPatches), nameof(IkSolverAimGetClampedIKPosition))); 20 | 21 | harmony.Patch(typeof(IKSolverFullBody).GetMethod(nameof(IKSolverFullBody.Solve)), 22 | new HarmonyMethod(typeof(FinalIkPatches), nameof(IKSolverFullBodySolve))); 23 | 24 | harmony.Patch(typeof(IKSolverFABRIKRoot).GetMethod(nameof(IKSolverFABRIKRoot.OnUpdate)), 25 | new HarmonyMethod(typeof(FinalIkPatches), nameof(IKSolverFABRIKRootOnUpdate))); 26 | } 27 | 28 | private static void IkSolverAimGetClampedIKPosition(ref IKSolverAim __instance) 29 | { 30 | if (__instance == null) return; 31 | __instance.clampSmoothing = Mathf.Clamp(__instance.clampSmoothing, 0, 2); 32 | } 33 | 34 | private static bool IkSolverIsValid(ref IKSolverHeuristic __instance, ref bool __result, ref string message) 35 | { 36 | if (__instance == null) return true; 37 | if (__instance.maxIterations <= 64) return true; 38 | 39 | __result = false; 40 | message = "The solver requested too many iterations."; 41 | 42 | return false; 43 | } 44 | 45 | private static void IKSolverFullBodySolve(ref IKSolverFullBody __instance) 46 | { 47 | if (__instance == null) return; 48 | __instance.iterations = Mathf.Clamp(__instance.iterations, 0, 10); 49 | } 50 | 51 | private static bool IKSolverFABRIKRootOnUpdate(ref IKSolverFABRIKRoot __instance) 52 | { 53 | if (__instance == null) 54 | return true; 55 | return __instance.iterations <= 10; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /AdvancedSafety/NativeStructs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace AdvancedSafety 5 | { 6 | // These are copypasted from Unhollower. TODO: use actual structs from unhollower once/if they become public 7 | [StructLayout(LayoutKind.Sequential)] 8 | internal struct Il2CppFieldInfo_24_1 9 | { 10 | public IntPtr name; // const char* 11 | public IntPtr typePtr; // const 12 | public IntPtr parentClassPtr; // non-const? 13 | public int offset; // If offset is -1, then it's thread static 14 | public uint token; 15 | } 16 | } -------------------------------------------------------------------------------- /AdvancedSafety/PriorityQueue.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace AdvancedSafety 4 | { 5 | public class PriorityQueue 6 | { 7 | private readonly List myBackingStorage; 8 | private readonly IComparer myComparer; 9 | 10 | public PriorityQueue(IComparer comparer) 11 | { 12 | myComparer = comparer; 13 | myBackingStorage = new List(); 14 | } 15 | 16 | public void Enqueue(T value) 17 | { 18 | myBackingStorage.Add(value); 19 | SiftUp(myBackingStorage.Count - 1); 20 | } 21 | 22 | public T Dequeue() 23 | { 24 | if (myBackingStorage.Count == 0) 25 | return default(T); 26 | Swap(0, myBackingStorage.Count - 1); 27 | var result = myBackingStorage[myBackingStorage.Count - 1]; 28 | myBackingStorage.RemoveAt(myBackingStorage.Count - 1); 29 | SiftDown(0); 30 | return result; 31 | } 32 | 33 | public T Peek() 34 | { 35 | if (myBackingStorage.Count == 0) 36 | return default(T); 37 | return myBackingStorage[0]; 38 | } 39 | 40 | public int Count => myBackingStorage.Count; 41 | 42 | private void Swap(int i1, int i2) 43 | { 44 | var value1 = myBackingStorage[i1]; 45 | var value2 = myBackingStorage[i2]; 46 | myBackingStorage[i1] = value2; 47 | myBackingStorage[i2] = value1; 48 | } 49 | 50 | private void SiftDown(int i) 51 | { 52 | var childIndex1 = i * 2 + 1; 53 | var childIndex2 = i * 2 + 2; 54 | if (childIndex1 >= myBackingStorage.Count) 55 | return; 56 | var child1 = myBackingStorage[childIndex1]; 57 | if (childIndex2 >= myBackingStorage.Count) 58 | { 59 | var compared = myComparer.Compare(myBackingStorage[i], child1); 60 | if (compared > 0) 61 | { 62 | Swap(i, childIndex1); 63 | } 64 | return; 65 | } 66 | var child2 = myBackingStorage[childIndex2]; 67 | var compared1 = myComparer.Compare(myBackingStorage[i], child1); 68 | var compared2 = myComparer.Compare(myBackingStorage[i], child2); 69 | if (compared1 > 0 || compared2 > 0) 70 | { 71 | var compared12 = myComparer.Compare(child1, child2); 72 | if (compared12 > 0) 73 | { 74 | Swap(i, childIndex2); 75 | SiftDown(childIndex2); 76 | } 77 | else 78 | { 79 | Swap(i, childIndex1); 80 | SiftDown(childIndex1); 81 | } 82 | } 83 | } 84 | 85 | private void SiftUp(int i) 86 | { 87 | if (i == 0) 88 | return; 89 | var parentIndex = (i - 1) / 2; 90 | var compared = myComparer.Compare(myBackingStorage[i], myBackingStorage[parentIndex]); 91 | if (compared < 0) 92 | { 93 | Swap(i, parentIndex); 94 | SiftUp(parentIndex); 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /AdvancedSafety/QuickMenuHideAvatarButtonHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UIExpansionKit; 3 | using UnityEngine; 4 | 5 | namespace AdvancedSafety 6 | { 7 | public class QuickMenuHideAvatarButtonHandler : MonoBehaviour 8 | { 9 | private float myTimeAccumulator; 10 | 11 | public QuickMenuHideAvatarButtonHandler(IntPtr ptr) : base(ptr) 12 | { 13 | } 14 | 15 | private void Update() 16 | { 17 | myTimeAccumulator += Time.deltaTime; 18 | if (myTimeAccumulator < .5f) return; 19 | 20 | myTimeAccumulator = 0; 21 | 22 | var player = UiExpansionKitSupport.GetUserSelectedInQm()?.GetPlayer(); 23 | if (player == null) return; 24 | var vrcPlayer = player.prop_VRCPlayer_0; 25 | if (vrcPlayer == null) return; 26 | 27 | UiExpansionKitSupport.QuickMenuUpdateTick(vrcPlayer); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /AdvancedSafety/ScanningReflectionCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using UnhollowerRuntimeLib.XrefScans; 5 | using VRC; 6 | 7 | namespace AdvancedSafety 8 | { 9 | public static class ScanningReflectionCache 10 | { 11 | private static Action ourReloadAllAvatarsDelegate; 12 | 13 | public static void ReloadAllAvatars(bool excludeSelf) 14 | { 15 | // xref yoinked from https://github.com/loukylor/VRC-Mods/commit/11abd2a62caaae5d0f817e20a0f6852d632da6bb#diff-ba8a2891e08655e0e158ea852ef76bbcb1ea58fc831a6817875c5efa21fef610R84 (GPLv3) 16 | if (ourReloadAllAvatarsDelegate == null) 17 | { 18 | var targetMethod = typeof(VRCPlayer).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly) 19 | .Single(it => it.Name.StartsWith("Method_Public_Void_Boolean_") && it.GetParameters().Length == 1 && 20 | it.GetParameters()[0].IsOptional && XrefScanner.UsedBy(it).Any(jt => 21 | { 22 | if (jt.Type != XrefType.Method) return false; 23 | var m = jt.TryResolve(); 24 | if (m == null) return false; 25 | return m.DeclaringType == typeof(FeaturePermissionManager) && 26 | m.Name.StartsWith("Method_Public_Void_"); 27 | })); 28 | ourReloadAllAvatarsDelegate = (Action) Delegate.CreateDelegate(typeof(Action), targetMethod); 29 | } 30 | 31 | ourReloadAllAvatarsDelegate(VRCPlayer.field_Internal_Static_VRCPlayer_0, excludeSelf); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /AdvancedSafety/SortingOrderHammerer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnhollowerBaseLib; 3 | using UnityEngine; 4 | 5 | namespace AdvancedSafety 6 | { 7 | public class SortingOrderHammerer : MonoBehaviour 8 | { 9 | private Renderer[] myRenderersToHammer; 10 | 11 | public SortingOrderHammerer(IntPtr ptr) : base(ptr) 12 | { 13 | } 14 | 15 | private void Start() 16 | { 17 | myRenderersToHammer = gameObject.GetComponentsInChildren(true); 18 | } 19 | 20 | private void LateUpdate() 21 | { 22 | for (var i = 0; i < myRenderersToHammer.Length; i++) 23 | { 24 | if(myRenderersToHammer[i] is null) continue; 25 | 26 | try 27 | { 28 | myRenderersToHammer[i].sortingOrder = 0; 29 | } 30 | catch (Il2CppException) // this would imply a deleted renderer 31 | { 32 | myRenderersToHammer[i] = null; 33 | } 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /CameraMinus/CameraMinus.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | true 6 | 3.1.0.0 7 | CameraMinus 8 | 9 | 10 | 11 | none 12 | false 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Common/CustomizedMelonMod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using MelonLoader; 6 | 7 | #if CUSTOMIZED_MOD_INTERNAL 8 | internal 9 | #else 10 | public 11 | #endif 12 | abstract class CustomizedMelonMod : MelonMod 13 | { 14 | //internal static bool CheckWasSuccessful; 15 | //internal static bool MustStayFalse = false; 16 | //internal static bool MustStayTrue = true; 17 | //internal static bool RanCheck3 = false; 18 | 19 | static CustomizedMelonMod() 20 | { 21 | //LoaderIntegrityCheck.CheckIntegrity(); 22 | //CheckWasSuccessful = true; 23 | } 24 | 25 | protected CustomizedMelonMod() 26 | { 27 | //RuntimeHelpers.RunClassConstructor(typeof(CustomizedMelonMod).TypeHandle); 28 | // 29 | //if (CheckWasSuccessful && !MustStayFalse && MustStayTrue) return; 30 | // 31 | //AnnoyingMessagePrinter.PrintWarningMessage(); 32 | // 33 | //Console.In.ReadLine(); 34 | // 35 | //Environment.Exit(1); 36 | //Marshal.GetDelegateForFunctionPointer(Marshal.AllocHGlobal(16))(); 37 | } 38 | 39 | public override void OnSceneWasLoaded(int buildIndex, string sceneName) 40 | { 41 | //if (RanCheck3) return; 42 | // 43 | //LoaderIntegrityCheck.CheckDummyThree(); 44 | //RanCheck3 = true; 45 | } 46 | 47 | protected void DoAfterUiManagerInit(Action code) 48 | { 49 | MelonCoroutines.Start(OnUiManagerInitCoro(code)); 50 | } 51 | 52 | private static IEnumerator OnUiManagerInitCoro(Action code) 53 | { 54 | while (VRCUiManager.prop_VRCUiManager_0 == null) 55 | yield return null; 56 | 57 | code(); 58 | } 59 | } -------------------------------------------------------------------------------- /Common/NativePatchUtils.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | using HarmonyLib; 7 | using MelonLoader; 8 | using UnhollowerBaseLib; 9 | 10 | #nullable enable 11 | 12 | internal static class NativePatchUtils 13 | { 14 | // ReSharper disable once CollectionNeverQueried.Local 15 | private static readonly List ourPinnedDelegates = new(); 16 | private static readonly Dictionary ourOriginalPointers = new(); 17 | 18 | internal static void NativePatch(MethodInfo original, out T callOriginal, MethodInfo patch) 19 | where T : MulticastDelegate 20 | { 21 | var patchDelegate = (T) Delegate.CreateDelegate(typeof(T), patch); 22 | NativePatch(original, out callOriginal, patchDelegate); 23 | } 24 | 25 | internal static void NativePatch(IntPtr originalPointer, out T callOriginal, MethodInfo patch, string? context = null) 26 | where T : MulticastDelegate 27 | { 28 | var patchDelegate = (T) Delegate.CreateDelegate(typeof(T), patch); 29 | NativePatch(originalPointer, out callOriginal, patchDelegate, context); 30 | } 31 | 32 | internal static unsafe void NativePatch(MethodInfo original, out T callOriginal, T patchDelegate) where T : MulticastDelegate 33 | { 34 | if (original == null) throw new ArgumentNullException(nameof(original)); 35 | 36 | var originalPointer = *(IntPtr*) (IntPtr) UnhollowerUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(original).GetValue(null); 37 | NativePatch(originalPointer, out callOriginal, patchDelegate, original.FullDescription()); 38 | } 39 | 40 | internal static unsafe void NativePatch(IntPtr originalPointer, out T callOriginal, T patchDelegate, string? context = null) where T : MulticastDelegate 41 | { 42 | ourPinnedDelegates.Add(patchDelegate); 43 | 44 | var patchPointer = Marshal.GetFunctionPointerForDelegate(patchDelegate); 45 | MelonUtils.NativeHookAttach((IntPtr)(&originalPointer), patchPointer); 46 | if (ourOriginalPointers.ContainsKey(originalPointer)) 47 | MelonLogger.Warning($"Method {context ?? patchDelegate.Method.FullDescription()} has multiple native patches within single mod. Bug?"); 48 | ourOriginalPointers[originalPointer] = patchPointer; 49 | callOriginal = Marshal.GetDelegateForFunctionPointer(originalPointer); 50 | } 51 | 52 | internal static unsafe void UnpatchAll() 53 | { 54 | foreach (var keyValuePair in ourOriginalPointers) 55 | { 56 | var pointer = keyValuePair.Key; 57 | MelonUtils.NativeHookDetach((IntPtr) (&pointer), keyValuePair.Value); 58 | } 59 | 60 | ourOriginalPointers.Clear(); 61 | ourPinnedDelegates.Clear(); 62 | } 63 | 64 | [StructLayout(LayoutKind.Sequential)] 65 | internal struct NativeString 66 | { 67 | public IntPtr Data; 68 | public long Capacity; 69 | public long Unknown; 70 | public long Length; 71 | public int Unknown2; 72 | } 73 | } -------------------------------------------------------------------------------- /Common/_dummy2_.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/Common/_dummy2_.dll -------------------------------------------------------------------------------- /Common/_dummy_.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/Common/_dummy_.dll -------------------------------------------------------------------------------- /EmojiPageButtons/EmojiPageButtons.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | true 6 | 1.0.3.0 7 | EmojiPageButtons 8 | 9 | 10 | 11 | none 12 | false 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /FavCat/Adapters/DbAvatarAdapter.cs: -------------------------------------------------------------------------------- 1 | using FavCat.CustomLists; 2 | using FavCat.Database.Stored; 3 | using UnityEngine; 4 | using VRC.Core; 5 | 6 | namespace FavCat.Adapters 7 | { 8 | internal class DbAvatarAdapter : IPickerElement, IStoredModelAdapter 9 | { 10 | private readonly StoredAvatar myAvatar; 11 | private readonly StoredFavorite? myFavorite; 12 | 13 | public DbAvatarAdapter(StoredAvatar avatar, StoredFavorite? favorite) 14 | { 15 | myAvatar = avatar; 16 | myFavorite = favorite; 17 | } 18 | 19 | public string Name => myAvatar.Name; 20 | public string ImageUrl => myAvatar.ThumbnailUrl; 21 | public string Id => myAvatar.AvatarId; 22 | public bool IsPrivate => myAvatar.ReleaseStatus != "public"; 23 | public bool SupportsDesktop => myAvatar.SupportedPlatforms == ApiModel.SupportedPlatforms.StandaloneWindows || myAvatar.SupportedPlatforms == ApiModel.SupportedPlatforms.All; 24 | public bool SupportsQuest => myAvatar.SupportedPlatforms == ApiModel.SupportedPlatforms.Android || myAvatar.SupportedPlatforms == ApiModel.SupportedPlatforms.All; 25 | public bool IsInaccessible => IsPrivate && myAvatar.AuthorId != APIUser.CurrentUser.id; 26 | 27 | public StoredAvatar Model => myAvatar; 28 | public StoredFavorite? StoredFavorite => myFavorite; 29 | 30 | public Color? CornerIconColor => null; 31 | } 32 | } -------------------------------------------------------------------------------- /FavCat/Adapters/DbPlayerAdapter.cs: -------------------------------------------------------------------------------- 1 | using FavCat.CustomLists; 2 | using FavCat.Database.Stored; 3 | using FavCat.Modules; 4 | using UnityEngine; 5 | 6 | namespace FavCat.Adapters 7 | { 8 | public class DbPlayerAdapter : IPickerElement, IStoredModelAdapter 9 | { 10 | private readonly StoredPlayer myPlayer; 11 | private readonly StoredFavorite? myFavorite; 12 | 13 | public DbPlayerAdapter(StoredPlayer player, StoredFavorite? favorite) 14 | { 15 | myPlayer = player; 16 | myFavorite = favorite; 17 | } 18 | 19 | public string Id => myPlayer.PlayerId; 20 | public string Name => myPlayer.Name; 21 | public string ImageUrl => myPlayer.ThumbnailUrl; 22 | public bool IsPrivate => PlayersModule.GetOnlineApiUser(Id)?.location == "private"; 23 | public bool IsInaccessible => false; 24 | public bool SupportsDesktop => false; 25 | public bool SupportsQuest => false; 26 | public StoredPlayer Model => myPlayer; 27 | public StoredFavorite? StoredFavorite => myFavorite; 28 | 29 | public Color? CornerIconColor 30 | { 31 | get 32 | { 33 | var onlineUser = PlayersModule.GetOnlineApiUser(Id); 34 | if (onlineUser == null) 35 | return Color.gray; 36 | 37 | switch (onlineUser.status) 38 | { 39 | case "join me": 40 | return new Color(0, 0.75f, 0.75f, 1); 41 | case "online": 42 | case "active": 43 | return new Color(0, 0.75f, 0, 1); 44 | case "ask me": 45 | return new Color(1f, 0.5f, 0, 1); 46 | case "busy": 47 | return new Color(0.75f, 0, 0, 1); 48 | default: 49 | return new Color(0.5f, 0.5f, 0, 1); 50 | } 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /FavCat/Adapters/DbWorldAdapter.cs: -------------------------------------------------------------------------------- 1 | using FavCat.CustomLists; 2 | using FavCat.Database.Stored; 3 | using UnityEngine; 4 | using VRC.Core; 5 | 6 | namespace FavCat.Adapters 7 | { 8 | public class DbWorldAdapter : IPickerElement, IStoredModelAdapter 9 | { 10 | private readonly StoredWorld myWorld; 11 | private readonly StoredFavorite? myFavorite; 12 | 13 | public DbWorldAdapter(StoredWorld world, StoredFavorite? favorite) 14 | { 15 | myWorld = world; 16 | myFavorite = favorite; 17 | } 18 | 19 | public string Id => myWorld.WorldId; 20 | public string Name => myWorld.Name; 21 | public string ImageUrl => myWorld.ThumbnailUrl; 22 | public bool IsPrivate => myWorld.ReleaseStatus != "public"; 23 | public bool IsInaccessible => false; 24 | 25 | public bool SupportsDesktop => (myWorld.SupportedPlatforms & ApiModel.SupportedPlatforms.StandaloneWindows) != 0; 26 | public bool SupportsQuest => (myWorld.SupportedPlatforms & ApiModel.SupportedPlatforms.Android) != 0; 27 | 28 | public StoredWorld Model => myWorld; 29 | public StoredFavorite? StoredFavorite => myFavorite; 30 | 31 | public Color? CornerIconColor => null; 32 | } 33 | } -------------------------------------------------------------------------------- /FavCat/Adapters/IStoredModelAdapter.cs: -------------------------------------------------------------------------------- 1 | using FavCat.Database.Stored; 2 | 3 | namespace FavCat.Adapters 4 | { 5 | public interface IStoredModelAdapter where T: class 6 | { 7 | T Model { get; } 8 | StoredFavorite? StoredFavorite { get; } 9 | } 10 | } -------------------------------------------------------------------------------- /FavCat/AssetsHandler.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Reflection; 3 | using FavCat.CustomLists; 4 | using UnhollowerRuntimeLib; 5 | using UnityEngine; 6 | 7 | #nullable disable 8 | 9 | namespace FavCat 10 | { 11 | public static class AssetsHandler 12 | { 13 | private static AssetBundle myAssetBundle; 14 | 15 | public static GameObject ListPrefab; 16 | public static GameObject PickerPrefab; 17 | 18 | public static Sprite IconPC; 19 | public static Sprite IconQuest; 20 | public static Sprite IconUni; 21 | 22 | public static Sprite PreviewLoading; 23 | public static Sprite PreviewError; 24 | 25 | public static void Load() 26 | { 27 | T NoUnload(T obj) where T: Object 28 | { 29 | obj.hideFlags |= HideFlags.DontUnloadUnusedAsset; 30 | return obj; 31 | } 32 | 33 | using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("FavCat.extraui")) 34 | using (var tempStream = new MemoryStream((int) stream.Length)) 35 | { 36 | stream.CopyTo(tempStream); 37 | 38 | myAssetBundle = AssetBundle.LoadFromMemory_Internal(tempStream.ToArray(), 0); 39 | myAssetBundle.hideFlags |= HideFlags.DontUnloadUnusedAsset; 40 | } 41 | 42 | var screensRoot = GameObject.Find("UserInterface/MenuContent/Screens").transform; 43 | ListPrefab = Object.Instantiate(myAssetBundle.LoadAsset_Internal("Assets/ExtraUi/CustomUiList.prefab", Il2CppType.Of()).Cast(), screensRoot, false); 44 | ListPrefab.SetActive(false); 45 | 46 | var pickerPoolRoot = new GameObject("CustomPickerPool"); 47 | pickerPoolRoot.transform.SetParent(screensRoot); 48 | 49 | var pickerPrefab = Object.Instantiate(myAssetBundle.LoadAsset_Internal("Assets/ExtraUi/CustomPicker.prefab", Il2CppType.Of()).Cast(), screensRoot, false); 50 | pickerPrefab.SetActive(false); 51 | PickerPrefab = pickerPrefab; 52 | 53 | IconPC = NoUnload(myAssetBundle.LoadAsset("Assets/ExtraUI/EUI-IconPC.png", Il2CppType.Of()).Cast()); 54 | IconQuest = NoUnload(myAssetBundle.LoadAsset("Assets/ExtraUI/EUI-IconQ.png", Il2CppType.Of()).Cast()); 55 | IconUni = NoUnload(myAssetBundle.LoadAsset("Assets/ExtraUI/EUI-IconU.png", Il2CppType.Of()).Cast()); 56 | 57 | PreviewLoading = NoUnload(myAssetBundle.LoadAsset("Assets/ExtraUI/EUI-LoadingPreview.png", Il2CppType.Of()).Cast()); 58 | PreviewError = NoUnload(myAssetBundle.LoadAsset("Assets/ExtraUI/EUI-ErrorPreview.png", Il2CppType.Of()).Cast()); 59 | 60 | new PickerPool(pickerPrefab, pickerPoolRoot); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /FavCat/CustomLists/IPickerElement.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace FavCat.CustomLists 4 | { 5 | public interface IPickerElement 6 | { 7 | string Id { get; } 8 | string Name { get; } 9 | string ImageUrl { get; } 10 | 11 | bool IsPrivate { get; } 12 | bool IsInaccessible { get; } 13 | bool SupportsDesktop { get; } 14 | bool SupportsQuest { get; } 15 | 16 | Color? CornerIconColor { get; } 17 | } 18 | } -------------------------------------------------------------------------------- /FavCat/CustomLists/PickerPool.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace FavCat.CustomLists 5 | { 6 | public class PickerPool 7 | { 8 | private readonly GameObject myPickerPrefab; 9 | private readonly Transform myPoolRoot; 10 | public static PickerPool Instance { get; private set; } 11 | 12 | private readonly Stack myObjects = new Stack(); 13 | 14 | private int myObjectsBorrowed; 15 | 16 | public PickerPool(GameObject pickerPrefab, GameObject poolRoot) 17 | { 18 | myPickerPrefab = pickerPrefab; 19 | myPoolRoot = poolRoot.transform; 20 | 21 | poolRoot.SetActive(false); 22 | 23 | Instance = this; 24 | } 25 | 26 | public GameObject Request() 27 | { 28 | myObjectsBorrowed++; 29 | 30 | if (myObjects.Count == 0) 31 | { 32 | var result = Object.Instantiate(myPickerPrefab); 33 | result.AddComponent(); 34 | return result; 35 | } 36 | 37 | return myObjects.Pop(); 38 | } 39 | 40 | public void Release(GameObject go) 41 | { 42 | myObjectsBorrowed--; 43 | 44 | /*if (myObjects.Count > myObjectsBorrowed + 100) 45 | { 46 | Object.Destroy(go); 47 | return; 48 | }*/ // todo: better shrinking? 49 | 50 | go.transform.SetParent(myPoolRoot, false); 51 | myObjects.Push(go); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /FavCat/Database/DatabaseEntity.cs: -------------------------------------------------------------------------------- 1 | namespace FavCat.Database 2 | { 3 | public enum DatabaseEntity 4 | { 5 | Avatar, 6 | Player, 7 | World 8 | } 9 | } -------------------------------------------------------------------------------- /FavCat/Database/LocalStoreDatabase.Avatar.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FavCat.Database.Stored; 6 | using MelonLoader; 7 | using UIExpansionKit.API; 8 | using VRC.Core; 9 | 10 | namespace FavCat.Database 11 | { 12 | public partial class LocalStoreDatabase 13 | { 14 | internal void RunBackgroundAvatarSearch(string text, Action> callback) 15 | { 16 | MelonLogger.Msg($"Running local avatar search for text {text}"); 17 | var ownerId = APIUser.CurrentUser.id; 18 | Task.Run(() => { 19 | var searchText = text.ToLowerInvariant(); 20 | var list = myStoredAvatars 21 | .Find(stored => 22 | (stored.Name.ToLower().Contains(searchText) || 23 | stored.Description != null && stored.Description.ToLower().Contains(searchText) || 24 | stored.AuthorName.ToLower().Contains(searchText)) 25 | && (stored.ReleaseStatus == "public" || stored.AuthorId == ownerId)) 26 | .ToList(); 27 | 28 | callback(list); 29 | }).NoAwait(); 30 | } 31 | 32 | internal void RunBackgroundAvatarSearchByUser(string userId, Action> callback) 33 | { 34 | MelonLogger.Msg($"Running local avatar search for user {userId}"); 35 | var ownerId = APIUser.CurrentUser.id; 36 | Task.Run(() => { 37 | var list = myStoredAvatars.Find(stored => 38 | stored.AuthorId == userId && (stored.ReleaseStatus == "public" || stored.AuthorId == ownerId)) 39 | .ToList(); 40 | 41 | callback(list); 42 | }).NoAwait(); 43 | } 44 | 45 | private StoredAvatar? GetAvatar(string id) 46 | { 47 | return myStoredAvatars.FindById(id); 48 | } 49 | 50 | public void UpdateStoredAvatar(ApiAvatar avatar) 51 | { 52 | var id = avatar.id; 53 | if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(avatar.name)) return; 54 | 55 | var storedAvatar = new StoredAvatar 56 | { 57 | AvatarId = id, 58 | AuthorId = avatar.authorId, 59 | Name = avatar.name, 60 | Description = avatar.description, 61 | AuthorName = avatar.authorName, 62 | ThumbnailUrl = avatar.thumbnailImageUrl, 63 | ImageUrl = avatar.imageUrl, 64 | ReleaseStatus = avatar.releaseStatus, 65 | Platform = avatar.platform, 66 | CreatedAt = DateTime.FromBinary(avatar.created_at.ToBinary()), 67 | UpdatedAt = DateTime.FromBinary(avatar.updated_at.ToBinary()), 68 | SupportedPlatforms = avatar.supportedPlatforms 69 | }; 70 | 71 | myUpdateThreadQueue.Enqueue(() => 72 | { 73 | var preExisting = GetAvatar(id); 74 | 75 | if (preExisting != null) 76 | { 77 | if (preExisting.UpdatedAt > storedAvatar.UpdatedAt) return; 78 | 79 | if (avatar.assetUrl == null) storedAvatar.SupportedPlatforms = preExisting.SupportedPlatforms; 80 | 81 | storedAvatar.Description ??= preExisting.Description; 82 | } 83 | 84 | myStoredAvatars.Upsert(storedAvatar); 85 | }); 86 | } 87 | 88 | public void CompletelyDeleteAvatar(string avatarId) 89 | { 90 | myStoredAvatars.Delete(avatarId); 91 | AvatarFavorites.DeleteFavoriteFromAllCategories(avatarId); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /FavCat/Database/LocalStoreDatabase.Player.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FavCat.Database.Stored; 6 | using MelonLoader; 7 | using UIExpansionKit.API; 8 | using VRC.Core; 9 | 10 | namespace FavCat.Database 11 | { 12 | public partial class LocalStoreDatabase 13 | { 14 | public void UpdateStoredPlayer(APIUser player) 15 | { 16 | if (string.IsNullOrEmpty(player.id) || string.IsNullOrEmpty(player.displayName)) 17 | return; 18 | 19 | var storedPlayer = new StoredPlayer 20 | { 21 | PlayerId = player.id, 22 | Name = player.displayName, 23 | ThumbnailUrl = player.profilePicThumbnailImageUrl // already includes override/avatar check 24 | }; 25 | 26 | myUpdateThreadQueue.Enqueue(() => 27 | { 28 | myStoredPlayers.Upsert(storedPlayer); 29 | }); 30 | } 31 | 32 | internal void RunBackgroundPlayerSearch(string text, Action> callback) 33 | { 34 | MelonLogger.Msg($"Running local player search for text {text}"); 35 | Task.Run(() => { 36 | var searchText = text.ToLowerInvariant(); 37 | var list = myStoredPlayers.Find(stored => stored.Name.Contains(searchText)).ToList(); 38 | 39 | callback(list); 40 | }).NoAwait(); 41 | } 42 | 43 | public void CompletelyDeletePlayer(string userId) 44 | { 45 | myStoredPlayers.Delete(userId); 46 | PlayerFavorites.DeleteFavoriteFromAllCategories(userId); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /FavCat/Database/LocalStoreDatabase.World.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FavCat.Database.Stored; 6 | using MelonLoader; 7 | using UIExpansionKit.API; 8 | using VRC.Core; 9 | 10 | namespace FavCat.Database 11 | { 12 | public partial class LocalStoreDatabase 13 | { 14 | internal void RunBackgroundWorldSearch(string text, Action> callback) 15 | { 16 | MelonLogger.Msg($"Running local world search for text {text}"); 17 | Task.Run(() => { 18 | var searchText = text.ToLowerInvariant(); 19 | var list = myStoredWorlds.Find(stored => 20 | stored.Name.ToLower().Contains(searchText) || 21 | stored.Description != null && stored.Description.ToLower().Contains(searchText) || 22 | stored.AuthorName.ToLower().Contains(searchText)).ToList(); 23 | 24 | callback(list); 25 | }).NoAwait(); 26 | } 27 | 28 | public void UpdateStoredWorld(ApiWorld world) 29 | { 30 | var id = world.id; 31 | if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(world.name)) return; 32 | 33 | var storedWorld = new StoredWorld 34 | { 35 | WorldId = id, 36 | AuthorId = world.authorId, 37 | Name = world.name, 38 | Description = world.description, 39 | AuthorName = world.authorName, 40 | ThumbnailUrl = world.thumbnailImageUrl, 41 | ImageUrl = world.imageUrl, 42 | ReleaseStatus = world.releaseStatus, 43 | Platform = world.platform, 44 | Version = world.version, 45 | CreatedAt = DateTime.FromBinary(world.created_at.ToBinary()), 46 | UpdatedAt = DateTime.FromBinary(world.updated_at.ToBinary()), 47 | SupportedPlatforms = world.supportedPlatforms, 48 | 49 | Capacity = world.capacity, 50 | Tags = world.tags.ToArray(), 51 | }; 52 | 53 | var hasNoAssetUrl = world.assetUrl == null; 54 | 55 | myUpdateThreadQueue.Enqueue(() => 56 | { 57 | var preExisting = myStoredWorlds.FindById(id); 58 | if (preExisting != null) 59 | { 60 | if (hasNoAssetUrl) storedWorld.SupportedPlatforms = preExisting.SupportedPlatforms; 61 | 62 | storedWorld.Description ??= preExisting.Description; 63 | } 64 | 65 | myStoredWorlds.Upsert(storedWorld); 66 | }); 67 | } 68 | 69 | public void CompletelyDeleteWorld(string worldId) 70 | { 71 | myStoredWorlds.Delete(worldId); 72 | WorldFavorites.DeleteFavoriteFromAllCategories(worldId); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /FavCat/Database/Stored/INamedStoredObject.cs: -------------------------------------------------------------------------------- 1 | namespace FavCat.Database.Stored 2 | { 3 | public interface INamedStoredObject 4 | { 5 | string Name { get; } 6 | string? AuthorName { get; } 7 | } 8 | } -------------------------------------------------------------------------------- /FavCat/Database/Stored/StoredAvatar.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LiteDB; 3 | using VRC.Core; 4 | 5 | #nullable disable 6 | 7 | namespace FavCat.Database.Stored 8 | { 9 | public class StoredAvatar: INamedStoredObject 10 | { 11 | [BsonId] public string AvatarId { get; set; } 12 | public string Name { get; set; } 13 | public string? Description { get; set; } 14 | public string AuthorId { get; set; } 15 | public string AuthorName { get; set; } 16 | public string ImageUrl { get; set; } 17 | public string ThumbnailUrl { get; set; } 18 | public string ReleaseStatus { get; set; } 19 | public string Platform { get; set; } 20 | public DateTime CreatedAt { get; set; } 21 | public DateTime UpdatedAt { get; set; } 22 | public ApiModel.SupportedPlatforms SupportedPlatforms { get; set; } 23 | } 24 | } -------------------------------------------------------------------------------- /FavCat/Database/Stored/StoredCategory.cs: -------------------------------------------------------------------------------- 1 | using LiteDB; 2 | 3 | #nullable disable 4 | 5 | namespace FavCat.Database.Stored 6 | { 7 | public class StoredCategory 8 | { 9 | [BsonId] public string CategoryName { get; set; } 10 | public string SortType { get; set; } 11 | public int VisibleRows { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /FavCat/Database/Stored/StoredCategoryOrder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using LiteDB; 3 | 4 | namespace FavCat.Database.Stored 5 | { 6 | public struct CategoryInfo 7 | { 8 | public string Name { get; set; } 9 | public bool IsExternal { get; set; } 10 | } 11 | 12 | public class StoredCategoryOrder 13 | { 14 | [BsonId] public DatabaseEntity EntityType { get; set; } 15 | public List Order { get; set; } = new List(); 16 | public List DefaultListsToHide { get; set; } = new List(); 17 | } 18 | } -------------------------------------------------------------------------------- /FavCat/Database/Stored/StoredFavorite.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LiteDB; 3 | 4 | namespace FavCat.Database.Stored 5 | { 6 | public class StoredFavorite 7 | { 8 | [BsonId] public int FavoriteId { get; set; } 9 | 10 | public string ObjectId { get; set; } 11 | public string Category { get; set; } 12 | public DateTime AddedOn { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /FavCat/Database/Stored/StoredImageInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LiteDB; 3 | 4 | #nullable disable 5 | 6 | namespace FavCat.Database.Stored 7 | { 8 | public class StoredImageInfo 9 | { 10 | [BsonId] public string Id { get; set; } 11 | public DateTime LastAccessed { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /FavCat/Database/Stored/StoredPlayer.cs: -------------------------------------------------------------------------------- 1 | using LiteDB; 2 | 3 | #nullable disable 4 | 5 | namespace FavCat.Database.Stored 6 | { 7 | public class StoredPlayer : INamedStoredObject 8 | { 9 | [BsonId] public string PlayerId { get; set; } 10 | public string Name { get; set; } 11 | public string ThumbnailUrl { get; set; } 12 | 13 | [BsonIgnore] public string? AuthorName => null; 14 | } 15 | } -------------------------------------------------------------------------------- /FavCat/Database/Stored/StoredWorld.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LiteDB; 3 | using VRC.Core; 4 | 5 | #nullable disable 6 | 7 | namespace FavCat.Database.Stored 8 | { 9 | public class StoredWorld : INamedStoredObject 10 | { 11 | [BsonId] public string WorldId { get; set; } 12 | public string Name { get; set; } 13 | public string? Description { get; set; } 14 | public string AuthorId { get; set; } 15 | public string AuthorName { get; set; } 16 | public string ImageUrl { get; set; } 17 | public string ThumbnailUrl { get; set; } 18 | public string ReleaseStatus { get; set; } 19 | public string Platform { get; set; } 20 | public DateTime CreatedAt { get; set; } 21 | public DateTime UpdatedAt { get; set; } 22 | public int Version { get; set; } 23 | public ApiModel.SupportedPlatforms SupportedPlatforms { get; set; } 24 | 25 | public string[] Tags { get; set; } 26 | public int Capacity { get; set; } 27 | } 28 | } -------------------------------------------------------------------------------- /FavCat/ExportProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Text.RegularExpressions; 4 | using System.Threading.Tasks; 5 | using FavCat.Database; 6 | using FavCat.Database.Stored; 7 | 8 | namespace FavCat 9 | { 10 | public static class ExportProcessor 11 | { 12 | public static bool IsExportingFavorites; 13 | public static int TotalCategories; 14 | public static int ProcessedCategories; 15 | public static async Task DoExportFavorites(DatabaseFavoriteHandler favorites) where T:class, INamedStoredObject 16 | { 17 | IsExportingFavorites = true; 18 | ProcessedCategories = 0; 19 | TotalCategories = 1; 20 | try 21 | { 22 | await Task.Delay(100); 23 | 24 | var exportDir = "UserData/FavCatExport"; 25 | if (!Directory.Exists(exportDir)) 26 | Directory.CreateDirectory(exportDir); 27 | 28 | var storedCategories = favorites.GetCategories().ToList(); 29 | TotalCategories = storedCategories.Count; 30 | foreach (var category in storedCategories) 31 | { 32 | var fileName = favorites.EntityType + "-" + SanitizeForFileName(category.CategoryName) + ".txt"; 33 | using var writer = new StreamWriter(exportDir + "/" + fileName); 34 | foreach (var listFavorite in favorites.ListFavorites(category.CategoryName).ToList()) 35 | { 36 | var authorNameSuffix = listFavorite.Item2.AuthorName; 37 | authorNameSuffix = authorNameSuffix == null ? "" : $" by {authorNameSuffix}"; 38 | await writer.WriteLineAsync(listFavorite.Item1.ObjectId + " " + listFavorite.Item2.Name + authorNameSuffix) 39 | .ConfigureAwait(false); 40 | } 41 | 42 | ProcessedCategories++; 43 | } 44 | } 45 | finally 46 | { 47 | IsExportingFavorites = false; 48 | } 49 | } 50 | 51 | private static readonly Regex ourBadFileChars = new Regex("[\\/+:*?<>\"|]"); 52 | private static string SanitizeForFileName(string s) 53 | { 54 | return ourBadFileChars.Replace(s, ""); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /FavCat/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.RegularExpressions; 3 | using UnityEngine; 4 | 5 | namespace FavCat 6 | { 7 | public static class Extensions 8 | { 9 | internal static string StripParenthesis(this string s) 10 | { 11 | return Regex.Replace(s, "\\s*\\([0-9\\s]*\\)\\s*", "", RegexOptions.None); 12 | } 13 | 14 | internal static V GetOrDefault(this IDictionary dict, K key, V defaultValue) 15 | { 16 | return dict.TryGetValue(key, out var value) ? value : defaultValue; 17 | } 18 | 19 | internal static void SetSiblingIndex2(this Transform t, int index) 20 | { 21 | var currentIndex = t.GetSiblingIndex(); 22 | if(index < currentIndex || index == t.parent.childCount) 23 | t.SetSiblingIndex(index); 24 | else if (index == currentIndex) 25 | return; 26 | else // this kind of move needs to account for the spot vacated by this transform 27 | t.SetSiblingIndex(index - 1); 28 | } 29 | 30 | internal static T GetOrAddComponent(this Component c) where T : Component 31 | { 32 | var existing = c.GetComponent(); 33 | if (existing) return existing; 34 | return c.gameObject.AddComponent(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /FavCat/FavCat.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | true 4 | net48 5 | latest 6 | true 7 | enable 8 | False 9 | 1.1.15.0 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | $(OutputPath) 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /FavCat/FavCat.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | False -------------------------------------------------------------------------------- /FavCat/FavCatSettings.cs: -------------------------------------------------------------------------------- 1 | using MelonLoader; 2 | using UIExpansionKit.API; 3 | 4 | #nullable disable 5 | 6 | namespace FavCat 7 | { 8 | public static class FavCatSettings 9 | { 10 | private const string SettingsCategory = "FavCat"; 11 | internal static MelonPreferences_Entry DatabasePath; 12 | internal static MelonPreferences_Entry ImageCachePath; 13 | private static MelonPreferences_Entry AnnoyingMessageSeen; 14 | 15 | internal static MelonPreferences_Entry EnableAvatarFavs; 16 | internal static MelonPreferences_Entry EnableWorldFavs; 17 | internal static MelonPreferences_Entry EnablePlayerFavs; 18 | 19 | private static MelonPreferences_Entry ImageCacheMode; 20 | private static MelonPreferences_Entry ImageCacheMaxSize; 21 | internal static MelonPreferences_Entry HidePopupAfterFav; 22 | 23 | internal static MelonPreferences_Entry MakeClickSounds; 24 | internal static MelonPreferences_Entry AvatarSearchMode; 25 | internal static MelonPreferences_Entry SortPlayersByOnline; 26 | 27 | internal static MelonPreferences_Entry SortPlayersByJoinable; 28 | 29 | internal static void RegisterSettings() 30 | { 31 | var avatarSearchModeName = "AvatarSearchMode"; 32 | 33 | var category = MelonPreferences.CreateCategory(SettingsCategory, "FavCat"); 34 | 35 | DatabasePath = category.CreateEntry("DatabasePath", "./UserData", "Database directory path", is_hidden: true, dont_save_default: false); 36 | ImageCachePath = category.CreateEntry("ImageCachePath", "./UserData", "Image cache directory path", is_hidden: true, dont_save_default: false); 37 | AnnoyingMessageSeen = category.CreateEntry("AnnoyingMessageSeen", "", is_hidden: true); 38 | 39 | EnableAvatarFavs = category.CreateEntry("EnableAvatarFavs", true, "Enable avatar favorites (restart required)"); 40 | EnableWorldFavs = category.CreateEntry("EnableWorldFavs", true, "Enable world favorites (restart required)"); 41 | EnablePlayerFavs = category.CreateEntry("EnablePlayerFavs", true, "Enable player favorites (restart required)"); 42 | 43 | ImageCacheMode = category.CreateEntry("ImageCachingMode", "full", "Image caching mode"); 44 | ImageCacheMaxSize = category.CreateEntry("ImageCacheMaxSize", 4096, "Image cache max size (MB)"); 45 | HidePopupAfterFav = category.CreateEntry("HidePopupAfterFav", true, "Hide favorite popup after (un)favoriting a world or a player"); 46 | 47 | MakeClickSounds = category.CreateEntry("MakeClickSounds", true, "Click sounds"); 48 | AvatarSearchMode = category.CreateEntry(avatarSearchModeName, "select", "Avatar search result action"); 49 | SortPlayersByOnline = category.CreateEntry(nameof(SortPlayersByOnline), true, "Show offline players at the end of the list"); 50 | 51 | SortPlayersByJoinable = category.CreateEntry(nameof(SortPlayersByJoinable), true, "Show players in private instances at the end of the list"); 52 | 53 | ExpansionKitApi.RegisterSettingAsStringEnum(SettingsCategory, "ImageCachingMode", new []{("full", "Full local image cache (fastest, safest)"), ("fast", "Fast, use more RAM"), ("builtin", "Preserve RAM, more API requests")}); 54 | ExpansionKitApi.RegisterSettingAsStringEnum(SettingsCategory, avatarSearchModeName, new []{("select", "Select avatar"), ("author", "Show avatar author (safer)")}); 55 | } 56 | 57 | public static bool UseLocalImageCache => ImageCacheMode.Value == "full"; 58 | public static bool CacheImagesInMemory => ImageCacheMode.Value == "fast"; 59 | public static long MaxCacheSizeBytes => ImageCacheMaxSize.Value * 1024L * 1024L; 60 | 61 | public static string DontShowAnnoyingMessage 62 | { 63 | get => AnnoyingMessageSeen.Value; 64 | set 65 | { 66 | AnnoyingMessageSeen.Value = value; 67 | MelonPreferences.Save(); 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /FavCat/ReFetchFavoritesProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using System.Threading.Tasks; 7 | using FavCat.Database.Stored; 8 | using LiteDB; 9 | using MelonLoader; 10 | using UIExpansionKit.API; 11 | using VRC.Core; 12 | using Random = UnityEngine.Random; 13 | 14 | namespace FavCat 15 | { 16 | public static class ReFetchFavoritesProcessor 17 | { 18 | public static bool ImportRunning { get; private set; } 19 | 20 | public static string ImportStatusOuter { get; private set; } = "Not importing"; 21 | public static string ImportStatusInner { get; private set; } = ""; 22 | 23 | public static async Task ReFetchFavorites() 24 | { 25 | ImportRunning = true; 26 | ImportStatusOuter = "Re-fetch running..."; 27 | 28 | var database = FavCatMod.Database; 29 | 30 | ImportStatusOuter = "Fetching worlds..."; 31 | var worldFavs = database.WorldFavorites.myStoredFavorites.FindAll().ToList(); 32 | for (var i = 0; i < worldFavs.Count; i++) 33 | { 34 | ImportStatusInner = i + "/" + worldFavs.Count; 35 | 36 | var storedFavorite = worldFavs[i]; 37 | if (database.myStoredWorlds.FindById(storedFavorite.ObjectId) != null) continue; 38 | 39 | await TaskUtilities.YieldToMainThread(); 40 | 41 | new ApiWorld {id = storedFavorite.ObjectId}.Fetch(null, new Action(c => 42 | { 43 | if (c.Code == 404) database.CompletelyDeleteWorld(storedFavorite.ObjectId); 44 | })); 45 | 46 | await Task.Delay(TimeSpan.FromSeconds(5f + Random.Range(0f, 5f))).ConfigureAwait(false); 47 | } 48 | 49 | ImportStatusOuter = "Fetching players..."; 50 | var playerFavs = database.PlayerFavorites.myStoredFavorites.FindAll().ToList(); 51 | for (var i = 0; i < playerFavs.Count; i++) 52 | { 53 | ImportStatusInner = i + "/" + playerFavs.Count; 54 | 55 | var storedFavorite = playerFavs[i]; 56 | if (database.myStoredPlayers.FindById(storedFavorite.ObjectId) != null) continue; 57 | 58 | await TaskUtilities.YieldToMainThread(); 59 | 60 | new APIUser {id = storedFavorite.ObjectId}.Fetch(new Action(c => 61 | { 62 | if (c.Code == 404) database.CompletelyDeletePlayer(storedFavorite.ObjectId); 63 | })); 64 | 65 | await Task.Delay(TimeSpan.FromSeconds(5f + Random.Range(0f, 5f))).ConfigureAwait(false); 66 | } 67 | 68 | ImportRunning = false; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /FavCat/ScanningReflectionCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using UnhollowerRuntimeLib.XrefScans; 5 | using VRC; 6 | using VRC.Core; 7 | 8 | namespace FavCat 9 | { 10 | public static class ScanningReflectionCache 11 | { 12 | private delegate void DisplayErrorAvatarDelegate(SimpleAvatarPedestal @this); 13 | private delegate void PedestalRefreshDelegate(SimpleAvatarPedestal @this, ApiAvatar avatar); 14 | 15 | private static DisplayErrorAvatarDelegate? ourDisplayErrorAvatarDelegate; 16 | private static PedestalRefreshDelegate? ourPedestalRefreshDelegate; 17 | 18 | private static Action? ourShowWorldInfoPageDelegate; 19 | 20 | public static void DisplayWorldInfoPage(ApiWorld world, ApiWorldInstance? instance, bool hasInstanceId, APIUser? user) 21 | { 22 | if (ourShowWorldInfoPageDelegate == null) 23 | { 24 | var target = typeof(UiWorldList) 25 | .GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static).Single(it => 26 | it.Name.StartsWith("Method_Public_Static_Void_ApiWorld_ApiWorldInstance_Boolean_APIUser_") && 27 | XrefScanner.XrefScan(it).Any(jt => 28 | jt.Type == XrefType.Global && jt.ReadAsObject()?.ToString() == 29 | "UserInterface/MenuContent/Screens/WorldInfo")); 30 | 31 | ourShowWorldInfoPageDelegate = (Action) Delegate.CreateDelegate(typeof(Action), target); 32 | } 33 | 34 | ourShowWorldInfoPageDelegate(world, instance, hasInstanceId, user); 35 | } 36 | 37 | public static void DisplayErrorAvatar(this SimpleAvatarPedestal @this) 38 | { 39 | if (ourDisplayErrorAvatarDelegate == null) 40 | { 41 | var target = typeof(SimpleAvatarPedestal) 42 | .GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance).Single( 43 | it => 44 | { 45 | if (it.ReturnType != typeof(void) || it.Name.Contains("_PDM_")) return false; 46 | var parameters = it.GetParameters(); 47 | if (parameters.Length != 0) 48 | return false; 49 | 50 | return XrefScanner.XrefScan(it).Any(jt => 51 | jt.Type == XrefType.Global && jt.ReadAsObject()?.ToString() == "local"); 52 | }); 53 | 54 | ourDisplayErrorAvatarDelegate = 55 | (DisplayErrorAvatarDelegate) Delegate.CreateDelegate(typeof(DisplayErrorAvatarDelegate), target); 56 | } 57 | 58 | ourDisplayErrorAvatarDelegate(@this); 59 | } 60 | 61 | public static void Refresh(this SimpleAvatarPedestal pedestal, ApiAvatar avatar) 62 | { 63 | if (ourPedestalRefreshDelegate == null) 64 | { 65 | var target = typeof(SimpleAvatarPedestal) 66 | .GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance).Single( 67 | it => 68 | { 69 | if (it.ReturnType != typeof(void)) return false; 70 | var parameters = it.GetParameters(); 71 | if (parameters.Length != 1 || parameters[0].ParameterType != typeof(ApiAvatar)) 72 | return false; 73 | 74 | var strings = XrefScanner.XrefScan(it) 75 | .Select(jt => jt.Type == XrefType.Global ? jt.ReadAsObject()?.ToString() : null) 76 | .Where(jt => jt != null).ToHashSet(); 77 | return strings.Contains("Refreshing with : "); 78 | }); 79 | 80 | ourPedestalRefreshDelegate = 81 | (PedestalRefreshDelegate) Delegate.CreateDelegate(typeof(PedestalRefreshDelegate), target); 82 | } 83 | 84 | ourPedestalRefreshDelegate(pedestal, avatar); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /FavCat/extraui: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/FavCat/extraui -------------------------------------------------------------------------------- /Finitizer/Finitizer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48 5 | true 6 | 1.3.2.0 7 | true 8 | latest 9 | true 10 | none 11 | false 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /FriendsPlusHome/FriendsPlusHome.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | true 6 | 1.1.2.0 7 | FriendsPlusHome 8 | 9 | 10 | 11 | none 12 | false 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /IKTweaks/AnimationsHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using UnhollowerBaseLib; 4 | using UnityEngine; 5 | 6 | namespace IKTweaks 7 | { 8 | internal class AnimationsHandler 9 | { 10 | private readonly Il2CppStructArray myMuscles = new(HumanTrait.MuscleCount); 11 | private readonly HumanPoseHandler myPoseHandler; 12 | private readonly Transform myHips; 13 | private readonly CachedSolver mySolver; 14 | 15 | public AnimationsHandler(HumanPoseHandler poseHandler, Transform hips, in CachedSolver solver) 16 | { 17 | myPoseHandler = poseHandler; 18 | myHips = hips; 19 | mySolver = solver; 20 | } 21 | 22 | internal void ResetBonePositions(bool onlySpine, bool hasLegTargets) 23 | { 24 | if (mySolver.Solver.IKPositionWeight < 0.9f) return; 25 | 26 | myHips.get_position_Injected(out var hipPos); 27 | myHips.get_rotation_Injected(out var hipRot); 28 | 29 | myPoseHandler.GetHumanPose(out var bodyPos, out var bodyRot, myMuscles); 30 | 31 | for (var i = 0; i < myMuscles.Count; i++) 32 | { 33 | var currentMask = ourBoneResetMasks[i]; 34 | if (onlySpine && currentMask != BoneResetMask.Spine) continue; 35 | if (!hasLegTargets && (currentMask == BoneResetMask.LeftLeg || currentMask == BoneResetMask.RightLeg)) continue; 36 | 37 | if (IkTweaksSettings.IgnoreAnimationsModeParsed == IgnoreAnimationsMode.All) 38 | { 39 | myMuscles[i] *= currentMask == BoneResetMask.Never ? 1 : 0; 40 | continue; 41 | } 42 | 43 | switch (currentMask) 44 | { 45 | case BoneResetMask.Never: 46 | break; 47 | case BoneResetMask.Spine: 48 | myMuscles[i] *= 1 - mySolver.Spine.pelvisPositionWeight; 49 | break; 50 | case BoneResetMask.LeftArm: 51 | myMuscles[i] *= 1 - mySolver.LeftArm.positionWeight; 52 | break; 53 | case BoneResetMask.RightArm: 54 | myMuscles[i] *= 1 - mySolver.RightArm.positionWeight; 55 | break; 56 | case BoneResetMask.LeftLeg: 57 | myMuscles[i] *= 1 - mySolver.LeftLeg.positionWeight; 58 | break; 59 | case BoneResetMask.RightLeg: 60 | myMuscles[i] *= 1 - mySolver.RightLeg.positionWeight; 61 | break; 62 | default: 63 | throw new ArgumentOutOfRangeException(); 64 | } 65 | } 66 | 67 | myPoseHandler.SetHumanPose(ref bodyPos, ref bodyRot, myMuscles); 68 | 69 | myHips.position = hipPos; 70 | myHips.rotation = hipRot; 71 | } 72 | 73 | 74 | private enum BoneResetMask 75 | { 76 | Never, 77 | Spine, 78 | LeftArm, 79 | RightArm, 80 | LeftLeg, 81 | RightLeg, 82 | } 83 | 84 | private static readonly string[] ourNeverBones = { "Index", "Thumb", "Middle", "Ring", "Little", "Jaw", "Eye" }; 85 | private static readonly string[] ourArmBones = { "Arm", "Forearm", "Hand", "Shoulder" }; 86 | private static readonly string[] ourLegBones = { "Leg", "Foot", "Toes" }; 87 | 88 | private static BoneResetMask JudgeBone(string name) 89 | { 90 | if (ourNeverBones.Any(name.Contains)) 91 | return BoneResetMask.Never; 92 | 93 | if (ourArmBones.Any(name.Contains)) 94 | { 95 | return name.Contains("Left") ? BoneResetMask.LeftArm : BoneResetMask.RightArm; 96 | } 97 | 98 | if (ourLegBones.Any(name.Contains)) 99 | return name.Contains("Left") ? BoneResetMask.LeftLeg : BoneResetMask.RightLeg; 100 | 101 | return BoneResetMask.Spine; 102 | } 103 | 104 | private static readonly BoneResetMask[] ourBoneResetMasks = HumanTrait.MuscleName.Select(JudgeBone).ToArray(); 105 | } 106 | } -------------------------------------------------------------------------------- /IKTweaks/CachedSolver.cs: -------------------------------------------------------------------------------- 1 | using RootMotion.FinalIK; 2 | 3 | namespace IKTweaks 4 | { 5 | public struct CachedSolver 6 | { 7 | public readonly IKSolverVR Solver; 8 | public readonly IKSolverVR.Spine Spine; 9 | public readonly IKSolverVR.Leg LeftLeg; 10 | public readonly IKSolverVR.Leg RightLeg; 11 | public readonly IKSolverVR.Arm LeftArm; 12 | public readonly IKSolverVR.Arm RightArm; 13 | public readonly IKSolverVR.Locomotion Locomotion; 14 | 15 | public CachedSolver(IKSolverVR solver) 16 | { 17 | Solver = solver; 18 | Spine = solver.spine; 19 | LeftArm = solver.leftArm; 20 | LeftLeg = solver.leftLeg; 21 | RightArm = solver.rightArm; 22 | RightLeg = solver.rightLeg; 23 | Locomotion = solver.locomotion; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /IKTweaks/HandOffsetsManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using VRC.SDKBase; 4 | 5 | namespace IKTweaks 6 | { 7 | public class HandOffsetsManager: IDisposable 8 | { 9 | private readonly Transform myLeftHandOffset; 10 | private readonly Transform myRightHandOffset; 11 | 12 | private readonly float myOriginalScale; 13 | 14 | public HandOffsetsManager(VRCVrIkController controller) 15 | { 16 | var ikControllerParent = controller.field_Private_IkController_0.transform; 17 | 18 | var leftEffector = ikControllerParent.Find("LeftEffector"); 19 | var rightEffector = ikControllerParent.Find("RightEffector"); 20 | 21 | myLeftHandOffset = MakeTarget(leftEffector); 22 | myRightHandOffset = MakeTarget(rightEffector); 23 | 24 | var vrik = controller.field_Private_VRIK_0; 25 | vrik.solver.leftArm.target = myLeftHandOffset; 26 | vrik.solver.rightArm.target = myRightHandOffset; 27 | 28 | myOriginalScale = vrik.GetComponent().ViewPosition.y; 29 | 30 | UpdatePositionOffset(Vector3.zero, IkTweaksSettings.HandPositionOffset.Value); 31 | UpdateRotationOffset(Vector3.zero, IkTweaksSettings.HandAngleOffset.Value); 32 | } 33 | 34 | internal void UpdatePositionOffset(Vector3 _, Vector3 newOffset) 35 | { 36 | newOffset *= myOriginalScale; 37 | 38 | myLeftHandOffset.localPosition = newOffset; 39 | myRightHandOffset.localPosition = new Vector3 { x = -newOffset.x, y = newOffset.y, z = newOffset.z }; 40 | } 41 | 42 | internal void UpdateRotationOffset(Vector3 _, Vector3 newOffset) 43 | { 44 | myLeftHandOffset.localEulerAngles = newOffset; 45 | myRightHandOffset.localEulerAngles = new Vector3 { x = newOffset.x, y = -newOffset.y, z = -newOffset.z }; 46 | } 47 | 48 | private static Transform MakeTarget(Transform parent) 49 | { 50 | var newXf = new GameObject("IktOffset").transform; 51 | newXf.SetParent(parent, false); 52 | return newXf; 53 | } 54 | 55 | public void Dispose() 56 | { 57 | if (myLeftHandOffset != null) UnityEngine.Object.Destroy(myLeftHandOffset.gameObject); 58 | if (myRightHandOffset != null) UnityEngine.Object.Destroy(myRightHandOffset.gameObject); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /IKTweaks/IKTweaks.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net48 4 | true 5 | latest 6 | true 7 | 2.0.0 8 | true 9 | 10 | 11 | none 12 | false 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /ILRepack/ILRepack.Lib.MSBuild.Task.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/ILRepack/ILRepack.Lib.MSBuild.Task.dll -------------------------------------------------------------------------------- /ILRepack/ILRepack.Lib.MSBuild.Task.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | $(ProjectDir)ILRepack.targets 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | $([System.IO.Directory]::GetFiles("%(Directories.Identity)", "*", System.IO.SearchOption.AllDirectories).get_Length()) 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ILRepack/ILRepack.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/ILRepack/ILRepack.dll -------------------------------------------------------------------------------- /IntegrityCheckGenerator/IntegrityCheckGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /IntegrityCheckWeaver/DummyThree.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Mono.Cecil; 4 | using Mono.Cecil.Cil; 5 | 6 | namespace IntegrityCheckWeaver 7 | { 8 | public static class DummyThree 9 | { 10 | public static byte[] ProduceDummyThree() 11 | { 12 | var assemblyName = new AssemblyNameDefinition(Utils.CompletelyRandomString(), new Version(1, 0, 0)); 13 | var assembly = AssemblyDefinition.CreateAssembly(assemblyName, assemblyName.Name + ".dll", ModuleKind.Dll); 14 | 15 | var moduleType = assembly.MainModule.GetType(""); 16 | 17 | void AddFunnyDelegate() 18 | { 19 | var dgType = new TypeDefinition("", Utils.CompletelyRandomString(), TypeAttributes.Sealed, assembly.MainModule.ImportReference(typeof(MulticastDelegate))); 20 | var invokeMethod = new MethodDefinition("Invoke", MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | 21 | MethodAttributes.Public, assembly.MainModule.ImportReference(typeof(void))); 22 | invokeMethod.ImplAttributes = MethodImplAttributes.CodeTypeMask; 23 | dgType.Methods.Add(invokeMethod); 24 | 25 | assembly.MainModule.Types.Add(dgType); 26 | 27 | dgType.Methods.Add(MakeCctor(assembly)); 28 | 29 | // Try triggering type loads 30 | moduleType.Fields.Add(new FieldDefinition(Utils.CompletelyRandomString(), FieldAttributes.Private | FieldAttributes.Static, dgType)); 31 | dgType.Fields.Add(new FieldDefinition(Utils.CompletelyRandomString(), FieldAttributes.Private | FieldAttributes.Static, dgType)); 32 | } 33 | 34 | for (var i = 0; i < Utils.RandomInt(5, 30); i++) 35 | AddFunnyDelegate(); 36 | 37 | moduleType.Methods.Add(MakeCctor(assembly)); 38 | 39 | var memoryStream = new MemoryStream(); 40 | 41 | assembly.Write(memoryStream); 42 | 43 | return memoryStream.ToArray(); 44 | } 45 | 46 | private static MethodDefinition MakeCctor(AssemblyDefinition assembly) 47 | { 48 | var voidReference = assembly.MainModule.ImportReference(typeof(void)); 49 | var cctorMethod = new MethodDefinition(".cctor", MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.SpecialName | 50 | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName, voidReference); 51 | var cctorBuilder = cctorMethod.Body.GetILProcessor(); 52 | 53 | cctorBuilder.Emit(OpCodes.Ldc_I4_1); 54 | cctorBuilder.Emit(OpCodes.Conv_I); 55 | cctorBuilder.Emit(OpCodes.Calli, new CallSite(voidReference)); 56 | cctorBuilder.Emit(OpCodes.Ret); 57 | 58 | return cctorMethod; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /IntegrityCheckWeaver/IntegrityCheckWeaver.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net472 6 | net6.0;net472 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /IntegrityCheckWeaver/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Mono.Cecil; 6 | using Mono.Cecil.Cil; 7 | 8 | namespace IntegrityCheckWeaver 9 | { 10 | static class Program 11 | { 12 | /// 13 | /// Why, you may ask? 14 | /// Because certain individuals with incredibly loose morals are hell-bent on making the game worse for everyone 15 | /// 16 | static int Main(string[] args) 17 | { 18 | if (args.Length <= 0 || !File.Exists(args[0])) 19 | { 20 | Console.Error.WriteLine("Bad arguments"); 21 | return 1; 22 | } 23 | 24 | using var assembly = AssemblyDefinition.ReadAssembly(new FileStream(args[0], FileMode.Open, FileAccess.ReadWrite)); 25 | var modType = assembly.MainModule.Types.SingleOrDefault(it => it.BaseType?.Name == "MelonMod"); 26 | 27 | if (modType == null) 28 | { 29 | Console.Error.WriteLine("Required types not found"); 30 | return 1; 31 | } 32 | 33 | var dummyOneResource = DummyThree.ProduceDummyThree(); 34 | var dummyOneName = Utils.CompletelyRandomString() + ".dll"; 35 | assembly.MainModule.Resources.Add(new EmbeddedResource(dummyOneName, ManifestResourceAttributes.Private, dummyOneResource)); 36 | 37 | var dummyThreeResource = DummyThree.ProduceDummyThree(); 38 | var dummyThreeName = Utils.CompletelyRandomString() + ".dll"; 39 | assembly.MainModule.Resources.Add(new EmbeddedResource(dummyThreeName, ManifestResourceAttributes.Private, dummyThreeResource)); 40 | 41 | var dummyTwoName = Utils.CompletelyRandomString() + ".dll"; 42 | 43 | assembly.MainModule.Resources.Remove(assembly.MainModule.Resources.Single(it => it.Name.EndsWith("_dummy_.dll"))); // is replaced 44 | assembly.MainModule.Resources.Single(it => it.Name.EndsWith("_dummy2_.dll")).Name = dummyTwoName; 45 | 46 | var methodRenameMap = CleanMethods(modType); 47 | 48 | foreach (var method in modType.Methods) 49 | foreach (var instr in method.Body.Instructions) 50 | if (instr.OpCode == OpCodes.Ldstr) 51 | { 52 | var value = (string)instr.Operand; 53 | instr.Operand = value switch 54 | { 55 | "_dummy_.dll" => dummyOneName, 56 | "_dummy2_.dll" => dummyTwoName, 57 | "_dummy3_.dll" => dummyThreeName, 58 | _ => methodRenameMap.TryGetValue(value, out var renamed) ? renamed : value 59 | }; 60 | } 61 | 62 | assembly.Write(); 63 | 64 | return 0; 65 | } 66 | 67 | private static readonly HashSet ourNamesToRename = new() 68 | { 69 | "CheckA", 70 | "CheckB", 71 | "CheckC", 72 | "PatchTest", 73 | "ReturnFalse", 74 | "CheckWasSuccessful", 75 | "MustStayFalse", 76 | "MustStayTrue", 77 | "RanCheck3", 78 | "CheckDummyThree", 79 | "ourAnnoyingMessages" 80 | }; 81 | 82 | private static Dictionary CleanMethods(TypeDefinition type) 83 | { 84 | var result = new Dictionary(); 85 | 86 | foreach (var method in type.Methods) 87 | if (ourNamesToRename.Contains(method.Name)) 88 | { 89 | var newName = Utils.CompletelyRandomString(); 90 | result[method.Name] = newName; 91 | method.Name = newName; 92 | } 93 | 94 | foreach (var property in type.Properties) 95 | if (ourNamesToRename.Contains(property.Name)) 96 | property.Name = Utils.CompletelyRandomString(); 97 | 98 | foreach (var field in type.Fields) 99 | if (ourNamesToRename.Contains(field.Name)) 100 | field.Name = Utils.CompletelyRandomString(); 101 | 102 | return result; 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /IntegrityCheckWeaver/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace IntegrityCheckWeaver 5 | { 6 | public static class Utils 7 | { 8 | private static readonly Random ourRandom = new(DateTime.Now.GetHashCode()); 9 | private static readonly (char, int)[] ourFrequencyTable = { 10 | ('E', 21912), ('T', 16587), ('A', 14810), ('O', 14003), ('I', 13318), ('N', 12666), ('S', 11450), 11 | ('R', 10977), ('H', 10795), ('D', 7874), ('L', 7253), ('U', 5246), ('C', 4943), ('M', 4761), ('F', 4200), 12 | ('Y', 3853), ('W', 3819), ('G', 3693), ('P', 3316), ('B', 2715), ('V', 2019), ('K', 1257), ('X', 315), 13 | ('Q', 205), ('J', 188), ('Z', 128), 14 | }; 15 | 16 | private static readonly int ourFrequencyTableSum = ourFrequencyTable.Sum(it => it.Item2); 17 | 18 | private static char RandomLetter() 19 | { 20 | var v = ourRandom.Next(0, ourFrequencyTableSum); 21 | var i = 0; 22 | while (v > ourFrequencyTable[i].Item2) 23 | { 24 | v -= ourFrequencyTable[i].Item2; 25 | i++; 26 | } 27 | 28 | return char.ToLowerInvariant(ourFrequencyTable[i].Item1); 29 | } 30 | 31 | internal static string CompletelyRandomString() 32 | { 33 | var length = ourRandom.Next(5, 21); 34 | var chars = new char[length]; 35 | 36 | for (var i = 0; i < length; i++) 37 | { 38 | chars[i] = RandomLetter(); 39 | if (ourRandom.Next(0, i + 1) == 0) 40 | chars[i] = char.ToUpperInvariant(chars[i]); 41 | } 42 | 43 | return new string(chars); 44 | } 45 | 46 | internal static int RandomInt(int from, int to) 47 | { 48 | return ourRandom.Next(from, to); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /JoinNotifier/JoinNotifier.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net48 4 | true 5 | true 6 | true 7 | JoinNotifier 8 | 9 | 10 | none 11 | false 12 | 1.0.7 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /JoinNotifier/NetworkManagerHooks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MelonLoader; 3 | using VRC; 4 | using VRC.Core; 5 | 6 | namespace JoinNotifier 7 | { 8 | public static class NetworkManagerHooks 9 | { 10 | private static bool IsInitialized; 11 | private static bool SeenFire; 12 | private static bool AFiredFirst; 13 | 14 | public static event Action OnJoin; 15 | public static event Action OnLeave; 16 | 17 | public static void EventHandlerA(Player player) 18 | { 19 | if (!SeenFire) 20 | { 21 | AFiredFirst = true; 22 | SeenFire = true; 23 | 24 | MelonDebug.Msg("A fired first"); 25 | } 26 | 27 | if (player == null) return; 28 | (AFiredFirst ? OnJoin : OnLeave)?.Invoke(player); 29 | } 30 | 31 | public static void EventHandlerB(Player player) 32 | { 33 | if (!SeenFire) 34 | { 35 | AFiredFirst = false; 36 | SeenFire = true; 37 | 38 | MelonDebug.Msg("B fired first"); 39 | } 40 | 41 | if (player == null) return; 42 | (AFiredFirst ? OnLeave : OnJoin)?.Invoke(player); 43 | } 44 | 45 | public static void Initialize() 46 | { 47 | if (IsInitialized) return; 48 | if (ReferenceEquals(NetworkManager.field_Internal_Static_NetworkManager_0, null)) return; 49 | 50 | var field0 = NetworkManager.field_Internal_Static_NetworkManager_0.field_Internal_VRCEventDelegate_1_Player_0; 51 | var field1 = NetworkManager.field_Internal_Static_NetworkManager_0.field_Internal_VRCEventDelegate_1_Player_1; 52 | 53 | AddDelegate(field0, EventHandlerA); 54 | AddDelegate(field1, EventHandlerB); 55 | 56 | IsInitialized = true; 57 | } 58 | 59 | private static void AddDelegate(VRCEventDelegate field, Action eventHandlerA) 60 | { 61 | field.field_Private_HashSet_1_UnityAction_1_T_0.Add(eventHandlerA); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /JoinNotifier/joinnotifier.assetbundle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/JoinNotifier/joinnotifier.assetbundle -------------------------------------------------------------------------------- /LagFreeScreenshots/API/LfsApi.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace LagFreeScreenshots.API 4 | { 5 | public static class LfsApi 6 | { 7 | /// 8 | /// Called after a creenshot is taken and written to disk 9 | /// 10 | public static event ScreenshotSavedEventV2? OnScreenshotSavedV2; 11 | 12 | public delegate void ScreenshotSavedEventV2(string filePath, int width, int height, MetadataV2? metadata); 13 | 14 | internal static void InvokeScreenshotSaved(string filePath, int width, int height, MetadataV2? metadataV2) => 15 | OnScreenshotSavedV2?.Invoke(filePath, width, height, metadataV2); 16 | } 17 | } -------------------------------------------------------------------------------- /LagFreeScreenshots/API/MetadataV2.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Globalization; 3 | using System.Linq; 4 | using UnityEngine; 5 | using VRC; 6 | using VRC.Core; 7 | 8 | namespace LagFreeScreenshots.API 9 | { 10 | public class MetadataV2 11 | { 12 | public readonly ScreenshotRotation ImageRotation; 13 | public readonly APIUser ApiUser; 14 | public readonly ApiWorldInstance WorldInstance; 15 | public Vector3 Position; 16 | public readonly List<(Player, Vector3)> PlayerList; 17 | 18 | public MetadataV2(ScreenshotRotation imageRotation, APIUser apiUser, ApiWorldInstance apiWorldInstance, Vector3 position, List<(Player, Vector3)> playerList) 19 | { 20 | ImageRotation = imageRotation; 21 | ApiUser = apiUser; 22 | WorldInstance = apiWorldInstance; 23 | Position = position; 24 | PlayerList = playerList; 25 | } 26 | 27 | public override string ToString() 28 | { 29 | var worldString = "null,0,Not in any world"; 30 | if (WorldInstance != null && WorldInstance.world != null) 31 | worldString = WorldInstance.world.id + "," + WorldInstance.name + "," + WorldInstance.world.name; 32 | 33 | var positionString = Position.x.ToString(CultureInfo.InvariantCulture) + "," + Position.y.ToString(CultureInfo.InvariantCulture) + "," + Position.z.ToString(CultureInfo.InvariantCulture); 34 | 35 | return "lfs|2|author:" 36 | + ApiUser.id + "," + ApiUser.displayName 37 | + "|world:" + worldString 38 | + "|pos:" + positionString 39 | + (ImageRotation != ScreenshotRotation.NoRotation ? "|rq:" + ImageRotation : "") 40 | + "|players:" + string.Join(";", PlayerList.Select(PlayerListToString)); 41 | } 42 | 43 | private static string PlayerListToString((Player, Vector3) playerData) 44 | { 45 | if (playerData.Item1 == null || playerData.Item1.prop_APIUser_0 == null) return "null,0,0,0,null"; 46 | return playerData.Item1.prop_APIUser_0.id + "," + 47 | playerData.Item2.x.ToString("0.00", CultureInfo.InvariantCulture) + "," + 48 | playerData.Item2.y.ToString("0.00", CultureInfo.InvariantCulture) + "," + 49 | playerData.Item2.z.ToString("0.00", CultureInfo.InvariantCulture) + "," + 50 | playerData.Item1.prop_APIUser_0.displayName; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /LagFreeScreenshots/API/ScreenshotRotation.cs: -------------------------------------------------------------------------------- 1 | namespace LagFreeScreenshots.API 2 | { 3 | public enum ScreenshotRotation 4 | { 5 | AutoRotationDisabled = -1, 6 | NoRotation = 0, 7 | Clockwise90 = 1, 8 | Clockwise180 = 2, 9 | CounterClockwise90 = 3 10 | } 11 | } -------------------------------------------------------------------------------- /LagFreeScreenshots/AwaitProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using MelonLoader; 6 | 7 | namespace LagFreeScreenshots 8 | { 9 | public class AwaitProvider 10 | { 11 | private readonly Queue myToMainThreadQueue = new Queue(); 12 | 13 | public void Flush() 14 | { 15 | List toProcess; 16 | 17 | if (myToMainThreadQueue.Count == 0) 18 | return; 19 | 20 | lock (myToMainThreadQueue) 21 | { 22 | toProcess = myToMainThreadQueue.ToList(); 23 | myToMainThreadQueue.Clear(); 24 | } 25 | 26 | foreach (var action in toProcess) 27 | { 28 | try 29 | { 30 | action(); 31 | } 32 | catch (Exception ex) 33 | { 34 | MelonLogger.Warning($"Exception in task: {ex}"); 35 | } 36 | } 37 | } 38 | 39 | public YieldAwaitable Yield() 40 | { 41 | return new YieldAwaitable(myToMainThreadQueue); 42 | } 43 | 44 | public readonly struct YieldAwaitable : INotifyCompletion 45 | { 46 | private readonly Queue myToMainThreadQueue; 47 | 48 | public YieldAwaitable(Queue toMainThreadQueue) 49 | { 50 | myToMainThreadQueue = toMainThreadQueue; 51 | } 52 | 53 | public bool IsCompleted => false; 54 | 55 | public YieldAwaitable GetAwaiter() => this; 56 | 57 | public void GetResult() { } 58 | 59 | public void OnCompleted(Action continuation) 60 | { 61 | lock (myToMainThreadQueue) 62 | myToMainThreadQueue.Enqueue(continuation); 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /LagFreeScreenshots/EventHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LagFreeScreenshots.API; 3 | 4 | namespace LagFreeScreenshots 5 | { 6 | [Obsolete("Use LagFreeScreenshots.API.LfsApi")] 7 | public static class EventHandler 8 | { 9 | /// 10 | /// Calls when an screenshot is saved 11 | /// 12 | public static event Action OnScreenshotSaved; 13 | 14 | internal static void InvokeScreenshotSaved(string filePath, int width, int height, MetadataV2 metadata) 15 | { 16 | OnScreenshotSaved?.Invoke(filePath, width, height, metadata == null ? null : new Metadata(metadata)); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LagFreeScreenshots/LagFreeScreenshots.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | true 6 | true 7 | latest 8 | LagFreeScreenshots 9 | 10 | 11 | 12 | none 13 | false 14 | 1.4.1.0 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /LagFreeScreenshots/Metadata.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using LagFreeScreenshots.API; 5 | using UnityEngine; 6 | using VRC; 7 | using VRC.Core; 8 | 9 | namespace LagFreeScreenshots 10 | { 11 | [Obsolete("Use LagFreeScreenshots.API.MetadataV2")] 12 | public class Metadata 13 | { 14 | public int ImageRotation; 15 | public APIUser ApiUser; 16 | public ApiWorldInstance WorldInstance; 17 | public Vector3 Position; 18 | public List> PlayerList; 19 | 20 | public Metadata(int imageRotation, APIUser apiUser, ApiWorldInstance apiWorldInstance, Vector3 position, List> playerList) 21 | { 22 | ImageRotation = imageRotation; 23 | ApiUser = apiUser; 24 | WorldInstance = apiWorldInstance; 25 | Position = position; 26 | PlayerList = playerList; 27 | } 28 | 29 | public Metadata(MetadataV2 newMetadata) : this((int) newMetadata.ImageRotation, newMetadata.ApiUser, 30 | newMetadata.WorldInstance, newMetadata.Position, 31 | newMetadata.PlayerList.ConvertAll(it => Tuple.Create(it.Item1, it.Item2))) 32 | { 33 | 34 | } 35 | 36 | public string ConvertToString() 37 | { 38 | var worldString = "null,0,Not in any world"; 39 | if (WorldInstance != null && WorldInstance.world != null) 40 | worldString = WorldInstance.world.id + "," + WorldInstance.name + "," + WorldInstance.world.name; 41 | 42 | var positionString = Position.x.ToString(CultureInfo.InvariantCulture) + "," + Position.y.ToString(CultureInfo.InvariantCulture) + "," + Position.z.ToString(CultureInfo.InvariantCulture); 43 | 44 | return "lfs|2|author:" 45 | + ApiUser.id + "," + ApiUser.displayName 46 | + "|world:" + worldString 47 | + "|pos:" + positionString 48 | + (ImageRotation != -1 ? "|rq:" + ImageRotation : "") 49 | + "|players:" + string.Join(";", PlayerList.ConvertAll(new Converter, string>(PlayerListToString))); 50 | } 51 | 52 | private static string PlayerListToString(Tuple playerData) 53 | { 54 | if (playerData.Item1 == null || playerData.Item1.prop_APIUser_0 == null) return "null,0,0,0,null"; 55 | return playerData.Item1.prop_APIUser_0.id + "," + 56 | playerData.Item2.x.ToString("0.00", CultureInfo.InvariantCulture) + "," + 57 | playerData.Item2.y.ToString("0.00", CultureInfo.InvariantCulture) + "," + 58 | playerData.Item2.z.ToString("0.00", CultureInfo.InvariantCulture) + "," + 59 | playerData.Item1.prop_APIUser_0.displayName; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /LagFreeScreenshots/PngUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Text; 3 | 4 | namespace LagFreeScreenshots 5 | { 6 | public static class PngUtils 7 | { 8 | // https://stackoverflow.com/questions/24082305/how-is-png-crc-calculated-exactly 9 | private static readonly uint[] ourCRCTable = Enumerable.Range(0, 256).Select(n => 10 | { 11 | uint c = (uint) n; 12 | for (var k = 0; k <= 7; k++) 13 | { 14 | if ((c & 1) == 1) 15 | c = 0xEDB88320 ^ ((c >> 1) & 0x7FFFFFFF); 16 | else 17 | c = ((c >> 1) & 0x7FFFFFFF); 18 | } 19 | 20 | return c; 21 | }).ToArray(); 22 | 23 | private static uint PngCrc32(byte[] stream, int offset, int length, uint crc) 24 | { 25 | uint c = crc ^ 0xffffffff; 26 | var endOffset = offset + length; 27 | for (var i = offset; i < endOffset; i++) 28 | { 29 | c = ourCRCTable[(c ^ stream[i]) & 255] ^ ((c >> 8) & 0xFFFFFF); 30 | } 31 | 32 | return c ^ 0xffffffff; 33 | } 34 | 35 | internal static byte[] ProducePngDescriptionTextChunk(string text) 36 | { 37 | var keyword = "Description"; 38 | var chunkDataSize = keyword.Length + 1 + 1 + 1 + 1 + 1 + Encoding.UTF8.GetByteCount(text); 39 | var chunkBytes = new byte[12 + chunkDataSize]; 40 | chunkBytes[0] = (byte) (chunkDataSize >> 24); 41 | chunkBytes[1] = (byte) (chunkDataSize >> 16); 42 | chunkBytes[2] = (byte) (chunkDataSize >> 8); 43 | chunkBytes[3] = (byte) (chunkDataSize >> 0); 44 | 45 | chunkBytes[4] = (byte) 'i'; 46 | chunkBytes[5] = (byte) 'T'; 47 | chunkBytes[6] = (byte) 'X'; 48 | chunkBytes[7] = (byte) 't'; 49 | 50 | Encoding.UTF8.GetBytes(keyword, 0, keyword.Length, chunkBytes, 8); 51 | 52 | chunkBytes[8 + keyword.Length + 0] = 0; // null separator 53 | chunkBytes[8 + keyword.Length + 1] = 0; // compression flag 54 | chunkBytes[8 + keyword.Length + 2] = 0; // compression method 55 | chunkBytes[8 + keyword.Length + 3] = 0; // null separator 56 | chunkBytes[8 + keyword.Length + 4] = 0; // null separator 57 | 58 | Encoding.UTF8.GetBytes(text, 0, text.Length, chunkBytes, 8 + keyword.Length + 5); 59 | 60 | var crc = PngCrc32(chunkBytes, 4, chunkBytes.Length - 8, 0); 61 | 62 | chunkBytes[chunkBytes.Length - 4] = (byte) (crc >> 24); 63 | chunkBytes[chunkBytes.Length - 3] = (byte) (crc >> 16); 64 | chunkBytes[chunkBytes.Length - 2] = (byte) (crc >> 8); 65 | chunkBytes[chunkBytes.Length - 1] = (byte) (crc >> 0); 66 | 67 | return chunkBytes; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /LagFreeScreenshots/PresetScreenshotSizes.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace LagFreeScreenshots 4 | { 5 | internal enum PresetScreenshotSizes 6 | { 7 | Default, 8 | Custom, 9 | 10 | [Description("Thumbnail 100x100")] 11 | Thumbnail, 12 | 13 | [Description("Square 1024x1024")] 14 | Square, 15 | 16 | [Description("1280x720")] 17 | _720p, 18 | 19 | [Description("1920x1080 (VRC default)")] 20 | _1080p, 21 | 22 | [Description("4K (3840x2160)")] 23 | _4K, 24 | 25 | [Description("8K (7680x4320)")] 26 | _8K, 27 | 28 | [Description("12K (11520x6480)")] 29 | _12K, 30 | 31 | [Description("16K (15360x8640)")] 32 | _16K, 33 | } 34 | } -------------------------------------------------------------------------------- /Libs/.touch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/Libs/.touch -------------------------------------------------------------------------------- /LibsAlt/.touch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/LibsAlt/.touch -------------------------------------------------------------------------------- /MirrorResolutionUnlimiter/MirrorResolutionUnlimiter.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net48 4 | true 5 | true 6 | latest 7 | true 8 | 1.1.4.0 9 | none 10 | 11 | 12 | 13 | {85b11d98-e013-4e5d-95f3-5437de63acf4} 14 | UIExpansionKit 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /MirrorResolutionUnlimiter/OriginalPixelLightsSettingKeeper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace MirrorResolutionUnlimiter 5 | { 6 | public class OriginalPixelLightsSettingKeeper : MonoBehaviour 7 | { 8 | public bool OriginalValue; 9 | 10 | public OriginalPixelLightsSettingKeeper(IntPtr obj0) : base(obj0) 11 | { 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /MirrorResolutionUnlimiter/UiExtensionsAddon.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using UIExpansionKit.API; 3 | using UnityEngine; 4 | using VRC.SDKBase; 5 | 6 | namespace MirrorResolutionUnlimiter 7 | { 8 | public static class UiExtensionsAddon 9 | { 10 | private static int PlayerLayer = 1 << 9; // todo: ask unity for these 11 | private static int PlayerLocalLayer = 1 << 10; 12 | private static int UiLayer = 1 << 5; 13 | private static int UiMenuLayer = 1 << 12; 14 | private static int UiInternalLayer = 1 << 19; 15 | private static int MirrorReflectionLayer = 1 << 18; 16 | 17 | [MethodImpl(MethodImplOptions.NoInlining)] 18 | public static void Init() 19 | { 20 | ExpansionKitApi.GetExpandedMenu(ExpandedMenu.SettingsMenu).AddSimpleButton("Optimize mirrors", OptimizeMirrors); 21 | ExpansionKitApi.GetExpandedMenu(ExpandedMenu.SettingsMenu).AddSimpleButton("Beautify mirrors", BeautifyMirrors); 22 | 23 | ExpansionKitApi.RegisterSettingAsStringEnum(MirrorResolutionUnlimiterMod.ModCategory, 24 | MirrorResolutionUnlimiterMod.PixelLightsSetting, 25 | new[] {("default", "World default"), ("allow", "Force allow"), ("disable", "Force disable")}); 26 | } 27 | 28 | private static void BeautifyMirrors() 29 | { 30 | foreach (var vrcMirrorReflection in Object.FindObjectsOfType()) 31 | if (vrcMirrorReflection.isActiveAndEnabled) 32 | if (MirrorResolutionUnlimiterMod.UiInMirrors.Value) 33 | vrcMirrorReflection.m_ReflectLayers = -1 & ~PlayerLocalLayer; 34 | else 35 | vrcMirrorReflection.m_ReflectLayers = 36 | -1 & ~UiLayer & ~UiMenuLayer & ~PlayerLocalLayer & ~UiInternalLayer; 37 | 38 | } 39 | 40 | private static void OptimizeMirrors() 41 | { 42 | foreach (var vrcMirrorReflection in Object.FindObjectsOfType()) 43 | if (vrcMirrorReflection.isActiveAndEnabled) 44 | vrcMirrorReflection.m_ReflectLayers = PlayerLayer | MirrorReflectionLayer | (MirrorResolutionUnlimiterMod.UiInMirrors.Value ? UiMenuLayer | UiInternalLayer | UiLayer : 0); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /ParticleAndBoneLimiterSettings/CustomParticleSettingsUiHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UIExpansionKit; 3 | using UnityEngine; 4 | 5 | namespace ParticleAndBoneLimiterSettings 6 | { 7 | public class CustomParticleSettingsUiHandler : MonoBehaviour 8 | { 9 | internal static PreloadedBundleContents UixBundle; 10 | 11 | public CustomParticleSettingsUiHandler(IntPtr ptr) : base(ptr) 12 | { 13 | } 14 | 15 | private void Awake() 16 | { 17 | ParticleAndBoneLimiterSettingsMod.InitializeSettingsCategory(gameObject); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /ParticleAndBoneLimiterSettings/ParticleAndBoneLimiterSettings.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | true 6 | 1.1.5.0 7 | ParticleAndBoneLimiterSettings 8 | 9 | 10 | 11 | none 12 | false 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ReleaseChangelog.md: -------------------------------------------------------------------------------- 1 | Read the [Malicious Mods and you](https://github.com/knah/VRCMods/blob/master/Malicious-Mods.md) doc! 2 | 3 | Changes: 4 | * LagFreeScreenshot: fixed for build 1203 5 | * ScaleGoesBrr: fixed issues with UI having weird scale if calibrating while scaled 6 | * ScaleGoesBrr: fixed head-follow calibration failing to follow the head vertically 7 | * IKTweaks: initial update for build 1203/IK 2.0 8 | * Removed all calibration-related options 9 | * You must use IK 2.0 for IKTweaks to work. It won't do anything for legacy IK. 10 | * Most mod features now work in 3/4-point tracking too 11 | * IKT spine solver now obeys lock modes. "Lock head" is same as old IKTweaks, "Lock both" is "Lock head but ignore angle limits" 12 | * Added an option to disable elbow-torso avoidance 13 | * Overall performance improvements 14 | * Fully open-source now - no FinalIK dependency anymore! 15 | 16 | **USE IT AT YOUR OWN RISK.** Modding the client is against VRChat ToS. I am not responsible for any bans or other punishments you may get by using these mods! -------------------------------------------------------------------------------- /ScaleGoesBrr/ScaleGoesBrr.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | true 6 | none 7 | 1.1.2.0 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SparkleBeGone/SparkleBeGone.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | true 6 | true 7 | 1.1.0.0 8 | true 9 | latest 10 | SparkleBeGone 11 | 12 | 13 | 14 | none 15 | false 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /SparkleBeGone/sparklebegone: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/SparkleBeGone/sparklebegone -------------------------------------------------------------------------------- /Styletor/API/StyletorApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace Styletor.API 6 | { 7 | /// 8 | /// Provides an API for other mods to be used 9 | /// Be aware that Styletor is GPLv3 (unlike UI Expansion Kit, which is LGPLv3), so your mod has to be provided under a GPL-compatible license (so [a]GPLv3) to use these in any form 10 | /// Check README for another way to provide styles embedded in a mod without using these 11 | /// 12 | public static class StyletorApi 13 | { 14 | internal static readonly List>>> StyleProviders = new(); 15 | 16 | /// 17 | /// Registers the given style provider. 18 | /// The provided Func should return an enumerable with named ZIP file streams with styles 19 | /// Streams returned will be closed by Styletor 20 | /// This Func can be called at any time when styles are being reloaded 21 | /// 22 | public static void RegisterStylesProvider(Func>> zipStyleStreams) 23 | { 24 | StyleProviders.Add(zipStyleStreams); 25 | ReloadStyles(); 26 | } 27 | 28 | /// 29 | /// Reloads all styles, including styles on disk and style providers 30 | /// 31 | public static void ReloadStyles() 32 | { 33 | StyletorMod.Instance.ReloadStyles(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Styletor/BundledStyles/basic-recolorable.styletor.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/Styletor/BundledStyles/basic-recolorable.styletor.zip -------------------------------------------------------------------------------- /Styletor/BundledStyles/bigger-mic-button.styletor.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/Styletor/BundledStyles/bigger-mic-button.styletor.zip -------------------------------------------------------------------------------- /Styletor/BundledStyles/default-fixes.styletor.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/Styletor/BundledStyles/default-fixes.styletor.zip -------------------------------------------------------------------------------- /Styletor/BundledStyles/qm-background-lemon.styletor.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/Styletor/BundledStyles/qm-background-lemon.styletor.zip -------------------------------------------------------------------------------- /Styletor/ExtraHandlers/UiLasersHandler.cs: -------------------------------------------------------------------------------- 1 | using Styletor.Utils; 2 | using UIExpansionKit; 3 | using UnityEngine; 4 | using VRC.UI; 5 | 6 | namespace Styletor.ExtraHandlers 7 | { 8 | public class UiLasersHandler 9 | { 10 | private readonly SpriteRenderer myMouseCursorSprite; 11 | private readonly SpriteRenderer myLeftDotSprite; 12 | private readonly SpriteRenderer myRightDotSprite; 13 | 14 | private readonly HandDotCursor myLeftHand; 15 | private readonly HandDotCursor myRightHand; 16 | 17 | private readonly SettingsHolder mySettings; 18 | 19 | private readonly Color myDefaultDotColor; 20 | private readonly Color myDefaultCursorColor; 21 | 22 | private readonly Color myDefaultLaserColorDark; 23 | private readonly Color myDefaultLaserColorBright; 24 | 25 | public UiLasersHandler(SettingsHolder settings) 26 | { 27 | mySettings = settings; 28 | 29 | myMouseCursorSprite = UnityUtils 30 | .FindInactiveObjectInActiveRoot("_Application/CursorManager/MouseArrow/VRCUICursorIcon")! 31 | .GetComponent(); 32 | 33 | myDefaultCursorColor = myMouseCursorSprite.color; 34 | 35 | myLeftDotSprite = UnityUtils 36 | .FindInactiveObjectInActiveRoot("_Application/CursorManager/DotLeftHand/VRCUICursorIcon")! 37 | .GetComponent(); 38 | 39 | myDefaultDotColor = myLeftDotSprite.color; 40 | 41 | myRightDotSprite = UnityUtils 42 | .FindInactiveObjectInActiveRoot("_Application/CursorManager/DotRightHand/VRCUICursorIcon")! 43 | .GetComponent(); 44 | 45 | myLeftHand = UnityUtils 46 | .FindInactiveObjectInActiveRoot("_Application/CursorManager/DotLeftHand")! 47 | .GetComponent(); 48 | 49 | myDefaultLaserColorDark = myLeftHand.field_Public_Color_0; 50 | myDefaultLaserColorBright = myLeftHand.field_Public_Color_1; 51 | 52 | myRightHand = UnityUtils 53 | .FindInactiveObjectInActiveRoot("_Application/CursorManager/DotRightHand")! 54 | .GetComponent(); 55 | 56 | mySettings.RegisterUpdateDelegate(mySettings.UiLasersModeEntry, mySettings.UiLasersColorEntry, UpdateColors); 57 | } 58 | 59 | public void UpdateColors() 60 | { 61 | var color = mySettings.GetColorForMode(mySettings.UiLasersModeEntry, mySettings.UiLasersColorEntry); 62 | 63 | myLeftDotSprite.color = color ?? myDefaultDotColor; 64 | myRightDotSprite.color = color ?? myDefaultDotColor; 65 | myMouseCursorSprite.color = color ?? myDefaultCursorColor; 66 | 67 | var colorDarker = color?.RGBMultipliedClamped(0.5f); 68 | 69 | myLeftHand.field_Public_Color_0 = colorDarker ?? myDefaultLaserColorDark; 70 | myRightHand.field_Public_Color_0 = colorDarker ?? myDefaultLaserColorDark; 71 | 72 | myLeftHand.field_Public_Color_1 = color ?? myDefaultLaserColorBright; 73 | myRightHand.field_Public_Color_1 = color ?? myDefaultLaserColorBright; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /Styletor/Jsons/SpriteJson.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Styletor.Jsons 4 | { 5 | public class SpriteJson 6 | { 7 | // These correspond to Sprite.border 8 | public float BorderLeft; 9 | public float BorderBottom; 10 | public float BorderRight; 11 | public float BorderTop; 12 | 13 | // These correspond to Sprite.pivot, in 0-1 range 14 | public float PivotX = 0.5f; 15 | public float PivotY = 0.5f; 16 | 17 | // Sprite.pixelsPerUnit 18 | public float PixelsPerUnit = 100; 19 | 20 | public SpriteJson() 21 | { 22 | } 23 | 24 | public SpriteJson(Sprite sprite) 25 | { 26 | var pivotRel = sprite.pivot / sprite.rect.size; 27 | var border = sprite.border; 28 | 29 | PixelsPerUnit = sprite.pixelsPerUnit; 30 | PivotX = pivotRel.x; 31 | PivotY = pivotRel.y; 32 | 33 | BorderLeft = border.x; 34 | BorderBottom = border.y; 35 | BorderRight = border.z; 36 | BorderTop = border.w; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Styletor/Jsons/StyleMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Styletor.Jsons 4 | { 5 | public class StyleMetadata 6 | { 7 | /// 8 | /// User-visible name of this style 9 | /// 10 | public string Name = UnnamedName; 11 | 12 | /// 13 | /// User-visible description of this style 14 | /// 15 | public string Description = ""; 16 | 17 | /// 18 | /// User-visible author name of this style 19 | /// 20 | public string Author = ""; 21 | 22 | /// 23 | /// VRC build number this style was created for, i.e. "1137" 24 | /// 25 | public string? VrcBuildNumber; 26 | 27 | /// 28 | /// If true, this style is a mix-in and can be disabled or enabled (as opposed to chosen). It will be applied after VRC base style 29 | /// 30 | public bool IsMixin; 31 | 32 | /// 33 | /// Sorting priority of this mix-in relative to others. 34 | /// Mixins with larger numbers will override properties from mixins with lower numbers. 35 | /// User-chosen non-mixin style is applied at priority 0 36 | /// 37 | public int MixinPriority = 1; 38 | 39 | /// 40 | /// If true, this mix-in starts disabled by default. 41 | /// Mostly useful for bundled styles. 42 | /// 43 | public bool DisabledByDefault; 44 | 45 | /// 46 | /// A list of image names that will be turned into grayscale and overridden. 47 | /// VRChat has some graphics with baked-in colors, which usually makes them non-colorable. 48 | /// Use this so that you don't have to include (copyrighted) VRChat assets turned grayscale into your skin. 49 | /// 50 | public List SpritesToGrayscale = new(); 51 | 52 | public const string UnnamedName = ""; 53 | } 54 | } -------------------------------------------------------------------------------- /Styletor/Styles/ColorizerManager.cs: -------------------------------------------------------------------------------- 1 | using MelonLoader; 2 | using Styletor.Utils; 3 | using UnityEngine; 4 | 5 | namespace Styletor.Styles 6 | { 7 | public class ColorizerManager 8 | { 9 | private readonly SettingsHolder mySettings; 10 | public string MenuColorBase { get; private set; } = ""; 11 | public string MenuColorHighlight { get; private set; } = ""; 12 | public string MenuColorBackground { get; private set; } = ""; 13 | public string MenuColorDarklight { get; private set; } = ""; 14 | 15 | public string MenuColorText { get; private set; } = ""; 16 | public string MenuColorTextHigh { get; private set; } = ""; 17 | 18 | public string MenuColorAccent { get; private set; } = ""; 19 | public string MenuColorAccentDarker { get; private set; } = ""; 20 | 21 | public ColorizerManager(SettingsHolder settings) 22 | { 23 | mySettings = settings; 24 | 25 | settings.BaseColorEntry.OnValueChanged += (_, _) => { UpdateColors(); }; 26 | settings.AccentColorEntry.OnValueChanged += (_, _) => { UpdateColors(); }; 27 | settings.TextColorEntry.OnValueChanged += (_, _) => { UpdateColors(); }; 28 | UpdateColors(); 29 | } 30 | 31 | private void UpdateColors() 32 | { 33 | UpdateColors(mySettings.BaseColor, mySettings.AccentColor, mySettings.TextColor); 34 | } 35 | 36 | public string ReplacePlaceholders(string input) 37 | { 38 | return input 39 | .Replace("$BASE$", MenuColorBase) 40 | .Replace("$HIGH$", MenuColorHighlight) 41 | .Replace("$BG$", MenuColorBackground) 42 | .Replace("$DARK$", MenuColorDarklight) 43 | .Replace("$TEXT$", MenuColorText) 44 | .Replace("$TEXTHI$", MenuColorTextHigh) 45 | .Replace("$ACCT$", MenuColorAccent) 46 | .Replace("$ACCDK$", MenuColorAccentDarker) 47 | ; 48 | } 49 | 50 | public void UpdateColors(Color @base, Color accent, Color text) 51 | { 52 | var highlight = @base.RGBMultipliedClamped(1.1f); 53 | var background = @base.RGBMultipliedClamped(0.9f); 54 | var dark = @base.RGBMultipliedClamped(0.5f); 55 | 56 | MenuColorBase = ColorToHex(@base); 57 | MenuColorHighlight = ColorToHex(highlight); 58 | MenuColorBackground = ColorToHex(background); 59 | MenuColorDarklight = ColorToHex(dark); 60 | 61 | MenuColorText = ColorToHex(text.RGBMultipliedClamped(0.9f)); 62 | MenuColorTextHigh = ColorToHex(text); 63 | 64 | MenuColorAccent = ColorToHex(accent); 65 | MenuColorAccentDarker = ColorToHex(accent.RGBMultipliedClamped(0.7f)); 66 | } 67 | 68 | private static string PartToHex(float f) => ((int)(f * 255)).ToString("x2"); 69 | private static string ColorToHex(Color c) => $"#{PartToHex(c.r)}{PartToHex(c.g)}{PartToHex(c.b)}"; 70 | } 71 | } -------------------------------------------------------------------------------- /Styletor/Styletor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | 0.3.3.0 6 | latest 7 | enable 8 | none 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | False 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Styletor/Utils/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Il2CppSystem.IO; 3 | using UnhollowerBaseLib; 4 | using UnityEngine; 5 | using Object = UnityEngine.Object; 6 | using Stream = System.IO.Stream; 7 | 8 | #nullable enable 9 | 10 | namespace Styletor.Utils 11 | { 12 | public static class Utils 13 | { 14 | private delegate byte LoadTextureDelegate(IntPtr texturePtr, IntPtr arrayPtr, byte makeNonReadable); 15 | private delegate IntPtr EncodeAsPngDelegate(IntPtr texturePtr); 16 | 17 | private static readonly LoadTextureDelegate ourLoadTextureDelegate = IL2CPP.ResolveICall("UnityEngine.ImageConversion::LoadImage"); 18 | private static readonly EncodeAsPngDelegate ourEncodeAsPng = IL2CPP.ResolveICall("UnityEngine.ImageConversion::EncodeToPNG"); 19 | 20 | public static Il2CppStructArray? EncodeAsPng(this Texture2D texture) 21 | { 22 | var arrayPtr = ourEncodeAsPng(texture.Pointer); 23 | if (arrayPtr == IntPtr.Zero) return null; 24 | return new Il2CppStructArray(arrayPtr); 25 | } 26 | 27 | public static Texture2D? LoadTexture(string filePath) 28 | { 29 | return LoadTexture(File.ReadAllBytes(filePath)); 30 | } 31 | 32 | public static Texture2D? LoadTexture(Stream stream) 33 | { 34 | return LoadTexture(stream.ReadAllBytes()); 35 | } 36 | 37 | public static Texture2D? LoadTexture(Il2CppStructArray bytes) 38 | { 39 | var texture = new Texture2D(2, 2, TextureFormat.ARGB32, true); 40 | var success = ourLoadTextureDelegate(texture.Pointer, bytes.Pointer, 1); 41 | if (success == 0) 42 | { 43 | Object.Destroy(texture); 44 | return null; 45 | } 46 | 47 | return texture; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /TrueShaderAntiCrash/DxbcShaderFilter.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/TrueShaderAntiCrash/DxbcShaderFilter.dll -------------------------------------------------------------------------------- /TrueShaderAntiCrash/ShaderFilterApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace TrueShaderAntiCrash 5 | { 6 | public static class ShaderFilterApi 7 | { 8 | public const string DLLName = "DxbcShaderFilter"; 9 | 10 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 11 | private delegate void TriBool(bool limitLoops, bool limitGeometry, bool limitTesselation); 12 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 13 | private delegate void OneFloat(float value); 14 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 15 | private delegate void OneInt(int value); 16 | 17 | private static TriBool ourSetFilterState; 18 | private static OneFloat ourSetTess; 19 | private static OneInt ourSetLoops; 20 | private static OneInt ourSetGeom; 21 | 22 | public static void Init(IntPtr hmodule) 23 | { 24 | ourSetFilterState = Marshal.GetDelegateForFunctionPointer(GetProcAddress(hmodule, nameof(SetFilteringState))); 25 | ourSetTess = Marshal.GetDelegateForFunctionPointer(GetProcAddress(hmodule, nameof(SetMaxTesselationPower))); 26 | ourSetLoops = Marshal.GetDelegateForFunctionPointer(GetProcAddress(hmodule, nameof(SetLoopLimit))); 27 | ourSetGeom = Marshal.GetDelegateForFunctionPointer(GetProcAddress(hmodule, nameof(SetGeometryLimit))); 28 | } 29 | 30 | [DllImport("kernel32", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)] 31 | static extern IntPtr GetProcAddress(IntPtr hModule, string procName); 32 | 33 | public static void SetFilteringState(bool limitLoops, bool limitGeometry, bool limitTesselation) => ourSetFilterState(limitLoops, limitGeometry, limitTesselation); 34 | public static void SetMaxTesselationPower(float maxTesselation) => ourSetTess(maxTesselation); 35 | public static void SetLoopLimit(int limit) => ourSetLoops(limit); 36 | public static void SetGeometryLimit(int limit) => ourSetGeom(limit); 37 | } 38 | } -------------------------------------------------------------------------------- /TrueShaderAntiCrash/TrueShaderAntiCrash.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48 5 | latest 6 | 1.0.6.0 7 | true 8 | TrueShaderAntiCrash 9 | 10 | 11 | 12 | none 13 | false 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Turbones/BoneDeleteHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace Turbones 5 | { 6 | public class BoneDeleteHandler : MonoBehaviour 7 | { 8 | public BoneDeleteHandler(IntPtr ptr) : base(ptr) 9 | { 10 | } 11 | 12 | private void OnDestroy() 13 | { 14 | foreach (var bone in GetComponents()) 15 | { 16 | JigglySolverApi.DynamicBoneOnDestroyPatch(bone.Pointer); 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Turbones/JigglyRustSolver.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatchedPlusPlus/VRCMods/b20a738530ece2eb3bc118de9852ed0ba51c26e6/Turbones/JigglyRustSolver.dll -------------------------------------------------------------------------------- /Turbones/NativeStructs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Turbones 5 | { 6 | 7 | [StructLayout(LayoutKind.Sequential)] 8 | internal struct ICallOffsets 9 | { 10 | public IntPtr get_transform_component; 11 | public IntPtr get_transform_ltw_matrix; 12 | public IntPtr get_component_enabled; 13 | public IntPtr set_transform_position_and_rotation; 14 | public IntPtr get_transform_child_count; 15 | 16 | public IntPtr gchandle_create; 17 | public IntPtr gchandle_drop; 18 | public IntPtr gchandle_get; 19 | 20 | public IntPtr transform_get_local_rotation; 21 | } 22 | 23 | [StructLayout(LayoutKind.Sequential)] 24 | internal struct ColliderComponentOffsets { 25 | public uint collider_radius; 26 | public uint collider_height; 27 | public uint collider_center; 28 | public uint collider_bound; 29 | public uint collider_direction; 30 | } 31 | 32 | [StructLayout(LayoutKind.Sequential)] 33 | internal struct ParticleClassOffsets { 34 | public uint transform; 35 | public uint parent_index; 36 | public uint damping; 37 | public uint elasticity; 38 | public uint stiffness; 39 | public uint inert; 40 | public uint radius; 41 | public uint end_offset; 42 | } 43 | 44 | [StructLayout(LayoutKind.Sequential)] 45 | internal struct BoneComponentOffsets { 46 | public uint particle_list; 47 | public uint collider_list; 48 | public uint gravity; 49 | public uint force; 50 | public uint local_gravity; 51 | public uint freeze_axis; 52 | public uint update_rate; 53 | public uint root; 54 | } 55 | 56 | [StructLayout(LayoutKind.Sequential)] 57 | internal struct ListClassOffsets 58 | { 59 | public uint size; 60 | public uint store; 61 | public uint array_store; 62 | } 63 | 64 | [StructLayout(LayoutKind.Sequential)] 65 | internal struct ObjectOffsets 66 | { 67 | public uint cached_ptr; 68 | } 69 | } -------------------------------------------------------------------------------- /Turbones/Turbones.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | true 6 | true 7 | 1.1.2.0 8 | Turbones 9 | 10 | 11 | 12 | none 13 | false 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /UIExpansionKit/API/BuiltinUiUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Il2CppSystem.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.UI; 5 | 6 | namespace UIExpansionKit.API 7 | { 8 | public static class BuiltinUiUtils 9 | { 10 | /// 11 | /// Shows a VRC keyboard input popup. Requires a big menu page to be open. 12 | /// 13 | /// Text displayed in popup header 14 | /// Initial text in the input 15 | /// The input type 16 | /// true means a numeric keypad 17 | /// Invoked when the user clicks the confirm button 18 | /// The text on confirmation button 19 | /// Invoked if user cancels text input 20 | /// Text show in the input field if no text is already there 21 | /// Keep this set to true or the universe will implode 22 | /// Invoked with the instance of input popup shown 23 | public static void ShowInputPopup(string title, string initialText, InputField.InputType inputType, bool isNumeric, 24 | string confirmButtonText, Action, Text> onComplete, Action onCancel, 25 | string placeholderText, bool closeAfterInput, Action onPopupShown) 26 | { 27 | ScanningReflectionCache.ShowUiInputPopup(title, initialText, inputType, isNumeric, confirmButtonText, onComplete, onCancel, placeholderText, closeAfterInput, onPopupShown); 28 | } 29 | 30 | /// 31 | /// Shows a VRC keyboard input popup. Requires a big menu page to be open. 32 | /// 33 | /// Text displayed in popup header 34 | /// Initial text in the input 35 | /// The input type 36 | /// true means a numeric keypad 37 | /// Invoked when the user clicks the confirm button 38 | /// The text on confirmation button 39 | /// Invoked if user cancels text input 40 | /// Text show in the input field if no text is already there 41 | /// Keep this set to true or the universe will implode 42 | /// Invoked with the instance of input popup shown 43 | /// If true, text length limit label will be shown 44 | /// Maximum text length 45 | public static void ShowInputPopup(string title, string initialText, InputField.InputType inputType, bool isNumeric, 46 | string confirmButtonText, Action, Text> onComplete, Action onCancel = null, 47 | string placeholderText = "Enter text...", bool closeAfterInput = true, Action onPopupShown = null, 48 | bool showLimitLabel = false, int textLengthLimit = 0) 49 | { 50 | ScanningReflectionCache.ShowUiInputPopup(title, initialText, inputType, isNumeric, confirmButtonText, onComplete, onCancel, placeholderText, closeAfterInput, onPopupShown, showLimitLabel, textLengthLimit); 51 | } 52 | 53 | public static event Action QuickMenuClosed; 54 | public static event Action FullMenuClosed; 55 | public static event Action OnMenuOpened; 56 | 57 | internal static void InvokeQuickMenuClosed() => QuickMenuClosed?.Invoke(); 58 | internal static void InvokeFullMenuClosed() => FullMenuClosed?.Invoke(); 59 | internal static void InvokeMenuOpened(ExpandedMenu menu) => OnMenuOpened?.Invoke(menu); 60 | } 61 | } -------------------------------------------------------------------------------- /UIExpansionKit/API/Classes/AwaitProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using MelonLoader; 6 | 7 | namespace UIExpansionKit.API.Classes 8 | { 9 | /// 10 | /// This class implements a simple delegate queue intended for interop with async/await 11 | /// 12 | public class AwaitProvider 13 | { 14 | private readonly Queue myToMainThreadQueue = new(); 15 | /// 16 | /// The name of this queue specified in constructor; used in exception messages 17 | /// 18 | public readonly string QueueName; 19 | 20 | /// 21 | /// Creates a new queue 22 | /// 23 | /// Queue's name; used in exception messages 24 | public AwaitProvider(string queueName) 25 | { 26 | QueueName = queueName; 27 | } 28 | 29 | /// 30 | /// Invokes all delegates currently in the queue. 31 | /// 32 | public void Flush() 33 | { 34 | List toProcess; 35 | 36 | if (myToMainThreadQueue.Count == 0) 37 | return; 38 | 39 | lock (myToMainThreadQueue) 40 | { 41 | toProcess = myToMainThreadQueue.ToList(); 42 | myToMainThreadQueue.Clear(); 43 | } 44 | 45 | foreach (var action in toProcess) 46 | { 47 | try 48 | { 49 | action(); 50 | } 51 | catch (Exception ex) 52 | { 53 | MelonLogger.Warning($"Exception in delegate queue {QueueName}: {ex}"); 54 | } 55 | } 56 | } 57 | 58 | /// 59 | /// Adds an action to the queue. It will be invoked next time `Flush` is called. 60 | /// 61 | public void Add(Action action) 62 | { 63 | if (action == null) throw new ArgumentNullException(nameof(action)); 64 | lock (myToMainThreadQueue) 65 | myToMainThreadQueue.Enqueue(action); 66 | } 67 | 68 | /// 69 | /// Returns an awaitable object (usable with `await` keyword). After it is awaited, async method execution execution will continue when `Flush` is invoked 70 | /// 71 | public YieldAwaitable Yield() 72 | { 73 | return new(myToMainThreadQueue); 74 | } 75 | 76 | public readonly struct YieldAwaitable : INotifyCompletion 77 | { 78 | private readonly Queue myToMainThreadQueue; 79 | 80 | public YieldAwaitable(Queue toMainThreadQueue) 81 | { 82 | myToMainThreadQueue = toMainThreadQueue; 83 | } 84 | 85 | public bool IsCompleted => false; 86 | 87 | public YieldAwaitable GetAwaiter() => this; 88 | 89 | public void GetResult() { } 90 | 91 | public void OnCompleted(Action continuation) 92 | { 93 | lock (myToMainThreadQueue) 94 | myToMainThreadQueue.Enqueue(continuation); 95 | } 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /UIExpansionKit/API/Controls/FluentExtensions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace UIExpansionKit.API.Controls 4 | { 5 | public static class FluentExtensions 6 | { 7 | public static T SetVisible(this T @this, bool visible) where T : IMenuControl 8 | { 9 | @this.Visible = visible; 10 | return @this; 11 | } 12 | 13 | public static T SetText(this T @this, string text) where T : IMenuControlWithText 14 | { 15 | @this.Text = text; 16 | return @this; 17 | } 18 | 19 | public static T SetAnchor(this T @this, TextAnchor anchor) where T : IMenuControlWithText 20 | { 21 | @this.Anchor = anchor; 22 | return @this; 23 | } 24 | 25 | public static T SetSelected(this T @this, bool selected) where T : IMenuToggle 26 | { 27 | @this.Selected = selected; 28 | return @this; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /UIExpansionKit/API/Controls/Interfaces.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | #nullable enable 5 | 6 | namespace UIExpansionKit.API.Controls 7 | { 8 | public interface IMenuControl 9 | { 10 | /// 11 | /// Whether this control is visible or not. 12 | /// Invisible controls don't take up space in grid-like UI layouts 13 | /// 14 | public bool Visible { get; set; } 15 | 16 | 17 | /// 18 | /// This event is called when an instance of this control is created. 19 | /// It's recommended to not rely on its internal structure, unless the prefab for it is supplied by you. 20 | /// 21 | public event Action OnInstanceCreated; 22 | 23 | /// 24 | /// The current instance of this control. 25 | /// 26 | public GameObject? CurrentInstance { get; } 27 | } 28 | 29 | public interface IMenuControlWithText : IMenuControl 30 | { 31 | /// 32 | /// The current text shown on this control 33 | /// 34 | public string Text { get; set; } 35 | 36 | /// 37 | /// Text alignment for this control 38 | /// 39 | public TextAnchor Anchor { get; set; } 40 | } 41 | 42 | public interface IMenuButton : IMenuControlWithText 43 | { 44 | } 45 | 46 | public interface IMenuToggle : IMenuControlWithText 47 | { 48 | /// 49 | /// The state of the toggle. Manually setting it will fire set handler if the state changed. 50 | /// 51 | public bool Selected { get; set; } 52 | } 53 | 54 | public interface IMenuLabel : IMenuControlWithText 55 | { 56 | } 57 | } -------------------------------------------------------------------------------- /UIExpansionKit/API/ExpandedMenu.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UIExpansionKit.API 4 | { 5 | /// 6 | /// Menus supported by UI Expansion Kit 7 | /// 8 | public enum ExpandedMenu 9 | { 10 | /// 11 | /// Normal QuickMenu (nobody selected) 12 | /// 13 | QuickMenu, 14 | 15 | /// 16 | /// QuickMenu with a user (in instance) selected 17 | /// 18 | UserQuickMenu, 19 | 20 | /// 21 | /// Big avatar menu 22 | /// 23 | AvatarMenu, 24 | 25 | /// 26 | /// Big list of worlds menu 27 | /// 28 | WorldMenu, 29 | 30 | /// 31 | /// Big world details menu 32 | /// 33 | WorldDetailsMenu, 34 | 35 | /// 36 | /// Big user details menu 37 | /// 38 | UserDetailsMenu, 39 | 40 | /// 41 | /// Big social menu 42 | /// 43 | SocialMenu, 44 | 45 | /// 46 | /// Big settings menu. This constant indicates the usual sidebar of big menu, not Mod Settings popup 47 | /// 48 | SettingsMenu, 49 | 50 | /// 51 | /// Big safety menu 52 | /// 53 | SafetyMenu, 54 | 55 | /// 56 | /// Here Quick Menu tab 57 | /// 58 | QuickMenuHere, 59 | 60 | [Obsolete("There is no Emote page in Quick Menu anymore; buttons added here will end up on the Here page")] 61 | EmoteQuickMenu = QuickMenuHere, 62 | 63 | /// 64 | /// Emoji Quick Menu page 65 | /// 66 | EmojiQuickMenu, 67 | 68 | /// 69 | /// Camera Quick Menu page 70 | /// 71 | CameraQuickMenu, 72 | 73 | /// 74 | /// UI Elements Quick Menu page 75 | /// 76 | UiElementsQuickMenu, 77 | 78 | /// 79 | /// Quick Menu Audio Settings tab 80 | /// 81 | QuickMenuAudioSettings, 82 | 83 | [Obsolete("There is no Moderation page in Quick Menu anymore; buttons added here will end up on the Here page")] 84 | ModerationQuickMenu = QuickMenuAudioSettings, 85 | 86 | /// 87 | /// Quick Menu avatar stats/info page 88 | /// 89 | AvatarStatsQuickMenu, 90 | 91 | /// 92 | /// The invites tab in quick menu 93 | /// 94 | InvitesTab, 95 | 96 | /// 97 | /// The handheld camera object 98 | /// 99 | Camera, 100 | 101 | /// 102 | /// The user selection quick menu for not-in-instance users 103 | /// 104 | UserQuickMenuRemote, 105 | } 106 | } -------------------------------------------------------------------------------- /UIExpansionKit/API/ICustomLayoutedMenu.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using UIExpansionKit.API.Controls; 4 | using UnityEngine; 5 | 6 | namespace UIExpansionKit.API 7 | { 8 | [SuppressMessage("ReSharper", "MethodOverloadWithOptionalParameter")] 9 | public interface ICustomLayoutedMenu 10 | { 11 | [Obsolete("Use the overload without instanceConsumer")] 12 | void AddSimpleButton(string text, Action onClick, Action instanceConsumer = null); 13 | 14 | /// 15 | /// Adds a simple button to this custom menu 16 | /// 17 | /// User-visible button text 18 | /// Button click action 19 | IMenuButton AddSimpleButton(string text, Action onClick); 20 | 21 | /// 22 | /// Adds a simple button to this custom menu 23 | /// 24 | /// User-visible button text, receives the pressed button as parameter 25 | /// Button click action 26 | IMenuButton AddSimpleButton(string text, Action onClick); 27 | 28 | [Obsolete("Use the overload without instanceConsumer")] 29 | void AddToggleButton(string text, Action onClick, Func getInitialState = null, Action instanceConsumer = null); 30 | 31 | /// 32 | /// Adds a toggle button to this custom menu 33 | /// 34 | /// User-visible button text 35 | /// This action will be called when button state is toggled 36 | /// (optional) this func will be called to get the initial state of this button. If will default to not-set if this is not provided. 37 | /// (optional) this action will be invoked when the button is instantiated 38 | IMenuToggle AddToggleButton(string text, Action onClick, Func getInitialState = null); 39 | 40 | [Obsolete("Use the overload without instanceConsumer")] 41 | void AddCustomButton(GameObject gameObject, Action instanceConsumer = null); 42 | 43 | /// 44 | /// Registers a custom button prefab. This prefab can be instantiated multiple times. 45 | /// 46 | /// Button prefab 47 | IMenuControl AddCustomButton(GameObject gameObject); 48 | 49 | [Obsolete("Use the overload without instanceConsumer")] 50 | void AddLabel(string text, Action instanceConsumer = null); 51 | 52 | /// 53 | /// Adds a label to custom menu 54 | /// 55 | /// User-visible text 56 | IMenuLabel AddLabel(string text); 57 | 58 | /// 59 | /// Adds an empty spot in menu layout. 60 | /// 61 | void AddSpacer(); 62 | 63 | /// 64 | /// Adds an empty spot in menu layout. 65 | /// 66 | IMenuControl AddSpacerEx(); 67 | 68 | /// 69 | /// This event is called when this menu's content root in created. 70 | /// Your mod code should subscribe to this event if you want to add custom gameobjects to the menu. 71 | /// 72 | event Action OnContentRootCreated; 73 | 74 | /// 75 | /// If true, created buttons will be more appropriate for quick menu usage (i.e. square buttons) as opposed to plain menu lists. 76 | /// 77 | void SetUseQuickMenuLikeComponents(bool isQuickMenu); 78 | } 79 | } -------------------------------------------------------------------------------- /UIExpansionKit/API/ICustomShowableLayoutedMenu.cs: -------------------------------------------------------------------------------- 1 | namespace UIExpansionKit.API 2 | { 3 | public interface ICustomShowableLayoutedMenu : ICustomLayoutedMenu, IShowableMenu 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /UIExpansionKit/API/IShowableMenu.cs: -------------------------------------------------------------------------------- 1 | namespace UIExpansionKit.API 2 | { 3 | public interface IShowableMenu 4 | { 5 | /// 6 | /// Shows this menu on top of parent menu. 7 | /// Requires the parent menu to be open. 8 | /// 9 | /// If another custom menu(s) is/are already showing, and this is false, the other menu(s) will be hidden; otherwise, this menu will be shown on top of it/them 10 | void Show(bool onTop = false); 11 | 12 | /// 13 | /// Hides this menu if it's showing. If there are menus showing on top of it, they will be hidden too. 14 | /// 15 | void Hide(); 16 | } 17 | } -------------------------------------------------------------------------------- /UIExpansionKit/API/LayoutDescription.cs: -------------------------------------------------------------------------------- 1 | namespace UIExpansionKit.API 2 | { 3 | /// 4 | /// This struct describes the required layout for a custom menu 5 | /// 6 | public struct LayoutDescription 7 | { 8 | /// 9 | /// Number of columns in grid layout of this menu. Does not change menu width. 10 | /// 11 | public int NumColumns; 12 | 13 | /// 14 | /// Height of single row in menu-units 15 | /// 16 | public int RowHeight; 17 | 18 | /// 19 | /// Number of rows visible at a time. Adding more rows will enable scrolling. Affects menu height. 20 | /// 21 | public int NumRows; 22 | 23 | public static LayoutDescription QuickMenu3Columns = new LayoutDescription {NumColumns = 3, RowHeight = 380 / 3, NumRows = 3}; 24 | public static LayoutDescription QuickMenu4Columns = new LayoutDescription {NumColumns = 4, RowHeight = 95, NumRows = 4 }; 25 | public static LayoutDescription WideSlimList = new LayoutDescription {NumColumns = 1, RowHeight = 50, NumRows = 8 }; 26 | 27 | public LayoutDescription With(int? numColumns = null, int? rowHeight = null, int? numRows = null) 28 | { 29 | return new LayoutDescription 30 | { 31 | NumColumns = numColumns ?? NumColumns, 32 | RowHeight = rowHeight ?? RowHeight, 33 | NumRows = numRows ?? NumRows, 34 | }; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /UIExpansionKit/API/TaskUtilities.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using MelonLoader; 3 | using UIExpansionKit.API.Classes; 4 | 5 | namespace UIExpansionKit.API 6 | { 7 | /// 8 | /// This class contains utility methods for Task/async/await-based code 9 | /// 10 | public static class TaskUtilities 11 | { 12 | internal static readonly AwaitProvider ourMainThreadQueue = new("UIExpansionKit.ToMainThread"); 13 | internal static readonly AwaitProvider ourFrameEndQueue = new("UIExpansionKit.FrameEnd"); 14 | 15 | /// 16 | /// Returns an awaitable object used to return an async method's execution to the main thread. 17 | /// After the returned object is awaited, execution will continue on main thread inside of `Update` event 18 | /// Can also be used to wait for the next frame 19 | /// 20 | public static AwaitProvider.YieldAwaitable YieldToMainThread() 21 | { 22 | return ourMainThreadQueue.Yield(); 23 | } 24 | 25 | /// 26 | /// Returns an awaitable object used to return an async method's execution to the main thread. 27 | /// After the returned object is awaited, execution will continue on main thread inside of `OnGUI` event 28 | /// 29 | public static AwaitProvider.YieldAwaitable YieldToFrameEnd() 30 | { 31 | return ourFrameEndQueue.Yield(); 32 | } 33 | 34 | /// 35 | /// Adds a handler to a Task that prints a message to console if an exception is thrown within that task 36 | /// 37 | /// A string that will be included in the error message to identify the task 38 | public static void NoAwait(this Task task, string taskInfo = "Task") 39 | { 40 | task.ContinueWith(tsk => 41 | { 42 | if (tsk.IsFaulted) 43 | MelonLogger.Error($"Free-floating {taskInfo} failed with exception: {tsk.Exception}"); 44 | }); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /UIExpansionKit/ButtonFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MelonLoader; 3 | using TMPro; 4 | using UIExpansionKit.API; 5 | using UnityEngine; 6 | using UnityEngine.UI; 7 | using Object = UnityEngine.Object; 8 | 9 | namespace UIExpansionKit 10 | { 11 | internal static class ButtonFactory 12 | { 13 | internal static void CreateButtonForRegistration(ExpansionKitApi.ButtonRegistration registration, Transform root, bool isQuickMenu) 14 | { 15 | try 16 | { 17 | CreateButtonForRegistrationImpl(registration, root, isQuickMenu); 18 | } 19 | catch (Exception ex) 20 | { 21 | MelonLogger.Error($"Exception when creating a button for registration of {registration}: {ex}"); 22 | } 23 | } 24 | 25 | private static void CreateButtonForRegistrationImpl(ExpansionKitApi.ButtonRegistration registration, Transform root, bool isQuickMenu) 26 | { 27 | if (registration.Prefab != null) 28 | { 29 | var newObject = Object.Instantiate(registration.Prefab, root, false); 30 | registration.InstanceConsumer?.Invoke(newObject); 31 | } 32 | else if(registration.Text != null) 33 | { 34 | var stuff = UiExpansionKitMod.Instance.StuffBundle; 35 | 36 | if (registration.Action != null) 37 | { 38 | var clickButtonPrefab = stuff.QuickMenuButton; 39 | 40 | var buttonInstance = Object.Instantiate(clickButtonPrefab, root, false); 41 | var textComponent = buttonInstance.GetComponentInChildren(true); 42 | textComponent.text = registration.Text; 43 | var legacyTextComponent = buttonInstance.GetComponentInChildren(true); 44 | legacyTextComponent.text = registration.Text; 45 | buttonInstance.GetComponent