2023-10-03 00:44:19 +00:00
|
|
|
--[[
|
|
|
|
Copyright (c) 2023 Zunawe
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
|
|
in the Software without restriction, including without limitation the rights
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
|
|
copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
SOFTWARE.
|
|
|
|
]]
|
|
|
|
|
|
|
|
local SCRIPT_VERSION = 1
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
-- Set to log incoming requests
|
|
|
|
-- Will cause lag due to large console output
|
|
|
|
local DEBUG = false
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
--[[
|
|
|
|
This script expects to receive JSON and will send JSON back. A message should
|
|
|
|
be a list of 1 or more requests which will be executed in order. Each request
|
|
|
|
will have a corresponding response in the same order.
|
|
|
|
|
|
|
|
Every individual request and response is a JSON object with at minimum one
|
|
|
|
field `type`. The value of `type` determines what other fields may exist.
|
|
|
|
|
|
|
|
To get the script version, instead of JSON, send "VERSION" to get the script
|
|
|
|
version directly (e.g. "2").
|
|
|
|
|
|
|
|
#### Ex. 1
|
|
|
|
|
|
|
|
Request: `[{"type": "PING"}]`
|
|
|
|
|
|
|
|
Response: `[{"type": "PONG"}]`
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
#### Ex. 2
|
|
|
|
|
|
|
|
Request: `[{"type": "LOCK"}, {"type": "HASH"}]`
|
|
|
|
|
|
|
|
Response: `[{"type": "LOCKED"}, {"type": "HASH_RESPONSE", "value": "F7D18982"}]`
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
#### Ex. 3
|
|
|
|
|
|
|
|
Request:
|
|
|
|
|
|
|
|
```json
|
|
|
|
[
|
|
|
|
{"type": "GUARD", "address": 100, "expected_data": "aGVsbG8=", "domain": "System Bus"},
|
|
|
|
{"type": "READ", "address": 500, "size": 4, "domain": "ROM"}
|
|
|
|
]
|
|
|
|
```
|
|
|
|
|
|
|
|
Response:
|
|
|
|
|
|
|
|
```json
|
|
|
|
[
|
|
|
|
{"type": "GUARD_RESPONSE", "address": 100, "value": true},
|
|
|
|
{"type": "READ_RESPONSE", "value": "dGVzdA=="}
|
|
|
|
]
|
|
|
|
```
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
#### Ex. 4
|
|
|
|
|
|
|
|
Request:
|
|
|
|
|
|
|
|
```json
|
|
|
|
[
|
|
|
|
{"type": "GUARD", "address": 100, "expected_data": "aGVsbG8=", "domain": "System Bus"},
|
|
|
|
{"type": "READ", "address": 500, "size": 4, "domain": "ROM"}
|
|
|
|
]
|
|
|
|
```
|
|
|
|
|
|
|
|
Response:
|
|
|
|
|
|
|
|
```json
|
|
|
|
[
|
|
|
|
{"type": "GUARD_RESPONSE", "address": 100, "value": false},
|
|
|
|
{"type": "GUARD_RESPONSE", "address": 100, "value": false}
|
|
|
|
]
|
|
|
|
```
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
### Supported Request Types
|
|
|
|
|
|
|
|
- `PING`
|
|
|
|
Does nothing; resets timeout.
|
|
|
|
|
|
|
|
Expected Response Type: `PONG`
|
|
|
|
|
|
|
|
- `SYSTEM`
|
|
|
|
Returns the system of the currently loaded ROM (N64, GBA, etc...).
|
|
|
|
|
|
|
|
Expected Response Type: `SYSTEM_RESPONSE`
|
|
|
|
|
|
|
|
- `PREFERRED_CORES`
|
|
|
|
Returns the user's default cores for systems with multiple cores. If the
|
|
|
|
current ROM's system has multiple cores, the one that is currently
|
|
|
|
running is very probably the preferred core.
|
|
|
|
|
|
|
|
Expected Response Type: `PREFERRED_CORES_RESPONSE`
|
|
|
|
|
|
|
|
- `HASH`
|
|
|
|
Returns the hash of the currently loaded ROM calculated by BizHawk.
|
|
|
|
|
|
|
|
Expected Response Type: `HASH_RESPONSE`
|
|
|
|
|
2025-01-12 07:03:31 +00:00
|
|
|
- `MEMORY_SIZE`
|
|
|
|
Returns the size in bytes of the specified memory domain.
|
|
|
|
|
|
|
|
Expected Response Type: `MEMORY_SIZE_RESPONSE`
|
|
|
|
|
|
|
|
Additional Fields:
|
|
|
|
- `domain` (`string`): The name of the memory domain to check
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
- `GUARD`
|
|
|
|
Checks a section of memory against `expected_data`. If the bytes starting
|
|
|
|
at `address` do not match `expected_data`, the response will have `value`
|
|
|
|
set to `false`, and all subsequent requests will not be executed and
|
|
|
|
receive the same `GUARD_RESPONSE`.
|
|
|
|
|
|
|
|
Expected Response Type: `GUARD_RESPONSE`
|
|
|
|
|
|
|
|
Additional Fields:
|
|
|
|
- `address` (`int`): The address of the memory to check
|
|
|
|
- `expected_data` (string): A base64 string of contiguous data
|
|
|
|
- `domain` (`string`): The name of the memory domain the address
|
|
|
|
corresponds to
|
|
|
|
|
|
|
|
- `LOCK`
|
|
|
|
Halts emulation and blocks on incoming requests until an `UNLOCK` request
|
|
|
|
is received or the client times out. All requests processed while locked
|
|
|
|
will happen on the same frame.
|
|
|
|
|
|
|
|
Expected Response Type: `LOCKED`
|
|
|
|
|
|
|
|
- `UNLOCK`
|
|
|
|
Resumes emulation after the current list of requests is done being
|
|
|
|
executed.
|
|
|
|
|
|
|
|
Expected Response Type: `UNLOCKED`
|
|
|
|
|
|
|
|
- `READ`
|
|
|
|
Reads an array of bytes at the provided address.
|
|
|
|
|
|
|
|
Expected Response Type: `READ_RESPONSE`
|
|
|
|
|
|
|
|
Additional Fields:
|
|
|
|
- `address` (`int`): The address of the memory to read
|
|
|
|
- `size` (`int`): The number of bytes to read
|
|
|
|
- `domain` (`string`): The name of the memory domain the address
|
|
|
|
corresponds to
|
|
|
|
|
|
|
|
- `WRITE`
|
|
|
|
Writes an array of bytes to the provided address.
|
|
|
|
|
|
|
|
Expected Response Type: `WRITE_RESPONSE`
|
|
|
|
|
|
|
|
Additional Fields:
|
|
|
|
- `address` (`int`): The address of the memory to write to
|
|
|
|
- `value` (`string`): A base64 string representing the data to write
|
|
|
|
- `domain` (`string`): The name of the memory domain the address
|
|
|
|
corresponds to
|
|
|
|
|
|
|
|
- `DISPLAY_MESSAGE`
|
|
|
|
Adds a message to the message queue which will be displayed using
|
|
|
|
`gui.addmessage` according to the message interval.
|
|
|
|
|
|
|
|
Expected Response Type: `DISPLAY_MESSAGE_RESPONSE`
|
|
|
|
|
|
|
|
Additional Fields:
|
|
|
|
- `message` (`string`): The string to display
|
|
|
|
|
|
|
|
- `SET_MESSAGE_INTERVAL`
|
|
|
|
Sets the minimum amount of time to wait between displaying messages.
|
|
|
|
Potentially useful if you add many messages quickly but want players
|
|
|
|
to be able to read each of them.
|
|
|
|
|
|
|
|
Expected Response Type: `SET_MESSAGE_INTERVAL_RESPONSE`
|
|
|
|
|
|
|
|
Additional Fields:
|
|
|
|
- `value` (`number`): The number of seconds to set the interval to
|
|
|
|
|
|
|
|
|
|
|
|
### Response Types
|
|
|
|
|
|
|
|
- `PONG`
|
|
|
|
Acknowledges `PING`.
|
|
|
|
|
|
|
|
- `SYSTEM_RESPONSE`
|
|
|
|
Contains the name of the system for currently running ROM.
|
|
|
|
|
|
|
|
Additional Fields:
|
|
|
|
- `value` (`string`): The returned system name
|
|
|
|
|
|
|
|
- `PREFERRED_CORES_RESPONSE`
|
|
|
|
Contains the user's preferred cores for systems with multiple supported
|
|
|
|
cores. Currently includes NES, SNES, GB, GBC, DGB, SGB, PCE, PCECD, and
|
|
|
|
SGX.
|
|
|
|
|
|
|
|
Additional Fields:
|
|
|
|
- `value` (`{[string]: [string]}`): A dictionary map from system name to
|
|
|
|
core name
|
|
|
|
|
|
|
|
- `HASH_RESPONSE`
|
|
|
|
Contains the hash of the currently loaded ROM calculated by BizHawk.
|
|
|
|
|
|
|
|
Additional Fields:
|
|
|
|
- `value` (`string`): The returned hash
|
|
|
|
|
2025-01-12 07:03:31 +00:00
|
|
|
- `MEMORY_SIZE_RESPONSE`
|
|
|
|
Contains the size in bytes of the specified memory domain.
|
|
|
|
|
|
|
|
Additional Fields:
|
|
|
|
- `value` (`number`): The size of the domain in bytes
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
- `GUARD_RESPONSE`
|
|
|
|
The result of an attempted `GUARD` request.
|
|
|
|
|
|
|
|
Additional Fields:
|
|
|
|
- `value` (`boolean`): true if the memory was validated, false if not
|
|
|
|
- `address` (`int`): The address of the memory that was invalid (the same
|
|
|
|
address provided by the `GUARD`, not the address of the individual invalid
|
|
|
|
byte)
|
|
|
|
|
|
|
|
- `LOCKED`
|
|
|
|
Acknowledges `LOCK`.
|
|
|
|
|
|
|
|
- `UNLOCKED`
|
|
|
|
Acknowledges `UNLOCK`.
|
|
|
|
|
|
|
|
- `READ_RESPONSE`
|
|
|
|
Contains the result of a `READ` request.
|
|
|
|
|
|
|
|
Additional Fields:
|
|
|
|
- `value` (`string`): A base64 string representing the read data
|
|
|
|
|
|
|
|
- `WRITE_RESPONSE`
|
|
|
|
Acknowledges `WRITE`.
|
|
|
|
|
|
|
|
- `DISPLAY_MESSAGE_RESPONSE`
|
|
|
|
Acknowledges `DISPLAY_MESSAGE`.
|
|
|
|
|
|
|
|
- `SET_MESSAGE_INTERVAL_RESPONSE`
|
|
|
|
Acknowledges `SET_MESSAGE_INTERVAL`.
|
|
|
|
|
|
|
|
- `ERROR`
|
|
|
|
Signifies that something has gone wrong while processing a request.
|
|
|
|
|
|
|
|
Additional Fields:
|
|
|
|
- `err` (`string`): A description of the problem
|
|
|
|
]]
|
|
|
|
|
BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired
- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
- Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
- To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
4. The third concurrent script will then fail on both and actually give an `address already in use` error.
- I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
- As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
- Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
- Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 14:00:46 +00:00
|
|
|
local bizhawk_version = client.getversion()
|
|
|
|
local bizhawk_major, bizhawk_minor, bizhawk_patch = bizhawk_version:match("(%d+)%.(%d+)%.?(%d*)")
|
|
|
|
bizhawk_major = tonumber(bizhawk_major)
|
|
|
|
bizhawk_minor = tonumber(bizhawk_minor)
|
|
|
|
if bizhawk_patch == "" then
|
|
|
|
bizhawk_patch = 0
|
|
|
|
else
|
|
|
|
bizhawk_patch = tonumber(bizhawk_patch)
|
|
|
|
end
|
|
|
|
|
|
|
|
local lua_major, lua_minor = _VERSION:match("Lua (%d+)%.(%d+)")
|
|
|
|
lua_major = tonumber(lua_major)
|
|
|
|
lua_minor = tonumber(lua_minor)
|
|
|
|
|
|
|
|
if lua_major > 5 or (lua_major == 5 and lua_minor >= 3) then
|
|
|
|
require("lua_5_3_compat")
|
|
|
|
end
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
local base64 = require("base64")
|
|
|
|
local socket = require("socket")
|
|
|
|
local json = require("json")
|
|
|
|
|
BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired
- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
- Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
- To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
4. The third concurrent script will then fail on both and actually give an `address already in use` error.
- I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
- As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
- Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
- Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 14:00:46 +00:00
|
|
|
local SOCKET_PORT_FIRST = 43055
|
|
|
|
local SOCKET_PORT_RANGE_SIZE = 5
|
|
|
|
local SOCKET_PORT_LAST = SOCKET_PORT_FIRST + SOCKET_PORT_RANGE_SIZE
|
2023-10-03 00:44:19 +00:00
|
|
|
|
|
|
|
local STATE_NOT_CONNECTED = 0
|
|
|
|
local STATE_CONNECTED = 1
|
|
|
|
|
|
|
|
local server = nil
|
|
|
|
local client_socket = nil
|
|
|
|
|
|
|
|
local current_state = STATE_NOT_CONNECTED
|
|
|
|
|
|
|
|
local timeout_timer = 0
|
|
|
|
local message_timer = 0
|
|
|
|
local message_interval = 0
|
|
|
|
local prev_time = 0
|
|
|
|
local current_time = 0
|
|
|
|
|
|
|
|
local locked = false
|
|
|
|
|
|
|
|
local rom_hash = nil
|
|
|
|
|
|
|
|
function queue_push (self, value)
|
|
|
|
self[self.right] = value
|
|
|
|
self.right = self.right + 1
|
|
|
|
end
|
|
|
|
|
|
|
|
function queue_is_empty (self)
|
|
|
|
return self.right == self.left
|
|
|
|
end
|
|
|
|
|
|
|
|
function queue_shift (self)
|
|
|
|
value = self[self.left]
|
|
|
|
self[self.left] = nil
|
|
|
|
self.left = self.left + 1
|
|
|
|
return value
|
|
|
|
end
|
|
|
|
|
|
|
|
function new_queue ()
|
|
|
|
local queue = {left = 1, right = 1}
|
|
|
|
return setmetatable(queue, {__index = {is_empty = queue_is_empty, push = queue_push, shift = queue_shift}})
|
|
|
|
end
|
|
|
|
|
|
|
|
local message_queue = new_queue()
|
|
|
|
|
|
|
|
function lock ()
|
|
|
|
locked = true
|
|
|
|
client_socket:settimeout(2)
|
|
|
|
end
|
|
|
|
|
|
|
|
function unlock ()
|
|
|
|
locked = false
|
|
|
|
client_socket:settimeout(0)
|
|
|
|
end
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
request_handlers = {
|
|
|
|
["PING"] = function (req)
|
|
|
|
local res = {}
|
2023-10-03 00:44:19 +00:00
|
|
|
|
|
|
|
res["type"] = "PONG"
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
return res
|
|
|
|
end,
|
|
|
|
|
|
|
|
["SYSTEM"] = function (req)
|
|
|
|
local res = {}
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
res["type"] = "SYSTEM_RESPONSE"
|
|
|
|
res["value"] = emu.getsystemid()
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
return res
|
|
|
|
end,
|
|
|
|
|
|
|
|
["PREFERRED_CORES"] = function (req)
|
|
|
|
local res = {}
|
2023-10-03 00:44:19 +00:00
|
|
|
local preferred_cores = client.getconfig().PreferredCores
|
2024-02-16 07:59:57 +00:00
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
res["type"] = "PREFERRED_CORES_RESPONSE"
|
|
|
|
res["value"] = {}
|
|
|
|
res["value"]["NES"] = preferred_cores.NES
|
|
|
|
res["value"]["SNES"] = preferred_cores.SNES
|
|
|
|
res["value"]["GB"] = preferred_cores.GB
|
|
|
|
res["value"]["GBC"] = preferred_cores.GBC
|
|
|
|
res["value"]["DGB"] = preferred_cores.DGB
|
|
|
|
res["value"]["SGB"] = preferred_cores.SGB
|
|
|
|
res["value"]["PCE"] = preferred_cores.PCE
|
|
|
|
res["value"]["PCECD"] = preferred_cores.PCECD
|
|
|
|
res["value"]["SGX"] = preferred_cores.SGX
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
return res
|
|
|
|
end,
|
|
|
|
|
|
|
|
["HASH"] = function (req)
|
|
|
|
local res = {}
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
res["type"] = "HASH_RESPONSE"
|
|
|
|
res["value"] = rom_hash
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
return res
|
|
|
|
end,
|
2023-10-03 00:44:19 +00:00
|
|
|
|
2025-01-12 07:03:31 +00:00
|
|
|
["MEMORY_SIZE"] = function (req)
|
|
|
|
local res = {}
|
|
|
|
|
|
|
|
res["type"] = "MEMORY_SIZE_RESPONSE"
|
|
|
|
res["value"] = memory.getmemorydomainsize(req["domain"])
|
|
|
|
|
|
|
|
return res
|
|
|
|
end,
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
["GUARD"] = function (req)
|
|
|
|
local res = {}
|
|
|
|
local expected_data = base64.decode(req["expected_data"])
|
2023-10-03 00:44:19 +00:00
|
|
|
local actual_data = memory.read_bytes_as_array(req["address"], #expected_data, req["domain"])
|
|
|
|
|
|
|
|
local data_is_validated = true
|
|
|
|
for i, byte in ipairs(actual_data) do
|
|
|
|
if byte ~= expected_data[i] then
|
|
|
|
data_is_validated = false
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
res["type"] = "GUARD_RESPONSE"
|
2023-10-03 00:44:19 +00:00
|
|
|
res["value"] = data_is_validated
|
|
|
|
res["address"] = req["address"]
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
return res
|
|
|
|
end,
|
|
|
|
|
|
|
|
["LOCK"] = function (req)
|
|
|
|
local res = {}
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
res["type"] = "LOCKED"
|
|
|
|
lock()
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
return res
|
|
|
|
end,
|
|
|
|
|
|
|
|
["UNLOCK"] = function (req)
|
|
|
|
local res = {}
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
res["type"] = "UNLOCKED"
|
|
|
|
unlock()
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
return res
|
|
|
|
end,
|
|
|
|
|
|
|
|
["READ"] = function (req)
|
|
|
|
local res = {}
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
res["type"] = "READ_RESPONSE"
|
|
|
|
res["value"] = base64.encode(memory.read_bytes_as_array(req["address"], req["size"], req["domain"]))
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
return res
|
|
|
|
end,
|
|
|
|
|
|
|
|
["WRITE"] = function (req)
|
|
|
|
local res = {}
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
res["type"] = "WRITE_RESPONSE"
|
|
|
|
memory.write_bytes_as_array(req["address"], base64.decode(req["value"]), req["domain"])
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
return res
|
|
|
|
end,
|
|
|
|
|
|
|
|
["DISPLAY_MESSAGE"] = function (req)
|
|
|
|
local res = {}
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
res["type"] = "DISPLAY_MESSAGE_RESPONSE"
|
|
|
|
message_queue:push(req["message"])
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
return res
|
|
|
|
end,
|
|
|
|
|
|
|
|
["SET_MESSAGE_INTERVAL"] = function (req)
|
|
|
|
local res = {}
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
res["type"] = "SET_MESSAGE_INTERVAL_RESPONSE"
|
|
|
|
message_interval = req["value"]
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
return res
|
|
|
|
end,
|
|
|
|
|
|
|
|
["default"] = function (req)
|
|
|
|
local res = {}
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
res["type"] = "ERROR"
|
|
|
|
res["err"] = "Unknown command: "..req["type"]
|
|
|
|
|
2024-02-16 07:59:57 +00:00
|
|
|
return res
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
|
|
|
|
function process_request (req)
|
|
|
|
if request_handlers[req["type"]] then
|
|
|
|
return request_handlers[req["type"]](req)
|
|
|
|
else
|
|
|
|
return request_handlers["default"](req)
|
|
|
|
end
|
2023-10-03 00:44:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Receive data from AP client and send message back
|
|
|
|
function send_receive ()
|
|
|
|
local message, err = client_socket:receive()
|
|
|
|
|
|
|
|
-- Handle errors
|
|
|
|
if err == "closed" then
|
|
|
|
if current_state == STATE_CONNECTED then
|
|
|
|
print("Connection to client closed")
|
|
|
|
end
|
|
|
|
current_state = STATE_NOT_CONNECTED
|
|
|
|
return
|
|
|
|
elseif err == "timeout" then
|
|
|
|
unlock()
|
|
|
|
return
|
|
|
|
elseif err ~= nil then
|
|
|
|
print(err)
|
|
|
|
current_state = STATE_NOT_CONNECTED
|
|
|
|
unlock()
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Reset timeout timer
|
|
|
|
timeout_timer = 5
|
|
|
|
|
|
|
|
-- Process received data
|
|
|
|
if DEBUG then
|
|
|
|
print("Received Message ["..emu.framecount().."]: "..'"'..message..'"')
|
|
|
|
end
|
|
|
|
|
|
|
|
if message == "VERSION" then
|
BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired
- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
- Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
- To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
4. The third concurrent script will then fail on both and actually give an `address already in use` error.
- I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
- As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
- Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
- Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 14:00:46 +00:00
|
|
|
client_socket:send(tostring(SCRIPT_VERSION).."\n")
|
2023-10-03 00:44:19 +00:00
|
|
|
else
|
|
|
|
local res = {}
|
|
|
|
local data = json.decode(message)
|
|
|
|
local failed_guard_response = nil
|
|
|
|
for i, req in ipairs(data) do
|
|
|
|
if failed_guard_response ~= nil then
|
|
|
|
res[i] = failed_guard_response
|
|
|
|
else
|
|
|
|
-- An error is more likely to cause an NLua exception than to return an error here
|
|
|
|
local status, response = pcall(process_request, req)
|
|
|
|
if status then
|
|
|
|
res[i] = response
|
|
|
|
|
|
|
|
-- If the GUARD validation failed, skip the remaining commands
|
|
|
|
if response["type"] == "GUARD_RESPONSE" and not response["value"] then
|
|
|
|
failed_guard_response = response
|
|
|
|
end
|
|
|
|
else
|
2024-01-02 10:32:03 +00:00
|
|
|
if type(response) ~= "string" then response = "Unknown error" end
|
2023-10-03 00:44:19 +00:00
|
|
|
res[i] = {type = "ERROR", err = response}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
client_socket:send(json.encode(res).."\n")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired
- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
- Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
- To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
4. The third concurrent script will then fail on both and actually give an `address already in use` error.
- I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
- As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
- Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
- Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 14:00:46 +00:00
|
|
|
function initialize_server ()
|
|
|
|
local err
|
|
|
|
local port = SOCKET_PORT_FIRST
|
|
|
|
local res = nil
|
|
|
|
|
|
|
|
server, err = socket.socket.tcp4()
|
|
|
|
while res == nil and port <= SOCKET_PORT_LAST do
|
|
|
|
res, err = server:bind("localhost", port)
|
|
|
|
if res == nil and err ~= "address already in use" then
|
|
|
|
print(err)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if res == nil then
|
|
|
|
port = port + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if port > SOCKET_PORT_LAST then
|
|
|
|
print("Too many instances of connector script already running. Exiting.")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
res, err = server:listen(0)
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
if err ~= nil then
|
|
|
|
print(err)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired
- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
- Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
- To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
4. The third concurrent script will then fail on both and actually give an `address already in use` error.
- I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
- As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
- Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
- Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 14:00:46 +00:00
|
|
|
server:settimeout(0)
|
|
|
|
end
|
|
|
|
|
|
|
|
function main ()
|
2023-10-03 00:44:19 +00:00
|
|
|
while true do
|
BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired
- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
- Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
- To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
4. The third concurrent script will then fail on both and actually give an `address already in use` error.
- I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
- As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
- Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
- Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 14:00:46 +00:00
|
|
|
if server == nil then
|
|
|
|
initialize_server()
|
|
|
|
end
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
current_time = socket.socket.gettime()
|
|
|
|
timeout_timer = timeout_timer - (current_time - prev_time)
|
|
|
|
message_timer = message_timer - (current_time - prev_time)
|
|
|
|
prev_time = current_time
|
|
|
|
|
|
|
|
if message_timer <= 0 and not message_queue:is_empty() then
|
|
|
|
gui.addmessage(message_queue:shift())
|
|
|
|
message_timer = message_interval
|
|
|
|
end
|
|
|
|
|
|
|
|
if current_state == STATE_NOT_CONNECTED then
|
BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired
- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
- Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
- To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
4. The third concurrent script will then fail on both and actually give an `address already in use` error.
- I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
- As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
- Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
- Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 14:00:46 +00:00
|
|
|
if emu.framecount() % 30 == 0 then
|
|
|
|
print("Looking for client...")
|
2023-10-03 00:44:19 +00:00
|
|
|
local client, timeout = server:accept()
|
|
|
|
if timeout == nil then
|
|
|
|
print("Client connected")
|
|
|
|
current_state = STATE_CONNECTED
|
|
|
|
client_socket = client
|
BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired
- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
- Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
- To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
4. The third concurrent script will then fail on both and actually give an `address already in use` error.
- I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
- As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
- Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
- Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 14:00:46 +00:00
|
|
|
server:close()
|
|
|
|
server = nil
|
2023-10-03 00:44:19 +00:00
|
|
|
client_socket:settimeout(0)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
repeat
|
|
|
|
send_receive()
|
|
|
|
until not locked
|
|
|
|
|
|
|
|
if timeout_timer <= 0 then
|
|
|
|
print("Client timed out")
|
|
|
|
current_state = STATE_NOT_CONNECTED
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
coroutine.yield()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
event.onexit(function ()
|
|
|
|
print("\n-- Restarting Script --\n")
|
|
|
|
if server ~= nil then
|
|
|
|
server:close()
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
if bizhawk_major < 2 or (bizhawk_major == 2 and bizhawk_minor < 7) then
|
|
|
|
print("Must use BizHawk 2.7.0 or newer")
|
|
|
|
else
|
2025-01-12 06:54:48 +00:00
|
|
|
if bizhawk_major > 2 or (bizhawk_major == 2 and bizhawk_minor > 10) then
|
|
|
|
print("Warning: This version of BizHawk is newer than this script. If it doesn't work, consider downgrading to 2.10.")
|
|
|
|
end
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
if emu.getsystemid() == "NULL" then
|
|
|
|
print("No ROM is loaded. Please load a ROM.")
|
|
|
|
while emu.getsystemid() == "NULL" do
|
|
|
|
emu.frameadvance()
|
|
|
|
end
|
|
|
|
end
|
BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired
- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
- Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
- To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
4. The third concurrent script will then fail on both and actually give an `address already in use` error.
- I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
- As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
- Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
- Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 14:00:46 +00:00
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
rom_hash = gameinfo.getromhash()
|
|
|
|
|
BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired
- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
- Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
- To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
4. The third concurrent script will then fail on both and actually give an `address already in use` error.
- I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
- As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
- Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
- Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 14:00:46 +00:00
|
|
|
print("Waiting for client to connect. This may take longer the more instances of this script you have open at once.\n")
|
2023-10-03 00:44:19 +00:00
|
|
|
|
|
|
|
local co = coroutine.create(main)
|
|
|
|
function tick ()
|
|
|
|
local status, err = coroutine.resume(co)
|
BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired
- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
- Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
- To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
4. The third concurrent script will then fail on both and actually give an `address already in use` error.
- I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
- As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
- Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
- Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 14:00:46 +00:00
|
|
|
|
|
|
|
if not status and err ~= "cannot resume dead coroutine" then
|
2023-10-03 00:44:19 +00:00
|
|
|
print("\nERROR: "..err)
|
|
|
|
print("Consider reporting this crash.\n")
|
|
|
|
|
|
|
|
if server ~= nil then
|
|
|
|
server:close()
|
|
|
|
end
|
BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired
- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
- Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
- To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
4. The third concurrent script will then fail on both and actually give an `address already in use` error.
- I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
- As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
- Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
- Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 14:00:46 +00:00
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
co = coroutine.create(main)
|
|
|
|
end
|
|
|
|
end
|
BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired
- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
- Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
- To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
4. The third concurrent script will then fail on both and actually give an `address already in use` error.
- I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
- As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
- Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
- Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 14:00:46 +00:00
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
-- Gambatte has a setting which can cause script execution to become
|
|
|
|
-- misaligned, so for GB and GBC we explicitly set the callback on
|
|
|
|
-- vblank instead.
|
|
|
|
-- https://github.com/TASEmulators/BizHawk/issues/3711
|
2023-12-12 04:48:20 +00:00
|
|
|
if emu.getsystemid() == "GB" or emu.getsystemid() == "GBC" or emu.getsystemid() == "SGB" then
|
2023-10-03 00:44:19 +00:00
|
|
|
event.onmemoryexecute(tick, 0x40, "tick", "System Bus")
|
|
|
|
else
|
|
|
|
event.onframeend(tick)
|
|
|
|
end
|
BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired
- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
- Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
- To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
4. The third concurrent script will then fail on both and actually give an `address already in use` error.
- I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
- As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
- Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
- Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 14:00:46 +00:00
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
while true do
|
|
|
|
emu.frameadvance()
|
|
|
|
end
|
|
|
|
end
|