public string Compare(SyncInfo other) { if (!maps.Select(m => m.mapId).SequenceEqual(other.maps.Select(m => m.mapId))) { return($"Map instances don't match"); } for (int i = 0; i < maps.Count; i++) { if (!maps[i].map.SequenceEqual(other.maps[i].map)) { return($"Wrong random state on map {maps[i].mapId}"); } } if (!world.SequenceEqual(other.world)) { return("Wrong random state for the world"); } if (!cmds.SequenceEqual(other.cmds)) { return("Random state from commands doesn't match"); } if (!simulating && !other.simulating && traceHashes.Any() && other.traceHashes.Any() && !traceHashes.SequenceEqual(other.traceHashes)) { return("Trace hashes don't match"); } return(null); }
private void OnDesynced(SyncInfo one, SyncInfo two, string error) { if (TickPatch.Skipping) { return; } Multiplayer.Client.Send(Packets.Client_Desynced); var local = one.local ? one : two; var remote = !one.local ? one : two; if (local.traces.Any()) { PrintTrace(local, remote); } try { var desyncFile = PrepareNextDesyncFile(); var replay = Replay.ForSaving(Replay.ReplayFile(desyncFile, Multiplayer.DesyncsDir)); replay.WriteCurrentData(); var savedGame = ScribeUtil.WriteExposable(Verse.Current.Game, "game", true, ScribeMetaHeaderUtility.WriteMetaHeader); using (var zip = replay.ZipFile) { zip.AddEntry("sync_local", local.Serialize()); zip.AddEntry("sync_remote", remote.Serialize()); zip.AddEntry("game_snapshot", savedGame); zip.AddEntry("static_fields", MpDebugTools.StaticFieldsToString()); var desyncInfo = new ByteWriter(); desyncInfo.WriteBool(Multiplayer.session.ArbiterPlaying); desyncInfo.WriteInt32(lastValidTick); desyncInfo.WriteBool(lastValidArbiter); desyncInfo.WriteString(MpVersion.Version); desyncInfo.WriteBool(MpVersion.IsDebug); desyncInfo.WriteBool(Prefs.DevMode); desyncInfo.WriteInt32(Multiplayer.session.players.Count); desyncInfo.WriteBool(Multiplayer.WorldComp.debugMode); zip.AddEntry("desync_info", desyncInfo.ToArray()); zip.Save(); } } catch (Exception e) { Log.Error($"Exception writing desync info: {e}"); } Find.WindowStack.windows.Clear(); Find.WindowStack.Add(new DesyncedWindow(error)); }
public void Add(SyncInfo info) { if (Multiplayer.session.desynced) { return; } if (TickPatch.Skipping) { return; } if (buffer.Count == 0) { buffer.Add(info); return; } if (buffer[0].local == info.local) { buffer.Add(info); if (buffer.Count > 30) { buffer.RemoveAt(0); } } else { while (buffer.Count > 0 && buffer[0].startTick < info.startTick) { buffer.RemoveAt(0); } if (buffer.Count == 0) { buffer.Add(info); } else if (buffer.First().startTick == info.startTick) { var first = buffer.RemoveFirst(); var error = first.Compare(info); if (error != null) { MpLog.Log($"Desynced {lastValidTick}: {error}"); Multiplayer.session.desynced = true; OnMainThread.Enqueue(() => OnDesynced(first, info, error)); } else { lastValidTick = first.startTick; lastValidArbiter = Multiplayer.session.ArbiterPlaying; } } } }
private void PrintTrace(SyncInfo local, SyncInfo remote) { Log.Message($"Printing {local.traces.Count} traces"); int diffAt = -1; int count = Math.Min(local.traceHashes.Count, remote.traceHashes.Count); for (int i = 0; i < count; i++) { if (local.traceHashes[i] != remote.traceHashes[i]) { diffAt = i; break; } } if (diffAt == -1) { diffAt = count; } File.WriteAllText("local_traces.txt", local.TracesToString(diffAt - 40, diffAt + 40)); Multiplayer.Client.Send(Packets.Client_Debug, local.startTick, diffAt - 40, diffAt + 40); }
public static string Get(Replay replay) { var text = new StringBuilder(); using (var zip = replay.ZipFile) { try { text.AppendLine($"[header]"); using (var reader = new XmlTextReader(new MemoryStream(zip["game_snapshot"].GetBytes()))) { reader.ReadToNextElement(); reader.ReadToNextElement(); text.AppendLine(reader.ReadOuterXml()); } } catch (Exception e) { text.AppendLine(e.Message); } text.AppendLine(); try { text.AppendLine("[info]"); text.AppendLine(zip["info"].GetString()); } catch { } text.AppendLine(); SyncInfo local = null; try { local = PrintSyncInfo(text, zip, "sync_local"); } catch { } text.AppendLine(); SyncInfo remote = null; try { remote = PrintSyncInfo(text, zip, "sync_remote"); } catch { } text.AppendLine(); try { text.AppendLine("[desync_info]"); var desyncInfo = new ByteReader(zip["desync_info"].GetBytes()); text.AppendLine($"Arbiter online: {desyncInfo.ReadBool()}"); text.AppendLine($"Last valid tick: {desyncInfo.ReadInt32()}"); text.AppendLine($"Last valid arbiter online: {desyncInfo.ReadBool()}"); text.AppendLine($"Mod version: {desyncInfo.ReadString()}"); text.AppendLine($"Mod is debug: {desyncInfo.ReadBool()}"); text.AppendLine($"Dev mode: {desyncInfo.ReadBool()}"); text.AppendLine($"Player count: {desyncInfo.ReadInt32()}"); text.AppendLine($"Game debug mode: {desyncInfo.ReadBool()}"); } catch { } text.AppendLine(); if (local != null && remote != null) { text.AppendLine("[compare]"); for (int i = 0; i < Math.Min(local.maps.Count, remote.maps.Count); i++) { var localMap = local.maps[i].map; var remoteMap = remote.maps[i].map; bool equal = localMap.SequenceEqual(remoteMap); text.AppendLine($"Map {local.maps[i].mapId}: {equal}"); if (!equal) { for (int j = 0; j < Math.Min(localMap.Count, remoteMap.Count); j++) { text.AppendLine($"{localMap[j]} {remoteMap[j]} {(localMap[j] != remoteMap[j] ? "x" : "")}"); } } } text.AppendLine($"World: {local.world.SequenceEqual(remote.world)}"); text.AppendLine($"Cmds: {local.cmds.SequenceEqual(remote.cmds)}"); } text.AppendLine(); try { text.AppendLine("[map_cmds]"); foreach (var cmd in Replay.DeserializeCmds(zip["maps/000_0_cmds"].GetBytes())) { PrintCmdInfo(text, cmd); } } catch { } text.AppendLine(); try { text.AppendLine("[world_cmds]"); foreach (var cmd in Replay.DeserializeCmds(zip["world/000_cmds"].GetBytes())) { PrintCmdInfo(text, cmd); } } catch { } } return(text.ToString()); void PrintCmdInfo(StringBuilder builder, ScheduledCommand cmd) { builder.Append($"{cmd.type} {cmd.ticks} {cmd.mapId} {cmd.factionId}"); if (cmd.type == CommandType.Sync) { builder.Append($" {Sync.handlers[BitConverter.ToInt32(cmd.data, 0)]}"); } builder.AppendLine(); } SyncInfo PrintSyncInfo(StringBuilder builder, ZipFile zip, string file) { builder.AppendLine($"[{file}]"); var sync = SyncInfo.Deserialize(new ByteReader(zip[file].GetBytes())); builder.AppendLine($"Start: {sync.startTick}"); builder.AppendLine($"Was simulating: {sync.simulating}"); builder.AppendLine($"Map count: {sync.maps.Count}"); builder.AppendLine($"Last map state: {sync.maps.Select(m => $"{m.mapId}/{m.map.LastOrDefault()}/{m.map.Count}").ToStringSafeEnumerable()}"); builder.AppendLine($"Last world state: {sync.world.LastOrDefault()}/{sync.world.Count}"); builder.AppendLine($"Last cmd state: {sync.cmds.LastOrDefault()}/{sync.cmds.Count}"); builder.AppendLine($"Trace hashes: {sync.traceHashes.Count}"); return(sync); } }
public void HandleDesyncCheck(ByteReader data) { Multiplayer.game?.sync.Add(SyncInfo.Deserialize(data)); }
public static string Get(Replay replay) { var text = new StringBuilder(); using (var zip = replay.ZipFile) { try { text.AppendLine("[info]"); text.AppendLine(zip["info"].GetString()); text.AppendLine(); } catch { } SyncInfo local = null; try { local = PrintSyncInfo(text, zip, "sync_local"); } catch { } SyncInfo remote = null; try { remote = PrintSyncInfo(text, zip, "sync_remote"); } catch { } try { text.AppendLine("[desync_info]"); var desyncInfo = new ByteReader(zip["desync_info"].GetBytes()); text.AppendLine($"Arbiter online: {desyncInfo.ReadBool()}"); text.AppendLine($"Last valid tick: {desyncInfo.ReadInt32()}"); text.AppendLine($"Last valid arbiter online: {desyncInfo.ReadBool()}"); text.AppendLine($"Mod version: {desyncInfo.ReadString()}"); text.AppendLine($"Mod is debug: {desyncInfo.ReadBool()}"); text.AppendLine($"Dev mode: {desyncInfo.ReadBool()}"); text.AppendLine(); } catch { } if (local != null && remote != null) { text.AppendLine("[compare]"); for (int i = 0; i < Math.Min(local.maps.Count, remote.maps.Count); i++) { var localMap = local.maps[i].map; var remoteMap = remote.maps[i].map; bool equal = localMap.SequenceEqual(remoteMap); text.AppendLine($"Map {local.maps[i].mapId}: {equal}"); if (!equal) { for (int j = 0; j < Math.Min(localMap.Count, remoteMap.Count); j++) { text.AppendLine($"{localMap[j]} {remoteMap[j]} {(localMap[j] != remoteMap[j] ? "x" : "")}"); } } } text.AppendLine($"World: {local.world.SequenceEqual(remote.world)}"); text.AppendLine($"Cmds: {local.cmds.SequenceEqual(remote.cmds)}"); } } return(text.ToString()); SyncInfo PrintSyncInfo(StringBuilder builder, ZipFile zip, string file) { builder.AppendLine($"[{file}]"); var sync = SyncInfo.Deserialize(new ByteReader(zip[file].GetBytes())); builder.AppendLine($"Start: {sync.startTick}"); builder.AppendLine($"Map count: {sync.maps.Count}"); builder.AppendLine($"Last map state: {sync.maps.Select(m => $"{m.mapId}/{m.map.LastOrDefault()}/{m.map.Count}").ToStringSafeEnumerable()}"); builder.AppendLine($"Last world state: {sync.world.LastOrDefault()}/{sync.world.Count}"); builder.AppendLine($"Last cmd state: {sync.cmds.LastOrDefault()}/{sync.cmds.Count}"); builder.AppendLine($"Trace hashes: {sync.traceHashes.Count}"); builder.AppendLine(); return(sync); } }
private void PrintTrace(SyncInfo local, SyncInfo remote) { File.WriteAllText("host_traces.txt", local.traces.Join(delimiter: "\n\n")); Multiplayer.Client.Send(Packets.Client_Debug, local.startTick); }
private void PrintTrace(SyncInfo local, SyncInfo remote) { File.WriteAllText("host_traces.txt", local.TracesToString()); Multiplayer.Client.Send(Packets.Client_Debug, local.startTick); }