public ReplayConnection(string replayFilename) { Filename = replayFilename; // Parse replay data into a struct that can be fed to the game in chunks // to avoid issues with all immediate orders being resolved on the first tick. using (var rs = File.OpenRead(replayFilename)) { var chunk = new Chunk(); while (rs.Position < rs.Length) { var client = rs.ReadInt32(); if (client == ReplayMetadata.MetaStartMarker) break; var packetLen = rs.ReadInt32(); var packet = rs.ReadBytes(packetLen); var frame = BitConverter.ToInt32(packet, 0); chunk.Packets.Add(Pair.New(client, packet)); if (packet.Length == 5 && packet[4] == 0xBF) continue; // disconnect else if (packet.Length >= 5 && packet[4] == 0x65) continue; // sync else if (frame == 0) { // Parse replay metadata from orders stream var orders = packet.ToOrderList(null); foreach (var o in orders) { if (o.OrderString == "StartGame") IsValid = true; else if (o.OrderString == "SyncInfo" && !IsValid) LobbyInfo = Session.Deserialize(o.TargetString); } } else { // Regular order - finalize the chunk chunk.Frame = frame; chunks.Enqueue(chunk); chunk = new Chunk(); TickCount = Math.Max(TickCount, frame); } } } ordersFrame = LobbyInfo.GlobalSettings.OrderLatency; }
static void SendNextChunk(Connection c) { try { var data = c.Stream.Read(Math.Min(DownloadChunkSize, c.RemainingBytes)); if (data.Length != 0) { var chunk = new Chunk { Index = c.NextChunk++, Count = c.NumChunks, Data = Convert.ToBase64String(data) }; DispatchOrdersToClient(c, 0, 0, new ServerOrder("FileChunk", FieldSaver.Save(chunk).Nodes.WriteToString()).Serialize()); } c.RemainingBytes -= data.Length; if (c.RemainingBytes == 0) { GetClient(c).State = Session.ClientState.NotReady; c.Stream.Dispose(); c.Stream = null; SyncLobbyInfo(); } } catch (Exception e) { DropClient(c, e); } }
public ReplayConnection(string replayFilename) { Filename = replayFilename; // Parse replay data into a struct that can be fed to the game in chunks // to avoid issues with all immediate orders being resolved on the first tick. using (var rs = File.OpenRead(replayFilename)) { var chunk = new Chunk(); while (rs.Position < rs.Length) { var client = rs.ReadInt32(); if (client == ReplayMetadata.MetaStartMarker) break; var packetLen = rs.ReadInt32(); var packet = rs.ReadBytes(packetLen); var frame = BitConverter.ToInt32(packet, 0); chunk.Packets.Add(Pair.New(client, packet)); if (frame != int.MaxValue && (!lastClientsFrame.ContainsKey(client) || frame > lastClientsFrame[client])) lastClientsFrame[client] = frame; if (packet.Length == 5 && packet[4] == 0xBF) continue; // disconnect else if (packet.Length >= 5 && packet[4] == 0x65) continue; // sync else if (frame == 0) { // Parse replay metadata from orders stream var orders = packet.ToOrderList(null); foreach (var o in orders) { if (o.OrderString == "StartGame") IsValid = true; else if (o.OrderString == "SyncInfo" && !IsValid) LobbyInfo = Session.Deserialize(o.TargetString); } } else { // Regular order - finalize the chunk chunk.Frame = frame; chunks.Enqueue(chunk); chunk = new Chunk(); TickCount = Math.Max(TickCount, frame); } } var lastClientToDisconnect = lastClientsFrame.MaxBy(kvp => kvp.Value).Key; // 2nd parse : replace all disconnect packets without frame with real // disconnect frame // NOTE: to modify/remove if a reconnect feature is set foreach (var tmpChunk in chunks) { foreach (var tmpPacketPair in tmpChunk.Packets) { var client = tmpPacketPair.First; // Don't replace the final disconnection packet - we still want this to end the replay. if (client == lastClientToDisconnect) continue; var packet = tmpPacketPair.Second; if (packet.Length == 5 && packet[4] == 0xBF) { var lastClientFrame = lastClientsFrame[client]; var lastFramePacket = BitConverter.GetBytes(lastClientFrame); Array.Copy(lastFramePacket, packet, lastFramePacket.Length); } } } } ordersFrame = LobbyInfo.GlobalSettings.OrderLatency; }