 1698c17caa
			
		
	
	1698c17caa
	
	
	
		
			
			* Fix links to TASVideos.org using HTTP * Revise all docs mentioning Lua in EmuHawk which are in English resolves TASEmulators/BizHawk#3650 * Correct capitalisation of "BizHawk" in strings and camelCase identifiers * Use the term "EmuHawk" when referring to the app, in English docs --------- Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
		
			
				
	
	
		
			141 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| -- 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")
 | |
| local udp = socket.socket.udp()
 | |
| require('common')
 | |
| 
 | |
| udp:setsockname('127.0.0.1', 55355)
 | |
| udp:settimeout(0)
 | |
| 
 | |
| while true do
 | |
|     -- 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+)")
 | |
|             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
 | |
|             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+)")
 | |
|             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
 | |
|                     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
 | |
| 
 | |
|     emu.frameadvance()
 | |
| end
 |