PeakSelfHostedPhoton Voice Fix
Makes PEAK voice chat work on a self-hosted Photon server. Companion to PeakSelfHostedPhoton. All players need it.
About this mod Author’s original text
PeakSelfHostedPhoton_Voice_Fix
Makes PEAK voice chat work on a self-hosted Photon server.
Companion to PeakSelfHostedPhoton,
which redirects the game to your own Photon Server but does not carry voice.
Install this alongside it and voice works.
Every player needs it. The patch changes the sending side, so a player without
it simply cannot be heard.
Tested on PEAK 2.1.a with Photon Server SDK 4.0.29 (the 2016 build) over
GpBinaryV16, four players, host on a public IP.
What it fixes
Two independent problems. The second only becomes visible once the first is
fixed, which is why this looks like a dead end at first.
- Protocol16 cannot serialize ByteArraySlice
Self-hosting forces the client onto GpBinaryV16, because a 2016-era server speaks
nothing newer. Photon Voice hands each Opus frame over as a ByteArraySlice, and
Protocol16 has no serializer for that type:
System.Exception: cannot serialize(): ExitGames.Client.Photon.ByteArraySlice
at ExitGames.Client.Photon.Protocol16.Serialize(...)
at Photon.Realtime.LoadBalancingPeer.OpRaiseEvent(...)
at Photon.Voice.LoadBalancingTransport.SendFrame(...)
at POpusCodec.OpusEncoder.Encode(...)
The encode thread dies on the first frame. The game itself is unaffected, since
PEAK's own RPCs use plain types.
The payload is built by Photon.Voice.LoadBalancingTransport2.buildFrameMessage
(not PhotonTransportProtocol.buildFrameMessage - the stack trace shows the base
class because SendFrame is declared there). A postfix copies the slice into a
plain byte and releases the slice back to the pool.
Safe on the receiving end: onVoiceFrameEvent already accepts both shapes, and on
the wire it is a byte array either way. The slice was only a send-side zero-copy
optimisation.
- Voice frames use a delivery mode old servers drop
With the first fix in, the client reports thousands of successful sends while the
server logs nothing at all from the voice peer:
voice: built=4998 raiseOk=5000 raiseFail=0 state=Joined
SendFrame picks the delivery mode like this:
if (flags & 1) -> Reliable
else if (cppCompatibilityMode) -> Reliable / Unreliable
else -> ReliableUnsequenced / UnreliableUnsequenced
The default path uses the unsequenced modes, which postdate Photon Server
4.0.28 (February 2016). The server drops those commands silently - no error, no
log line. Control operations go over the ordinary reliable channel, which is why
auth and join succeed while every audio frame vanishes.
A prefix on the LoadBalancingTransport2 constructor forces
cppCompatibilityMode = true, the flag that exists for exactly this.
Verifying
BepInEx/LogOutput.log should contain:
Patched Photon.Voice.LoadBalancingTransport2..ctor
Patched Photon.Voice.LoadBalancingTransport2.buildFrameMessage
Forced cppCompatibilityMode=true: voice frames will use Unreliable...
Voice frames are now sent as byte instead of ByteArraySlice (N bytes in the first frame).
The last line appears on the first word anyone speaks. If it never shows up, the
microphone is not producing frames and the problem is elsewhere.
cannot serialize(): ByteArraySlice should be gone from the game's Player.log.
Configuration
BepInEx/config/peak.selfhostedphoton.voicefix.cfg
| Setting | Default | What it does |
|---|---|---|
| Diagnostics / LogFrameCounters | false | Logs frames built, sends accepted, sends refused and client state every 250 frames. Only for working out why voice is silent - it writes a line every few seconds while anyone is talking. |
Compatibility
Both changes are harmless on GpBinaryV18 and on Photon Server 5, so there is no
need to remove this after moving to a newer server.
Cost is one array copy per frame (~50 per second) and giving up unsequenced
delivery. Next to Opus encoding, that is nothing.
Does nothing on the official Photon Cloud - it only matters when the client has
been pushed onto the legacy protocol.
Credits
Diagnosis done by reading the IL of PhotonVoice.API.dll and correlating client
counters against Photon Server o
Install & use Install: BepInEx plugin
- The archive from playbuff is the mod itself — usually a BepInEx plugin (a .dll or a plugin folder once unzipped).
- Recommended: use a community mod manager (such as r2modman) — pick this game, import the mod, and the manager sets up BepInEx and its dependencies for you.
- Manual route: install the BepInEx loader in the game folder, then drop the unzipped .dll or plugin folder into BepInEx/plugins/.
- Launch the game — the BepInEx console logs every plugin it loads; seeing this mod in the log means it is installed.
Mods with listed dependencies need those installed too, or they will not load. Options are usually changed in-game or in the plugin's config file.
Install these first, or the mod will not work: