diff --git a/DorkNet.ClientMod/Mod.cs b/DorkNet.ClientMod/Mod.cs index ec6a9d5..c3d7012 100644 --- a/DorkNet.ClientMod/Mod.cs +++ b/DorkNet.ClientMod/Mod.cs @@ -355,6 +355,11 @@ public class Mod : MelonMod $"(raisePhotonRate={Cfg.DesktopScreenShareRaisePhotonRate})"); } + TryPatchByName("RecRoom.Tools.MakerPenVisuals", + "set_LaserPointerEnabled", + args: new[] { typeof(bool) }, + prefix: nameof(MakerPenGiftPreviewPatches.LaserPointerEnabled_Prefix)); + RegisterDiagnostics(); Log.Msg("=== Client patches registered ==="); } @@ -769,7 +774,7 @@ public class Mod : MelonMod { // Look in all three patch holder classes — small enough that a // linear scan is cheaper than per-class lookups. - foreach (var holder in new[] { typeof(UriPatches), typeof(PhotonPatches), typeof(TlsPatches), typeof(DiagnosticPatches), typeof(SavePatches), typeof(ChatPatches), typeof(JoinPatches), typeof(RegistrationPatches), typeof(DebugConsolePatches), typeof(ScreenSharePatches) }) + foreach (var holder in new[] { typeof(UriPatches), typeof(PhotonPatches), typeof(TlsPatches), typeof(DiagnosticPatches), typeof(SavePatches), typeof(ChatPatches), typeof(JoinPatches), typeof(RegistrationPatches), typeof(DebugConsolePatches), typeof(ScreenSharePatches), typeof(MakerPenGiftPreviewPatches) }) { var m = holder.GetMethod(name, BindingFlags.Public | BindingFlags.Static); if (m is not null) return m; @@ -1540,6 +1545,52 @@ internal static class ScreenSharePatches } } +internal static class MakerPenGiftPreviewPatches +{ + private static bool _loggedMissingPointer; + private static bool _loggedMissingRestrictionFlag; + + public static bool LaserPointerEnabled_Prefix(object __instance, bool ONGBFDACHHG) + { + try + { + if (__instance is null) return true; + var t = __instance.GetType(); + if (ReadField(__instance, t, "pointerLineRenderer") is null) + { + if (!_loggedMissingPointer) + { + _loggedMissingPointer = true; + Mod.Log.Msg("[makerpen-preview] skipped LaserPointerEnabled on MakerPenVisuals with no pointerLineRenderer"); + } + return false; + } + + if (ONGBFDACHHG && ReadField(__instance, t, "EANJBIDHAMN") is null) + { + if (!_loggedMissingRestrictionFlag) + { + _loggedMissingRestrictionFlag = true; + Mod.Log.Msg("[makerpen-preview] skipped LaserPointerEnabled=true on MakerPenVisuals with no laser restriction flag"); + } + return false; + } + } + catch (Exception ex) + { + Mod.Log.Warning($"[makerpen-preview] guard failed, letting original run: {ex.Message}"); + } + + return true; + } + + private static object? ReadField(object instance, Type type, string name) + { + var field = AccessTools.Field(type, name) ?? AccessTools.Field(type.BaseType, name); + return field?.GetValue(instance); + } +} + internal static class UriPatches { public static void HttpManagerSendRequestString_Prefix(ref string url) diff --git a/DorkNet.Server/Controllers/API/Store/StorefrontsBuyController.cs b/DorkNet.Server/Controllers/API/Store/StorefrontsBuyController.cs index d6d6851..a362247 100644 --- a/DorkNet.Server/Controllers/API/Store/StorefrontsBuyController.cs +++ b/DorkNet.Server/Controllers/API/Store/StorefrontsBuyController.cs @@ -103,7 +103,7 @@ public class StorefrontsBuyController( StoreService.TryGetAvatarItemPayload(item.Slug, out var avatarItemType, out avatarItemDesc); var inventoryAvatarDesc = StoreService.InventoryAvatarItemDesc(avatarItemDesc); StoreService.TryGetConsumableItemDesc(item.Slug, out var consumableItemDesc); - StoreService.TryGetGiftBoxEquipmentPayload(item.Slug, out var equipmentPrefabName, out var equipmentModificationGuid); + StoreService.TryGetEquipmentPayload(item.Slug, out var equipmentPrefabName, out var equipmentModificationGuid); if (StoreService.TryGetHairDyePayload(item.Slug, out var hairDyeConsumableDesc, out var hairDyeColorGuid)) { avatarItemType = 1; @@ -352,7 +352,7 @@ public class StorefrontsBuyController( { StoreService.TryGetAvatarItemPayload(grantedSlug, out avatarItemType, out avatarItemDesc); StoreService.TryGetConsumableItemDesc(grantedSlug, out consumableItemDesc); - StoreService.TryGetGiftBoxEquipmentPayload(grantedSlug, out equipmentPrefabName, out equipmentModificationGuid); + StoreService.TryGetEquipmentPayload(grantedSlug, out equipmentPrefabName, out equipmentModificationGuid); if (StoreService.TryGetHairDyePayload(grantedSlug, out var hairDyeConsumableDesc, out var hairDyeColorGuid)) { avatarItemType = 1; diff --git a/DorkNet.Server/Services/StoreService.cs b/DorkNet.Server/Services/StoreService.cs index 045e427..47068ca 100644 --- a/DorkNet.Server/Services/StoreService.cs +++ b/DorkNet.Server/Services/StoreService.cs @@ -383,40 +383,6 @@ public class StoreService(DorkNetDbContext db, LevelService level, IConfiguratio out string modificationGuid) => TryGetEquipmentPayload(slug, out prefabName, out modificationGuid, out _); - public static bool TryGetGiftBoxEquipmentPayload( - string? slug, - out string prefabName, - out string modificationGuid, - out EquipmentSkinEntry? entry) - { - if (!TryGetEquipmentPayload(slug, out prefabName, out modificationGuid, out entry)) - return false; - - // The 2020 client previews GiftPackage equipment by spawning the - // prefab. Spawning [MakerPen] outside the real tool equip flow wakes - // MakerPen tool scripts without their visuals wired up, causing a - // repeating MakerPenVisuals null-ref/spin. Still grant the skin as - // inventory; just don't ask the gift-box/store-preview path to spawn it. - if (IsMakerPenPrefab(prefabName)) - { - prefabName = string.Empty; - modificationGuid = string.Empty; - entry = null; - return false; - } - - return true; - } - - public static bool TryGetGiftBoxEquipmentPayload( - string? slug, - out string prefabName, - out string modificationGuid) => - TryGetGiftBoxEquipmentPayload(slug, out prefabName, out modificationGuid, out _); - - private static bool IsMakerPenPrefab(string? prefabName) => - string.Equals(prefabName, "[MakerPen]", StringComparison.OrdinalIgnoreCase); - private async Task SeedEquipmentSkinsAsync(HashSet existingSlugs) { var catalog = _equipmentSkinCatalog.Value; @@ -983,7 +949,7 @@ public class StoreService(DorkNetDbContext db, LevelService level, IConfiguratio // here too. TryGetAvatarItemPayload(i.Slug, out var avatarItemType, out var avatarItemDesc); TryGetConsumableItemDesc(i.Slug, out var consumableItemDesc); - TryGetGiftBoxEquipmentPayload(i.Slug, out var equipmentPrefabName, out var equipmentModificationGuid, out var equipmentEntry); + TryGetEquipmentPayload(i.Slug, out var equipmentPrefabName, out var equipmentModificationGuid, out var equipmentEntry); var isHairDye = TryGetHairDyePayload(i.Slug, out var consumableHairDyeDesc, out var hairDyeColorGuid); if (isHairDye) {