mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 04:01:32 -06:00
BizHawkClient: Add BizHawkClient (#1978)
Adds a generic client that can communicate with BizHawk. Similar to SNIClient, but for arbitrary systems and doesn't have an intermediary application like SNI.
This commit is contained in:
119
data/lua/base64.lua
Normal file
119
data/lua/base64.lua
Normal file
@@ -0,0 +1,119 @@
|
||||
-- This file originates from this repository: https://github.com/iskolbin/lbase64
|
||||
-- It was modified to translate between base64 strings and lists of bytes instead of base64 strings and strings.
|
||||
|
||||
local base64 = {}
|
||||
|
||||
local extract = _G.bit32 and _G.bit32.extract -- Lua 5.2/Lua 5.3 in compatibility mode
|
||||
if not extract then
|
||||
if _G._VERSION == "Lua 5.4" then
|
||||
extract = load[[return function( v, from, width )
|
||||
return ( v >> from ) & ((1 << width) - 1)
|
||||
end]]()
|
||||
elseif _G.bit then -- LuaJIT
|
||||
local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band
|
||||
extract = function( v, from, width )
|
||||
return band( shr( v, from ), shl( 1, width ) - 1 )
|
||||
end
|
||||
elseif _G._VERSION == "Lua 5.1" then
|
||||
extract = function( v, from, width )
|
||||
local w = 0
|
||||
local flag = 2^from
|
||||
for i = 0, width-1 do
|
||||
local flag2 = flag + flag
|
||||
if v % flag2 >= flag then
|
||||
w = w + 2^i
|
||||
end
|
||||
flag = flag2
|
||||
end
|
||||
return w
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function base64.makeencoder( s62, s63, spad )
|
||||
local encoder = {}
|
||||
for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J',
|
||||
'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y',
|
||||
'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n',
|
||||
'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2',
|
||||
'3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do
|
||||
encoder[b64code] = char:byte()
|
||||
end
|
||||
return encoder
|
||||
end
|
||||
|
||||
function base64.makedecoder( s62, s63, spad )
|
||||
local decoder = {}
|
||||
for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do
|
||||
decoder[charcode] = b64code
|
||||
end
|
||||
return decoder
|
||||
end
|
||||
|
||||
local DEFAULT_ENCODER = base64.makeencoder()
|
||||
local DEFAULT_DECODER = base64.makedecoder()
|
||||
|
||||
local char, concat = string.char, table.concat
|
||||
|
||||
function base64.encode( arr, encoder )
|
||||
encoder = encoder or DEFAULT_ENCODER
|
||||
local t, k, n = {}, 1, #arr
|
||||
local lastn = n % 3
|
||||
for i = 1, n-lastn, 3 do
|
||||
local a, b, c = arr[i], arr[i + 1], arr[i + 2]
|
||||
local v = a*0x10000 + b*0x100 + c
|
||||
local s
|
||||
s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
|
||||
t[k] = s
|
||||
k = k + 1
|
||||
end
|
||||
if lastn == 2 then
|
||||
local a, b = arr[n-1], arr[n]
|
||||
local v = a*0x10000 + b*0x100
|
||||
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64])
|
||||
elseif lastn == 1 then
|
||||
local v = arr[n]*0x10000
|
||||
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64])
|
||||
end
|
||||
return concat( t )
|
||||
end
|
||||
|
||||
function base64.decode( b64, decoder )
|
||||
decoder = decoder or DEFAULT_DECODER
|
||||
local pattern = '[^%w%+%/%=]'
|
||||
if decoder then
|
||||
local s62, s63
|
||||
for charcode, b64code in pairs( decoder ) do
|
||||
if b64code == 62 then s62 = charcode
|
||||
elseif b64code == 63 then s63 = charcode
|
||||
end
|
||||
end
|
||||
pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) )
|
||||
end
|
||||
b64 = b64:gsub( pattern, '' )
|
||||
local t, k = {}, 1
|
||||
local n = #b64
|
||||
local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0
|
||||
for i = 1, padding > 0 and n-4 or n, 4 do
|
||||
local a, b, c, d = b64:byte( i, i+3 )
|
||||
local s
|
||||
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
|
||||
table.insert(t,extract(v,16,8))
|
||||
table.insert(t,extract(v,8,8))
|
||||
table.insert(t,extract(v,0,8))
|
||||
end
|
||||
if padding == 1 then
|
||||
local a, b, c = b64:byte( n-3, n-1 )
|
||||
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40
|
||||
table.insert(t,extract(v,16,8))
|
||||
table.insert(t,extract(v,8,8))
|
||||
elseif padding == 2 then
|
||||
local a, b = b64:byte( n-3, n-2 )
|
||||
local v = decoder[a]*0x40000 + decoder[b]*0x1000
|
||||
table.insert(t,extract(v,16,8))
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
return base64
|
564
data/lua/connector_bizhawk_generic.lua
Normal file
564
data/lua/connector_bizhawk_generic.lua
Normal file
@@ -0,0 +1,564 @@
|
||||
--[[
|
||||
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
|
||||
|
||||
--[[
|
||||
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`
|
||||
|
||||
- `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
|
||||
|
||||
- `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
|
||||
]]
|
||||
|
||||
local base64 = require("base64")
|
||||
local socket = require("socket")
|
||||
local json = require("json")
|
||||
|
||||
-- Set to log incoming requests
|
||||
-- Will cause lag due to large console output
|
||||
local DEBUG = false
|
||||
|
||||
local SOCKET_PORT = 43055
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
function process_request (req)
|
||||
local res = {}
|
||||
|
||||
if req["type"] == "PING" then
|
||||
res["type"] = "PONG"
|
||||
|
||||
elseif req["type"] == "SYSTEM" then
|
||||
res["type"] = "SYSTEM_RESPONSE"
|
||||
res["value"] = emu.getsystemid()
|
||||
|
||||
elseif req["type"] == "PREFERRED_CORES" then
|
||||
local preferred_cores = client.getconfig().PreferredCores
|
||||
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
|
||||
|
||||
elseif req["type"] == "HASH" then
|
||||
res["type"] = "HASH_RESPONSE"
|
||||
res["value"] = rom_hash
|
||||
|
||||
elseif req["type"] == "GUARD" then
|
||||
res["type"] = "GUARD_RESPONSE"
|
||||
local expected_data = base64.decode(req["expected_data"])
|
||||
|
||||
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
|
||||
|
||||
res["value"] = data_is_validated
|
||||
res["address"] = req["address"]
|
||||
|
||||
elseif req["type"] == "LOCK" then
|
||||
res["type"] = "LOCKED"
|
||||
lock()
|
||||
|
||||
elseif req["type"] == "UNLOCK" then
|
||||
res["type"] = "UNLOCKED"
|
||||
unlock()
|
||||
|
||||
elseif req["type"] == "READ" then
|
||||
res["type"] = "READ_RESPONSE"
|
||||
res["value"] = base64.encode(memory.read_bytes_as_array(req["address"], req["size"], req["domain"]))
|
||||
|
||||
elseif req["type"] == "WRITE" then
|
||||
res["type"] = "WRITE_RESPONSE"
|
||||
memory.write_bytes_as_array(req["address"], base64.decode(req["value"]), req["domain"])
|
||||
|
||||
elseif req["type"] == "DISPLAY_MESSAGE" then
|
||||
res["type"] = "DISPLAY_MESSAGE_RESPONSE"
|
||||
message_queue:push(req["message"])
|
||||
|
||||
elseif req["type"] == "SET_MESSAGE_INTERVAL" then
|
||||
res["type"] = "SET_MESSAGE_INTERVAL_RESPONSE"
|
||||
message_interval = req["value"]
|
||||
|
||||
else
|
||||
res["type"] = "ERROR"
|
||||
res["err"] = "Unknown command: "..req["type"]
|
||||
end
|
||||
|
||||
return res
|
||||
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
|
||||
local result, err client_socket:send(tostring(SCRIPT_VERSION).."\n")
|
||||
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
|
||||
res[i] = {type = "ERROR", err = response}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
client_socket:send(json.encode(res).."\n")
|
||||
end
|
||||
end
|
||||
|
||||
function main ()
|
||||
server, err = socket.bind("localhost", SOCKET_PORT)
|
||||
if err ~= nil then
|
||||
print(err)
|
||||
return
|
||||
end
|
||||
|
||||
while true do
|
||||
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
|
||||
if emu.framecount() % 60 == 0 then
|
||||
server:settimeout(2)
|
||||
local client, timeout = server:accept()
|
||||
if timeout == nil then
|
||||
print("Client connected")
|
||||
current_state = STATE_CONNECTED
|
||||
client_socket = client
|
||||
client_socket:settimeout(0)
|
||||
else
|
||||
print("No client found. Trying again...")
|
||||
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")
|
||||
elseif bizhawk_major > 2 or (bizhawk_major == 2 and bizhawk_minor > 9) then
|
||||
print("Warning: This version of BizHawk is newer than this script. If it doesn't work, consider downgrading to 2.9.")
|
||||
else
|
||||
if emu.getsystemid() == "NULL" then
|
||||
print("No ROM is loaded. Please load a ROM.")
|
||||
while emu.getsystemid() == "NULL" do
|
||||
emu.frameadvance()
|
||||
end
|
||||
end
|
||||
|
||||
rom_hash = gameinfo.getromhash()
|
||||
|
||||
print("Waiting for client to connect. Emulation will freeze intermittently until a client is found.\n")
|
||||
|
||||
local co = coroutine.create(main)
|
||||
function tick ()
|
||||
local status, err = coroutine.resume(co)
|
||||
|
||||
if not status then
|
||||
print("\nERROR: "..err)
|
||||
print("Consider reporting this crash.\n")
|
||||
|
||||
if server ~= nil then
|
||||
server:close()
|
||||
end
|
||||
|
||||
co = coroutine.create(main)
|
||||
end
|
||||
end
|
||||
|
||||
-- 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
|
||||
if emu.getsystemid() == "GB" or emu.getsystemid() == "GBC" then
|
||||
event.onmemoryexecute(tick, 0x40, "tick", "System Bus")
|
||||
else
|
||||
event.onframeend(tick)
|
||||
end
|
||||
|
||||
while true do
|
||||
emu.frameadvance()
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user