diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/SMAPI.Web/ViewModels/ModCompatibilityModel.cs | 2 | ||||
-rw-r--r-- | src/SMAPI.Web/ViewModels/ModModel.cs | 3 | ||||
-rw-r--r-- | src/SMAPI.Web/Views/LogParser/Index.cshtml | 6 | ||||
-rw-r--r-- | src/SMAPI.Web/Views/Mods/Index.cshtml | 34 | ||||
-rw-r--r-- | src/SMAPI.Web/wwwroot/Content/css/log-parser.css | 4 | ||||
-rw-r--r-- | src/SMAPI.Web/wwwroot/Content/css/mods.css | 47 | ||||
-rw-r--r-- | src/SMAPI.Web/wwwroot/Content/js/mods.js | 144 | ||||
-rw-r--r-- | src/SMAPI/Framework/Networking/MultiplayerPeer.cs | 60 | ||||
-rw-r--r-- | src/SMAPI/Framework/Networking/SGalaxyNetClient.cs | 52 | ||||
-rw-r--r-- | src/SMAPI/Framework/Networking/SGalaxyNetServer.cs | 63 | ||||
-rw-r--r-- | src/SMAPI/Framework/Networking/SLidgrenClient.cs | 19 | ||||
-rw-r--r-- | src/SMAPI/Framework/Networking/SLidgrenServer.cs | 114 | ||||
-rw-r--r-- | src/SMAPI/Framework/SCore.cs | 11 | ||||
-rw-r--r-- | src/SMAPI/Framework/SMultiplayer.cs | 90 | ||||
-rw-r--r-- | src/SMAPI/Patches/LidgrenServerPatch.cs | 89 | ||||
-rw-r--r-- | src/SMAPI/StardewModdingAPI.csproj | 9 |
16 files changed, 407 insertions, 340 deletions
diff --git a/src/SMAPI.Web/ViewModels/ModCompatibilityModel.cs b/src/SMAPI.Web/ViewModels/ModCompatibilityModel.cs index 61756176..85bf1e46 100644 --- a/src/SMAPI.Web/ViewModels/ModCompatibilityModel.cs +++ b/src/SMAPI.Web/ViewModels/ModCompatibilityModel.cs @@ -29,6 +29,8 @@ namespace StardewModdingAPI.Web.ViewModels public ModCompatibilityModel(WikiCompatibilityInfo info) { this.Status = info.Status.ToString(); + this.Status = this.Status.Substring(0, 1).ToLower() + this.Status.Substring(1); + this.Summary = info.Summary; this.BrokeIn = info.BrokeIn; if (info.UnofficialVersion != null) diff --git a/src/SMAPI.Web/ViewModels/ModModel.cs b/src/SMAPI.Web/ViewModels/ModModel.cs index 309ed828..0e7d2076 100644 --- a/src/SMAPI.Web/ViewModels/ModModel.cs +++ b/src/SMAPI.Web/ViewModels/ModModel.cs @@ -40,6 +40,9 @@ namespace StardewModdingAPI.Web.ViewModels /// <summary>A unique identifier for the mod that can be used in an anchor URL.</summary> public string Slug { get; set; } + /// <summary>The sites where the mod can be downloaded.</summary> + public string[] ModPageSites => this.ModPages.Select(p => p.Text).ToArray(); + /********* ** Public methods diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index 36eb9bb5..58830d64 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -145,14 +145,14 @@ else if (Model.ParsedLog?.IsValid == true) @if (!Model.ShowRaw) { <span class="notice txt"><i>click any mod to filter</i></span> - <span class="notice btn txt" v-on:click="showAllMods" v-show="stats.modsHidden > 0">show all</span> - <span class="notice btn txt" v-on:click="hideAllMods" v-show="stats.modsShown > 0 && stats.modsHidden > 0">hide all</span> + <span class="notice btn txt" v-on:click="showAllMods" v-bind:class="{ invisible: !anyModsHidden }">show all</span> + <span class="notice btn txt" v-on:click="hideAllMods" v-bind:class="{ invisible: !anyModsShown || !anyModsHidden }">hide all</span> } </caption> @foreach (var mod in Model.ParsedLog.Mods.Where(p => p.ContentPackFor == null)) { <tr v-on:click="toggleMod('@Model.GetSlug(mod.Name)')" class="mod-entry" v-bind:class="{ hidden: !showMods['@Model.GetSlug(mod.Name)'] }"> - <td><input type="checkbox" v-bind:checked="showMods['@Model.GetSlug(mod.Name)']" v-show="anyModsHidden" /></td> + <td><input type="checkbox" v-bind:checked="showMods['@Model.GetSlug(mod.Name)']" v-bind:class="{ invisible: !anyModsHidden }" /></td> <td v-pre> <strong>@mod.Name</strong> @mod.Version @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out LogModInfo[] contentPackList)) diff --git a/src/SMAPI.Web/Views/Mods/Index.cshtml b/src/SMAPI.Web/Views/Mods/Index.cshtml index b326fd36..372d6706 100644 --- a/src/SMAPI.Web/Views/Mods/Index.cshtml +++ b/src/SMAPI.Web/Views/Mods/Index.cshtml @@ -4,11 +4,11 @@ ViewData["Title"] = "SMAPI mod compatibility"; } @section Head { - <link rel="stylesheet" href="~/Content/css/mods.css?r=20181021" /> + <link rel="stylesheet" href="~/Content/css/mods.css?r=20181109" /> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/tablesorter@2.31.0/dist/js/jquery.tablesorter.combined.min.js" crossorigin="anonymous"></script> - <script src="~/Content/js/mods.js?r=20181021"></script> + <script src="~/Content/js/mods.js?r=20181109"></script> <script> $(function() { var data = @Json.Serialize(Model.Mods, new JsonSerializerSettings { Formatting = Formatting.None }); @@ -29,14 +29,22 @@ </div> <div id="app"> - <div> - <label for="search-box">Search: </label> - <input type="text" id="search-box" v-model="search" v-on:input="applySearch" /> - </div> - <div id="show-fields-option"> - <input type="checkbox" id="show-all-fields" v-model="showAllFields" /> - <label for="show-all-fields">show advanced fields</label> + <div id="options"> + <div> + <label for="search-box">Search: </label> + <input type="text" id="search-box" v-model="search" v-on:input="applyFilters" /> + </div> + <div id="filter-area"> + <input type="checkbox" id="show-advanced" v-model="showAdvanced" /> + <label for="show-advanced">show detailed options</label> + <div id="filters" v-show="showAdvanced"> + <div v-for="(filterGroup, key) in filters"> + {{key}}: <span v-for="filter in filterGroup" v-bind:class="{ active: filter.value }"><input type="checkbox" v-bind:id="filter.id" v-model="filter.value" v-on:change="applyFilters" /> <label v-bind:for="filter.id">{{filter.label}}</label></span> + </div> + </div> + </div> </div> + <div id="mod-count" v-show="showAdvanced">{{visibleCount}} mods shown.</div> <table class="wikitable" id="mod-list"> <thead> <tr> @@ -44,8 +52,8 @@ <th>links</th> <th>author</th> <th>compatibility</th> - <th v-show="showAllFields">broke in</th> - <th v-show="showAllFields">code</th> + <th v-show="showAdvanced">broke in</th> + <th v-show="showAdvanced">code</th> <th> </th> </tr> </thead> @@ -72,8 +80,8 @@ </div> <div v-for="(warning, i) in mod.Warnings">⚠ {{warning}}</div> </td> - <td class="mod-broke-in" v-html="mod.BetaCompatibility ? mod.BetaCompatibility.BrokeIn : mod.Compatibility.BrokeIn" v-show="showAllFields"></td> - <td v-show="showAllFields"> + <td class="mod-broke-in" v-html="mod.BetaCompatibility ? mod.BetaCompatibility.BrokeIn : mod.Compatibility.BrokeIn" v-show="showAdvanced"></td> + <td v-show="showAdvanced"> <span v-if="mod.SourceUrl"><a v-bind:href="mod.SourceUrl">source</a></span> <span v-else class="mod-closed-source">no source</span> </td> diff --git a/src/SMAPI.Web/wwwroot/Content/css/log-parser.css b/src/SMAPI.Web/wwwroot/Content/css/log-parser.css index 1fcd1bff..2f3dd0a1 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/log-parser.css +++ b/src/SMAPI.Web/wwwroot/Content/css/log-parser.css @@ -63,6 +63,10 @@ table#metadata, table#mods { box-shadow: 1px 1px 1px 1px #dddddd; } +.invisible { + visibility: hidden; +} + #mods { min-width: 400px; } diff --git a/src/SMAPI.Web/wwwroot/Content/css/mods.css b/src/SMAPI.Web/wwwroot/Content/css/mods.css index 9f82e3e6..730bfc2e 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/mods.css +++ b/src/SMAPI.Web/wwwroot/Content/css/mods.css @@ -18,7 +18,6 @@ table.wikitable { background-color:#f8f9fa; color:#222; - margin:1em 0; border:1px solid #a2a9b1; border-collapse:collapse } @@ -40,10 +39,40 @@ table.wikitable > caption { font-weight:bold } -#show-fields-option { +#options { + margin-bottom: 1em; +} + +#options #filter-area { opacity: 0.7; } +#options #filters { + margin-left: 2em; + padding-left: 0.5em; + border-left: 2px solid gray; +} + +#options #filters span { + padding: 2px; + margin: 2px; + display: inline-block; + border-radius: 3px; + color: #000; + border-color: #880000; + background-color: #fcc; + font-size: 0.9em; +} + +#options #filters span.active { + background: #cfc; +} + +#mod-count { + font-size: 0.8em; + opacity: 0.5; +} + #mod-list { font-size: 0.9em; } @@ -79,22 +108,22 @@ table.wikitable > caption { display: block; } -#mod-list tr[data-status="Ok"], -#mod-list tr[data-status="Optional"] { +#mod-list tr[data-status="ok"], +#mod-list tr[data-status="optional"] { background: #BFB; } -#mod-list tr[data-status="Workaround"], -#mod-list tr[data-status="Unofficial"] { +#mod-list tr[data-status="workaround"], +#mod-list tr[data-status="unofficial"] { background: #FFFEC6; } -#mod-list tr[data-status="Broken"] { +#mod-list tr[data-status="broken"] { background: #FBB; } -#mod-list tr[data-status="Obsolete"], -#mod-list tr[data-status="Abandoned"] { +#mod-list tr[data-status="obsolete"], +#mod-list tr[data-status="abandoned"] { background: #BBB; opacity: 0.7; } diff --git a/src/SMAPI.Web/wwwroot/Content/js/mods.js b/src/SMAPI.Web/wwwroot/Content/js/mods.js index 1af53906..2cff551f 100644 --- a/src/SMAPI.Web/wwwroot/Content/js/mods.js +++ b/src/SMAPI.Web/wwwroot/Content/js/mods.js @@ -6,7 +6,76 @@ smapi.modList = function (mods) { // init data var data = { mods: mods, - showAllFields: false, + visibleCount: mods.length, + showAdvanced: false, + filters: { + source: { + open: { + label: "open", + id: "show-open-source", + value: true + }, + closed: { + label: "closed", + id: "show-closed-source", + value: true + } + }, + status: { + ok: { + label: "ok", + id: "show-status-ok", + value: true + }, + optional: { + label: "optional", + id: "show-status-optional", + value: true + }, + unofficial: { + label: "unofficial", + id: "show-status-unofficial", + value: true + }, + workaround: { + label: "workaround", + id: "show-status-workaround", + value: true + }, + broken: { + label: "broken", + id: "show-status-broken", + value: true + }, + abandoned: { + label: "abandoned", + id: "show-status-abandoned", + value: true + }, + obsolete: { + label: "obsolete", + id: "show-status-obsolete", + value: true + } + }, + download: { + chucklefish: { + label: "Chucklefish", + id: "show-chucklefish", + value: true + }, + nexus: { + label: "Nexus", + id: "show-nexus", + value: true + }, + custom: { + label: "custom", + id: "show-custom", + value: true + } + } + }, search: "" }; for (var i = 0; i < data.mods.length; i++) { @@ -54,25 +123,82 @@ smapi.modList = function (mods) { }, methods: { /** - * Update the visibility of all mods based on the current search text. + * Update the visibility of all mods based on the current search text and filters. */ - applySearch: function () { + applyFilters: function () { // get search terms var words = data.search.toLowerCase().split(" "); - // make sure all words match + // apply criteria + data.visibleCount = data.mods.length; for (var i = 0; i < data.mods.length; i++) { var mod = data.mods[i]; - var match = true; - for (var w = 0; w < words.length; w++) { - if (mod.SearchableText.indexOf(words[w]) === -1) { - match = false; + mod.Visible = true; + + // check filters + if (!this.matchesFilters(mod)) { + mod.Visible = false; + data.visibleCount--; + continue; + } + + // check search terms (all search words should match) + if (words.length) { + for (var w = 0; w < words.length; w++) { + if (mod.SearchableText.indexOf(words[w]) === -1) { + mod.Visible = false; + data.visibleCount--; + break; + } + } + } + } + }, + + + /** + * Get whether a mod matches the current filters. + * @param {object} mod The mod to check. + * @returns {bool} Whether the mod matches the filters. + */ + matchesFilters: function(mod) { + var filters = data.filters; + + // check source + if (!filters.source.open.value && mod.SourceUrl) + return false; + if (!filters.source.closed.value && !mod.SourceUrl) + return false; + + // check status + var status = (mod.BetaCompatibility || mod.Compatibility).Status; + if (filters.status[status] && !filters.status[status].value) + return false; + + // check download sites + var ignoreSites = []; + + if (!filters.download.chucklefish.value) + ignoreSites.push("Chucklefish"); + if (!filters.download.nexus.value) + ignoreSites.push("Nexus"); + if (!filters.download.custom.value) + ignoreSites.push("custom"); + + if (ignoreSites.length) { + var anyLeft = false; + for (var i = 0; i < mod.ModPageSites.length; i++) { + if (ignoreSites.indexOf(mod.ModPageSites[i]) === -1) { + anyLeft = true; break; } } - mod.Visible = match; + if (!anyLeft) + return false; } + + return true; } } }); diff --git a/src/SMAPI/Framework/Networking/MultiplayerPeer.cs b/src/SMAPI/Framework/Networking/MultiplayerPeer.cs index 7f0fa4f7..44a71978 100644 --- a/src/SMAPI/Framework/Networking/MultiplayerPeer.cs +++ b/src/SMAPI/Framework/Networking/MultiplayerPeer.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Lidgren.Network; using StardewValley.Network; namespace StardewModdingAPI.Framework.Networking @@ -12,14 +11,8 @@ namespace StardewModdingAPI.Framework.Networking /********* ** Properties *********/ - /// <summary>The server through which to send messages, if this is an incoming farmhand.</summary> - private readonly SLidgrenServer Server; - - /// <summary>The client through which to send messages, if this is the host player.</summary> - private readonly SLidgrenClient Client; - - /// <summary>The network connection to the player.</summary> - private readonly NetConnection ServerConnection; + /// <summary>A method which sends a message to the peer.</summary> + private readonly Action<OutgoingMessage> SendMessageImpl; /********* @@ -53,11 +46,9 @@ namespace StardewModdingAPI.Framework.Networking /// <summary>Construct an instance.</summary> /// <param name="playerID">The player's unique ID.</param> /// <param name="model">The metadata to copy.</param> - /// <param name="server">The server through which to send messages.</param> - /// <param name="serverConnection">The server connection through which to send messages.</param> - /// <param name="client">The client through which to send messages.</param> + /// <param name="sendMessage">A method which sends a message to the peer.</param> /// <param name="isHost">Whether this is a connection to the host player.</param> - public MultiplayerPeer(long playerID, RemoteContextModel model, SLidgrenServer server, NetConnection serverConnection, SLidgrenClient client, bool isHost) + public MultiplayerPeer(long playerID, RemoteContextModel model, Action<OutgoingMessage> sendMessage, bool isHost) { this.PlayerID = playerID; this.IsHost = isHost; @@ -68,43 +59,7 @@ namespace StardewModdingAPI.Framework.Networking this.ApiVersion = model.ApiVersion; this.Mods = model.Mods.Select(mod => new MultiplayerPeerMod(mod)).ToArray(); } - this.Server = server; - this.ServerConnection = serverConnection; - this.Client = client; - } - - /// <summary>Construct an instance for a connection to an incoming farmhand.</summary> - /// <param name="playerID">The player's unique ID.</param> - /// <param name="model">The metadata to copy, if available.</param> - /// <param name="server">The server through which to send messages.</param> - /// <param name="serverConnection">The server connection through which to send messages.</param> - public static MultiplayerPeer ForConnectionToFarmhand(long playerID, RemoteContextModel model, SLidgrenServer server, NetConnection serverConnection) - { - return new MultiplayerPeer( - playerID: playerID, - model: model, - server: server, - serverConnection: serverConnection, - client: null, - isHost: false - ); - } - - /// <summary>Construct an instance for a connection to the host player.</summary> - /// <param name="playerID">The player's unique ID.</param> - /// <param name="model">The metadata to copy.</param> - /// <param name="client">The client through which to send messages.</param> - /// <param name="isHost">Whether this connection is for the host player.</param> - public static MultiplayerPeer ForConnectionToHost(long playerID, RemoteContextModel model, SLidgrenClient client, bool isHost) - { - return new MultiplayerPeer( - playerID: playerID, - model: model, - server: null, - serverConnection: null, - client: client, - isHost: isHost - ); + this.SendMessageImpl = sendMessage; } /// <summary>Get metadata for a mod installed by the player.</summary> @@ -123,10 +78,7 @@ namespace StardewModdingAPI.Framework.Networking /// <param name="message">The message to send.</param> public void SendMessage(OutgoingMessage message) { - if (this.IsHost) - this.Client.sendMessage(message); - else - this.Server.SendMessage(this.ServerConnection, message); + this.SendMessageImpl(message); } } } diff --git a/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs b/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs new file mode 100644 index 00000000..fddd423d --- /dev/null +++ b/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs @@ -0,0 +1,52 @@ +using System; +using Galaxy.Api; +using StardewValley.Network; +using StardewValley.SDKs; + +namespace StardewModdingAPI.Framework.Networking +{ + /// <summary>A multiplayer client used to connect to a hosted server. This is an implementation of <see cref="GalaxyNetClient"/> with callbacks for SMAPI functionality.</summary> + internal class SGalaxyNetClient : GalaxyNetClient + { + /********* + ** Properties + *********/ + /// <summary>A callback to raise when receiving a message. This receives the incoming message, a method to send an arbitrary message, and a callback to run the default logic.</summary> + private readonly Action<IncomingMessage, Action<OutgoingMessage>, Action> OnProcessingMessage; + + /// <summary>A callback to raise when sending a message. This receives the outgoing message, a method to send an arbitrary message, and a callback to resume the default logic.</summary> + private readonly Action<OutgoingMessage, Action<OutgoingMessage>, Action> OnSendingMessage; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="address">The remote address being connected.</param> + /// <param name="onProcessingMessage">A callback to raise when receiving a message. This receives the incoming message, a method to send an arbitrary message, and a callback to run the default logic.</param> + /// <param name="onSendingMessage">A callback to raise when sending a message. This receives the outgoing message, a method to send an arbitrary message, and a callback to resume the default logic.</param> + public SGalaxyNetClient(GalaxyID address, Action<IncomingMessage, Action<OutgoingMessage>, Action> onProcessingMessage, Action<OutgoingMessage, Action<OutgoingMessage>, Action> onSendingMessage) + : base(address) + { + this.OnProcessingMessage = onProcessingMessage; + this.OnSendingMessage = onSendingMessage; + } + + /// <summary>Send a message to the connected peer.</summary> + public override void sendMessage(OutgoingMessage message) + { + this.OnSendingMessage(message, base.sendMessage, () => base.sendMessage(message)); + } + + + /********* + ** Protected methods + *********/ + /// <summary>Process an incoming network message.</summary> + /// <param name="message">The message to process.</param> + protected override void processIncomingMessage(IncomingMessage message) + { + this.OnProcessingMessage(message, base.sendMessage, () => base.processIncomingMessage(message)); + } + } +} diff --git a/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs b/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs new file mode 100644 index 00000000..2fc92737 --- /dev/null +++ b/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs @@ -0,0 +1,63 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Galaxy.Api; +using StardewValley.Network; +using StardewValley.SDKs; + +namespace StardewModdingAPI.Framework.Networking +{ + /// <summary>A multiplayer server used to connect to an incoming player. This is an implementation of <see cref="LidgrenServer"/> that adds support for SMAPI's metadata context exchange.</summary> + internal class SGalaxyNetServer : GalaxyNetServer + { + /********* + ** Properties + *********/ + /// <summary>A callback to raise when receiving a message. This receives the incoming message, a method to send a message, and a callback to run the default logic.</summary> + private readonly Action<IncomingMessage, Action<OutgoingMessage>, Action> OnProcessingMessage; + + /// <summary>SMAPI's implementation of the game's core multiplayer logic.</summary> + private readonly SMultiplayer Multiplayer; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="gameServer">The underlying game server.</param> + /// <param name="multiplayer">SMAPI's implementation of the game's core multiplayer logic.</param> + /// <param name="onProcessingMessage">A callback to raise when receiving a message. This receives the incoming message, a method to send a message, and a callback to run the default logic.</param> + public SGalaxyNetServer(IGameServer gameServer, SMultiplayer multiplayer, Action<IncomingMessage, Action<OutgoingMessage>, Action> onProcessingMessage) + : base(gameServer) + { + this.Multiplayer = multiplayer; + this.OnProcessingMessage = onProcessingMessage; + } + + /// <summary>Read and process a message from the client.</summary> + /// <param name="peer">The Galaxy peer ID.</param> + /// <param name="messageStream">The data to process.</param> + [SuppressMessage("ReSharper", "AccessToDisposedClosure", Justification = "The callback is invoked synchronously.")] + protected override void onReceiveMessage(GalaxyID peer, Stream messageStream) + { + using (IncomingMessage message = new IncomingMessage()) + using (BinaryReader reader = new BinaryReader(messageStream)) + { + message.Read(reader); + this.OnProcessingMessage(message, outgoing => this.sendMessage(peer, outgoing), () => + { + if (this.peers.ContainsLeft(message.FarmerID) && (long)this.peers[message.FarmerID] == (long)peer.ToUint64()) + { + this.gameServer.processIncomingMessage(message); + } + else if (message.MessageType == StardewValley.Multiplayer.playerIntroduction) + { + NetFarmerRoot farmer = this.Multiplayer.readFarmer(message.Reader); + GalaxyID capturedPeer = new GalaxyID(peer.ToUint64()); + this.gameServer.checkFarmhandRequest(Convert.ToString(peer.ToUint64()), farmer, msg => this.sendMessage(capturedPeer, msg), () => this.peers[farmer.Value.UniqueMultiplayerID] = capturedPeer.ToUint64()); + } + }); + } + } + } +} diff --git a/src/SMAPI/Framework/Networking/SLidgrenClient.cs b/src/SMAPI/Framework/Networking/SLidgrenClient.cs index c05e6b76..02d9d68f 100644 --- a/src/SMAPI/Framework/Networking/SLidgrenClient.cs +++ b/src/SMAPI/Framework/Networking/SLidgrenClient.cs @@ -9,20 +9,21 @@ namespace StardewModdingAPI.Framework.Networking /********* ** Properties *********/ - /// <summary>A callback to raise when receiving a message. This receives the client instance, incoming message, and a callback to run the default logic.</summary> - private readonly Action<SLidgrenClient, IncomingMessage, Action> OnProcessingMessage; + /// <summary>A callback to raise when receiving a message. This receives the incoming message, a method to send an arbitrary message, and a callback to run the default logic.</summary> + private readonly Action<IncomingMessage, Action<OutgoingMessage>, Action> OnProcessingMessage; + + /// <summary>A callback to raise when sending a message. This receives the outgoing message, a method to send an arbitrary message, and a callback to resume the default logic.</summary> + private readonly Action<OutgoingMessage, Action<OutgoingMessage>, Action> OnSendingMessage; - /// <summary>A callback to raise when sending a message. This receives the client instance, outgoing message, and a callback to run the default logic.</summary> - private readonly Action<SLidgrenClient, OutgoingMessage, Action> OnSendingMessage; /********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="address">The remote address being connected.</param> - /// <param name="onProcessingMessage">A callback to raise when receiving a message. This receives the client instance, incoming message, and a callback to run the default logic.</param> - /// <param name="onSendingMessage">A callback to raise when sending a message. This receives the client instance, outgoing message, and a callback to run the default logic.</param> - public SLidgrenClient(string address, Action<SLidgrenClient, IncomingMessage, Action> onProcessingMessage, Action<SLidgrenClient, OutgoingMessage, Action> onSendingMessage) + /// <param name="onProcessingMessage">A callback to raise when receiving a message. This receives the incoming message, a method to send an arbitrary message, and a callback to run the default logic.</param> + /// <param name="onSendingMessage">A callback to raise when sending a message. This receives the outgoing message, a method to send an arbitrary message, and a callback to resume the default logic.</param> + public SLidgrenClient(string address, Action<IncomingMessage, Action<OutgoingMessage>, Action> onProcessingMessage, Action<OutgoingMessage, Action<OutgoingMessage>, Action> onSendingMessage) : base(address) { this.OnProcessingMessage = onProcessingMessage; @@ -32,7 +33,7 @@ namespace StardewModdingAPI.Framework.Networking /// <summary>Send a message to the connected peer.</summary> public override void sendMessage(OutgoingMessage message) { - this.OnSendingMessage(this, message, () => base.sendMessage(message)); + this.OnSendingMessage(message, base.sendMessage, () => base.sendMessage(message)); } @@ -43,7 +44,7 @@ namespace StardewModdingAPI.Framework.Networking /// <param name="message">The message to process.</param> protected override void processIncomingMessage(IncomingMessage message) { - this.OnProcessingMessage(this, message, () => base.processIncomingMessage(message)); + this.OnProcessingMessage(message, base.sendMessage, () => base.processIncomingMessage(message)); } } } diff --git a/src/SMAPI/Framework/Networking/SLidgrenServer.cs b/src/SMAPI/Framework/Networking/SLidgrenServer.cs index 060b433b..37e546a1 100644 --- a/src/SMAPI/Framework/Networking/SLidgrenServer.cs +++ b/src/SMAPI/Framework/Networking/SLidgrenServer.cs @@ -1,11 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Reflection; using Lidgren.Network; -using StardewModdingAPI.Framework.Reflection; -using StardewModdingAPI.Patches; -using StardewValley; using StardewValley.Network; namespace StardewModdingAPI.Framework.Networking @@ -16,133 +12,53 @@ namespace StardewModdingAPI.Framework.Networking /********* ** Properties *********/ + /// <summary>SMAPI's implementation of the game's core multiplayer logic.</summary> + private readonly SMultiplayer Multiplayer; - /// <summary>The constructor for the internal <c>NetBufferReadStream</c> type.</summary> - private readonly ConstructorInfo NetBufferReadStreamConstructor = SLidgrenServer.GetNetBufferReadStreamConstructor(); - - /// <summary>The constructor for the internal <c>NetBufferWriteStream</c> type.</summary> - private readonly ConstructorInfo NetBufferWriteStreamConstructor = SLidgrenServer.GetNetBufferWriteStreamConstructor(); - - /// <summary>A method which reads farmer data from the given binary reader.</summary> - private readonly Func<BinaryReader, NetFarmerRoot> ReadFarmer; - - /// <summary>A callback to raise when receiving a message. This receives the server instance, raw/parsed incoming message, and a callback to run the default logic.</summary> - private readonly Action<SLidgrenServer, NetIncomingMessage, IncomingMessage, Action> OnProcessingMessage; - - /// <summary>A callback to raise when sending a message. This receives the server instance, outgoing connection, outgoing message, target player ID, and a callback to run the default logic.</summary> - private readonly Action<SLidgrenServer, NetConnection, OutgoingMessage, Action> OnSendingMessage; - - /// <summary>The peer connections.</summary> - private readonly Bimap<long, NetConnection> Peers; - - /// <summary>The underlying net server.</summary> - private readonly IReflectedField<NetServer> Server; + /// <summary>A callback to raise when receiving a message. This receives the incoming message, a method to send a message, and a callback to run the default logic.</summary> + private readonly Action<IncomingMessage, Action<OutgoingMessage>, Action> OnProcessingMessage; /********* ** Public methods *********/ /// <summary>Construct an instance.</summary> + /// <param name="multiplayer">SMAPI's implementation of the game's core multiplayer logic.</param> /// <param name="gameServer">The underlying game server.</param> - /// <param name="reflection">Simplifies access to private code.</param> - /// <param name="readFarmer">A method which reads farmer data from the given binary reader.</param> - /// <param name="onProcessingMessage">A callback to raise when receiving a message. This receives the server instance, raw/parsed incoming message, and a callback to run the default logic.</param> - /// <param name="onSendingMessage">A callback to raise when sending a message. This receives the server instance, outgoing connection, outgoing message, and a callback to run the default logic.</param> - public SLidgrenServer(IGameServer gameServer, Reflector reflection, Func<BinaryReader, NetFarmerRoot> readFarmer, Action<SLidgrenServer, NetIncomingMessage, IncomingMessage, Action> onProcessingMessage, Action<SLidgrenServer, NetConnection, OutgoingMessage, Action> onSendingMessage) + /// <param name="onProcessingMessage">A callback to raise when receiving a message. This receives the incoming message, a method to send a message, and a callback to run the default logic.</param> + public SLidgrenServer(IGameServer gameServer, SMultiplayer multiplayer, Action<IncomingMessage, Action<OutgoingMessage>, Action> onProcessingMessage) : base(gameServer) { - this.ReadFarmer = readFarmer; + this.Multiplayer = multiplayer; this.OnProcessingMessage = onProcessingMessage; - this.OnSendingMessage = onSendingMessage; - this.Peers = reflection.GetField<Bimap<long, NetConnection>>(this, "peers").GetValue(); - this.Server = reflection.GetField<NetServer>(this, "server"); - } - - /// <summary>Send a message to a remote server.</summary> - /// <param name="connection">The network connection.</param> - /// <param name="message">The message to send.</param> - /// <remarks>This is an implementation of <see cref="LidgrenServer.sendMessage(NetConnection, OutgoingMessage)"/> which calls <see cref="OnSendingMessage"/>. This method is invoked via <see cref="LidgrenServerPatch.Prefix_LidgrenServer_SendMessage"/>.</remarks> - public void SendMessage(NetConnection connection, OutgoingMessage message) - { - this.OnSendingMessage(this, connection, message, () => - { - NetServer server = this.Server.GetValue(); - NetOutgoingMessage netMessage = server.CreateMessage(); - using (Stream bufferWriteStream = (Stream)this.NetBufferWriteStreamConstructor.Invoke(new object[] { netMessage })) - using (BinaryWriter writer = new BinaryWriter(bufferWriteStream)) - message.Write(writer); - - server.SendMessage(netMessage, connection, NetDeliveryMethod.ReliableOrdered); - }); } /// <summary>Parse a data message from a client.</summary> /// <param name="rawMessage">The raw network message to parse.</param> - /// <remarks>This is an implementation of <see cref="LidgrenServer.parseDataMessageFromClient"/> which calls <see cref="OnProcessingMessage"/>. This method is invoked via <see cref="LidgrenServerPatch.Prefix_LidgrenServer_ParseDataMessageFromClient"/>.</remarks> [SuppressMessage("ReSharper", "AccessToDisposedClosure", Justification = "The callback is invoked synchronously.")] - public bool ParseDataMessageFromClient(NetIncomingMessage rawMessage) + protected override void parseDataMessageFromClient(NetIncomingMessage rawMessage) { // add hook to call multiplayer core NetConnection peer = rawMessage.SenderConnection; using (IncomingMessage message = new IncomingMessage()) - using (Stream readStream = (Stream)this.NetBufferReadStreamConstructor.Invoke(new object[] { rawMessage })) + using (Stream readStream = new NetBufferReadStream(rawMessage)) using (BinaryReader reader = new BinaryReader(readStream)) { while (rawMessage.LengthBits - rawMessage.Position >= 8) { message.Read(reader); - this.OnProcessingMessage(this, rawMessage, message, () => + this.OnProcessingMessage(message, outgoing => this.sendMessage(rawMessage.SenderConnection, outgoing), () => { - if (this.Peers.ContainsLeft(message.FarmerID) && this.Peers[message.FarmerID] == peer) + if (this.peers.ContainsLeft(message.FarmerID) && this.peers[message.FarmerID] == peer) this.gameServer.processIncomingMessage(message); - else if (message.MessageType == Multiplayer.playerIntroduction) + else if (message.MessageType == StardewValley.Multiplayer.playerIntroduction) { - NetFarmerRoot farmer = this.ReadFarmer(message.Reader); - this.gameServer.checkFarmhandRequest("", farmer, msg => this.SendMessage(peer, msg), () => this.Peers[farmer.Value.UniqueMultiplayerID] = peer); + NetFarmerRoot farmer = this.Multiplayer.readFarmer(message.Reader); + this.gameServer.checkFarmhandRequest("", farmer, msg => this.sendMessage(peer, msg), () => this.peers[farmer.Value.UniqueMultiplayerID] = peer); } }); } } - - return false; - } - - - /********* - ** Private methods - *********/ - /// <summary>Get the constructor for the internal <c>NetBufferReadStream</c> type.</summary> - private static ConstructorInfo GetNetBufferReadStreamConstructor() - { - // get type - string typeName = $"StardewValley.Network.NetBufferReadStream, {Constants.GameAssemblyName}"; - Type type = Type.GetType(typeName); - if (type == null) - throw new InvalidOperationException($"Can't find type: {typeName}"); - - // get constructor - ConstructorInfo constructor = type.GetConstructor(new[] { typeof(NetBuffer) }); - if (constructor == null) - throw new InvalidOperationException($"Can't find constructor for type: {typeName}"); - - return constructor; - } - - /// <summary>Get the constructor for the internal <c>NetBufferWriteStream</c> type.</summary> - private static ConstructorInfo GetNetBufferWriteStreamConstructor() - { - // get type - string typeName = $"StardewValley.Network.NetBufferWriteStream, {Constants.GameAssemblyName}"; - Type type = Type.GetType(typeName); - if (type == null) - throw new InvalidOperationException($"Can't find type: {typeName}"); - - // get constructor - ConstructorInfo constructor = type.GetConstructor(new[] { typeof(NetBuffer) }); - if (constructor == null) - throw new InvalidOperationException($"Can't find constructor for type: {typeName}"); - - return constructor; } } } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 7e5b6e02..128659c7 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -136,6 +136,10 @@ namespace StardewModdingAPI.Framework this.EventManager = new EventManager(this.Monitor, this.ModRegistry); this.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry); + // redirect direct console output + if (this.MonitorForGame.WriteToConsole) + this.ConsoleManager.OnMessageIntercepted += message => this.HandleConsoleMessage(this.MonitorForGame, message); + // inject deprecation managers SemanticVersion.DeprecationManager = this.DeprecationManager; @@ -165,8 +169,7 @@ namespace StardewModdingAPI.Framework // apply game patches new GamePatcher(this.Monitor).Apply( - new DialogueErrorPatch(this.MonitorForGame, this.Reflection), - new LidgrenServerPatch() + new DialogueErrorPatch(this.MonitorForGame, this.Reflection) ); } @@ -344,10 +347,6 @@ namespace StardewModdingAPI.Framework /// <summary>Initialise SMAPI and mods after the game starts.</summary> private void InitialiseAfterGameStart() { - // redirect direct console output - if (this.MonitorForGame.WriteToConsole) - this.ConsoleManager.OnMessageIntercepted += message => this.HandleConsoleMessage(this.MonitorForGame, message); - // add headers if (this.Settings.DeveloperMode) this.Monitor.Log($"You configured SMAPI to run in developer mode. The console may be much more verbose. You can disable developer mode by installing the non-developer version of SMAPI, or by editing {Constants.ApiConfigPath}.", LogLevel.Info); diff --git a/src/SMAPI/Framework/SMultiplayer.cs b/src/SMAPI/Framework/SMultiplayer.cs index dc9b8b68..629fce1d 100644 --- a/src/SMAPI/Framework/SMultiplayer.cs +++ b/src/SMAPI/Framework/SMultiplayer.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using Lidgren.Network; +using Galaxy.Api; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using StardewModdingAPI.Events; @@ -12,6 +12,7 @@ using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Toolkit.Serialisation; using StardewValley; using StardewValley.Network; +using StardewValley.SDKs; namespace StardewModdingAPI.Framework { @@ -47,9 +48,6 @@ namespace StardewModdingAPI.Framework /// <summary>Manages SMAPI events.</summary> private readonly EventManager EventManager; - /// <summary>The players who are currently disconnecting.</summary> - private readonly IList<long> DisconnectingFarmers; - /// <summary>A callback to invoke when a mod message is received.</summary> private readonly Action<ModMessageModel> OnModMessageReceived; @@ -82,8 +80,6 @@ namespace StardewModdingAPI.Framework this.ModRegistry = modRegistry; this.Reflection = reflection; this.OnModMessageReceived = onModMessageReceived; - - this.DisconnectingFarmers = reflection.GetField<List<long>>(this, "disconnectingFarmers").GetValue(); } /// <summary>Handle sync messages from other players and perform other initial sync logic.</summary> @@ -106,46 +102,53 @@ namespace StardewModdingAPI.Framework /// <param name="client">The client to initialise.</param> public override Client InitClient(Client client) { - if (client is LidgrenClient) + switch (client) { - string address = this.Reflection.GetField<string>(client, "address").GetValue(); - return new SLidgrenClient(address, this.OnClientProcessingMessage, this.OnClientSendingMessage); - } + case LidgrenClient _: + { + string address = this.Reflection.GetField<string>(client, "address").GetValue(); + return new SLidgrenClient(address, this.OnClientProcessingMessage, this.OnClientSendingMessage); + } + + case GalaxyNetClient _: + { + GalaxyID address = this.Reflection.GetField<GalaxyID>(client, "lobbyId").GetValue(); + return new SGalaxyNetClient(address, this.OnClientProcessingMessage, this.OnClientSendingMessage); + } - return client; + default: + return client; + } } /// <summary>Initialise a server before the game connects to an incoming player.</summary> /// <param name="server">The server to initialise.</param> public override Server InitServer(Server server) { - if (server is LidgrenServer) + switch (server) { - IGameServer gameServer = this.Reflection.GetField<IGameServer>(server, "gameServer").GetValue(); - return new SLidgrenServer(gameServer, this.Reflection, this.readFarmer, this.OnServerProcessingMessage, this.OnServerSendingMessage); - } - - return server; - } + case LidgrenServer _: + { + IGameServer gameServer = this.Reflection.GetField<IGameServer>(server, "gameServer").GetValue(); + return new SLidgrenServer(gameServer, this, this.OnServerProcessingMessage); + } - /// <summary>A callback raised when sending a network message as the host player.</summary> - /// <param name="server">The server sending the message.</param> - /// <param name="connection">The connection to which a message is being sent.</param> - /// <param name="message">The message being sent.</param> - /// <param name="resume">Send the underlying message.</param> - protected void OnServerSendingMessage(SLidgrenServer server, NetConnection connection, OutgoingMessage message, Action resume) - { - if (this.Monitor.IsVerbose) - this.Monitor.Log($"SERVER SEND {(MessageType)message.MessageType} {message.FarmerID}", LogLevel.Trace); + case GalaxyNetServer _: + { + IGameServer gameServer = this.Reflection.GetField<IGameServer>(server, "gameServer").GetValue(); + return new SGalaxyNetServer(gameServer, this, this.OnServerProcessingMessage); + } - resume(); + default: + return server; + } } /// <summary>A callback raised when sending a message as a farmhand.</summary> - /// <param name="client">The client sending the message.</param> /// <param name="message">The message being sent.</param> - /// <param name="resume">Send the underlying message.</param> - protected void OnClientSendingMessage(SLidgrenClient client, OutgoingMessage message, Action resume) + /// <param name="sendMessage">Send an arbitrary message through the client.</param> + /// <param name="resume">Resume sending the underlying message.</param> + protected void OnClientSendingMessage(OutgoingMessage message, Action<OutgoingMessage> sendMessage, Action resume) { if (this.Monitor.IsVerbose) this.Monitor.Log($"CLIENT SEND {(MessageType)message.MessageType} {message.FarmerID}", LogLevel.Trace); @@ -154,7 +157,7 @@ namespace StardewModdingAPI.Framework { // sync mod context (step 1) case (byte)MessageType.PlayerIntroduction: - client.sendMessage((byte)MessageType.ModContext, this.GetContextSyncMessageFields()); + sendMessage(new OutgoingMessage((byte)MessageType.ModContext, Game1.player.UniqueMultiplayerID, this.GetContextSyncMessageFields())); resume(); break; @@ -166,11 +169,10 @@ namespace StardewModdingAPI.Framework } /// <summary>Process an incoming network message as the host player.</summary> - /// <param name="server">The server instance that received the connection.</param> - /// <param name="rawMessage">The raw network message that was received.</param> /// <param name="message">The message to process.</param> + /// <param name="sendMessage">A method which sends the given message to the client.</param> /// <param name="resume">Process the message using the game's default logic.</param> - public void OnServerProcessingMessage(SLidgrenServer server, NetIncomingMessage rawMessage, IncomingMessage message, Action resume) + public void OnServerProcessingMessage(IncomingMessage message, Action<OutgoingMessage> sendMessage, Action resume) { if (this.Monitor.IsVerbose) this.Monitor.Log($"SERVER RECV {(MessageType)message.MessageType} {message.FarmerID}", LogLevel.Trace); @@ -185,7 +187,7 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Received context for farmhand {message.FarmerID} running {(model != null ? $"SMAPI {model.ApiVersion} with {model.Mods.Length} mods" : "vanilla")}.", LogLevel.Trace); // store peer - MultiplayerPeer newPeer = MultiplayerPeer.ForConnectionToFarmhand(message.FarmerID, model, server, rawMessage.SenderConnection); + MultiplayerPeer newPeer = new MultiplayerPeer(message.FarmerID, model, sendMessage, isHost: false); if (this.Peers.ContainsKey(message.FarmerID)) { this.Monitor.Log($"Rejected mod context from farmhand {message.FarmerID}: already received context for that player.", LogLevel.Error); @@ -226,7 +228,7 @@ namespace StardewModdingAPI.Framework if (!this.Peers.ContainsKey(message.FarmerID)) { this.Monitor.Log($"Received connection for vanilla player {message.FarmerID}.", LogLevel.Trace); - MultiplayerPeer peer = MultiplayerPeer.ForConnectionToFarmhand(message.FarmerID, null, server, rawMessage.SenderConnection); + MultiplayerPeer peer = new MultiplayerPeer(message.FarmerID, null, sendMessage, isHost: false); this.AddPeer(peer, canBeHost: false); } @@ -245,11 +247,11 @@ namespace StardewModdingAPI.Framework } /// <summary>Process an incoming network message as a farmhand.</summary> - /// <param name="client">The client instance that received the connection.</param> /// <param name="message">The message to process.</param> - /// <param name="resume">Process the message using the game's default logic.</param> + /// <param name="sendMessage">Send an arbitrary message through the client.</param> + /// <param name="resume">Resume processing the message using the game's default logic.</param> /// <returns>Returns whether the message was handled.</returns> - public void OnClientProcessingMessage(SLidgrenClient client, IncomingMessage message, Action resume) + public void OnClientProcessingMessage(IncomingMessage message, Action<OutgoingMessage> sendMessage, Action resume) { if (this.Monitor.IsVerbose) this.Monitor.Log($"CLIENT RECV {(MessageType)message.MessageType} {message.FarmerID}", LogLevel.Trace); @@ -264,7 +266,7 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Received context for {(model?.IsHost == true ? "host" : "farmhand")} {message.FarmerID} running {(model != null ? $"SMAPI {model.ApiVersion} with {model.Mods.Length} mods" : "vanilla")}.", LogLevel.Trace); // store peer - MultiplayerPeer peer = MultiplayerPeer.ForConnectionToHost(message.FarmerID, model, client, model?.IsHost ?? this.HostPeer == null); + MultiplayerPeer peer = new MultiplayerPeer(message.FarmerID, model, sendMessage, isHost: model?.IsHost ?? this.HostPeer == null); if (peer.IsHost && this.HostPeer != null) { this.Monitor.Log($"Rejected mod context from host player {peer.PlayerID}: already received host data from {(peer.PlayerID == this.HostPeer.PlayerID ? "that player" : $"player {peer.PlayerID}")}.", LogLevel.Error); @@ -281,7 +283,7 @@ namespace StardewModdingAPI.Framework if (!this.Peers.ContainsKey(message.FarmerID) && this.HostPeer == null) { this.Monitor.Log($"Received connection for vanilla host {message.FarmerID}.", LogLevel.Trace); - this.AddPeer(MultiplayerPeer.ForConnectionToHost(message.FarmerID, null, client, isHost: true), canBeHost: false); + this.AddPeer(new MultiplayerPeer(message.FarmerID, null, sendMessage, isHost: true), canBeHost: false); } resume(); break; @@ -293,7 +295,7 @@ namespace StardewModdingAPI.Framework // store peer if (!this.Peers.TryGetValue(message.FarmerID, out MultiplayerPeer peer)) { - peer = MultiplayerPeer.ForConnectionToHost(message.FarmerID, null, client, isHost: this.HostPeer == null); + peer = new MultiplayerPeer(message.FarmerID, null, sendMessage, isHost: this.HostPeer == null); this.Monitor.Log($"Received connection for vanilla {(peer.IsHost ? "host" : "farmhand")} {message.FarmerID}.", LogLevel.Trace); this.AddPeer(peer, canBeHost: true); } @@ -316,7 +318,7 @@ namespace StardewModdingAPI.Framework /// <summary>Remove players who are disconnecting.</summary> protected override void removeDisconnectedFarmers() { - foreach (long playerID in this.DisconnectingFarmers) + foreach (long playerID in this.disconnectingFarmers) { if (this.Peers.TryGetValue(playerID, out MultiplayerPeer peer)) { diff --git a/src/SMAPI/Patches/LidgrenServerPatch.cs b/src/SMAPI/Patches/LidgrenServerPatch.cs deleted file mode 100644 index 6f937665..00000000 --- a/src/SMAPI/Patches/LidgrenServerPatch.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Reflection; -using Harmony; -using Lidgren.Network; -using StardewModdingAPI.Framework; -using StardewModdingAPI.Framework.Networking; -using StardewModdingAPI.Framework.Patching; -using StardewValley; -using StardewValley.Network; - -namespace StardewModdingAPI.Patches -{ - /// <summary>A Harmony patch to let SMAPI override <see cref="LidgrenServer"/> methods.</summary> - internal class LidgrenServerPatch : IHarmonyPatch - { - /********* - ** Accessors - *********/ - /// <summary>A unique name for this patch.</summary> - public string Name => $"{nameof(LidgrenServerPatch)}"; - - - /********* - ** Public methods - *********/ - /// <summary>Apply the Harmony patch.</summary> - /// <param name="harmony">The Harmony instance.</param> - public void Apply(HarmonyInstance harmony) - { - // override parseDataMessageFromClient - { - MethodInfo method = AccessTools.Method(typeof(LidgrenServer), "parseDataMessageFromClient"); - MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(LidgrenServerPatch.Prefix_LidgrenServer_ParseDataMessageFromClient)); - harmony.Patch(method, new HarmonyMethod(prefix), null); - } - - // override sendMessage - { - MethodInfo method = typeof(LidgrenServer).GetMethod("sendMessage", BindingFlags.NonPublic | BindingFlags.Instance, null, new [] { typeof(NetConnection), typeof(OutgoingMessage) }, null); - MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(LidgrenServerPatch.Prefix_LidgrenServer_SendMessage)); - harmony.Patch(method, new HarmonyMethod(prefix), null); - } - } - - - /********* - ** Private methods - *********/ - /// <summary>The method to call instead of the <see cref="LidgrenServer.parseDataMessageFromClient"/> method.</summary> - /// <param name="__instance">The instance being patched.</param> - /// <param name="dataMsg">The raw network message to parse.</param> - /// <param name="___peers">The private <c>peers</c> field on the <paramref name="__instance"/> instance.</param> - /// <param name="___gameServer">The private <c>gameServer</c> field on the <paramref name="__instance"/> instance.</param> - /// <returns>Returns whether to execute the original method.</returns> - /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] - private static bool Prefix_LidgrenServer_ParseDataMessageFromClient(LidgrenServer __instance, NetIncomingMessage dataMsg, Bimap<long, NetConnection> ___peers, IGameServer ___gameServer) - { - if (__instance is SLidgrenServer smapiServer) - { - smapiServer.ParseDataMessageFromClient(dataMsg); - return false; - } - - return true; - } - - /// <summary>The method to call instead of the <see cref="LidgrenServer.sendMessage"/> method.</summary> - /// <param name="__instance">The instance being patched.</param> - /// <param name="connection">The connection to which to send the message.</param> - /// <param name="___peers">The private <c>peers</c> field on the <paramref name="__instance"/> instance.</param> - /// <param name="___gameServer">The private <c>gameServer</c> field on the <paramref name="__instance"/> instance.</param> - /// <returns>Returns whether to execute the original method.</returns> - /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] - private static bool Prefix_LidgrenServer_SendMessage(LidgrenServer __instance, NetConnection connection, OutgoingMessage message, Bimap<long, NetConnection> ___peers, IGameServer ___gameServer) - { - if (__instance is SLidgrenServer smapiServer) - { - smapiServer.SendMessage(connection, message); - return false; - } - - return true; - } - } -} diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index 7f6444d1..70fe2bed 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -55,9 +55,7 @@ </PropertyGroup> <ItemGroup> <PackageReference Include="LargeAddressAware" Version="1.0.3" /> - <PackageReference Include="Lib.Harmony"> - <Version>1.2.0.1</Version> - </PackageReference> + <PackageReference Include="Lib.Harmony" Version="1.2.0.1" /> <PackageReference Include="Mono.Cecil" Version="0.10.1" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> </ItemGroup> @@ -176,13 +174,15 @@ <Compile Include="Framework\Events\ModPlayerEvents.cs" /> <Compile Include="Framework\Events\ModSpecialisedEvents.cs" /> <Compile Include="Framework\Events\ModWorldEvents.cs" /> - <Compile Include="Framework\Networking\MessageType.cs" /> <Compile Include="Framework\ModHelpers\DataHelper.cs" /> + <Compile Include="Framework\Networking\MessageType.cs" /> <Compile Include="Framework\Networking\ModMessageModel.cs" /> <Compile Include="Framework\Networking\MultiplayerPeer.cs" /> <Compile Include="Framework\Networking\MultiplayerPeerMod.cs" /> <Compile Include="Framework\Networking\RemoteContextModel.cs" /> <Compile Include="Framework\Networking\RemoteContextModModel.cs" /> + <Compile Include="Framework\Networking\SGalaxyNetClient.cs" /> + <Compile Include="Framework\Networking\SGalaxyNetServer.cs" /> <Compile Include="Framework\Networking\SLidgrenClient.cs" /> <Compile Include="Framework\Networking\SLidgrenServer.cs" /> <Compile Include="Framework\SCore.cs" /> @@ -325,7 +325,6 @@ <Compile Include="Metadata\InstructionMetadata.cs" /> <Compile Include="Mod.cs" /> <Compile Include="Patches\DialogueErrorPatch.cs" /> - <Compile Include="Patches\LidgrenServerPatch.cs" /> <Compile Include="PatchMode.cs" /> <Compile Include="GamePlatform.cs" /> <Compile Include="Program.cs" /> |