Skip to content

Commit 8e7cda2

Browse files
committed
ready to test more
1 parent 9a3b76f commit 8e7cda2

8 files changed

Lines changed: 206 additions & 6 deletions

File tree

Basis Server/BasisNetworkCore/Serializable/AdminRequest.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ public enum AdminRequestMode : byte
5555

5656
EnableShoutMode, // admin: enable shout mode for a player (non-spatialized broadcast voice)
5757
DisableShoutMode, // admin: disable shout mode for a player
58+
59+
GlobalToggleAvatars, // admin: toggle global avatar loading lock
60+
GlobalToggleProps, // admin: toggle global prop loading lock
61+
GlobalToggleWorlds, // admin: toggle global world loading lock
62+
GlobalGetLockState, // server→client: current global lock state
5863
}
5964
}
6065
}

Basis Server/BasisNetworkServer/BasisNetworking/BasisNetworkContentShare.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,35 @@ public static void HandleContentShareDrop(NetPacketReader reader, NetPeer peer)
3333
return;
3434
}
3535

36+
// Global lock check based on content type
37+
bool isAdmin = PermissionIntegration.HasValidRequirement(peer, PermNodes.All);
38+
if (!isAdmin)
39+
{
40+
bool blocked = false;
41+
string contentName = "";
42+
switch (msg.ContentType)
43+
{
44+
case ContentShareType.Avatar:
45+
blocked = BasisNetworkServer.Security.BasisGlobalLockManager.AvatarsLocked;
46+
contentName = "Avatar";
47+
break;
48+
case ContentShareType.Prop:
49+
blocked = BasisNetworkServer.Security.BasisGlobalLockManager.PropsLocked;
50+
contentName = "Prop";
51+
break;
52+
case ContentShareType.World:
53+
blocked = BasisNetworkServer.Security.BasisGlobalLockManager.WorldsLocked;
54+
contentName = "World";
55+
break;
56+
}
57+
if (blocked)
58+
{
59+
BNL.Log($"{contentName} content sharing is globally disabled. Rejected from peer {peer.Id}");
60+
BasisNetworkServer.Security.BasisPlayerModeration.SendBackMessage(peer, $"{contentName} loading is currently disabled by an admin.");
61+
return;
62+
}
63+
}
64+
3665
ServerContentShareMessage serverMsg = new ServerContentShareMessage
3766
{
3867
playerIdMessage = new PlayerIdMessage

Basis Server/BasisNetworkServer/BasisServerHandleEvents.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ public static void OnNetworkAccepted(NetPeer newPeer, ReadyMessage ReadyMessage,
267267
BasisNetworkOwnership.SendOutOwnershipInformation(newPeer);
268268
BasisNetworkPIPCamera.SendPIPStateToPeer(newPeer);
269269
BasisNetworkContentShare.SendAllSpheresToPeer(newPeer);
270+
BasisNetworkServer.Security.BasisGlobalLockManager.SendLockStateToPeer(newPeer);
270271
SendShoutStateToPeer(newPeer);
271272
}
272273
else
@@ -293,6 +294,24 @@ public static void SendAvatarMessageToClients(NetPacketReader Reader, NetPeer Pe
293294
ClientAvatarChangeMessage ClientAvatarChangeMessage = new ClientAvatarChangeMessage();
294295
ClientAvatarChangeMessage.Deserialize(Reader);
295296
Reader.Recycle();
297+
298+
// Global avatar lock: reject network broadcast but still save state locally
299+
if (BasisNetworkServer.Security.BasisGlobalLockManager.AvatarsLocked)
300+
{
301+
bool isAdmin = false;
302+
if (NetworkServer.AuthIdentity.NetIDToUUID(Peer, out string uuid))
303+
{
304+
isAdmin = PermissionIntegration.HasValidRequirement(uuid, PermNodes.All);
305+
}
306+
307+
if (!isAdmin)
308+
{
309+
BNL.Log($"Avatar loading is globally disabled. Rejected avatar change from peer {Peer.Id}");
310+
BasisNetworkServer.Security.BasisPlayerModeration.SendBackMessage(Peer, "Avatar loading is currently disabled by an admin.");
311+
return;
312+
}
313+
}
314+
296315
ServerAvatarChangeMessage serverAvatarChangeMessage = new ServerAvatarChangeMessage
297316
{
298317
clientAvatarChangeMessage = ClientAvatarChangeMessage,
@@ -709,16 +728,30 @@ public static void LoadResource(NetPacketReader Reader, NetPeer Peer,string UUID
709728
LocalLoadResource.UUIDOfCreator = UUID;
710729
Reader.Recycle();
711730

731+
bool isAdmin = PermissionIntegration.HasValidRequirement(UUID, PermNodes.All);
732+
712733
switch (LocalLoadResource.Mode)
713734
{
714735
case 0:
736+
if (!isAdmin && BasisNetworkServer.Security.BasisGlobalLockManager.PropsLocked)
737+
{
738+
BNL.Log($"Prop loading is globally disabled. Rejected request from {UUID}");
739+
BasisNetworkServer.Security.BasisPlayerModeration.SendBackMessage(Peer, "Prop loading is currently disabled by an admin.");
740+
return;
741+
}
715742
if (PermissionIntegration.HasValidRequirement(UUID, PermNodes.ResourceLoadProp) == false)
716743
{
717744
BNL.LogError($"Invalid Request To Load Gameobject From {UUID}");
718745
return;
719746
}
720747
break;
721748
case 1:
749+
if (!isAdmin && BasisNetworkServer.Security.BasisGlobalLockManager.WorldsLocked)
750+
{
751+
BNL.Log($"World loading is globally disabled. Rejected request from {UUID}");
752+
BasisNetworkServer.Security.BasisPlayerModeration.SendBackMessage(Peer, "World loading is currently disabled by an admin.");
753+
return;
754+
}
722755
if (PermissionIntegration.HasValidRequirement(UUID, PermNodes.ResourceLoadWorld) == false)
723756
{
724757
BNL.LogError($"Invalid Request To Load Scene From {UUID}");
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using Basis.Network.Core;
2+
using System.Threading;
3+
using static BasisNetworkCore.Serializable.SerializableBasis;
4+
5+
namespace BasisNetworkServer.Security
6+
{
7+
/// <summary>
8+
/// Server-wide toggles that admins can flip to globally disable
9+
/// avatar, prop, or world loading for all non-admin players.
10+
/// Thread-safe — reads/writes use interlocked operations.
11+
/// </summary>
12+
public static class BasisGlobalLockManager
13+
{
14+
// 0 = unlocked (loading allowed), 1 = locked (loading blocked)
15+
private static int _avatarsLocked;
16+
private static int _propsLocked;
17+
private static int _worldsLocked;
18+
19+
public static bool AvatarsLocked => Interlocked.CompareExchange(ref _avatarsLocked, 0, 0) == 1;
20+
public static bool PropsLocked => Interlocked.CompareExchange(ref _propsLocked, 0, 0) == 1;
21+
public static bool WorldsLocked => Interlocked.CompareExchange(ref _worldsLocked, 0, 0) == 1;
22+
23+
/// <summary>
24+
/// Toggle avatar loading. Returns the new state (true = locked).
25+
/// </summary>
26+
public static bool ToggleAvatars() => Toggle(ref _avatarsLocked);
27+
28+
/// <summary>
29+
/// Toggle prop loading. Returns the new state (true = locked).
30+
/// </summary>
31+
public static bool ToggleProps() => Toggle(ref _propsLocked);
32+
33+
/// <summary>
34+
/// Toggle world loading. Returns the new state (true = locked).
35+
/// </summary>
36+
public static bool ToggleWorlds() => Toggle(ref _worldsLocked);
37+
38+
private static bool Toggle(ref int field)
39+
{
40+
int prev, next;
41+
do
42+
{
43+
prev = field;
44+
next = prev == 0 ? 1 : 0;
45+
}
46+
while (Interlocked.CompareExchange(ref field, next, prev) != prev);
47+
return next == 1;
48+
}
49+
50+
/// <summary>
51+
/// Sends the current global lock state to a specific peer.
52+
/// Used when a new player connects so they know what's locked.
53+
/// </summary>
54+
public static void SendLockStateToPeer(NetPeer peer)
55+
{
56+
NetDataWriter writer = NetworkServer.RentWriter();
57+
new AdminRequest().Serialize(writer, AdminRequestMode.GlobalGetLockState);
58+
writer.Put(AvatarsLocked);
59+
writer.Put(PropsLocked);
60+
writer.Put(WorldsLocked);
61+
NetworkServer.TrySend(peer, writer, BasisNetworkCommons.AdminChannel, DeliveryMethod.ReliableOrdered);
62+
NetworkServer.ReturnWriter(writer);
63+
}
64+
65+
/// <summary>
66+
/// Broadcasts the current lock state to all connected clients.
67+
/// </summary>
68+
public static void BroadcastLockState()
69+
{
70+
NetDataWriter writer = NetworkServer.RentWriter();
71+
new AdminRequest().Serialize(writer, AdminRequestMode.GlobalGetLockState);
72+
writer.Put(AvatarsLocked);
73+
writer.Put(PropsLocked);
74+
writer.Put(WorldsLocked);
75+
NetworkServer.BroadcastMessageToClients(
76+
writer,
77+
BasisNetworkCommons.AdminChannel,
78+
NetworkServer.PeerSnapshot,
79+
DeliveryMethod.ReliableOrdered
80+
);
81+
NetworkServer.ReturnWriter(writer);
82+
}
83+
}
84+
}

Basis Server/BasisNetworkServer/Security/BasisPlayerModeration.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Linq;
99
using System.Text;
1010
using System.Xml.Serialization;
11+
using BasisServerHandle;
1112
using static BasisNetworkCore.Serializable.SerializableBasis;
1213
using static BasisPermissions.PermissionManager;
1314

@@ -300,6 +301,22 @@ public static void OnAdminMessage(NetPeer peer, NetPacketReader reader)
300301
HandleShoutMode(peer, reader, mode == AdminRequestMode.EnableShoutMode));
301302
break;
302303

304+
// ===== GLOBAL LOCK =====
305+
case AdminRequestMode.GlobalToggleAvatars:
306+
Require(peer, PermNodes.ModerationGlobalLock, () =>
307+
HandleGlobalToggle(peer, "Avatar", BasisGlobalLockManager.ToggleAvatars()));
308+
break;
309+
310+
case AdminRequestMode.GlobalToggleProps:
311+
Require(peer, PermNodes.ModerationGlobalLock, () =>
312+
HandleGlobalToggle(peer, "Prop", BasisGlobalLockManager.ToggleProps()));
313+
break;
314+
315+
case AdminRequestMode.GlobalToggleWorlds:
316+
Require(peer, PermNodes.ModerationGlobalLock, () =>
317+
HandleGlobalToggle(peer, "World", BasisGlobalLockManager.ToggleWorlds()));
318+
break;
319+
303320
// ===== PERMISSION EDIT =====
304321
case AdminRequestMode.SetUserGroup:
305322
case AdminRequestMode.SetUserNode:
@@ -414,6 +431,26 @@ private static void HandleShoutMode(NetPeer peer, NetPacketReader reader, bool e
414431
BasisServerHandle.BasisServerHandleEvents.BroadcastShoutModeState(id, enable);
415432
}
416433

434+
private static void HandleGlobalToggle(NetPeer peer, string contentType, bool nowLocked)
435+
{
436+
string state = nowLocked ? "DISABLED" : "ENABLED";
437+
string notification = $"{contentType} loading has been globally {state} by an admin.";
438+
BNL.Log(notification);
439+
440+
// Notify the admin who toggled it
441+
SendBackMessage(peer, $"{contentType} loading is now {state}.");
442+
443+
// Notify all clients about the change
444+
var writer = NetworkServer.RentWriter();
445+
new AdminRequest().Serialize(writer, AdminRequestMode.MessageAll);
446+
writer.Put(notification);
447+
NetworkServer.BroadcastMessageToClients(writer, BasisNetworkCommons.AdminChannel, NetworkServer.PeerSnapshot, DeliveryMethod.ReliableOrdered);
448+
NetworkServer.ReturnWriter(writer);
449+
450+
// Broadcast updated lock state so clients track it
451+
BasisGlobalLockManager.BroadcastLockState();
452+
}
453+
417454
public static void SendBackMessage(NetPeer peer, string msg)
418455
{
419456
if (string.IsNullOrEmpty(msg))

Basis Server/BasisNetworkServer/Security/PermissionManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public static class PermNodes
5151
public const string ModerationMessageAll = "basis.moderation.messageall";
5252
public const string ModerationTeleport = "basis.moderation.teleport";
5353
public const string ModerationShout = "basis.moderation.shout";
54+
public const string ModerationGlobalLock = "basis.moderation.globallock";
5455

5556
public const string PermissionsView = "basis.permissions.view";
5657
public const string PermissionsEdit = "basis.permissions.edit";
@@ -758,6 +759,7 @@ public void EnsureDefaults()
758759
adm.Nodes.Add(PermNodes.ModerationMessageAll);
759760
adm.Nodes.Add(PermNodes.ModerationTeleport);
760761
adm.Nodes.Add(PermNodes.ModerationShout);
762+
adm.Nodes.Add(PermNodes.ModerationGlobalLock);
761763
adm.Nodes.Add(PermNodes.PermissionsView);
762764

763765
_store.Groups["moderator"] = adm;

Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/ShoutModeOffProvider.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using Basis.Scripts.Networking;
21
using Basis.Scripts.Networking.NetworkedAvatar;
3-
using BasisPermissions;
42
using UnityEngine;
53

64
namespace Basis.BasisUI
@@ -22,9 +20,6 @@ static void OnShoutModeChanged(ushort playerId, bool enabled)
2220
if (BasisNetworkPlayer.LocalPlayer == null || playerId != BasisNetworkPlayer.LocalPlayer.playerId)
2321
return;
2422

25-
if (!BasisNetworkManagement.LocalPermissions.Contains(PermNodes.ModerationShout))
26-
return;
27-
2823
if (enabled && !_added)
2924
{
3025
_added = true;

Basis/Packages/com.basis.framework/Networking/Recievers/BasisShoutAudioDriver.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Basis.Scripts.BasisSdk.Helpers;
22
using Basis.Scripts.Device_Management;
33
using Basis.Scripts.Drivers;
4+
using Basis.Scripts.Networking;
45
using Basis.Scripts.Networking.NetworkedAvatar;
56
#if !UNITY_SERVER
67
using OpusSharp.Core;
@@ -101,7 +102,17 @@ public static void EnableShoutMode(ushort playerId)
101102

102103
// Now safe to enable - OnAudioFilterRead can process correctly
103104
entry.Receiver.HasAudioSource = true;
104-
entry.Driver.Initalized = true;
105+
106+
// Wire up the player's existing viseme driver so lip-sync works during shout mode
107+
if (BasisNetworkPlayers.RemotePlayers.TryGetValue(playerId, out BasisNetworkReceiver receiver))
108+
{
109+
entry.Driver.Initalize(receiver.AudioReceiverModule.visemeDriver);
110+
}
111+
else
112+
{
113+
entry.Driver.Initalized = true;
114+
}
115+
105116
entry.AudioSource.Play();
106117

107118
_entries[playerId] = entry;
@@ -141,6 +152,10 @@ public static void DisableShoutMode(ushort playerId)
141152

142153
if (entry.Driver != null)
143154
{
155+
// Detach the shared viseme driver before destroying so OnDestroy
156+
// doesn't clean up the player's viseme driver
157+
entry.Driver.BasisAudioAndVisemeDriver = null;
158+
entry.Driver.Initalized = false;
144159
Object.Destroy(entry.Driver);
145160
}
146161

0 commit comments

Comments
 (0)