// todo support methods with arguments (currently there has been no need for it) public static SyncDelegate RegisterSyncDelegate(Type inType, string nestedType, string methodName, string[] fields, Type[] args = null) { string typeName = $"{inType}+{nestedType}"; Type type = MpReflection.GetTypeByName(typeName); if (type == null) { throw new Exception($"Couldn't find type {typeName}"); } MethodInfo method = AccessTools.Method(type, methodName, args); if (method == null) { throw new Exception($"Couldn't find method {typeName}::{methodName}"); } MpUtil.MarkNoInlining(method); SyncDelegate handler = new SyncDelegate(type, method, fields); syncMethods[handler.method] = handler; handlers.Add(handler); PatchMethodForSync(method); return(handler); }
public static string MethodNameFromAddr(long addr, bool harmonyOriginals) { var domain = DomainPtr; var ji = mono_jit_info_table_find(domain, (IntPtr)addr); if (ji == IntPtr.Zero) { return(null); } var ptrToPrint = mono_jit_info_get_method(ji); var codeStart = (long)mono_jit_info_get_code_start(ji); if (harmonyOriginals) { var original = MpUtil.GetOriginalFromHarmonyReplacement(codeStart); if (original != null) { ptrToPrint = original.MethodHandle.Value; } } var name = mono_debug_print_stack_frame(ptrToPrint, -1, domain); return(string.IsNullOrEmpty(name) ? null : name); }
static void DrawSkippingWindow() { if (Multiplayer.Client == null || TickPatch.skipTo < 0) { return; } string text = $"{"MpSimulating".Translate()}{MpUtil.FixedEllipsis()}"; float textWidth = Text.CalcSize(text).x; float windowWidth = Math.Max(240f, textWidth + 40f); Rect rect = new Rect(0, 0, windowWidth, 75f).CenterOn(new Rect(0, 0, UI.screenWidth, UI.screenHeight)); if (Multiplayer.IsReplay && !TickPatch.disableSkipCancel && Event.current.type == EventType.KeyUp && Event.current.keyCode == KeyCode.Escape) { TickPatch.ClearSkipping(); Event.current.Use(); } Find.WindowStack.ImmediateWindow(SkippingWindowId, rect, WindowLayer.Super, () => { Text.Anchor = TextAnchor.MiddleCenter; Text.Font = GameFont.Small; Widgets.Label(rect.AtZero(), text); Text.Anchor = TextAnchor.UpperLeft; }, absorbInputAroundWindow: true); }
public override void DoWindowContents(Rect inRect) { string label = IsConnecting ? (ConnectingString + MpUtil.FixedEllipsis()) : result; if (Multiplayer.Client?.StateObj is ClientJoiningState joining && joining.state == JoiningState.Downloading) { label = $"MpDownloading".Translate(Multiplayer.Client.FragmentProgress); } const float buttonHeight = 40f; const float buttonWidth = 120f; Rect textRect = inRect; textRect.yMax -= (buttonHeight + 10f); Text.Anchor = TextAnchor.MiddleCenter; Widgets.Label(textRect, label); Text.Anchor = TextAnchor.UpperLeft; Rect buttonRect = new Rect((inRect.width - buttonWidth) / 2f, inRect.height - buttonHeight - 10f, buttonWidth, buttonHeight); if (Widgets.ButtonText(buttonRect, "CancelButton".Translate(), true, false, true)) { Close(); } }
public HostWindow(SaveFile file = null, bool withSimulation = false) { closeOnAccept = false; doCloseX = true; settings = MultiplayerMod.settings.serverSettings; this.withSimulation = withSimulation; this.file = file; settings.gameName = file?.gameName ?? Multiplayer.session?.gameName ?? $"{Multiplayer.username}'s game"; MultiplayerWorldComp.asyncTime = file?.asyncTime ?? false; if (file?.asyncTime ?? false) { asyncTimeLocked = true; // once enabled in a save, cannot be disabled } var localAddr = MpUtil.GetLocalIpAddress() ?? "127.0.0.1"; settings.lanAddress = localAddr; if (MpVersion.IsDebug) { debugMode = true; logDesyncTraces = true; } }
public static void TryConnectWithWindow(string address, int port, bool returnToServerBrowser = true) { Find.WindowStack.Add(new ConnectingWindow(address, port) { returnToServerBrowser = returnToServerBrowser }); Multiplayer.session = new MultiplayerSession { address = address, port = port }; NetManager netClient = new NetManager(new MpClientNetListener()) { EnableStatistics = true, IPv6Enabled = MpUtil.SupportsIPv6() ? IPv6Mode.SeparateSocket : IPv6Mode.Disabled }; netClient.Start(); netClient.ReconnectDelay = 300; netClient.MaxConnectAttempts = 8; Multiplayer.session.netClient = netClient; netClient.Connect(address, port, ""); }
public static SyncMethod RegisterSyncMethod(MethodInfo method, SyncType[] argTypes) { MpUtil.MarkNoInlining(method); SyncMethod handler = new SyncMethod((method.IsStatic ? null : method.DeclaringType), method, argTypes); syncMethods[method] = handler; handlers.Add(handler); PatchMethodForSync(method); return(handler); }
public HostWindow(SaveFile file = null) { closeOnAccept = false; doCloseX = true; this.file = file; settings.gameName = file?.gameName ?? $"{Multiplayer.username}'s game"; var localAddr = MpUtil.GetLocalIpAddress() ?? "127.0.0.1"; settings.lanAddress = localAddr; addressBuffer = localAddr; lan = true; settings.arbiter = true; }
public static void EarlyMarkNoInline(Assembly asm) { foreach (var type in asm.GetTypes()) { MpPatchExtensions.DoMpPatches(null, type)?.ForEach(m => MpUtil.MarkNoInlining(m)); var harmonyMethods = type.GetHarmonyMethods(); if (harmonyMethods?.Count > 0) { var original = MpUtil.GetOriginalMethod(HarmonyMethod.Merge(harmonyMethods)); if (original != null) { MpUtil.MarkNoInlining(original); } } } }
public static void RegisterAllSyncMethods() { foreach (Type type in MpUtil.AllModTypes()) { foreach (MethodInfo method in type.GetDeclaredMethods()) { if (!method.TryGetAttribute(out SyncMethodAttribute syncAttr)) { continue; } var syncMethod = RegisterSyncMethod(method, null); syncMethod.context = syncAttr.context; syncMethod.debugOnly = method.HasAttribute <SyncDebugOnlyAttribute>(); } } }
private void EarlyMarkNoInline() { foreach (var type in MpUtil.AllModTypes()) { MpPatchExtensions.DoMpPatches(null, type)?.ForEach(m => MpUtil.MarkNoInlining(m)); var harmonyMethods = type.GetHarmonyMethods(); if (harmonyMethods?.Count > 0) { var original = MpUtil.GetOriginalMethod(HarmonyMethod.Merge(harmonyMethods)); if (original != null) { MpUtil.MarkNoInlining(original); } } } }
public void TryAddStackTrace(string info = null, bool doTrace = true) { if (!ShouldCollect) { return; } Current.TryMarkSimulating(); var trace = doTrace ? MpUtil.FastStackTrace(4) : new MethodBase[0]; Current.traces.Add(new TraceInfo() { trace = trace, info = info }); current.traceHashes.Add(trace.Hash() ^ (info?.GetHashCode() ?? 0)); }
private void RecreateDialog() { var session = Multiplayer.WorldComp.trading[selectedTab]; MpTradeSession.SetTradeSession(session); dialog = MpUtil.NewObjectNoCtor <Dialog_Trade>(); dialog.quickSearchWidget = new QuickSearchWidget(); dialog.giftsOnly = session.giftsOnly; dialog.sorter1 = TransferableSorterDefOf.Category; dialog.sorter2 = TransferableSorterDefOf.MarketValue; dialog.CacheTradeables(); session.deal.uiShouldReset = UIShouldReset.None; removed.Clear(); added.Clear(); MpTradeSession.SetTradeSession(null); }
public HostWindow(SaveFile file = null, bool withSimulation = false) { closeOnAccept = false; doCloseX = true; settings = MultiplayerMod.settings.serverSettings; this.withSimulation = withSimulation; this.file = file; settings.gameName = file?.gameName ?? Multiplayer.session?.gameName ?? $"{Multiplayer.username}'s game"; var localAddr = MpUtil.GetLocalIpAddress() ?? "127.0.0.1"; settings.lanAddress = localAddr; if (MpVersion.IsDebug) { debugMode = true; } }
/// <summary> /// Logs the current stack so that in the event of a desync we have some stack traces. /// </summary> /// <param name="info">Any additional message to be logged with the stack</param> /// <param name="doTrace">Set to false to not actually log a stack, only the message</param> public void TryAddStackTraceForDesyncLog(string info = null, bool doTrace = true) { if (!ShouldCollect) { return; } CurrentOpinion.TryMarkSimulating(); //Get the current stack trace var trace = doTrace ? MpUtil.FastStackTrace(4) : new MethodBase[0]; //Add it to the list CurrentOpinion.desyncStackTraces.Add(new StackTraceLogItem { stackTrace = trace, additionalInfo = info }); //Calculate its hash and add it, for comparison with other opinions. currentOpinion.desyncStackTraceHashes.Add(trace.Hash() ^ (info?.GetHashCode() ?? 0)); }
static void DrawSkippingWindow() { if (Multiplayer.Client == null || !TickPatch.Skipping) { return; } string text = $"{TickPatch.skippingTextKey.Translate()}{MpUtil.FixedEllipsis()}"; float textWidth = Text.CalcSize(text).x; float windowWidth = Math.Max(240f, textWidth + 40f); float windowHeight = TickPatch.cancelSkip != null ? 100f : 75f; Rect rect = new Rect(0, 0, windowWidth, windowHeight).CenterOn(new Rect(0, 0, UI.screenWidth, UI.screenHeight)); if (TickPatch.canESCSkip && Event.current.type == EventType.KeyUp && Event.current.keyCode == KeyCode.Escape) { TickPatch.ClearSkipping(); Event.current.Use(); } Find.WindowStack.ImmediateWindow(SkippingWindowId, rect, WindowLayer.Super, () => { var textRect = rect.AtZero(); if (TickPatch.cancelSkip != null) { textRect.yMin += 5f; textRect.height -= 50f; } Text.Anchor = TextAnchor.MiddleCenter; Text.Font = GameFont.Small; Widgets.Label(textRect, text); Text.Anchor = TextAnchor.UpperLeft; if (TickPatch.cancelSkip != null && Widgets.ButtonText(new Rect(0, textRect.yMax, 100f, 35f).CenteredOnXIn(textRect), TickPatch.skipCancelButtonKey.Translate())) { TickPatch.cancelSkip(); } }, absorbInputAroundWindow: true); }
public override void DoWindowContents(Rect inRect) { string label = Ellipsis ? (ConnectingString + MpUtil.FixedEllipsis()) : result; const float buttonHeight = 40f; const float buttonWidth = 120f; Rect textRect = inRect; textRect.yMax -= (buttonHeight + 10f); float textWidth = Text.CalcSize(label).x; Text.Anchor = TextAnchor.MiddleCenter; Widgets.Label(textRect, label); Text.Anchor = TextAnchor.UpperLeft; Rect buttonRect = new Rect((inRect.width - buttonWidth) / 2f, inRect.height - buttonHeight - 10f, buttonWidth, buttonHeight); if (Widgets.ButtonText(buttonRect, "CancelButton".Translate(), true, false, true)) { Close(); } }
private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) { Type type = syncType.type; try { if (typeof(object) == type) { return(null); } if (type.IsByRef) { return(null); } if (SyncDictFast.syncWorkers.TryGetValue(type, out SyncWorkerEntry syncWorkerEntryEarly)) { object res = null; if (syncWorkerEntryEarly.shouldConstruct || type.IsValueType) { res = Activator.CreateInstance(type); } syncWorkerEntryEarly.Invoke(new ReadingSyncWorker(data), ref res); return(res); } if (syncType.expose) { if (!typeof(IExposable).IsAssignableFrom(type)) { throw new SerializationException($"Type {type} can't be exposed because it isn't IExposable"); } byte[] exposableData = data.ReadPrefixedBytes(); return(ExposableSerialization.ReadExposable(type, exposableData)); } if (typeof(ISynchronizable).IsAssignableFrom(type)) { var obj = Activator.CreateInstance(type); ((ISynchronizable)obj).Sync(new ReadingSyncWorker(data)); return(obj); } if (type.IsEnum) { Type underlyingType = Enum.GetUnderlyingType(type); return(Enum.ToObject(type, ReadSyncObject(data, underlyingType))); } if (type.IsArray) { if (type.GetArrayRank() != 1) { throw new SerializationException("Multi dimensional arrays aren't supported."); } ushort length = data.ReadUShort(); if (length == ushort.MaxValue) { return(null); } Type elementType = type.GetElementType(); Array arr = Array.CreateInstance(elementType, length); for (int i = 0; i < length; i++) { arr.SetValue(ReadSyncObject(data, elementType), i); } return(arr); } if (type.IsGenericType) { var genericTypeDefinition = type.GetGenericTypeDefinition(); if (genericTypeDefinition == typeof(List <>)) { ushort size = data.ReadUShort(); if (size == ushort.MaxValue) { return(null); } Type listObjType = type.GetGenericArguments()[0]; IList list = (IList)Activator.CreateInstance(type, size); for (int j = 0; j < size; j++) { list.Add(ReadSyncObject(data, listObjType)); } return(list); } if (genericTypeDefinition == typeof(IEnumerable <>)) { Type element = type.GetGenericArguments()[0]; return(ReadSyncObject(data, typeof(List <>).MakeGenericType(element))); } if (genericTypeDefinition == typeof(Nullable <>)) { bool isNull = data.ReadBool(); if (isNull) { return(null); } return(Activator.CreateInstance(type, ReadSyncObject(data, Nullable.GetUnderlyingType(type)))); } if (genericTypeDefinition == typeof(Dictionary <,>)) { Type[] arguments = type.GetGenericArguments(); Array keys = (Array)ReadSyncObject(data, arguments[0].MakeArrayType()); if (keys == null) { return(null); } Array values = (Array)ReadSyncObject(data, arguments[1].MakeArrayType()); IDictionary dictionary = (IDictionary)Activator.CreateInstance(type); for (int i = 0; i < keys.Length; i++) { var key = keys.GetValue(i); if (key != null) { dictionary.Add(key, values.GetValue(i)); } } return(dictionary); } if (genericTypeDefinition == typeof(Pair <,>)) { Type[] arguments = type.GetGenericArguments(); object[] parameters = { ReadSyncObject(data, arguments[0]), ReadSyncObject(data, arguments[1]), }; return(type.GetConstructors().First().Invoke(parameters)); } if (typeof(ITuple).IsAssignableFrom(genericTypeDefinition)) // ValueTuple or Tuple { Type[] arguments = type.GetGenericArguments(); int size = data.ReadInt32(); object[] values = new object[size]; for (int i = 0; i < size; i++) { values[i] = ReadSyncObject(data, arguments[i]); } return(type.GetConstructors().First().Invoke(values)); } } if (typeof(ISyncSimple).IsAssignableFrom(type)) { var obj = MpUtil.NewObjectNoCtor(type); foreach (var field in AccessTools.GetDeclaredFields(type)) { field.SetValue(obj, ReadSyncObject(data, field.FieldType)); } return(obj); } // Def is a special case until the workers can read their own type if (typeof(Def).IsAssignableFrom(type)) { ushort defTypeIndex = data.ReadUShort(); if (defTypeIndex == ushort.MaxValue) { return(null); } ushort shortHash = data.ReadUShort(); var defType = DefSerialization.DefTypes[defTypeIndex]; var def = DefSerialization.GetDef(defType, shortHash); if (def == null) { throw new SerializationException($"Couldn't find {defType} with short hash {shortHash}"); } return(def); } // Designators can't be handled by SyncWorkers due to the type change if (typeof(Designator).IsAssignableFrom(type)) { ushort desId = ReadSync <ushort>(data); type = ImplSerialization.designatorTypes[desId]; // Replaces the type! } // Where the magic happens if (SyncDict.syncWorkers.TryGetValue(type, out var syncWorkerEntry)) { object res = null; if (syncWorkerEntry.shouldConstruct || type.IsValueType) { res = Activator.CreateInstance(type); } syncWorkerEntry.Invoke(new ReadingSyncWorker(data), ref res); return(res); } throw new SerializationException("No reader for type " + type); } catch { Log.Error($"Multiplayer: Error reading type: {type}"); throw; } }
private void DrawInfoButtons() { float x = 0; const string WebsiteLink = "https://rimworldmultiplayer.com"; const string DiscordLink = "https://discord.gg/n5E2cb2Y4Z"; bool Button(Texture2D icon, string labelKey, string tip, Color baseIconColor, float iconSize = 24f) { var label = labelKey.Translate(); var labelWidth = Text.CalcSize(label).x; var btn = new Rect(x, 0, 24 + 1 + labelWidth, 24); var mouseOver = Mouse.IsOver(btn); MouseoverSounds.DoRegion(btn); TooltipHandler.TipRegion(btn, tip); using (MpStyle.Set(mouseOver ? Color.yellow : baseIconColor)) { GUI.DrawTexture(new Rect(x += (24 - iconSize) / 2, (24 - iconSize) / 2, iconSize, iconSize), icon); x += 24; } x += 1; using (MpStyle.Set(mouseOver ? Color.yellow : Color.white)) using (MpStyle.Set(TextAnchor.MiddleCenter)) MpUI.Label(new Rect(x, 0, labelWidth, 24f), labelKey.Translate()); x += labelWidth; x += 10; return(Widgets.ButtonInvisible(btn)); } const string compatLabel = "MpCompatibilityButton"; const string compatLabelDesc = "MpCompatibilityButtonDesc"; if (Button(TexButton.ToggleLog, compatLabel, MpUtil.TranslateWithDoubleNewLines(compatLabelDesc, 2), Color.grey, 20)) { Find.WindowStack.Add(new ModCompatWindow(null, false, false, null)); } if (Button(MultiplayerStatic.WebsiteIcon, "MpWebsiteButton", "MpLinkButtonDesc".Translate() + " " + WebsiteLink, Color.grey, 20)) { Application.OpenURL(WebsiteLink); } if (Button(MultiplayerStatic.DiscordIcon, "MpDiscordButton", "MpLinkButtonDesc".Translate() + " " + DiscordLink, Color.white)) { Application.OpenURL(DiscordLink); } if (false) // todo { Button( TexButton.NewItem, "MpActiveConfigsButton", "MpActiveConfigsButtonDesc1".Translate("Player's game") + "\n\n" + "MpActiveConfigsButtonDesc2".Translate(), Color.grey, 20 ); } }
static void DoDebugInfo() { if (Multiplayer.ShowDevInfo) { int timerLag = (TickPatch.tickUntil - TickPatch.Timer); StringBuilder text = new StringBuilder(); text.Append($"{Faction.OfPlayer.loadID} {Multiplayer.RealPlayerFaction?.loadID} {Find.UniqueIDsManager.nextThingID} j:{Find.UniqueIDsManager.nextJobID} {Find.TickManager.TicksGame} {TickPatch.Timer} {TickPatch.tickUntil} {timerLag} {TickPatch.maxBehind}"); text.Append($"\n{Time.deltaTime * 60f:0.0000} {TickPatch.tickTimer.ElapsedMilliseconds}"); text.Append($"\n{avgDelta = (avgDelta * 59.0 + Time.deltaTime * 60.0) / 60.0:0.0000}"); text.Append($"\n{avgTickTime = (avgTickTime * 59.0 + TickPatch.tickTimer.ElapsedMilliseconds) / 60.0:0.0000} {Find.World.worldObjects.settlements.Count}"); text.Append($"\n{Multiplayer.session?.localCmdId} {Multiplayer.session?.remoteCmdId} {Multiplayer.session?.remoteTickUntil}"); Rect rect = new Rect(80f, 60f, 330f, Text.CalcHeight(text.ToString(), 330f)); Widgets.Label(rect, text.ToString()); if (Input.GetKey(KeyCode.End)) { avgDelta = 0; avgTickTime = 0; } } if (Multiplayer.ShowDevInfo && Multiplayer.Client != null && Find.CurrentMap != null) { var async = Find.CurrentMap.AsyncTime(); StringBuilder text = new StringBuilder(); text.Append($"{Multiplayer.game.sync.knownClientOpinions.FirstOrDefault()?.isLocalClientsOpinion} {async.mapTicks} {TickPatch.shouldPause} {TickPatch.pausedAt} "); text.Append($"z: {Find.CurrentMap.haulDestinationManager.AllHaulDestinationsListForReading.Count()} d: {Find.CurrentMap.designationManager.allDesignations.Count} hc: {Find.CurrentMap.listerHaulables.ThingsPotentiallyNeedingHauling().Count}"); if (Find.CurrentMap.ParentFaction != null) { int faction = Find.CurrentMap.ParentFaction.loadID; MultiplayerMapComp comp = Find.CurrentMap.MpComp(); FactionMapData data = comp.factionData.GetValueSafe(faction); if (data != null) { text.Append($" h: {data.listerHaulables.ThingsPotentiallyNeedingHauling().Count}"); text.Append($" sg: {data.haulDestinationManager.AllGroupsListForReading.Count}"); } } text.Append($" {Multiplayer.GlobalIdBlock.Current} {Find.IdeoManager.IdeosInViewOrder.FirstOrDefault()?.id}"); text.Append($"\n{SyncFieldUtil.bufferedChanges.Sum(kv => kv.Value.Count)} {Find.UniqueIDsManager.nextThingID}"); text.Append($"\n{DeferredStackTracing.acc} {MpInput.Mouse2UpWithoutDrag} {Input.GetKeyUp(KeyCode.Mouse2)} {Input.GetKey(KeyCode.Mouse2)}"); text.Append($"\n{(uint)async.randState} {(uint)(async.randState >> 32)}"); text.Append($"\n{(uint)Multiplayer.WorldComp.randState} {(uint)(Multiplayer.WorldComp.randState >> 32)}"); text.Append($"\n{async.cmds.Count} {Multiplayer.WorldComp.cmds.Count} {async.slower.forceNormalSpeedUntil} {Multiplayer.GameComp.asyncTime}"); text.Append($"\nt{DeferredStackTracing.maxTraceDepth} p{SimplePool<StackTraceLogItemRaw>.FreeItemsCount} {DeferredStackTracingImpl.hashtableEntries}/{DeferredStackTracingImpl.hashtableSize} {DeferredStackTracingImpl.collisions}"); text.Append(Find.WindowStack.focusedWindow is ImmediateWindow win ? $"\nImmediateWindow: {MpUtil.DelegateMethodInfo(win.doWindowFunc?.Method)}" : $"\n{Find.WindowStack.focusedWindow}"); text.Append($"\n{UI.CurUICellSize()} {Find.WindowStack.windows.ToStringSafeEnumerable()}"); Rect rect1 = new Rect(80f, 170f, 330f, Text.CalcHeight(text.ToString(), 330f)); Widgets.Label(rect1, text.ToString()); } //if (Event.current.type == EventType.Repaint) // RandGetValuePatch.tracesThistick = 0; }
private void EarlyPatches() { // special case? MpUtil.MarkNoInlining(AccessTools.Method(typeof(OutfitForcedHandler), nameof(OutfitForcedHandler.Reset))); foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { var firstMethod = asm.GetType("Harmony.AccessTools")?.GetMethod("FirstMethod"); if (firstMethod != null) { harmony.Patch(firstMethod, new HarmonyMethod(typeof(AccessTools_FirstMethod_Patch), nameof(AccessTools_FirstMethod_Patch.Prefix))); } if (asm == typeof(HarmonyPatch).Assembly) { continue; } var emitCallParameter = asm.GetType("Harmony.MethodPatcher")?.GetMethod("EmitCallParameter", AccessTools.all); if (emitCallParameter != null) { harmony.Patch(emitCallParameter, new HarmonyMethod(typeof(PatchHarmony), emitCallParameter.GetParameters().Length == 4 ? nameof(PatchHarmony.EmitCallParamsPrefix4) : nameof(PatchHarmony.EmitCallParamsPrefix5))); } } { var prefix = new HarmonyMethod(AccessTools.Method(typeof(CaptureThingSetMakers), "Prefix")); harmony.Patch(AccessTools.Constructor(typeof(ThingSetMaker_MarketValue)), prefix); harmony.Patch(AccessTools.Constructor(typeof(ThingSetMaker_Nutrition)), prefix); } harmony.Patch( AccessTools.Method(typeof(ThingCategoryDef), "get_DescendantThingDefs"), new HarmonyMethod(typeof(ThingCategoryDef_DescendantThingDefsPatch), "Prefix"), new HarmonyMethod(typeof(ThingCategoryDef_DescendantThingDefsPatch), "Postfix") ); harmony.Patch( AccessTools.Method(typeof(ThingCategoryDef), "get_ThisAndChildCategoryDefs"), new HarmonyMethod(typeof(ThingCategoryDef_ThisAndChildCategoryDefsPatch), "Prefix"), new HarmonyMethod(typeof(ThingCategoryDef_ThisAndChildCategoryDefsPatch), "Postfix") ); harmony.Patch( AccessTools.Method(typeof(LoadedModManager), nameof(LoadedModManager.ParseAndProcessXML)), transpiler: new HarmonyMethod(typeof(ParseAndProcessXml_Patch), "Transpiler") ); harmony.Patch( AccessTools.Method(typeof(XmlNode), "get_ChildNodes"), postfix: new HarmonyMethod(typeof(XmlNodeListPatch), nameof(XmlNodeListPatch.XmlNode_ChildNodes_Postfix)) ); harmony.Patch( AccessTools.Method(typeof(XmlInheritance), nameof(XmlInheritance.TryRegisterAllFrom)), new HarmonyMethod(typeof(XmlInheritance_Patch), "Prefix"), new HarmonyMethod(typeof(XmlInheritance_Patch), "Postfix") ); harmony.Patch( AccessTools.Constructor(typeof(LoadableXmlAsset), new[] { typeof(string), typeof(string), typeof(string) }), new HarmonyMethod(typeof(LoadableXmlAssetCtorPatch), "Prefix") ); // Cross os compatibility harmony.Patch( AccessTools.Method(typeof(DirectXmlLoader), nameof(DirectXmlLoader.XmlAssetsInModFolder)), null, new HarmonyMethod(typeof(XmlAssetsInModFolderPatch), "Postfix") ); // Might fix some mod desyncs harmony.Patch( AccessTools.Constructor(typeof(Def), new Type[0]), new HarmonyMethod(typeof(RandPatches), nameof(RandPatches.Prefix)), new HarmonyMethod(typeof(RandPatches), nameof(RandPatches.Postfix)) ); }
private void DrawLan(Rect inRect) { Text.Anchor = TextAnchor.MiddleCenter; Widgets.Label(new Rect(inRect.x, 8f, inRect.width, 40), "MpLanSearching".Translate() + MpUtil.FixedEllipsis()); Text.Anchor = TextAnchor.UpperLeft; inRect.yMin += 40f; float margin = 100; Rect outRect = new Rect(margin, inRect.yMin + 10, inRect.width - 2 * margin, inRect.height - 20); float height = servers.Count * 40; Rect viewRect = new Rect(0, 0, outRect.width - 16f, height); Widgets.BeginScrollView(outRect, ref lanScroll, viewRect, true); float y = 0; int i = 0; foreach (LanServer server in servers) { Rect entryRect = new Rect(0, y, viewRect.width, 40); if (i % 2 == 0) { Widgets.DrawAltRect(entryRect); } Text.Anchor = TextAnchor.MiddleLeft; Widgets.Label(entryRect.Right(5), "" + server.endpoint); Text.Anchor = TextAnchor.MiddleCenter; Rect playButton = new Rect(entryRect.xMax - 75, entryRect.y + 5, 70, 40 - 10); if (Widgets.ButtonText(playButton, ">>")) { Close(false); Log.Message("Connecting to lan server"); Find.WindowStack.Add(new ConnectingWindow(server.endpoint.Address, server.endpoint.Port) { returnToServerBrowser = true }); } Text.Anchor = TextAnchor.UpperLeft; y += entryRect.height; i++; } Widgets.EndScrollView(); }