Lua 如何从 FCEUX 获取 PPU 内存?

How to get PPU memory from FCEUX in Lua?

我不确定这是否适合这个社区,但我想我会试一试。

FCEUX is an amazing emulator for the NES, which is feature rich with debugging tools. It also offers the ability for users to run Lua scripts which have access to various emulator functions. However, I can't seem to figure out how to access the PPU memory of the NES. It offers direct access to the CPU memory and the ROM data, but doesn't seem to have direct access to the PPU memory. Since the NES uses memory-mapped I/O,理论上可以从特殊的 CPU 内存地址获取数据,但这看起来很麻烦,而且可能会干扰仿真。

有人知道通过 FCEUX 的 Lua API 以编程方式提取 PPU 内存的方法吗?如果没有,有谁知道一个模拟器具有 API 以编程方式提取 PPU 内存?

在意识到 "Oh wait, I'm a programmer, and FCEUX is open source! So maybe I should take the time to look at their source/repository to see if I can answer the question myself!" 之后,我找到了我正在寻找的答案:

Commit [r3327] on 22-Dec-2016: add ppu lua library, only has readbyte and readbyterange so far

因此,在撰写本文时,在当前版本(2.2.3 于 2016 年 7 月 28 日发布)中,无法通过 Lua 访问 PPU 内存,但是可能会在未来的版本中提供。

此外,在查看 Nestopia and Jnes(其他两个看似最受欢迎的 NES 模拟器)之后,似乎这些模拟器不提供此类功能。至于是否有任何其他模拟器提供此功能仍然是一个悬而未决的问题,因为目前存在许多其他模拟器需要检查。

这是我使用的:

function memory.readbyteppu(a)
    memory.writebyte(0x2001,0x00) -- Turn off rendering
    memory.readbyte(0x2002) -- PPUSTATUS (reset address latch)
    memory.writebyte(0x2006,math.floor(a/0x100)) -- PPUADDR high byte
    memory.writebyte(0x2006,a % 0x100) -- PPUADDR low byte
    if a < 0x3f00 then 
        dummy=memory.readbyte(0x2007) -- PPUDATA (discard contents of internal buffer if not reading palette area)
    end
    ret=memory.readbyte(0x2007) -- PPUDATA
    memory.writebyte(0x2001,0x1e) -- Turn on rendering
    return ret
end

function memory.readbytesppu(a,l)
    memory.writebyte(0x2001,0x00) -- Turn off rendering
    local ret
    local i
    ret=""
    for i=0,l-1 do
        memory.readbyte(0x2002) -- PPUSTATUS (reset address latch)
        memory.writebyte(0x2006,math.floor((a+i)/0x100)) -- PPUADDR high byte
        memory.writebyte(0x2006,(a+i) % 0x100) -- PPUADDR low byte
        if (a+i) < 0x3f00 then 
            dummy=memory.readbyte(0x2007) -- PPUDATA (discard contents of internal buffer if not reading palette area)
        end
        ret=ret..string.char(memory.readbyte(0x2007)) -- PPUDATA
    end
    memory.writebyte(0x2001,0x1e) -- Turn on rendering
    return ret
end


function memory.writebyteppu(a,v)
    memory.writebyte(0x2001,0x00) -- Turn off rendering
    memory.readbyte(0x2002) -- PPUSTATUS (reset address latch)
    memory.writebyte(0x2006,math.floor(a/0x100)) -- PPUADDR high byte
    memory.writebyte(0x2006,a % 0x100) -- PPUADDR low byte
    memory.writebyte(0x2007,v) -- PPUDATA
    memory.writebyte(0x2001,0x1e) -- Turn on rendering
end

function memory.writebytesppu(a,str)
    memory.writebyte(0x2001,0x00) -- Turn off rendering

    local i
    for i = 0, #str-1 do
        memory.readbyte(0x2002) -- PPUSTATUS (reset address latch)
        memory.writebyte(0x2006,math.floor((a+i)/0x100)) -- PPUADDR high byte
        memory.writebyte(0x2006,(a+i) % 0x100) -- PPUADDR low byte
        memory.writebyte(0x2007,string.byte(str,i+1)) -- PPUDATA
    end

    memory.writebyte(0x2001,0x1e) -- Turn on rendering
end

在 2.2.3 中,它似乎不适用于旧的 PPU 内核,但在 2.2.2 中可以。适用于两个版本的新 ppu 内核。

FCEUX 2.3.0 开始,您可以使用 ppu.readbyte(int address)ppu.readbyterange(int address, int length)。不过,仍然没有写入字节。

要补充 SpiderDave 的答案,如果您在游戏刚完成为帧编写图形后的地址从 registerexec 回调中调用他的 writebyteppu hack,您的运气可能会更好。

-- For Rockman 2. Directly after all graphics update routines finish.
memory.registerexec(0xD031, function()
    local paletteBase = 0x3F00
    -- Make all BG colors pink
    for i = 0x00, 0x0F do
        memory.writebyteppu(paletteBase + i, 0x35)
    end
end)