public static void SaveGameCmdLocal() { Map map = Find.CurrentMap; byte[] mapData = ScribeUtil.WriteExposable(Current.Game, "map", true); File.WriteAllBytes($"map_{map.uniqueID}_{Multiplayer.username}.xml", mapData); }
private void ExposeFactionData() { if (Scribe.mode == LoadSaveMode.Saving) { int currentFactionId = Faction.OfPlayer.loadID; ScribeUtil.LookValue(currentFactionId, "currentFactionId"); var data = new Dictionary <int, FactionMapData>(factionMapData); data.Remove(currentFactionId); ScribeUtil.LookWithValueKey(ref data, "factionMapData", LookMode.Deep, map); } else { // The faction whose data is currently set Scribe_Values.Look(ref currentFactionId, "currentFactionId"); ScribeUtil.LookWithValueKey(ref factionMapData, "factionMapData", LookMode.Deep, map); if (factionMapData == null) { factionMapData = new Dictionary <int, FactionMapData>(); } } if (Scribe.mode == LoadSaveMode.PostLoadInit) { factionMapData[currentFactionId] = FactionMapData.FromMap(map, currentFactionId); } }
static void SaveGameCmd() { Map map = Find.Maps[0]; byte[] mapData = ScribeUtil.WriteExposable(Current.Game, "map", true); File.WriteAllBytes($"map_0_{Multiplayer.username}.xml", mapData); }
public override void ExposeData() { Scribe_Values.Look(ref username, "username"); Scribe_Values.Look(ref showCursors, "showCursors", true); Scribe_Values.Look(ref autoAcceptSteam, "autoAcceptSteam"); Scribe_Values.Look(ref transparentChat, "transparentChat"); Scribe_Values.Look(ref autosaveSlots, "autosaveSlots", 5); Scribe_Values.Look(ref aggressiveTicking, "aggressiveTicking", true); Scribe_Values.Look(ref syncModConfigs, "syncModConfigs", true); Scribe_Values.Look(ref showDevInfo, "showDevInfo"); Scribe_Values.Look(ref desyncTracesRadius, "desyncTracesRadius", 40); Scribe_Values.Look(ref serverAddress, "serverAddress", "127.0.0.1"); Scribe_Values.Look(ref pauseAutosaveCounter, "pauseAutosaveCounter", true); Scribe_Values.Look(ref showModCompatibility, "showModCompatibility", true); Scribe_Values.Look(ref autosaveOnDesync, "autosaveOnDesync", false); Scribe_Values.Look(ref resolutionForChat, "resolutionForChat"); ScribeUtil.LookRect(ref chatRect, "chatRect"); Scribe_Deep.Look(ref serverSettings, "serverSettings"); if (serverSettings == null) { serverSettings = new ServerSettings(); } }
public static void SaveGameLocal() { Game game = Current.Game; byte[] data = ScribeUtil.WriteExposable(game, "game", true); File.WriteAllBytes($"game_{Multiplayer.username}.xml", data); }
public static XmlDocument SaveGame() { SaveCompression.doSaveCompression = true; ScribeUtil.StartWritingToDoc(); Scribe.EnterNode("savegame"); ScribeMetaHeaderUtility.WriteMetaHeader(); Scribe.EnterNode("game"); int currentMapIndex = Current.Game.currentMapIndex; Scribe_Values.Look(ref currentMapIndex, "currentMapIndex", -1); Current.Game.ExposeSmallComponents(); World world = Current.Game.World; Scribe_Deep.Look(ref world, "world"); List <Map> maps = Find.Maps; Scribe_Collections.Look(ref maps, "maps", LookMode.Deep); Find.CameraDriver.Expose(); Scribe.ExitNode(); SaveCompression.doSaveCompression = false; return(ScribeUtil.FinishWritingToDoc()); }
public void LoadInfo() { using (var zip = ZipFile) { var doc = ScribeUtil.LoadDocument(zip["info"].GetBytes()); info = DirectXmlToObject.ObjectFromXml <ReplayInfo>(doc.DocumentElement, true); } }
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 ExposeData() { Scribe_Values.Look(ref id, "id"); if (Scribe.mode == LoadSaveMode.Saving) { Scribe_References.Look(ref dialog.faction, "faction"); ScribeUtil.LookValue(dialog.soundAmbient == SoundDefOf.RadioComms_Ambience, "radioMode"); Scribe_Values.Look(ref dialog.title, "title"); var nodes = dialog.curNode.TraverseNodes().ToList(); saveNodes = new List <DiaNodeSave>(); foreach (var node in nodes) { saveNodes.Add(new DiaNodeSave(this, node)); } fieldValues = nodes .SelectMany(n => n.options) .SelectMany(o => DelegateValues(o.action).Concat(DelegateValues(o.linkLateBind))) .Distinct(new FieldSaveEquality()) .ToList(); Scribe_Collections.Look(ref fieldValues, "fieldValues", LookMode.Deep); Scribe_Collections.Look(ref saveNodes, "nodes", LookMode.Deep); fieldValues = null; saveNodes = null; } else { Scribe_References.Look(ref faction, "faction"); Scribe_Values.Look(ref radioMode, "radioMode"); Scribe_Values.Look(ref title, "title"); Scribe_Collections.Look(ref fieldValues, "fieldValues", LookMode.Deep, this); Scribe_Collections.Look(ref saveNodes, "nodes", LookMode.Deep, this); } if (Scribe.mode == LoadSaveMode.PostLoadInit) { dialog = new Dialog_NodeTreeWithFactionInfo(saveNodes[0].node, faction, false, radioMode, title) { doCloseX = true, closeOnCancel = true }; faction = null; saveNodes = null; fieldValues = null; } }
public bool LoadInfo() { using (var zip = ZipFile) { var infoFile = zip["info"]; if (infoFile == null) { return(false); } var doc = ScribeUtil.LoadDocument(infoFile.GetBytes()); info = DirectXmlToObject.ObjectFromXml <ReplayInfo>(doc.DocumentElement, true); } return(true); }
static bool Prefix() { if (gameToLoad == null) { return(true); } SaveCompression.doSaveCompression = true; try { ScribeUtil.StartLoading(gameToLoad.SaveData); ScribeMetaHeaderUtility.LoadGameDataHeader(ScribeMetaHeaderUtility.ScribeHeaderMode.Map, false); Scribe.EnterNode("game"); Current.Game = new Game(); Current.Game.LoadGame(); // calls Scribe.loader.FinalizeLoading() SemiPersistent.ReadSemiPersistent(gameToLoad.SemiPersistent); } finally { SaveCompression.doSaveCompression = false; gameToLoad = null; } Log.Message("Game loaded"); if (Multiplayer.Client != null) { LongEventHandler.ExecuteWhenFinished(() => { // Inits all caches foreach (ITickable tickable in TickPatch.AllTickables.Where(t => !(t is ConstantTicker))) { tickable.Tick(); } if (!Current.Game.Maps.Any()) { MemoryUtility.UnloadUnusedUnityAssets(); Find.World.renderer.RegenerateAllLayersNow(); } }); } return(false); }
public void ExposeData() { if (Scribe.mode == LoadSaveMode.Saving) { Scribe_Values.Look(ref opt.text, "text"); Scribe_Values.Look(ref opt.resolveTree, "resolveTree"); ScribeUtil.LookValue(parent.saveNodes.FindIndex(n => n.node == opt.link), "linkIndex", true); Scribe_Values.Look(ref opt.disabled, "disabled"); Scribe_Values.Look(ref opt.disabledReason, "disabledReason"); Scribe_Defs.Look(ref opt.clickSound, "clickSound"); ScribeUtil.LookValue(parent.fieldValues.FindIndex(f => Equals(f.value, opt.action)), "actionIndex", true); ScribeUtil.LookValue(parent.fieldValues.FindIndex(f => Equals(f.value, opt.linkLateBind)), "linkLateBindIndex", true); } if (Scribe.mode == LoadSaveMode.LoadingVars) { Scribe_Values.Look(ref text, "text"); Scribe_Values.Look(ref resolveTree, "resolveTree"); Scribe_Values.Look(ref linkIndex, "linkIndex", -1); Scribe_Values.Look(ref disabled, "disabled"); Scribe_Values.Look(ref disabledReason, "disabledReason"); Scribe_Defs.Look(ref clickSound, "clickSound"); Scribe_Values.Look(ref actionIndex, "actionIndex"); Scribe_Values.Look(ref linkLateBindIndex, "linkLateBindIndex"); opt = new DiaOption() { text = text, resolveTree = resolveTree, disabled = disabled, disabledReason = disabledReason, clickSound = clickSound }; } if (Scribe.mode == LoadSaveMode.PostLoadInit) { opt.link = parent.saveNodes.ElementAtOrDefault(linkIndex)?.node; opt.action = (Action)parent.fieldValues.ElementAtOrDefault(actionIndex)?.value; opt.linkLateBind = (Func <DiaNode>)parent.fieldValues.ElementAtOrDefault(linkLateBindIndex)?.value; } }
private static XmlDocument GetGameDocument(List <int> mapsToLoad) { XmlDocument gameDoc = ScribeUtil.LoadDocument(Multiplayer.session.dataSnapshot.gameData); XmlNode gameNode = gameDoc.DocumentElement["game"]; foreach (int map in mapsToLoad) { using XmlReader reader = XmlReader.Create(new MemoryStream(Multiplayer.session.dataSnapshot.mapData[map])); XmlNode mapNode = gameDoc.ReadNode(reader); gameNode["maps"].AppendChild(mapNode); if (gameNode["currentMapIndex"] == null) { gameNode.AddNode("currentMapIndex", map.ToString()); } } return(gameDoc); }
public static void CacheGameData(XmlDocument doc) { XmlNode gameNode = doc.DocumentElement["game"]; XmlNode mapsNode = gameNode["maps"]; OnMainThread.cachedMapData.Clear(); OnMainThread.cachedMapCmds.Clear(); foreach (XmlNode mapNode in mapsNode) { int id = int.Parse(mapNode["uniqueID"].InnerText); byte[] mapData = ScribeUtil.XmlToByteArray(mapNode); OnMainThread.cachedMapData[id] = mapData; OnMainThread.cachedMapCmds[id] = new List <ScheduledCommand>(Find.Maps.First(m => m.uniqueID == id).AsyncTime().cmds); } gameNode["currentMapIndex"].RemoveFromParent(); mapsNode.RemoveAll(); byte[] gameData = ScribeUtil.XmlToByteArray(doc); OnMainThread.cachedAtTime = TickPatch.Timer; OnMainThread.cachedGameData = gameData; OnMainThread.cachedMapCmds[ScheduledCommand.Global] = new List <ScheduledCommand>(Multiplayer.WorldComp.cmds); }
/// <summary> /// Called by <see cref="AddClientOpinionAndCheckDesync"/> if the newly added opinion doesn't match with what other ones. /// </summary> /// <param name="oldOpinion">The first up-to-date client opinion present in <see cref="knownClientOpinions"/>, that disagreed with the new one</param> /// <param name="newOpinion">The opinion passed to <see cref="AddClientOpinionAndCheckDesync"/> that disagreed with the currently known opinions.</param> /// <param name="desyncMessage">The error message that explains exactly what desynced.</param> private void HandleDesync(ClientSyncOpinion oldOpinion, ClientSyncOpinion newOpinion, string desyncMessage) { Multiplayer.Client.Send(Packets.Client_Desynced); //Identify which of the two sync infos is local, and which is the remote. var local = oldOpinion.isLocalClientsOpinion ? oldOpinion : newOpinion; var remote = !oldOpinion.isLocalClientsOpinion ? oldOpinion : newOpinion; //Print arbiter desync stacktrace if it exists if (local.desyncStackTraces.Any()) { SaveStackTracesToDisk(local, remote); } try { //Get the filename of the next desync file to create. var desyncFilePath = FindFileNameForNextDesyncFile(); //Initialize the Replay object. var replay = Replay.ForSaving(Replay.ReplayFile(desyncFilePath, Multiplayer.DesyncsDir)); //Write the universal replay data (i.e. world and map folders, and the info file) so this desync can be reviewed as a standard replay. replay.WriteCurrentData(); //Dump our current game object. var savedGame = ScribeUtil.WriteExposable(Current.Game, "game", true, ScribeMetaHeaderUtility.WriteMetaHeader); using (var zip = replay.ZipFile) using (var desyncReport = new ZipFile()) //Create a desync report for uploading to the reports server. { //Write the local sync data var syncLocal = local.Serialize(); zip.AddEntry("sync_local", syncLocal); desyncReport.AddEntry("sync_local", syncLocal); //Write the remote sync data var syncRemote = remote.Serialize(); zip.AddEntry("sync_remote", syncRemote); desyncReport.AddEntry("sync_remote", syncRemote); //Dump the entire save file to the zip. zip.AddEntry("game_snapshot", savedGame); // desyncReport.AddEntry("game_snapshot", savedGame); //This ends up being about 15MB, we really don't want that. //Add local stack traces zip.AddEntry("local_stacks", GetDesyncStackTraces(local, remote, out _)); desyncReport.AddEntry("local_stacks", GetDesyncStackTraces(local, remote, out _)); //Add remote's stack traces zip.AddEntry("remote_stacks", GetDesyncStackTraces(remote, local, out _)); desyncReport.AddEntry("remote_stacks", GetDesyncStackTraces(remote, local, out _)); //Prepare the desync info var desyncInfo = new StringBuilder(); desyncInfo.AppendLine("###Tick Data###") .AppendLine($"Arbiter Connected And Playing|||{Multiplayer.session.ArbiterPlaying}") .AppendLine($"Last Valid Tick - Local|||{lastValidTick}") .AppendLine($"Arbiter Present on Last Tick|||{arbiterWasPlayingOnLastValidTick}") .AppendLine("\n###Version Data###") .AppendLine($"Multiplayer Mod Version|||{MpVersion.Version}") .AppendLine($"Rimworld Version and Rev|||{VersionControl.CurrentVersionStringWithRev}") .AppendLine("\n###Debug Options###") .AppendLine($"Multiplayer Debug Build - Client|||{MpVersion.IsDebug}") .AppendLine($"Multiplayer Debug Build - Host|||{Multiplayer.WorldComp.debugMode}") .AppendLine($"Rimworld Developer Mode - Client|||{Prefs.DevMode}") .AppendLine("\n###Server Info###") .AppendLine($"Player Count|||{Multiplayer.session.players.Count}") .AppendLine("\n###CPU Info###") .AppendLine($"Processor Name|||{SystemInfo.processorType}") .AppendLine($"Processor Speed (MHz)|||{SystemInfo.processorFrequency}") .AppendLine($"Thread Count|||{SystemInfo.processorCount}") .AppendLine("\n###GPU Info###") .AppendLine($"GPU Family|||{SystemInfo.graphicsDeviceVendor}") .AppendLine($"GPU Type|||{SystemInfo.graphicsDeviceType}") .AppendLine($"GPU Name|||{SystemInfo.graphicsDeviceName}") .AppendLine($"GPU VRAM|||{SystemInfo.graphicsMemorySize}") .AppendLine("\n###RAM Info###") .AppendLine($"Physical Memory Present|||{SystemInfo.systemMemorySize}") .AppendLine("\n###OS Info###") .AppendLine($"OS Type|||{SystemInfo.operatingSystemFamily}") .AppendLine($"OS Name and Version|||{SystemInfo.operatingSystem}"); //Save debug info to the zip zip.AddEntry("desync_info", desyncInfo.ToString()); desyncReport.AddEntry("desync_info", desyncInfo.ToString()); zip.Save(); //Add the basic info to the report desyncReport.AddEntry("info", zip["info"].GetBytes()); desyncReport.AddEntry("ign", Multiplayer.session.GetPlayerInfo(Multiplayer.session.playerId).username); desyncReport.AddEntry("steamName", SteamUtility.SteamPersonaName); //Report desync to the server var request = (HttpWebRequest)WebRequest.Create("https://multiplayer.samboycoding.me/api/desync/upload"); // var request = (HttpWebRequest) WebRequest.Create("http://localhost:4193/api/desync/upload"); request.Method = "POST"; request.ContentType = "application/zip"; using (var stream = new MemoryStream()) { desyncReport.Save(stream); stream.Seek(0, SeekOrigin.Begin); request.ContentLength = stream.Length; var data = new byte[stream.Length]; stream.Read(data, 0, data.Length); using (var outStream = request.GetRequestStream()) outStream.Write(data, 0, data.Length); } //TODO: Some user interaction here? try { using (var response = (HttpWebResponse)request.GetResponse()) { if (response.StatusCode != HttpStatusCode.OK) { Log.Error("Failed to report desync; Status code " + response.StatusCode); } else { using (var stream = response.GetResponseStream()) using (var reader = new StreamReader(stream)) { var desyncReportId = reader.ReadToEnd(); Log.Message("Desync Reported with ID " + desyncReportId); } } } } catch (WebException e) { Log.Error("Failed to report desync; " + e.Message); } } } catch (Exception e) { Log.Error($"Exception writing desync info: {e}"); } Find.WindowStack.windows.Clear(); Find.WindowStack.Add(new DesyncedWindow(desyncMessage)); }
public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncType) { MpContext context = data.MpContext(); Type type = syncType.type; var log = (data as LoggingByteWriter)?.Log; log?.Enter($"{type.FullName}: {obj ?? "null"}"); if (obj != null && !type.IsAssignableFrom(obj.GetType())) { throw new SerializationException($"Serializing with type {type} but got object of type {obj.GetType()}"); } try { if (typeof(object) == type) { return; } if (type.IsByRef) { return; } if (SyncDictFast.syncWorkers.TryGetValue(type, out var syncWorkerEntryEarly)) { syncWorkerEntryEarly.Invoke(new WritingSyncWorker(data), ref obj); return; } if (syncType.expose) { if (!typeof(IExposable).IsAssignableFrom(type)) { throw new SerializationException($"Type {type} can't be exposed because it isn't IExposable"); } IExposable exposable = obj as IExposable; byte[] xmlData = ScribeUtil.WriteExposable(exposable); LogXML(log, xmlData); data.WritePrefixedBytes(xmlData); return; } if (typeof(ISynchronizable).IsAssignableFrom(type)) { ((ISynchronizable)obj).Sync(new WritingSyncWorker(data)); return; } if (type.IsEnum) { Type enumType = Enum.GetUnderlyingType(type); WriteSyncObject(data, Convert.ChangeType(obj, enumType), enumType); return; } if (type.IsArray) { if (type.GetArrayRank() != 1) { throw new SerializationException("Multi dimensional arrays aren't supported."); } Type elementType = type.GetElementType(); Array arr = (Array)obj; if (arr == null) { data.WriteUShort(ushort.MaxValue); return; } if (arr.Length >= ushort.MaxValue) { throw new SerializationException($"Tried to serialize a {elementType}[] with too many ({arr.Length}) items."); } data.WriteUShort((ushort)arr.Length); foreach (object e in arr) { WriteSyncObject(data, e, elementType); } return; } if (type.IsGenericType) { Type genericTypeDefinition = type.GetGenericTypeDefinition(); if (genericTypeDefinition == typeof(List <>)) { IList list = (IList)obj; Type listObjType = type.GetGenericArguments()[0]; if (list == null) { data.WriteUShort(ushort.MaxValue); return; } if (list.Count >= ushort.MaxValue) { throw new SerializationException($"Tried to serialize a {type} with too many ({list.Count}) items."); } data.WriteUShort((ushort)list.Count); foreach (object e in list) { WriteSyncObject(data, e, listObjType); } return; } if (genericTypeDefinition == typeof(IEnumerable <>)) { IEnumerable e = (IEnumerable)obj; Type elementType = type.GetGenericArguments()[0]; var listType = typeof(List <>).MakeGenericType(elementType); if (e == null) { WriteSyncObject(data, null, listType); return; } IList list = (IList)Activator.CreateInstance(listType); foreach (var o in e) { list.Add(o); } WriteSyncObject(data, list, listType); return; } if (genericTypeDefinition == typeof(Nullable <>)) { bool isNull = obj == null; data.WriteBool(isNull); if (isNull) { return; } WriteSyncObject(data, obj, Nullable.GetUnderlyingType(type)); return; } if (genericTypeDefinition == typeof(Dictionary <,>)) { IDictionary dictionary = (IDictionary)obj; Type[] arguments = type.GetGenericArguments(); if (dictionary == null) { WriteSyncObject(data, null, arguments[0].MakeArrayType()); return; } Array keyArray = Array.CreateInstance(arguments[0], dictionary.Count); dictionary.Keys.CopyTo(keyArray, 0); Array valueArray = Array.CreateInstance(arguments[1], dictionary.Count); dictionary.Values.CopyTo(valueArray, 0); WriteSyncObject(data, keyArray, keyArray.GetType()); WriteSyncObject(data, valueArray, valueArray.GetType()); return; } if (genericTypeDefinition == typeof(Pair <,>)) { Type[] arguments = type.GetGenericArguments(); WriteSyncObject(data, AccessTools.DeclaredField(type, "first").GetValue(obj), arguments[0]); WriteSyncObject(data, AccessTools.DeclaredField(type, "second").GetValue(obj), arguments[1]); return; } if (typeof(ITuple).IsAssignableFrom(genericTypeDefinition)) // ValueTuple or Tuple { Type[] arguments = type.GetGenericArguments(); ITuple tuple = (ITuple)obj; data.WriteInt32(tuple.Length); for (int i = 0; i < tuple.Length; i++) { WriteSyncObject(data, tuple[i], arguments[i]); } return; } } if (typeof(ISyncSimple).IsAssignableFrom(type)) { foreach (var field in AccessTools.GetDeclaredFields(type)) { WriteSyncObject(data, field.GetValue(obj), field.FieldType); } return; } // Special case if (typeof(Def).IsAssignableFrom(type)) { if (obj is not Def def) { data.WriteUShort(ushort.MaxValue); return; } var defTypeIndex = Array.IndexOf(DefSerialization.DefTypes, def.GetType()); if (defTypeIndex == -1) { throw new SerializationException($"Unknown def type {def.GetType()}"); } data.WriteUShort((ushort)defTypeIndex); data.WriteUShort(def.shortHash); return; } // Special case for Designators to change the type if (typeof(Designator).IsAssignableFrom(type)) { data.WriteUShort((ushort)Array.IndexOf(ImplSerialization.designatorTypes, obj.GetType())); } // Where the magic happens if (SyncDict.syncWorkers.TryGetValue(type, out var syncWorkerEntry)) { syncWorkerEntry.Invoke(new WritingSyncWorker(data), ref obj); return; } log?.Node("No writer for " + type); throw new SerializationException("No writer for type " + type); } catch { Log.Error($"Multiplayer: Error writing type: {type}, obj: {obj}, obj type: {obj?.GetType()}"); throw; } finally { log?.Exit(); } }
public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncType) { MpContext context = data.MpContext(); Type type = syncType.type; LoggingByteWriter log = data as LoggingByteWriter; log?.LogEnter(type.FullName + ": " + (obj ?? "null")); if (obj != null && !type.IsAssignableFrom(obj.GetType())) { throw new SerializationException($"Serializing with type {type} but got object of type {obj.GetType()}"); } try { if (typeof(object) == type) { return; } if (type.IsByRef) { return; } if (syncWorkersEarly.TryGetValue(type, out var syncWorkerEntryEarly)) { syncWorkerEntryEarly.Invoke(new WritingSyncWorker(data), ref obj); return; } if (syncType.expose) { if (!typeof(IExposable).IsAssignableFrom(type)) { throw new SerializationException($"Type {type} can't be exposed because it isn't IExposable"); } IExposable exposable = obj as IExposable; data.WritePrefixedBytes(ScribeUtil.WriteExposable(exposable)); return; } if (typeof(ISynchronizable).IsAssignableFrom(type)) { ((ISynchronizable)obj).Sync(new WritingSyncWorker(data)); return; } if (type.IsEnum) { Type enumType = Enum.GetUnderlyingType(type); WriteSyncObject(data, Convert.ChangeType(obj, enumType), enumType); return; } if (type.IsArray && type.GetArrayRank() == 1) { Type elementType = type.GetElementType(); Array arr = (Array)obj; if (arr.Length > ushort.MaxValue) { throw new Exception($"Tried to serialize a {elementType}[] with too many ({arr.Length}) items."); } data.WriteUShort((ushort)arr.Length); foreach (object e in arr) { WriteSyncObject(data, e, elementType); } return; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List <>)) { ListType specialList = ListType.Normal; Type listType = type.GetGenericArguments()[0]; if (listType == typeof(Thing) && obj == Find.CurrentMap.listerThings.AllThings) { context.map = Find.CurrentMap; specialList = ListType.MapAllThings; } else if (listType == typeof(Designation) && obj == Find.CurrentMap.designationManager.allDesignations) { context.map = Find.CurrentMap; specialList = ListType.MapAllDesignations; } WriteSync(data, specialList); if (specialList == ListType.Normal) { IList list = (IList)obj; if (list.Count > ushort.MaxValue) { throw new Exception($"Tried to serialize a {type} with too many ({list.Count}) items."); } data.WriteUShort((ushort)list.Count); foreach (object e in list) { WriteSyncObject(data, e, listType); } } return; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable <>)) { bool isNull = obj == null; data.WriteBool(isNull); if (isNull) { return; } bool hasValue = (bool)obj.GetPropertyOrField("HasValue"); data.WriteBool(hasValue); Type nullableType = type.GetGenericArguments()[0]; if (hasValue) { WriteSyncObject(data, obj.GetPropertyOrField("Value"), nullableType); } return; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable <>)) { IEnumerable e = (IEnumerable)obj; Type elementType = type.GetGenericArguments()[0]; var listType = typeof(List <>).MakeGenericType(elementType); IList list = (IList)Activator.CreateInstance(listType); foreach (var o in e) { if (list.Count > ushort.MaxValue) { throw new Exception($"Tried to serialize a {type} with too many ({list.Count}) items."); } list.Add(o); } WriteSyncObject(data, list, listType); return; } // special case if (typeof(Def).IsAssignableFrom(type)) { Def def = obj as Def; data.WriteUShort(def != null ? def.shortHash : (ushort)0); return; } // Where the magic happens if (syncWorkers.TryGetValue(type, out var syncWorkerEntry)) { syncWorkerEntry.Invoke(new WritingSyncWorker(data), ref obj); return; } log?.LogNode("No writer for " + type); throw new SerializationException("No writer for type " + type); } catch (Exception e) { MpLog.Error($"Error writing type: {type}, obj: {obj}, {e}"); throw; } finally { log?.LogExit(); } }
/// <summary> /// Called by <see cref="AddClientOpinionAndCheckDesync"/> if the newly added opinion doesn't match with what other ones. /// </summary> /// <param name="oldOpinion">The first up-to-date client opinion present in <see cref="knownClientOpinions"/>, that disagreed with the new one</param> /// <param name="newOpinion">The opinion passed to <see cref="AddClientOpinionAndCheckDesync"/> that disagreed with the currently known opinions.</param> /// <param name="desyncMessage">The error message that explains exactly what desynced.</param> private void HandleDesync(ClientSyncOpinion oldOpinion, ClientSyncOpinion newOpinion, string desyncMessage) { Multiplayer.Client.Send(Packets.Client_Desynced); //Identify which of the two sync infos is local, and which is the remote. var local = oldOpinion.isLocalClientsOpinion ? oldOpinion : newOpinion; var remote = !oldOpinion.isLocalClientsOpinion ? oldOpinion : newOpinion; //Print arbiter desync stacktrace if it exists if (local.desyncStackTraces.Any()) { SaveStackTracesToDisk(local, remote); } try { //Get the filename of the next desync file to create. var desyncFilePath = FindFileNameForNextDesyncFile(); //Initialize the Replay object. var replay = Replay.ForSaving(Replay.ReplayFile(desyncFilePath, Multiplayer.DesyncsDir)); //Write the universal replay data (i.e. world and map folders, and the info file) so this desync can be reviewed as a standard replay. replay.WriteCurrentData(); //Dump our current game object. var savedGame = ScribeUtil.WriteExposable(Current.Game, "game", true, ScribeMetaHeaderUtility.WriteMetaHeader); using (var zip = replay.ZipFile) { //Write the local sync data var syncLocal = local.Serialize(); zip.AddEntry("sync_local", syncLocal); //Write the remote sync data var syncRemote = remote.Serialize(); zip.AddEntry("sync_remote", syncRemote); //Dump the entire save file to the zip. zip.AddEntry("game_snapshot", savedGame); //Prepare the desync info var desyncInfo = new StringBuilder(); desyncInfo.AppendLine("###Tick Data###") .AppendLine($"Arbiter Connected And Playing|||{Multiplayer.session.ArbiterPlaying}") .AppendLine($"Last Valid Tick - Local|||{lastValidTick}") .AppendLine($"Last Valid Tick - Arbiter|||{arbiterWasPlayingOnLastValidTick}") .AppendLine("\n###Version Data###") .AppendLine($"Multiplayer Mod Version|||{MpVersion.Version}") .AppendLine($"Rimworld Version and Rev|||{VersionControl.CurrentVersionStringWithRev}") .AppendLine("\n###Debug Options###") .AppendLine($"Multiplayer Debug Build - Client|||{MpVersion.IsDebug}") .AppendLine($"Multiplayer Debug Build - Host|||{Multiplayer.WorldComp.debugMode}") .AppendLine($"Rimworld Developer Mode - Client|||{Prefs.DevMode}") .AppendLine("\n###Server Info###") .AppendLine($"Player Count|||{Multiplayer.session.players.Count}") .AppendLine("\n###CPU Info###") .AppendLine($"Processor Name|||{SystemInfo.processorType}") .AppendLine($"Processor Speed (MHz)|||{SystemInfo.processorFrequency}") .AppendLine($"Thread Count|||{SystemInfo.processorCount}") .AppendLine("\n###GPU Info###") .AppendLine($"GPU Family|||{SystemInfo.graphicsDeviceVendor}") .AppendLine($"GPU Type|||{SystemInfo.graphicsDeviceType}") .AppendLine($"GPU Name|||{SystemInfo.graphicsDeviceName}") .AppendLine($"GPU VRAM|||{SystemInfo.graphicsMemorySize}") .AppendLine("\n###RAM Info###") .AppendLine($"Physical Memory Present|||{SystemInfo.systemMemorySize}") .AppendLine("\n###OS Info###") .AppendLine($"OS Type|||{SystemInfo.operatingSystemFamily}") .AppendLine($"OS Name and Version|||{SystemInfo.operatingSystem}"); //Save debug info to the zip zip.AddEntry("desync_info", desyncInfo.ToString()); zip.Save(); } } catch (Exception e) { Log.Error($"Exception writing desync info: {e}"); } Find.WindowStack.windows.Clear(); Find.WindowStack.Add(new DesyncedWindow(desyncMessage)); }