Archipelago/SNIClient.py

741 lines
28 KiB
Python
Raw Normal View History

2021-11-01 18:37:47 +00:00
from __future__ import annotations
2021-11-14 20:14:22 +00:00
import sys
2021-07-30 22:03:48 +00:00
import threading
import time
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
import multiprocessing
import os
import subprocess
import base64
2021-08-29 15:38:35 +00:00
import logging
import asyncio
import enum
import typing
2021-02-21 22:54:08 +00:00
from json import loads, dumps
# CommonClient import first to trigger ModuleUpdater
from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser
import Utils
from Utils import async_start
from MultiServer import mark_raw
if typing.TYPE_CHECKING:
from worlds.AutoSNIClient import SNIClient
2021-11-10 14:35:43 +00:00
if __name__ == "__main__":
Utils.init_logging("SNIClient", exception_logger="Client")
2019-12-09 18:27:56 +00:00
2020-01-18 14:04:39 +00:00
import colorama
from websockets.client import connect as websockets_connect, WebSocketClientProtocol
from websockets.exceptions import WebSocketException, ConnectionClosed
2021-07-30 22:03:48 +00:00
snes_logger = logging.getLogger("SNES")
2021-11-01 18:37:47 +00:00
2021-11-11 15:09:08 +00:00
class DeathState(enum.IntEnum):
killing_player = 1
alive = 2
dead = 3
class SNIClientCommandProcessor(ClientCommandProcessor):
ctx: SNIContext
2021-11-01 18:37:47 +00:00
def _cmd_slow_mode(self, toggle: str = "") -> None:
2021-04-01 09:40:58 +00:00
"""Toggle slow mode, which limits how fast you send / receive items."""
if toggle:
self.ctx.slow_mode = toggle.lower() in {"1", "true", "on"}
else:
self.ctx.slow_mode = not self.ctx.slow_mode
self.output(f"Setting slow mode to {self.ctx.slow_mode}")
@mark_raw
def _cmd_snes(self, snes_options: str = "") -> bool:
2021-11-01 18:37:47 +00:00
"""Connect to a snes. Optionally include network address of a snes to connect to,
otherwise show available devices; and a SNES device number if more than one SNES is detected.
Examples: "/snes", "/snes 1", "/snes localhost:23074 1" """
2023-02-07 09:16:39 +00:00
if self.ctx.snes_state in {SNESState.SNES_ATTACHED, SNESState.SNES_CONNECTED, SNESState.SNES_CONNECTING}:
self.output("Already connected to SNES. Disconnecting first.")
self._cmd_snes_close()
return self.connect_to_snes(snes_options)
def connect_to_snes(self, snes_options: str = "") -> bool:
snes_address = self.ctx.snes_address
snes_device_number = -1
2021-11-01 18:37:47 +00:00
options = snes_options.split()
num_options = len(options)
2021-11-01 18:37:47 +00:00
if num_options > 1:
snes_address = options[0]
snes_device_number = int(options[1])
elif num_options > 0:
snes_device_number = int(options[0])
2021-04-01 09:40:58 +00:00
self.ctx.snes_reconnect_address = None
if self.ctx.snes_connect_task:
self.ctx.snes_connect_task.cancel()
self.ctx.snes_connect_task = asyncio.create_task(snes_connect(self.ctx, snes_address, snes_device_number),
name="SNES Connect")
2021-04-01 09:40:58 +00:00
return True
2021-04-01 09:40:58 +00:00
def _cmd_snes_close(self) -> bool:
"""Close connection to a currently connected snes"""
self.ctx.snes_reconnect_address = None
self.ctx.cancel_snes_autoreconnect()
self.ctx.snes_state = SNESState.SNES_DISCONNECTED
2023-02-07 09:16:39 +00:00
if self.ctx.snes_socket and not self.ctx.snes_socket.closed:
async_start(self.ctx.snes_socket.close())
2021-04-01 09:40:58 +00:00
return True
else:
return False
# Left here for quick re-addition for debugging.
# def _cmd_snes_write(self, address, data):
# """Write the specified byte (base10) to the SNES' memory address (base16)."""
# if self.ctx.snes_state != SNESState.SNES_ATTACHED:
# self.output("No attached SNES Device.")
# return False
# snes_buffered_write(self.ctx, int(address, 16), bytes([int(data)]))
# async_start(snes_flush_writes(self.ctx))
# self.output("Data Sent")
# return True
2021-11-11 15:09:08 +00:00
# def _cmd_snes_read(self, address, size=1):
# """Read the SNES' memory address (base16)."""
# if self.ctx.snes_state != SNESState.SNES_ATTACHED:
# self.output("No attached SNES Device.")
# return False
# data = await snes_read(self.ctx, int(address, 16), size)
# self.output(f"Data Read: {data}")
# return True
2021-11-02 10:11:57 +00:00
class SNIContext(CommonContext):
command_processor: typing.Type[SNIClientCommandProcessor] = SNIClientCommandProcessor
lufia2ac: new features, bug fixes, and more (#1549) ### New features - ***Architect mode*** Usually the cave is randomized by the game, meaning that each attempt will produce a different dungeon. However, with this new feature the player can, between runs, opt into keeping the same cave. If activated, they will then encounter the same floor layouts, same enemy spawns, and same red chest contents as on their previous attempt. - ***Custom item pool*** Previously, the multiworld item pool consisted entirely of random blue chest items because, well, the permanent checks are blue chests and that's what one would normally get from these. While blue chest items often greatly increase your odds against regular enemies, being able to defeat the Master can be contingent on having an appropriate equipment setup of red chest items (such as Dekar blade) or even enemy drops (such as Hidora rock), most of which cannot normally be obtained from blue chests. With the custom item pool option, players now have the freedom to place any cave item into the multiworld itempool for their world. - ***Enemy floor number, enemy sprite, and enemy movement pattern randomization*** Experienced players can deduce a lot of information about the opposition they will be facing, for example: Given the current floor number, one can know in advance which of the enemy types will have a chance to spawn on that floor. And when seeing a particular enemy sprite, one can already know which enemy types one might have to face in battle if one were to come in contact with it, and also how that enemy group will move through the dungeon. Three new randomization options are added for players who want to spice up their game: one can shuffle which enemy types appear on which floor, one can shuffle which sprite is used by which enemy type, and one can shuffle which movement pattern is used by which sprite. - ***EXP modifier*** Just a simple multiplier option to allow people to level up faster. (For technical reasons, the maximum amount of EXP that can be awarded for a single enemy is limited to 65535, but even with the maximum allowed modifier of 500% there are only 6 enemy types in the cave that can reach this cap.) ### Balance change - ***proportionally adjust chest type distribution to accommodate increased blue chest chance*** One of the main problems that became apparent in the current version has to do with the distribution of chest contents. The game considers 6 categories, namely: consumable (mostly non-restorative), consumable (restorative), blue chest item, spell, gear, and weapon. Since only blue chests count as multiworld locations, we want to have a mechanism to customize the blue chest chance. Given how the chest types are detetermined in game, a naive implementation of an increased blue chest chance causes only the consumable chance to be decreased in return. In practice, this has resulted in some players of worlds with a high blue chest chance struggling (more than usual) to keep their party alive because they were always low on comsumables that restore HP and MP. The new algorithm tries to avoid this one-sided effect by having an increase in blue chest chance resulting in a decrease of all other types, calculated in such a way that the relative distribution of the other 5 categories stays (approximately) the same. ### Bug fixes - ***prevent using party member items if character is already in party*** This should have been changed at the same time that 6eb00621e39c930f5746f5f3c69a6bc19cd0e84a was made, but oh well... - ***fix glitched sprite when opening a chest immediately after receiving an item*** When opening a chest right after receiving a multiworld item (such that there were two item get animations in the exact same iteration of the game main loop), the item from the chest would display an incorrect sprite in the wrong place. Fixed by cleaning up some relevant memory addresses after getting the multiworld item. - ***fix death link*** There was a condition in `deathlink_kill_player` that looked kinda smart (it checked the time against `last_death_link`), but actually wasn't smart at all because `deathlink_kill_player` is executed as an async task and the main thread will update `last_death_link` after creating the task, meaning that whether or not the incoming death link would actually be passed to the game seems to have been up to a race condition. Fixed by simply removing that check. ### Other - ***add Lufia II Ancient Cave (and SMW) to the network diagram*** These two games were missing from the SNES sector. - ***implement get_filler_item_name*** Place a restorative consumable instead of a completely random item. (Now the only known problem with item links in lufia2ac is... that noone has ever tested item links. But this should be an improvement at least. Anyway, now #1172 can come ;) And btw., if you think that the implementation of random selection in this method looks weird, that's because it is indeed weird. (It tries to recreate the algorithm that the game itself uses when it generates a replacement item for a chest that would contain a spell that the party already knows.) - ***store all options in a dataclass*** This is basically like using #993 (but without actual support from core). It makes the lufia2ac world code much nicer to maintain because one doesn't have to change 5 different places anymore when adding or renaming an option. - ***remove master_hp.scale*** I have to admit: `scale` was a mistake. Never have I seen a single option value cause so many user misconceptions. Some people assume it affects enemies other than the Master; some people assume it affects stats other than HP; and many people will just assume it is a magic option that will somehow counterbalance whatever settings combination they are currently trying to shoot themselves in the foot with. On top of that, the `scale` mechanism probably doesn't provide a good user experience even when used for its intended purpose (since having reached floor XY in general doesn't mean you will have the power to deplete XY% of the Masters usual HP; especially given that, due to the randomness of loot, you are never guaranteed to be able to defeat the vanilla Master even when you have cleared 100% of the floors). The intended target audience of the `master_hp` option are people who want to fight the Master (and know how to fight it), but also want to lessen (to a degree of their choosing) the harsh dependence on the specific equipment setups that are usually required to win this fight even when having done all 99 floors. They can achieve this by setting the `master_hp` option to a numeric value appropriate for the level of challenge they are seeking. Therefore, nothing of value should be lost by removing the special `scale` value from the `master_hp` option, while at the same time a major source of user confusion will be eliminated. - ***typing*** This (combined with the switch to the option dataclass) greatly reduces the typing problems in the lufia2ac world. The remaining typing errors mostly fall into 4 categories: 1. Lambdas with defaults (which seem to be incorrectly reported as an error due to a mypy bug) 1. Classmethods that return instances (which could probably be improved using PEP 673 "Self" types, but that would require Python 3.11 as the minimum supported version) 1. Everything that inherits from TextChoice (which is a typing mess in core) 1. Everything related to asar.py (which does not have proper typing and lies outside of this project) ## How was this tested? https://discord.com/channels/731205301247803413/1080852357442707476 and others
2023-03-20 16:04:57 +00:00
game: typing.Optional[str] = None # set in validate_rom
items_handling: typing.Optional[int] = None # set in game_watcher
snes_connect_task: "typing.Optional[asyncio.Task[None]]" = None
snes_autoreconnect_task: typing.Optional["asyncio.Task[None]"] = None
snes_address: str
snes_socket: typing.Optional[WebSocketClientProtocol]
snes_state: SNESState
snes_attached_device: typing.Optional[typing.Tuple[int, str]]
snes_reconnect_address: typing.Optional[str]
snes_recv_queue: "asyncio.Queue[bytes]"
snes_request_lock: asyncio.Lock
snes_write_buffer: typing.List[typing.Tuple[int, bytes]]
snes_connector_lock: threading.Lock
death_state: DeathState
killing_player_task: "typing.Optional[asyncio.Task[None]]"
allow_collect: bool
slow_mode: bool
client_handler: typing.Optional[SNIClient]
awaiting_rom: bool
rom: typing.Optional[bytes]
prev_rom: typing.Optional[bytes]
hud_message_queue: typing.List[str] # TODO: str is a guess, is this right?
death_link_allow_survive: bool
def __init__(self, snes_address: str, server_address: str, password: str) -> None:
super(SNIContext, self).__init__(server_address, password)
2019-12-09 18:27:56 +00:00
2021-04-01 09:40:58 +00:00
# snes stuff
self.snes_address = snes_address
2019-12-09 18:27:56 +00:00
self.snes_socket = None
self.snes_state = SNESState.SNES_DISCONNECTED
2020-01-13 02:55:33 +00:00
self.snes_attached_device = None
self.snes_reconnect_address = None
2019-12-09 18:27:56 +00:00
self.snes_recv_queue = asyncio.Queue()
self.snes_request_lock = asyncio.Lock()
self.snes_write_buffer = []
2021-07-30 22:03:48 +00:00
self.snes_connector_lock = threading.Lock()
2021-11-11 15:09:08 +00:00
self.death_state = DeathState.alive # for death link flop behaviour
2021-11-11 20:32:42 +00:00
self.killing_player_task = None
self.allow_collect = False
self.slow_mode = False
2019-12-09 18:27:56 +00:00
self.client_handler = None
self.awaiting_rom = False
self.rom = None
self.prev_rom = None
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
async def connection_closed(self) -> None:
await super(SNIContext, self).connection_closed()
2021-04-01 09:40:58 +00:00
self.awaiting_rom = False
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
def event_invalid_slot(self) -> typing.NoReturn:
2021-04-01 09:40:58 +00:00
if self.snes_socket is not None and not self.snes_socket.closed:
async_start(self.snes_socket.close())
raise Exception("Invalid ROM detected, "
"please verify that you have loaded the correct rom and reconnect your snes (/snes)")
2021-04-01 09:40:58 +00:00
async def server_auth(self, password_requested: bool = False) -> None:
2021-04-01 09:40:58 +00:00
if password_requested and not self.password:
await super(SNIContext, self).server_auth(password_requested)
2021-04-01 09:40:58 +00:00
if self.rom is None:
self.awaiting_rom = True
2021-07-30 22:03:48 +00:00
snes_logger.info(
"No ROM detected, awaiting snes connection to authenticate to the multiworld server (/snes)")
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
return
2021-04-01 09:40:58 +00:00
self.awaiting_rom = False
# TODO: This looks kind of hacky...
# Context.auth is meant to be the "name" parameter in send_connect,
# which has to be a str (bytes is not json serializable).
# But here, Context.auth is being used for something else
# (where it has to be bytes because it is compared with rom elsewhere).
# If we need to save something to compare with rom elsewhere,
# it should probably be in a different variable,
# and let auth be used for what it's meant for.
2021-04-01 09:40:58 +00:00
self.auth = self.rom
auth = base64.b64encode(self.rom).decode()
await self.send_connect(name=auth)
2021-11-01 18:37:47 +00:00
def cancel_snes_autoreconnect(self) -> bool:
if self.snes_autoreconnect_task:
self.snes_autoreconnect_task.cancel()
self.snes_autoreconnect_task = None
return True
return False
def on_deathlink(self, data: typing.Dict[str, typing.Any]) -> None:
2021-11-11 20:32:42 +00:00
if not self.killing_player_task or self.killing_player_task.done():
self.killing_player_task = asyncio.create_task(deathlink_kill_player(self))
super(SNIContext, self).on_deathlink(data)
async def handle_deathlink_state(self, currently_dead: bool, death_text: str = "") -> None:
2021-11-12 13:58:48 +00:00
# in this state we only care about triggering a death send
if self.death_state == DeathState.alive:
if currently_dead:
self.death_state = DeathState.dead
await self.send_death(death_text)
2021-11-12 13:58:48 +00:00
# in this state we care about confirming a kill, to move state to dead
elif self.death_state == DeathState.killing_player:
# this is being handled in deathlink_kill_player(ctx) already
pass
# in this state we wait until the player is alive again
elif self.death_state == DeathState.dead:
if not currently_dead:
self.death_state = DeathState.alive
async def shutdown(self) -> None:
await super(SNIContext, self).shutdown()
self.cancel_snes_autoreconnect()
if self.snes_connect_task:
try:
await asyncio.wait_for(self.snes_connect_task, 1)
except asyncio.TimeoutError:
self.snes_connect_task.cancel()
def on_package(self, cmd: str, args: typing.Dict[str, typing.Any]) -> None:
if cmd in {"Connected", "RoomUpdate"}:
if "checked_locations" in args and args["checked_locations"]:
new_locations = set(args["checked_locations"])
self.checked_locations |= new_locations
self.locations_scouted |= new_locations
# Items belonging to the player should not be marked as checked in game,
# since the player will likely need that item.
# Once the games handled by SNIClient gets made to be remote items,
# this will no longer be needed.
async_start(self.send_msgs([{"cmd": "LocationScouts", "locations": list(new_locations)}]))
if self.client_handler is not None:
self.client_handler.on_package(self, cmd, args)
def run_gui(self) -> None:
from kvui import GameManager
class SNIManager(GameManager):
logging_pairs = [
("Client", "Archipelago"),
("SNES", "SNES"),
]
base_title = "Archipelago SNI Client"
self.ui = SNIManager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") # type: ignore
async def deathlink_kill_player(ctx: SNIContext) -> None:
2021-11-11 20:07:17 +00:00
ctx.death_state = DeathState.killing_player
2021-11-11 20:32:42 +00:00
while ctx.death_state == DeathState.killing_player and \
ctx.snes_state == SNESState.SNES_ATTACHED:
if ctx.client_handler is None:
continue
await ctx.client_handler.deathlink_kill_player(ctx)
2021-11-11 20:07:17 +00:00
ctx.last_death_link = time.time()
2021-11-11 15:09:08 +00:00
_global_snes_reconnect_delay = 5
2021-11-01 18:37:47 +00:00
class SNESState(enum.IntEnum):
SNES_DISCONNECTED = 0
SNES_CONNECTING = 1
SNES_CONNECTED = 2
SNES_ATTACHED = 3
2019-12-09 18:27:56 +00:00
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
def launch_sni() -> None:
sni_path = Utils.get_settings()["sni_options"]["sni_path"]
2019-12-09 18:27:56 +00:00
2021-07-07 01:45:27 +00:00
if not os.path.isdir(sni_path):
sni_path = Utils.local_path(sni_path)
if os.path.isdir(sni_path):
dir_entry: "os.DirEntry[str]"
for dir_entry in os.scandir(sni_path):
if dir_entry.is_file():
lower_file = dir_entry.name.lower()
if (lower_file.startswith("sni.") and not lower_file.endswith(".proto")) or (lower_file == "sni"):
sni_path = dir_entry.path
break
2021-07-07 01:45:27 +00:00
if os.path.isfile(sni_path):
2021-07-30 22:03:48 +00:00
snes_logger.info(f"Attempting to start {sni_path}")
2021-11-09 11:53:05 +00:00
import sys
if not sys.stdout: # if it spawns a visible console, may as well populate it
subprocess.Popen(os.path.abspath(sni_path), cwd=os.path.dirname(sni_path))
2021-07-30 23:40:27 +00:00
else:
2022-03-31 03:08:15 +00:00
proc = subprocess.Popen(os.path.abspath(sni_path), cwd=os.path.dirname(sni_path),
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try:
proc.wait(.1) # wait a bit to see if startup fails (missing dependencies)
snes_logger.info('Failed to start SNI. Try running it externally for error output.')
except subprocess.TimeoutExpired:
pass # seems to be running
else:
2021-07-30 22:03:48 +00:00
snes_logger.info(
2021-07-07 01:45:27 +00:00
f"Attempt to start SNI was aborted as path {sni_path} was not found, "
f"please start it yourself if it is not running")
2019-12-09 18:27:56 +00:00
async def _snes_connect(ctx: SNIContext, address: str, retry: bool = True) -> WebSocketClientProtocol:
2020-01-13 02:55:33 +00:00
address = f"ws://{address}" if "://" not in address else address
2021-07-30 22:03:48 +00:00
snes_logger.info("Connecting to SNI at %s ..." % address)
seen_problems: typing.Set[str] = set()
while True:
try:
snes_socket = await websockets_connect(address, ping_timeout=None, ping_interval=None)
except Exception as e:
problem = "%s" % e
# only tell the user about new problems, otherwise silently lay in wait for a working connection
if problem not in seen_problems:
seen_problems.add(problem)
2021-07-30 22:03:48 +00:00
snes_logger.error(f"Error connecting to SNI ({problem})")
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
2020-04-07 02:18:26 +00:00
if len(seen_problems) == 1:
2021-07-07 01:45:27 +00:00
# this is the first problem. Let's try launching SNI if it isn't already running
launch_sni()
await asyncio.sleep(1)
else:
return snes_socket
if not retry:
break
2020-04-12 02:44:03 +00:00
class SNESRequest(typing.TypedDict):
Opcode: str
Space: str
Operands: typing.List[str]
# TODO: When Python 3.11 is the lowest version supported, `Operands` can use `typing.NotRequired` (pep-0655)
# Then the `Operands` key doesn't need to be given for opcodes that don't use it.
async def get_snes_devices(ctx: SNIContext) -> typing.List[str]:
socket = await _snes_connect(ctx, ctx.snes_address) # establish new connection to poll
DeviceList_Request: SNESRequest = {
"Opcode": "DeviceList",
"Space": "SNES",
"Operands": []
}
await socket.send(dumps(DeviceList_Request))
2020-04-12 02:44:03 +00:00
reply: typing.Dict[str, typing.Any] = loads(await socket.recv())
devices: typing.List[str] = reply['Results'] if 'Results' in reply and len(reply['Results']) > 0 else []
2020-04-07 02:18:26 +00:00
if not devices:
2021-07-30 22:03:48 +00:00
snes_logger.info('No SNES device found. Please connect a SNES device to SNI.')
while not devices and not ctx.exit_event.is_set():
await asyncio.sleep(0.1)
await socket.send(dumps(DeviceList_Request))
reply = loads(await socket.recv())
devices = reply['Results'] if 'Results' in reply and len(reply['Results']) > 0 else []
if devices:
await verify_snes_app(socket)
await socket.close()
return sorted(devices)
2019-12-09 18:27:56 +00:00
async def verify_snes_app(socket: WebSocketClientProtocol) -> None:
AppVersion_Request = {
"Opcode": "AppVersion",
}
await socket.send(dumps(AppVersion_Request))
app: str = loads(await socket.recv())["Results"][0]
2021-11-12 13:58:48 +00:00
if "SNI" not in app:
snes_logger.warning(f"Warning: Did not find SNI as the endpoint, instead {app} was found.")
async def snes_connect(ctx: SNIContext, address: str, deviceIndex: int = -1) -> None:
global _global_snes_reconnect_delay
if ctx.snes_socket is not None and ctx.snes_state == SNESState.SNES_CONNECTED:
2021-07-30 23:40:27 +00:00
if ctx.rom:
snes_logger.error('Already connected to SNES, with rom loaded.')
else:
snes_logger.error('Already connected to SNI, likely awaiting a device.')
return
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
ctx.cancel_snes_autoreconnect()
device = None
recv_task = None
ctx.snes_state = SNESState.SNES_CONNECTING
socket = await _snes_connect(ctx, address)
ctx.snes_socket = socket
ctx.snes_state = SNESState.SNES_CONNECTED
try:
devices = await get_snes_devices(ctx)
device_count = len(devices)
2019-12-09 18:27:56 +00:00
if device_count == 1:
2020-01-13 02:55:33 +00:00
device = devices[0]
elif ctx.snes_reconnect_address:
assert ctx.snes_attached_device
2020-01-13 02:55:33 +00:00
if ctx.snes_attached_device[1] in devices:
device = ctx.snes_attached_device[1]
else:
device = devices[ctx.snes_attached_device[0]]
elif device_count > 1:
if deviceIndex == -1:
snes_logger.info(f"Found {device_count} SNES devices. "
f"Connect to one with /snes <address> <device number>. For example /snes {address} 1")
for idx, availableDevice in enumerate(devices):
2021-08-29 02:47:19 +00:00
snes_logger.info(str(idx + 1) + ": " + availableDevice)
elif (deviceIndex < 0) or (deviceIndex - 1) > device_count:
2021-08-29 02:47:19 +00:00
snes_logger.warning("SNES device number out of range")
else:
device = devices[deviceIndex - 1]
2021-11-01 18:37:47 +00:00
if device is None:
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
await snes_disconnect(ctx)
return
2021-07-30 22:03:48 +00:00
snes_logger.info("Attaching to " + device)
2019-12-09 18:27:56 +00:00
Attach_Request: SNESRequest = {
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
"Opcode": "Attach",
"Space": "SNES",
"Operands": [device]
2019-12-09 18:27:56 +00:00
}
await ctx.snes_socket.send(dumps(Attach_Request))
ctx.snes_state = SNESState.SNES_ATTACHED
2020-01-13 02:55:33 +00:00
ctx.snes_attached_device = (devices.index(device), device)
ctx.snes_reconnect_address = address
2019-12-09 18:27:56 +00:00
recv_task = asyncio.create_task(snes_recv_loop(ctx))
except Exception as e:
2023-02-07 09:16:39 +00:00
ctx.snes_state = SNESState.SNES_DISCONNECTED
if task_alive(recv_task):
2019-12-09 18:27:56 +00:00
if not ctx.snes_socket.closed:
await ctx.snes_socket.close()
else:
if ctx.snes_socket is not None:
if not ctx.snes_socket.closed:
await ctx.snes_socket.close()
ctx.snes_socket = None
2023-02-07 09:16:39 +00:00
snes_logger.error(f"Error connecting to snes ({e}), retrying in {_global_snes_reconnect_delay} seconds")
ctx.snes_autoreconnect_task = asyncio.create_task(snes_autoreconnect(ctx), name="snes auto-reconnect")
_global_snes_reconnect_delay *= 2
else:
_global_snes_reconnect_delay = ctx.starting_reconnect_delay
snes_logger.info(f"Attached to {device}")
2020-03-13 02:53:20 +00:00
async def snes_disconnect(ctx: SNIContext) -> None:
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
if ctx.snes_socket:
if not ctx.snes_socket.closed:
await ctx.snes_socket.close()
ctx.snes_socket = None
2023-02-07 09:16:39 +00:00
def task_alive(task: typing.Optional[asyncio.Task]) -> bool:
if task:
return not task.done()
return False
async def snes_autoreconnect(ctx: SNIContext) -> None:
await asyncio.sleep(_global_snes_reconnect_delay)
2023-02-07 09:16:39 +00:00
if not ctx.snes_socket and not task_alive(ctx.snes_connect_task):
address = ctx.snes_reconnect_address if ctx.snes_reconnect_address else ctx.snes_address
ctx.snes_connect_task = asyncio.create_task(snes_connect(ctx, address), name="SNES Connect")
2019-12-09 18:27:56 +00:00
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
async def snes_recv_loop(ctx: SNIContext) -> None:
2019-12-09 18:27:56 +00:00
try:
if ctx.snes_socket is None:
raise Exception("invalid context state - snes_socket not connected")
2019-12-09 18:27:56 +00:00
async for msg in ctx.snes_socket:
ctx.snes_recv_queue.put_nowait(typing.cast(bytes, msg))
2021-07-30 22:03:48 +00:00
snes_logger.warning("Snes disconnected")
2019-12-09 18:27:56 +00:00
except Exception as e:
if not isinstance(e, WebSocketException):
2021-07-30 22:03:48 +00:00
snes_logger.exception(e)
snes_logger.error("Lost connection to the snes, type /snes to reconnect")
2019-12-09 18:27:56 +00:00
finally:
socket, ctx.snes_socket = ctx.snes_socket, None
if socket is not None and not socket.closed:
await socket.close()
ctx.snes_state = SNESState.SNES_DISCONNECTED
2019-12-09 18:27:56 +00:00
ctx.snes_recv_queue = asyncio.Queue()
ctx.hud_message_queue = []
ctx.rom = None
2019-12-09 18:27:56 +00:00
2020-01-13 02:55:33 +00:00
if ctx.snes_reconnect_address:
snes_logger.info(f"... automatically reconnecting to snes in {_global_snes_reconnect_delay} seconds")
assert ctx.snes_autoreconnect_task is None
ctx.snes_autoreconnect_task = asyncio.create_task(snes_autoreconnect(ctx), name="snes auto-reconnect")
2020-01-13 02:55:33 +00:00
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
async def snes_read(ctx: SNIContext, address: int, size: int) -> typing.Optional[bytes]:
2019-12-09 18:27:56 +00:00
try:
await ctx.snes_request_lock.acquire()
if (
ctx.snes_state != SNESState.SNES_ATTACHED or
ctx.snes_socket is None or
not ctx.snes_socket.open or
ctx.snes_socket.closed
):
2019-12-09 18:27:56 +00:00
return None
GetAddress_Request: SNESRequest = {
"Opcode": "GetAddress",
"Space": "SNES",
"Operands": [hex(address)[2:], hex(size)[2:]]
2019-12-09 18:27:56 +00:00
}
try:
await ctx.snes_socket.send(dumps(GetAddress_Request))
except ConnectionClosed:
2019-12-09 18:27:56 +00:00
return None
data: bytes = bytes()
2019-12-09 18:27:56 +00:00
while len(data) < size:
try:
data += await asyncio.wait_for(ctx.snes_recv_queue.get(), 5)
except asyncio.TimeoutError:
break
if len(data) != size:
2021-07-30 22:03:48 +00:00
snes_logger.error('Error reading %s, requested %d bytes, received %d' % (hex(address), size, len(data)))
2019-12-09 18:27:56 +00:00
if len(data):
2021-07-30 22:03:48 +00:00
snes_logger.error(str(data))
snes_logger.warning('Communication Failure with SNI')
2019-12-09 18:27:56 +00:00
if ctx.snes_socket is not None and not ctx.snes_socket.closed:
await ctx.snes_socket.close()
return None
return data
finally:
ctx.snes_request_lock.release()
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
async def snes_write(ctx: SNIContext, write_list: typing.List[typing.Tuple[int, bytes]]) -> bool:
2019-12-09 18:27:56 +00:00
try:
await ctx.snes_request_lock.acquire()
if ctx.snes_state != SNESState.SNES_ATTACHED or ctx.snes_socket is None or \
not ctx.snes_socket.open or ctx.snes_socket.closed:
2019-12-09 18:27:56 +00:00
return False
PutAddress_Request: SNESRequest = {"Opcode": "PutAddress", "Operands": [], 'Space': 'SNES'}
2021-07-07 01:45:27 +00:00
try:
2019-12-09 18:27:56 +00:00
for address, data in write_list:
PutAddress_Request['Operands'] = [hex(address)[2:], hex(len(data))[2:]]
if ctx.snes_socket is not None:
await ctx.snes_socket.send(dumps(PutAddress_Request))
await ctx.snes_socket.send(data)
else:
snes_logger.warning(f"Could not send data to SNES: {data}")
except ConnectionClosed:
2021-07-07 01:45:27 +00:00
return False
2019-12-09 18:27:56 +00:00
return True
finally:
ctx.snes_request_lock.release()
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
def snes_buffered_write(ctx: SNIContext, address: int, data: bytes) -> None:
if ctx.snes_write_buffer and (ctx.snes_write_buffer[-1][0] + len(ctx.snes_write_buffer[-1][1])) == address:
# append to existing write command, bundling them
2019-12-09 18:27:56 +00:00
ctx.snes_write_buffer[-1] = (ctx.snes_write_buffer[-1][0], ctx.snes_write_buffer[-1][1] + data)
else:
ctx.snes_write_buffer.append((address, data))
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
async def snes_flush_writes(ctx: SNIContext) -> None:
2019-12-09 18:27:56 +00:00
if not ctx.snes_write_buffer:
return
# swap buffers
ctx.snes_write_buffer, writes = [], ctx.snes_write_buffer
await snes_write(ctx, writes)
2019-12-09 18:27:56 +00:00
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
async def game_watcher(ctx: SNIContext) -> None:
perf_counter = time.perf_counter()
2019-12-09 18:27:56 +00:00
while not ctx.exit_event.is_set():
try:
await asyncio.wait_for(ctx.watcher_event.wait(), 0.125)
except asyncio.TimeoutError:
pass
ctx.watcher_event.clear()
2019-12-09 18:27:56 +00:00
if not ctx.rom or not ctx.client_handler:
ctx.finished_game = False
ctx.death_link_allow_survive = False
from worlds.AutoSNIClient import AutoSNIClientRegister
ctx.client_handler = await AutoSNIClientRegister.get_handler(ctx)
if not ctx.client_handler:
continue
if not ctx.rom:
continue
if not ctx.prev_rom or ctx.prev_rom != ctx.rom:
ctx.locations_checked = set()
ctx.locations_scouted = set()
ctx.locations_info = {}
ctx.prev_rom = ctx.rom
2022-03-15 12:55:57 +00:00
if ctx.awaiting_rom:
await ctx.server_auth(False)
elif ctx.server is None:
snes_logger.warning("ROM detected but no active multiworld server connection. " +
"Connect using command: /connect server:port")
2022-03-15 12:55:57 +00:00
if not ctx.client_handler:
continue
2022-03-15 12:55:57 +00:00
try:
rom_validated = await ctx.client_handler.validate_rom(ctx)
except Exception as e:
snes_logger.error(f"An error occurred, see logs for details: {e}")
text_file_logger = logging.getLogger()
text_file_logger.exception(e)
rom_validated = False
2022-03-15 12:55:57 +00:00
if not rom_validated or (ctx.auth and ctx.auth != ctx.rom):
snes_logger.warning("ROM change detected, please reconnect to the multiworld server")
await ctx.disconnect(allow_autoreconnect=True)
ctx.client_handler = None
ctx.rom = None
ctx.command_processor(ctx).connect_to_snes()
continue
2022-03-15 12:55:57 +00:00
delay = 7 if ctx.slow_mode else 0
if time.perf_counter() - perf_counter < delay:
continue
2022-03-15 12:55:57 +00:00
perf_counter = time.perf_counter()
2022-03-15 12:55:57 +00:00
try:
await ctx.client_handler.game_watcher(ctx)
except Exception as e:
snes_logger.error(f"An error occurred, see logs for details: {e}")
text_file_logger = logging.getLogger()
text_file_logger.exception(e)
await snes_disconnect(ctx)
2022-03-15 12:55:57 +00:00
async def run_game(romfile: str) -> None:
auto_start = typing.cast(typing.Union[bool, str],
Utils.get_settings()["sni_options"].get("snes_rom_start", True))
if auto_start is True:
import webbrowser
webbrowser.open(romfile)
elif isinstance(auto_start, str) and os.path.isfile(auto_start):
subprocess.Popen([auto_start, romfile],
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
2021-11-01 18:37:47 +00:00
async def main() -> None:
WebUI (#100) * Object-Oriented base changes for web-ui prep * remove debug raise * optimize broadcast to serialize once * Implement WebUI socket, static assets, and classes - Still need to wrap logging functions and send output to UI - UI commands are successfully being sent to the server * GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served * Update MultiServer status when a user disconnects / reconnects * Implement colored item and hint checks, improve GUI readability * Fix improper formatting on received items * Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects * Minor text change for itemFound * Fixed a very wrong comment * Fixed client commands not working, fixed un-helpful error messages appearing in GUI * Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in * Convert WebUI to React /w Redux. WebSocket communications not yet operational. * WebUI fully converted to React / Redux. - Websocket communication operational - Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active * Restore some features lost in WebUI - Restore (found) notification on hints if the item has already been obtained - Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue * Fix a grammatical UI big causing player names to show only an apostrophe when possessive * Add support for multiple SNES Devices, and switching between them * freeze support for client * make sure flask works when frozen * UI Improvements - Hint messages now actually show a found status via ✔ and ❌ emoji - Active player name is always a different color than other players (orange for now) - Add a toggle to show only entries relevant to the active player - Added a WidgetArea - Added a notes widget * Received items now marked as relevant * Include production build for deployment * Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues. * include webUi folder in setup.py * Bugfixes for Monitor - Fix a bug causing the monitor window to grow beyond it's intended content limit - Reduced monitor content limit to 200 items - Ensured each monitor entry has a unique key * Prevent eslint from yelling at me about stupid things * Add button to collapse sidebar, press enter on empty server input to disconnect on purpose * WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output * Update WebUI to v2.2.1 - Added color to WebUI for entrance-span - Make !missing show total count at bottom of list to match /missing behavior * Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint - Also fix a bug in the WebUI causing the entrance location to always show as "somewhere" * Update WebUI color palette (this cost me $50) * allow text console input alongside web-ui * remove Flask a bit overkill for what we're doing * remove jinja2 * Update WebUI to work with new hosting mechanism * with flask gone, we no longer need subprocess shenanigans * If multiple web ui clients try to run, at least present a working console * Update MultiClient and WebUI to handle multiple clients simultaneously. - The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to - Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port. - Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab * Use proper name for WebUI * move webui into /data with other data files * make web ui optional This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that. * fix direct server start * re-add connection timer * fix indentation Co-authored-by: Chris <chris@legendserver.info>
2020-06-03 19:29:43 +00:00
multiprocessing.freeze_support()
2021-11-09 11:53:05 +00:00
parser = get_base_parser()
parser.add_argument('diff_file', default="", type=str, nargs="?",
2021-01-03 13:32:32 +00:00
help='Path to a Archipelago Binary Patch file')
parser.add_argument('--snes', default='localhost:23074', help='Address of the SNI server.')
parser.add_argument('--loglevel', default='info', choices=['debug', 'info', 'warning', 'error', 'critical'])
2019-12-09 18:27:56 +00:00
args = parser.parse_args()
2021-11-09 11:53:05 +00:00
if args.diff_file:
import Patch
logging.info("Patch file was supplied. Creating sfc rom..")
try:
meta, romfile = Patch.create_rom_file(args.diff_file)
except Exception as e:
Utils.messagebox('Error', str(e), True)
raise
args.connect = meta["server"]
logging.info(f"Wrote rom file to {romfile}")
2021-11-14 20:14:22 +00:00
if args.diff_file.endswith(".apsoe"):
import webbrowser
async_start(run_game(romfile))
await _snes_connect(SNIContext(args.snes, args.connect, args.password), args.snes, False)
webbrowser.open(f"http://www.evermizer.com/apclient/#server={meta['server']}")
2021-11-14 20:14:22 +00:00
logging.info("Starting Evermizer Client in your Browser...")
import time
time.sleep(3)
sys.exit()
elif args.diff_file.endswith(".aplttp"):
from worlds.alttp.Client import get_alttp_settings
adjustedromfile, adjusted = get_alttp_settings(romfile)
async_start(run_game(adjustedromfile if adjusted else romfile))
2021-11-14 20:14:22 +00:00
else:
async_start(run_game(romfile))
ctx = SNIContext(args.snes, args.connect, args.password)
2019-12-09 18:27:56 +00:00
if ctx.server_task is None:
2021-04-01 09:40:58 +00:00
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
2021-08-20 20:31:17 +00:00
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
2021-07-30 22:03:48 +00:00
ctx.snes_connect_task = asyncio.create_task(snes_connect(ctx, ctx.snes_address), name="SNES Connect")
2021-04-01 09:40:58 +00:00
watcher_task = asyncio.create_task(game_watcher(ctx), name="GameWatcher")
2019-12-09 18:27:56 +00:00
await ctx.exit_event.wait()
ctx.server_address = None
2020-01-13 02:55:33 +00:00
ctx.snes_reconnect_address = None
2019-12-09 18:27:56 +00:00
if ctx.snes_socket is not None and not ctx.snes_socket.closed:
await ctx.snes_socket.close()
await watcher_task
await ctx.shutdown()
2019-12-09 18:27:56 +00:00
2019-12-09 18:27:56 +00:00
if __name__ == '__main__':
colorama.init()
asyncio.run(main())
colorama.deinit()