Archipelago/data/lua/connector_ladx_bizhawk.lua

146 lines
6.1 KiB
Lua
Raw Normal View History

-- SPDX-FileCopyrightText: 2023 Wilhelm Schürmann <wimschuermann@googlemail.com>
--
-- SPDX-License-Identifier: MIT
-- This script attempts to implement the basic functionality needed in order for
-- the LADXR Archipelago client to be able to talk to EmuHawk instead of RetroArch
-- by reproducing the RetroArch API with EmuHawk's Lua interface.
--
-- RetroArch UDP API: https://github.com/libretro/RetroArch/blob/master/command.c
--
-- Only
-- VERSION
-- GET_STATUS
-- READ_CORE_MEMORY
-- WRITE_CORE_MEMORY
-- commands are supported right now.
--
-- USAGE:
-- Load this script in EmuHawk ("Tools" -> "Lua Console" -> "Script" -> "Open Script", or drag+drop)
--
-- All inconsistencies (like missing newlines for some commands) of the RetroArch
-- UDP API (network_cmd_enable) are reproduced as-is in order for clients written to work with
-- RetroArch's current API to "just work"(tm).
--
-- This script has only been tested on GB(C). If you have made sure it works for N64 or other
-- cores supported by EmuHawk, please let me know. Note that GET_STATUS, at the very least, will
-- have to be adjusted.
--
--
-- NOTE:
-- EmuHawk's Lua API is very trigger-happy on throwing exceptions.
-- Emulation will continue fine, but the RetroArch API layer will stop working. This
-- is indicated only by an exception visible in the Lua console, which most players
-- will probably not have in the foreground.
--
-- pcall(), the usual way to catch exceptions in Lua, doesn't appear to be supported at all,
-- meaning that error/exception handling is not easily possible.
--
-- This means that a lot more error checking would need to happen before e.g. reading/writing
-- memory. Since the end goal, according to AP's Discord, seems to be SNI integration of GB(C),
-- no further fault-proofing has been done on this.
--
local socket = require("socket")
LADX: Fix getting old items over and over again in Bizhawk (#2011) There was a bug that randomly after opening and closing the menu, some players on Bizhawk would get old items again. Tracking this down took multiple hours over the course of several weeks. The root cause turned out to be reading from the System Bus domain while an DMA copy was happening. Doing so is undefined behavior on GBC (though I'm sure some game relies on it). On Gambatte, you end up reading some garbage byte no matter what the read is (unsure what the providence of the byte is - some garbage, some register, the actual DMA data, who knows?). Normally, this isn't an issue, as Bizhawk callbacks only happen during vblank/halt, which is generally a state where we have valid WRAM to read from. However - a setting is being passed around the community for Bizhawk that changes the frame counter to go from "only when Vblank happens" to "whenever some number of audio samples have happened" which causes the bizhawk callbacks to happen....nearly whenever. Including during a DMA. You can tell this is happening if you print the `PC` register when reading memory - if it matches `FFXX` then you are executing in a routine in HRAM and likely doing a DMA. Additionally, the check items counter specifically is in WRAM Bank 1 which could be swapped out of - will have to keep an eye on this - generally LADX lives in Bank 1, but there are a few things that use the other banks (swap space for some objects??). This could be a problem on any platform - if we get more reports of bad items gets, that's probably why. Also, fixes some logging that was never getting reenabled.
2023-07-27 14:08:14 +00:00
udp = socket.socket.udp()
2023-05-03 21:35:14 +00:00
require('common')
udp:setsockname('127.0.0.1', 55355)
udp:settimeout(0)
LADX: Fix getting old items over and over again in Bizhawk (#2011) There was a bug that randomly after opening and closing the menu, some players on Bizhawk would get old items again. Tracking this down took multiple hours over the course of several weeks. The root cause turned out to be reading from the System Bus domain while an DMA copy was happening. Doing so is undefined behavior on GBC (though I'm sure some game relies on it). On Gambatte, you end up reading some garbage byte no matter what the read is (unsure what the providence of the byte is - some garbage, some register, the actual DMA data, who knows?). Normally, this isn't an issue, as Bizhawk callbacks only happen during vblank/halt, which is generally a state where we have valid WRAM to read from. However - a setting is being passed around the community for Bizhawk that changes the frame counter to go from "only when Vblank happens" to "whenever some number of audio samples have happened" which causes the bizhawk callbacks to happen....nearly whenever. Including during a DMA. You can tell this is happening if you print the `PC` register when reading memory - if it matches `FFXX` then you are executing in a routine in HRAM and likely doing a DMA. Additionally, the check items counter specifically is in WRAM Bank 1 which could be swapped out of - will have to keep an eye on this - generally LADX lives in Bank 1, but there are a few things that use the other banks (swap space for some objects??). This could be a problem on any platform - if we get more reports of bad items gets, that's probably why. Also, fixes some logging that was never getting reenabled.
2023-07-27 14:08:14 +00:00
function on_vblank()
-- Attempt to lessen the CPU load by only polling the UDP socket every x frames.
-- x = 10 is entirely arbitrary, very little thought went into it.
-- We could try to make use of client.get_approx_framerate() here, but the values returned
-- seemed more or less arbitrary as well.
--
-- NOTE: Never mind the above, the LADXR Archipelago client appears to run into problems with
-- interwoven GET_STATUS calls, leading to stopped communication.
-- For GB(C), polling the socket on every frame is OK-ish, so we just do that.
--
--while emu.framecount() % 10 ~= 0 do
-- emu.frameadvance()
--end
local data, msg_or_ip, port_or_nil = udp:receivefrom()
if data then
-- "data" format is "COMMAND [PARAMETERS] [...]"
local command = string.match(data, "%S+")
if command == "VERSION" then
-- 1.14 is the latest RetroArch release at the time of writing this, no other reason
-- for choosing this here.
udp:sendto("1.14.0\n", msg_or_ip, port_or_nil)
elseif command == "GET_STATUS" then
local status = "PLAYING"
if client.ispaused() then
status = "PAUSED"
end
if emu.getsystemid() == "GBC" then
-- Actual reply from RetroArch's API:
-- "GET_STATUS PLAYING game_boy,AP_62468482466172374046_P1_Lonk,crc32=3ecb7b6f"
-- CRC32 isn't readily available through the Lua API. We could calculate
-- it ourselves, but since LADXR doesn't make use of this field it is
-- simply replaced by the hash that EmuHawk _does_ make available.
udp:sendto(
"GET_STATUS " .. status .. " game_boy," ..
string.gsub(gameinfo.getromname(), "[%s,]", "_") ..
",romhash=" ..
gameinfo.getromhash() .. "\n",
msg_or_ip, port_or_nil
)
else -- No ROM loaded
-- NOTE: No newline is intentional here for 1:1 RetroArch compatibility
udp:sendto("GET_STATUS CONTENTLESS", msg_or_ip, port_or_nil)
end
elseif command == "READ_CORE_MEMORY" then
local _, address, length = string.match(data, "(%S+) (%S+) (%S+)")
2023-05-03 21:35:14 +00:00
address = stripPrefix(address, "0x")
address = tonumber(address, 16)
length = tonumber(length)
-- NOTE: mainmemory.read_bytes_as_array() would seem to be the obvious choice
-- here instead, but it isn't. At least for Sameboy and Gambatte, the "main"
-- memory differs (ROM vs WRAM).
-- Using memory.read_bytes_as_array() and explicitly using the System Bus
-- as the active memory domain solves this incompatibility, allowing us
-- to hopefully use whatever GB(C) emulator we want.
local mem = memory.read_bytes_as_array(address, length, "System Bus")
local hex_string = ""
for _, v in ipairs(mem) do
hex_string = hex_string .. string.format("%02X ", v)
end
LADX: Fix getting old items over and over again in Bizhawk (#2011) There was a bug that randomly after opening and closing the menu, some players on Bizhawk would get old items again. Tracking this down took multiple hours over the course of several weeks. The root cause turned out to be reading from the System Bus domain while an DMA copy was happening. Doing so is undefined behavior on GBC (though I'm sure some game relies on it). On Gambatte, you end up reading some garbage byte no matter what the read is (unsure what the providence of the byte is - some garbage, some register, the actual DMA data, who knows?). Normally, this isn't an issue, as Bizhawk callbacks only happen during vblank/halt, which is generally a state where we have valid WRAM to read from. However - a setting is being passed around the community for Bizhawk that changes the frame counter to go from "only when Vblank happens" to "whenever some number of audio samples have happened" which causes the bizhawk callbacks to happen....nearly whenever. Including during a DMA. You can tell this is happening if you print the `PC` register when reading memory - if it matches `FFXX` then you are executing in a routine in HRAM and likely doing a DMA. Additionally, the check items counter specifically is in WRAM Bank 1 which could be swapped out of - will have to keep an eye on this - generally LADX lives in Bank 1, but there are a few things that use the other banks (swap space for some objects??). This could be a problem on any platform - if we get more reports of bad items gets, that's probably why. Also, fixes some logging that was never getting reenabled.
2023-07-27 14:08:14 +00:00
hex_string = hex_string:sub(1, -2) -- Hang head in shame, remove last " "
local reply = string.format("%s %02x %s\n", command, address, hex_string)
udp:sendto(reply, msg_or_ip, port_or_nil)
elseif command == "WRITE_CORE_MEMORY" then
local _, address = string.match(data, "(%S+) (%S+)")
2023-05-03 21:35:14 +00:00
address = stripPrefix(address, "0x")
address = tonumber(address, 16)
local to_write = {}
local i = 1
for byte_str in string.gmatch(data, "%S+") do
if i > 2 then
2023-05-03 21:35:14 +00:00
byte_str = stripPrefix(byte_str, "0x")
table.insert(to_write, tonumber(byte_str, 16))
end
i = i + 1
end
memory.write_bytes_as_array(address, to_write, "System Bus")
local reply = string.format("%s %02x %d\n", command, address, i - 3)
udp:sendto(reply, msg_or_ip, port_or_nil)
end
end
LADX: Fix getting old items over and over again in Bizhawk (#2011) There was a bug that randomly after opening and closing the menu, some players on Bizhawk would get old items again. Tracking this down took multiple hours over the course of several weeks. The root cause turned out to be reading from the System Bus domain while an DMA copy was happening. Doing so is undefined behavior on GBC (though I'm sure some game relies on it). On Gambatte, you end up reading some garbage byte no matter what the read is (unsure what the providence of the byte is - some garbage, some register, the actual DMA data, who knows?). Normally, this isn't an issue, as Bizhawk callbacks only happen during vblank/halt, which is generally a state where we have valid WRAM to read from. However - a setting is being passed around the community for Bizhawk that changes the frame counter to go from "only when Vblank happens" to "whenever some number of audio samples have happened" which causes the bizhawk callbacks to happen....nearly whenever. Including during a DMA. You can tell this is happening if you print the `PC` register when reading memory - if it matches `FFXX` then you are executing in a routine in HRAM and likely doing a DMA. Additionally, the check items counter specifically is in WRAM Bank 1 which could be swapped out of - will have to keep an eye on this - generally LADX lives in Bank 1, but there are a few things that use the other banks (swap space for some objects??). This could be a problem on any platform - if we get more reports of bad items gets, that's probably why. Also, fixes some logging that was never getting reenabled.
2023-07-27 14:08:14 +00:00
end
LADX: Fix getting old items over and over again in Bizhawk (#2011) There was a bug that randomly after opening and closing the menu, some players on Bizhawk would get old items again. Tracking this down took multiple hours over the course of several weeks. The root cause turned out to be reading from the System Bus domain while an DMA copy was happening. Doing so is undefined behavior on GBC (though I'm sure some game relies on it). On Gambatte, you end up reading some garbage byte no matter what the read is (unsure what the providence of the byte is - some garbage, some register, the actual DMA data, who knows?). Normally, this isn't an issue, as Bizhawk callbacks only happen during vblank/halt, which is generally a state where we have valid WRAM to read from. However - a setting is being passed around the community for Bizhawk that changes the frame counter to go from "only when Vblank happens" to "whenever some number of audio samples have happened" which causes the bizhawk callbacks to happen....nearly whenever. Including during a DMA. You can tell this is happening if you print the `PC` register when reading memory - if it matches `FFXX` then you are executing in a routine in HRAM and likely doing a DMA. Additionally, the check items counter specifically is in WRAM Bank 1 which could be swapped out of - will have to keep an eye on this - generally LADX lives in Bank 1, but there are a few things that use the other banks (swap space for some objects??). This could be a problem on any platform - if we get more reports of bad items gets, that's probably why. Also, fixes some logging that was never getting reenabled.
2023-07-27 14:08:14 +00:00
event.onmemoryexecute(on_vblank, 0x40, "ap_connector_vblank")
while true do
emu.yield()
end