archipelagomw / archipelago.multiclient.net Goto Github PK
View Code? Open in Web Editor NEWA client library for use with .NET based prog-langs for interfacing with Archipelago hosts.
License: MIT License
A client library for use with .NET based prog-langs for interfacing with Archipelago hosts.
License: MIT License
When calling session.Locations.GetLocationNameFromId()
for a location whose name is "CD: Siegbräu"
, I received instead "CD: Siegbr��u"
. I think maybe some part of the network/JSON infrastructure is losing the encoding information?
The example code presented in README.MD
leaves first time users with some questions:
CompleteLocationChecks
do? It might seem obvious, but I had to do a double take and look at an example client to know for sure.CompleteLocationChecks
and ScoutLocationsAsync
?receivedItemsHelper.DequeueItem()
, but does not hint to the reader as to what information within the dequeued object might be useful. What is a client responsible for doing with this info?A minimalistic example class with connection error handling, basic message handler, basic item received callback, and a "report location completion" function would go a long way to helping new devs.
As far as I can tell, the GetDataPackage
API and the data it returns isn't exposed publicly anywhere in the high-level API, and as a consequence there's no way to get an item or location's name unless it's exactly the item you most recently received. It would be useful to have access to this information one way or another.
Occasionally AllItemsReceived has a null item appended to it, preventing clients that attempt to update their received items by reading this collection from getting the actual item, while they update their internal index. Unknown what exactly causes the issue, only know that manually tracking received items in the game client and occasionally verifying it against the lib collection would trigger them to be mismatched, despite the index being the same.
Exception when trying to retrieve item name through session.Locations.GetLocationNameFromId(locationName);
Iterating through location ID's does not result in an error. Checked the spoiler log and there were no locations with the same id.
Exception in IL2CPP-to-Managed trampoline, not passing it to il2cpp: System.ArgumentException: An item with the same key has already been added. Key: 257
at System.Collections.Generic.Dictionary2[TKey,TValue].TryInsert (TKey key, TValue value, System.Collections.Generic.InsertionBehavior behavior) [0x000dd] in <986ed57b9a8f4699a3c59a69eb05944a>:0
at System.Collections.Generic.Dictionary2[TKey,TValue].Add (TKey key, TValue value) [0x00000] in <986ed57b9a8f4699a3c59a69eb05944a>:0
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement] (System.Collections.Generic.IEnumerable1[T] source, System.Func2[T,TResult] keySelector, System.Func2[T,TResult] elementSelector, System.Collections.Generic.IEqualityComparer1[T] comparer) [0x0009e] in <c8122f496f05432baca55ffb7d139a58>:0
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement] (System.Collections.Generic.IEnumerable1[T] source, System.Func2[T,TResult] keySelector, System.Func2[T,TResult] elementSelector) [0x00000] in <c8122f496f05432baca55ffb7d139a58>:0
at Archipelago.MultiClient.Net.Helpers.LocationCheckHelper.GetLocationNameFromId (System.Int32 locationId) [0x0006b] in <17d96186d9fe43bbb3a20afc3fa3c45d>:0
at TunicRandomizer.TunicArchipelagoClient.TunicArchipelagoClient.Connect (System.String host, System.Int32 port, System.String player, System.String password) [0x0013b] in <69b91c7de2bc4ae291624c6da21b8154>:0
at TunicRandomizer.Patches.MenuPatches+<>c.<pushDefault_OptionsPatch>b__11_1 () [0x00042] in <69b91c7de2bc4ae291624c6da21b8154>:0
at (wrapper dynamic-method) UnhollowerRuntimeLib.DelegateSupport.(il2cpp delegate trampoline) System.Void(intptr,UnhollowerBaseLib.Runtime.Il2CppMethodInfo*)
Should support taking in one URL string to attempt to parse.
Format is: [scheme][<user>[:<password>]@]<address>:<port>
If scheme
is not provided or is not ws://
then it should be replaced with ws://
. If user
(and subsequently, optionally, password) is provided then perhaps we can cache that and allow for a streamlined connection process?
Allow for them to be garbage collected in a safe manner if the objects lose all strong references, are disposed, or otherwise orphaned.
.NET Core 3.1 introduced a default type converter for the Version type. Serialization still works due to the NamedTupleInterchangeConverter mimicking the previous conversion semantics for serialization. However, there is no deserializer in .NET Core 3.1 which can deserialize a Version object which was serialized with the library. (So the lib can't talk to itself, how funny is that?)
Suggestion: we implement a custom Version class, finally, which meets the spec for AP and use that. Version object seems to have served its purpose and I'm only suggesting we replace it as a result of this issue.
I believe we can set up custom loggers in WebSocketSharp's WebSocket
object. We should have it log somewhere other than stderr or wherever it currently goes since in Unity games that almost always ends up in the Unity player logs which is disparate from where other errors typically go (such as stdout, modloader logs, etc.).
We can possibly provide options to allow callers to provide their own loggers and then provide another NuGet package with logger implementations for things like BepInEx
or Hollow Knight's ModdingApi
, as examples.
System.Drawing.dll
as a dependency, causing this runtime error:[Info : Console] 9/1/2022 9:33:06 AM|Error|WebSocket.messagec:0|System.IO.FileNotFoundException: Could not load file or assembly 'System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies.
File name: 'System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
at Archipelago.MultiClient.Net.Helpers.MessageLogHelper.Socket_PacketReceived (Archipelago.MultiClient.Net.ArchipelagoPacketBase packet) [0x00000] in <filename unknown>:0
at (wrapper delegate-invoke) Archipelago.MultiClient.Net.Helpers.ArchipelagoSocketHelperDelagates/PacketReceivedHandler:invoke_void__this___ArchipelagoPacketBase (Archipelago.MultiClient.Net.ArchipelagoPacketBase)
at (wrapper delegate-invoke) Archipelago.MultiClient.Net.Helpers.ArchipelagoSocketHelperDelagates/PacketReceivedHandler:invoke_void__this___ArchipelagoPacketBase (Archipelago.MultiClient.Net.ArchipelagoPacketBase)
at Archipelago.MultiClient.Net.Helpers.ArchipelagoSocketHelper.OnMessageReceived (System.Object sender, WebSocketSharp.MessageEventArgs e) [0x00000] in <filename unknown>:0
at WebSocketSharp.Ext.Emit[MessageEventArgs] (System.EventHandler`1 eventHandler, System.Object sender, WebSocketSharp.MessageEventArgs e) [0x00000] in <filename unknown>:0
at WebSocketSharp.WebSocket.messagec (WebSocketSharp.MessageEventArgs e) [0x00000] in <filename unknown>:0
[Info : Console] 9/1/2022 10:24:41 AM|Error|WebSocket.messagec:0|System.InvalidProgramException: Invalid IL code in System.Drawing.Color:get_White (): method body is empty.
at Archipelago.MultiClient.Net.Helpers.MessagePart..ctor (MessagePartType type, Archipelago.MultiClient.Net.Models.JsonMessagePart messagePart, Nullable`1 color) [0x00000] in <filename unknown>:0
at Archipelago.MultiClient.Net.Helpers.MessageLogHelper.GetMessagePart (Archipelago.MultiClient.Net.Models.JsonMessagePart part) [0x00000] in <filename unknown>:0
at System.Linq.Enumerable+<CreateSelectIterator>c__Iterator10`2[Archipelago.MultiClient.Net.Models.JsonMessagePart,Archipelago.MultiClient.Net.Helpers.MessagePart].MoveNext () [0x00000] in <filename unknown>:0
at System.Collections.Generic.List`1[Archipelago.MultiClient.Net.Helpers.MessagePart].AddEnumerable (IEnumerable`1 enumerable) [0x00000] in <filename unknown>:0
at System.Collections.Generic.List`1[Archipelago.MultiClient.Net.Helpers.MessagePart]..ctor (IEnumerable`1 collection) [0x00000] in <filename unknown>:0
at System.Linq.Enumerable.ToArray[MessagePart] (IEnumerable`1 source) [0x00000] in <filename unknown>:0
at Archipelago.MultiClient.Net.Helpers.MessageLogHelper.GetParsedData (Archipelago.MultiClient.Net.Packets.PrintJsonPacket packet) [0x00000] in <filename unknown>:0
at Archipelago.MultiClient.Net.Helpers.MessageLogHelper.TriggerOnMessageReceived (Archipelago.MultiClient.Net.Packets.PrintJsonPacket printJsonPacket) [0x00000] in <filename unknown>:0
at Archipelago.MultiClient.Net.Helpers.MessageLogHelper.Socket_PacketReceived (Archipelago.MultiClient.Net.ArchipelagoPacketBase packet) [0x00000] in <filename unknown>:0
at (wrapper delegate-invoke) Archipelago.MultiClient.Net.Helpers.ArchipelagoSocketHelperDelagates/PacketReceivedHandler:invoke_void__this___ArchipelagoPacketBase (Archipelago.MultiClient.Net.ArchipelagoPacketBase)
at (wrapper delegate-invoke) Archipelago.MultiClient.Net.Helpers.ArchipelagoSocketHelperDelagates/PacketReceivedHandler:invoke_void__this___ArchipelagoPacketBase (Archipelago.MultiClient.Net.ArchipelagoPacketBase)
at Archipelago.MultiClient.Net.Helpers.ArchipelagoSocketHelper.OnMessageReceived (System.Object sender, WebSocketSharp.MessageEventArgs e) [0x00000] in <filename unknown>:0
at WebSocketSharp.Ext.Emit[MessageEventArgs] (System.EventHandler`1 eventHandler, System.Object sender, WebSocketSharp.MessageEventArgs e) [0x00000] in <filename unknown>:0
at WebSocketSharp.WebSocket.messagec (WebSocketSharp.MessageEventArgs e) [0x00000] in <filename unknown>:0
Due to the transfer limit of 1024 bytes at
, in both sending and receiving partial unicode characters may end up being encoded/decoded, which leads to corruption of the package.The unit tests for the Data Storage are flaky as they regulairly hit the key retreival timeout
such error look like this:
Timed out retrieving data for key "Item". This may be due to an attempt to retrieve a value from the DataStorageHelper in a synchronous fashion from within a PacketReceived handler. When using the DataStorageHelper from within code which runs on the websocket thread then use the asynchronous getters. Ex: "DataStorageHelper["Item"].GetAsync().ContinueWith(x => {});" Be aware that DataStorageHelper calls tend to cause packet responses, so making a call from within a PacketReceived handler may cause an infinite loop.
Workaround: Generally re-run failed jobs on the PR will fix this issue
The versions for .Net Framework 3.5 or Net Framework 4.0 use https://github.com/sta/websocket-sharp
Websocket Sharp
this library is sort of the only option that is available for websockets that also work for unity games
Its widely used in various projects but also not really maintained and WSS support is currently broken
This might also be caused by supported TLS versions, especially for lower .net framework versions TLS support is limited
https://learn.microsoft.com/en-us/dotnet/framework/network-programming/tls
Required for 3.0 release:
I think I've tracked this down to a library issue.
When receiving exact copies of the same item from the exact same location, the produced NetItems compare identical. This causes ReceivedItemHelper:Socket_PacketReceived
to skip the duplicate items -- preventing it from being added to the itemQueue, among other things.
The first time this happens, the client is never notified of the new item. The second time it happens, a resync triggers (since Multiclient realizes it only has N items in its list when it should have N+1) and from the caller'sperspective it receives two new items: the first two items ever sent. This behavior is witnessed with ArchipelagoMW-HollowKnight/Archipelago.HollowKnight#79.
This currently can only happen with /send, !getitem, and startinventory -- all of which can have multiple instances of the same item ID, sender, recipient, and location.
AllLocationsChecked on LocationCheckHelper is only filled with the locations the game reports as checked by calling CompleteLocationChecks or CompleteLocationChecksAsync, however the initial value of locations_checked that is send on the connect package is not taken into account, neither are the updated values of locations_checked as part of RoomUpdated packages. As a result of this, !collect or coop is not properly taken into account
Using the data storage for Synchronous retrieval in code that runs on websocket thread will cause the thread to wait for a Retrieved packet, but that can never be processes as the thread itself is waiting
you can reproduce this with int x = session.DataStorage["A"] inside a session.Socket.PackedReceived() handler or other code that runs on the websocket thrread like session.Locations.CheckedLocationsUpdated() handler
A workaround is to use asynchronous retreivals in such case like session.DataStorage["A"].GetAsync(x => { })
I propose to add a max timeout to the synchronous retrieval and log and throw an error if this time is exceeded
Should an exception be raised during the method TryConnectAndLogin, such as if the Connected package coming from the server fails to parse (be it due to intentional destruction or #95), then the Exception that bubbles up out of the library is just a Timeout exception, which is confusing and wrong.
Currently the Archipelago.MultiClient.Net still uses the old outdated GetDataPackage api, where you specificy which games to exclude from retrieval, this should be change to use the new api where you specify games to query
Optionally, there was a report on discord where a user ended up with a datapackage cache of 0 bytes and this resulted in a crash. we could add additional checks to prevent such crashes and invalidate the cache in such cases
Optionally 2, With an ever growing list of games, it begins to make sense to only load the cache of the games that are actually being played on the server. we instead of the datapackagecache.archipelagocache use a folder, with one file for each game in it with the version number in the filename, so we can avoid loading it into memory when the version does not match as we know we got to update it and at the same time we could selectively only load the games we need to server the current multiworld
since the websockets packets are garenteed to preserver order it is highly recommended to requests updates for the data package before sending the connect request, this will ensure that the datapackage response is received before the connection result, and therefor if the connection is sucsesfull the datapackage will be processed before any received item packages
I recently started working on Inscryption which uses .NET Standard 2.0 and I get this error when calling ArchipelagoSessionFactory.CreateSession:
Invalid type Archipelago.MultiClient.Net.Converters.ArchipelagoPacketConverter for instance field Archipelago.MultiClient.Net.Helpers.ArchipelagoSocketHelper:Converter
at Archipelago.MultiClient.Net.ArchipelagoSessionFactory.CreateSession (System.String hostname, System.Int32 port) [0x00007] in <608ab731db5a4a4fabc8e725e7273242>:0
at Archipelago_Inscryption.Archipelago.ArchipelagoClient.CreateSession () [0x00001] in <f39eb8b90aa64a5e967d32da2dda428e>:0
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.