Expand shared Sprinter MCP with managed launch, raw reads and key input

This commit is contained in:
Александр Петров
2026-09-17 22:57:27 +03:00
parent a43e7bda89
commit 5dc7998324
22 changed files with 1120 additions and 67 deletions
+107 -1
View File
@@ -8,6 +8,8 @@ function exports.startplugin()
local lfs = require("lfs")
local directory = assert(os.getenv("SDBG_IPC_DIR"), "Нужен SDBG_IPC_DIR")
local session = assert(os.getenv("SDBG_SESSION_ID"), "Нужен SDBG_SESSION_ID")
local snapdir = os.getenv("SDBG_SNAP_DIR")
local snapshot_sequence = 0
local generation, sequence = 0, 0
local events, owned = {}, {}
local state, pending = "initializing", nil
@@ -53,6 +55,37 @@ function exports.startplugin()
assert(type(value)=="number" and value%1==0 and value>=0 and value<=limit,"Недопустимое число")
return value
end
local function first_screen()
for _,screen in pairs(machine().screens) do return screen end
error("Экран MAME не найден")
end
local function share_info()
local result={shares={},regions={}}
for tag,share in pairs(machine().memory.shares) do
result.shares[#result.shares+1]={tag=tag,size=share.size}
end
for tag,region in pairs(machine().memory.regions) do
result.regions[#result.regions+1]={tag=tag,size=region.size}
end
table.sort(result.shares,function(a,b) return a.tag<b.tag end)
table.sort(result.regions,function(a,b) return a.tag<b.tag end)
return result
end
local function read_share(tag,address,length)
assert(type(tag)=="string" and #tag>0 and #tag<=128,"Нужен точный tag share")
local share=machine().memory.shares[tag]
assert(share,"Share не найден: "..tag)
local size=number(share.size,0x10000000)
address=number(address,size)
length=number(length,4096)
assert(length>=1 and address+length<=size,"Чтение за пределами share")
local bytes={}
for offset=0,length-1 do
bytes[#bytes+1]=string.format("%02x",share:read_u8(address+offset))
end
return {tag=tag,address=address,length=length,size=size,hex=table.concat(bytes),
generation=generation}
end
local invalidate
local function dispatch(request)
assert(request.session==session,"Чужая сессия")
@@ -65,9 +98,13 @@ function exports.startplugin()
session_started=true
return {protocol=1,session=session,capabilities={snapshot=true,memory=true,
instruction_step=true,step_over=true,step_out=true,
bank_guard=true,deferred_breakpoints=true,console_print=true},state=state}
bank_guard=true,deferred_breakpoints=true,console_print=true,
shares=true,screen_pixels=true,screen_snapshot=snapdir~=nil,
program_memory=true,list_ports=true},state=state}
elseif command=="snapshot" then
local result={state=state,time=now(),paused=machine().paused}
local screen=first_screen()
result.frame=screen:frame_number()
result.keyboards=setmetatable({}, {__jsontype="object"})
for tag,kbd in pairs(machine().natkeyboard.keyboards) do
result.keyboards[tag]=kbd.enabled
@@ -149,6 +186,75 @@ function exports.startplugin()
bytes[#bytes+1]=string.format("%02x",symbols:memory_value(":maincpu","p",address+offset,1,true))
end
return {hex=table.concat(bytes)}
elseif command=="program_memory" then
stopped(request)
local address=number(args.address,0x3ffff)
local length=number(args.length,4096)
assert(length>=1 and address+length<=0x40000,
"Чтение за пределами raw program space")
local symbols=emu.symbol_table(cpu())
local bytes={}
for offset=0,length-1 do
bytes[#bytes+1]=string.format("%02x",symbols:memory_value(
":maincpu","p",address+offset,1,true))
end
return {space="program",address=address,length=length,
hex=table.concat(bytes),generation=generation}
elseif command=="list_ports" then
local result={ports={},truncated=false}
local fields_seen=0
for tag,port in pairs(machine().ioport.ports) do
if #result.ports>=512 then result.truncated=true;break end
local item={tag=tag,fields={}}
for name,field in pairs(port.fields) do
if fields_seen>=4096 then result.truncated=true;break end
item.fields[#item.fields+1]={name=tostring(name),mask=field.mask}
fields_seen=fields_seen+1
end
table.sort(item.fields,function(a,b) return a.mask<b.mask end)
result.ports[#result.ports+1]=item
end
table.sort(result.ports,function(a,b) return a.tag<b.tag end)
return result
elseif command=="list_shares" then
return share_info()
elseif command=="read_share" then
return read_share(args.tag,args.address,args.length)
elseif command=="read_vram" then
local matches={}
for tag in pairs(machine().memory.shares) do
if tag:find("vram",1,true) then matches[#matches+1]=tag end
end
assert(#matches==1,"Нужен ровно один VRAM share; используйте list_shares")
return read_share(matches[1],args.address,args.length)
elseif command=="screen_pixels" then
local x=number(args.x,4095)
local y=number(args.y,4095)
local width=number(args.width,512)
local height=number(args.height,512)
assert(width>=1 and height>=1 and width*height<=8192,
"Прямоугольник должен содержать 1..8192 пикселей")
local screen=first_screen()
local values={}
for dy=0,height-1 do
for dx=0,width-1 do
values[#values+1]=string.format("%04X",screen:pixel(x+dx,y+dy)&0xffff)
end
end
return {x=x,y=y,width=width,height=height,format="pen16be-hex",
hex=table.concat(values),frame=screen:frame_number(),
emulated_time=now(),stale_frame=state=="stopped",generation=generation}
elseif command=="screen_snapshot" then
assert(snapdir and #snapdir>0,"Каталог снимков не настроен")
snapshot_sequence=snapshot_sequence+1
local screen=first_screen()
local path=string.format("%s/sdbg_%06d.png",snapdir,snapshot_sequence)
local err=screen:snapshot(path)
assert(err==nil,"Снимок не создан: "..tostring(err))
local size=lfs.attributes(path,"size")
assert(size and size>0 and size<=8388608,"Неверный размер снимка")
return {path=path,size=size,format="png",frame=screen:frame_number(),
emulated_time=now(),stale_frame=state=="stopped",generation=generation}
elseif command=="console_print" then
stopped(request)
local text=args.text