107 lines
5.3 KiB
Markdown
107 lines
5.3 KiB
Markdown
# Request to the client-side agent (dumped game image)
|
|
|
|
## Context
|
|
|
|
We have a third-party NGO/LiteNetLib server that the game connects to. The LiteNetLib
|
|
transport handshake **fails in a specific, reproducible way**, and we've narrowed it to one
|
|
thing: the client silently ignores our `ConnectAccept`.
|
|
|
|
Evidence, so you know what's already excluded:
|
|
|
|
- The client's `ConnectRequest` is byte-identical in shape to a stock LiteNetLib 1.x request:
|
|
`protocolId = 13`, `HeaderSize = 18`, `addrSize = 16`, plus **3 bytes of connect data
|
|
`01 00 11`**. So the client's outbound path is stock or near-stock.
|
|
- Our server replies with a stock 15-byte `ConnectAccept` whose `connectionTime` matches.
|
|
- The client ignores it and resends `ConnectRequest` ~11 times, all with the *same*
|
|
`connectionTime` — i.e. one attempt being retried, never leaving `ConnectionState.Outgoing`.
|
|
- Meanwhile our server thinks it is connected, pings, gets no Pong, and times out at 5s.
|
|
- A synthetic stock-LiteNetLib-1.2.0 client, run from the *same machine and NAT* as the game,
|
|
connects to the same server successfully. So this is not the network, not NAT, and not our
|
|
server. It is client-side.
|
|
- We reproduced the exact symptom against a stock client by making the accept the wrong
|
|
length: `NetConnectAcceptPacket.FromData` begins `if (packet.Size != Size) return null`,
|
|
which yields precisely this silent-ignore-and-retry behaviour.
|
|
|
|
**Working hypothesis: the game's bundled LiteNetLib is forked and expects a `ConnectAccept`
|
|
that differs from stock — most likely a different length.** We need the exact layout.
|
|
|
|
The bundled LiteNetLib lives inside `Netcode.Transports.LiteNetLib.dll` (reported earlier as
|
|
116 types, "LiteNetLib itself bundled inside"). Third-party assemblies appear to have kept
|
|
their real names, so these types should be findable by name rather than by obfuscated slug.
|
|
|
|
## Stock reference (LiteNetLib 1.2.0, MIT) — diff against this
|
|
|
|
```csharp
|
|
internal sealed class NetConnectAcceptPacket
|
|
{
|
|
public const int Size = 15;
|
|
public readonly long ConnectionTime;
|
|
public readonly byte ConnectionNumber;
|
|
public readonly int PeerId;
|
|
public readonly bool PeerNetworkChanged;
|
|
|
|
public static NetConnectAcceptPacket FromData(NetPacket packet)
|
|
{
|
|
if (packet.Size != Size) return null;
|
|
long connectionId = BitConverter.ToInt64(packet.RawData, 1);
|
|
byte connectionNumber = packet.RawData[9];
|
|
if (connectionNumber >= NetConstants.MaxConnectionNumber) return null;
|
|
byte isReused = packet.RawData[10];
|
|
if (isReused > 1) return null;
|
|
int peerId = BitConverter.ToInt32(packet.RawData, 11);
|
|
if (peerId < 0) return null;
|
|
return new NetConnectAcceptPacket(connectionId, connectionNumber, peerId, isReused == 1);
|
|
}
|
|
|
|
public static NetPacket Make(long connectTime, byte connectNum, int localPeerId)
|
|
{
|
|
var packet = new NetPacket(PacketProperty.ConnectAccept, 0);
|
|
FastBitConverter.GetBytes(packet.RawData, 1, connectTime);
|
|
packet.RawData[9] = connectNum;
|
|
FastBitConverter.GetBytes(packet.RawData, 11, localPeerId);
|
|
return packet;
|
|
}
|
|
}
|
|
```
|
|
|
|
Stock wire layout: `[0]` property (6) | `[1..8]` connectionTime | `[9]` connectionNumber |
|
|
`[10]` isReused | `[11..14]` peerId.
|
|
|
|
## What we need
|
|
|
|
**Q1 (highest value — this likely ends the investigation).**
|
|
`LiteNetLib.NetConnectAcceptPacket` in the bundled copy:
|
|
- the value of `Size`;
|
|
- every field and its byte offset;
|
|
- the full body of `FromData`, especially every early-return/rejection condition;
|
|
- the full signature and body of `Make`.
|
|
If `Size != 15`, tell us what the extra bytes are and where `Make` writes them.
|
|
|
|
**Q2.** `LiteNetLib.NetConstants.ProtocolId`. We derived 13 from the wire; confirm from the
|
|
image. Also `MaxConnectionNumber` if it differs from stock (4).
|
|
|
|
**Q3.** Is `NetManager` constructed with a `PacketLayerBase` (a second constructor argument,
|
|
or a non-null `_extraPacketLayer` field)? If yes, name the layer type and give
|
|
`ExtraPacketSizeForLayer` plus the bodies of `ProcessInboundPacket` / `ProcessOutBoundPacket`.
|
|
We ruled a layer out by inference from wire bytes; we want it confirmed from the image.
|
|
|
|
**Q4.** `LiteNetLib.NetConnectRequestPacket.HeaderSize`, and **what writes the 3 connect-data
|
|
bytes `01 00 11`**. Find the `NetManager.Connect(...)` call site — expected to be in the NGO
|
|
voice session wrapper (`BAOFAOBLAMJ`) or in `LiteNetLibTransport` — and show how that
|
|
`NetDataWriter` is populated. If those bytes are a version/handshake field, we need to know
|
|
whether the server is expected to validate or echo them.
|
|
|
|
**Q5 (only if cheap).** In `NetPeer`, the method handling an inbound accept (stock name
|
|
`ProcessConnectAccept`): list every condition that returns false. That is the definitive list
|
|
of reasons our accept could be dropped.
|
|
|
|
## Output format
|
|
|
|
For each question: the decompiled source if you have it, otherwise field offsets and constant
|
|
values. Give **byte offsets and exact integer values** rather than prose. Where the fork
|
|
matches stock, just say "matches stock" — we only need the diff.
|
|
|
|
If something genuinely isn't present in the image, say so explicitly. Do not infer the layout
|
|
from stock LiteNetLib or reason about what it "should" be — we already have stock, and a
|
|
plausible-but-wrong layout costs us more than a "not found".
|