How to get PPU memory from FCEUX in Lua?

871 Views Asked by At

I'm not sure if this is the right community for this, but figured I'd give it a try.

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, it is possible - in theory - to get the data from special CPU memory addresses, but this seems cumbersome, and also like it might interfere with emulation.

Does anyone know of a way to programmatically extract the PPU memory via FCEUX's Lua API? If not, does anyone know of an emulator which features an API to extract PPU memory programmatically?

3

There are 3 best solutions below

0
On BEST ANSWER

As of FCEUX 2.3.0, you can use ppu.readbyte(int address) and ppu.readbyterange(int address, int length). Still nothing for writing bytes, though.

To add onto SpiderDave's answer, you might have better luck if you call his writebyteppu hack from a registerexec callback at an address just after the game has finished writing its graphics for the frame.

-- 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)
0
On

After having the realization that "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!", I found the answer I was looking for:

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

So it would seem that as of the time of this writing, PPU memory access via Lua is not possible in the current release version (2.2.3 released 28-Jul-2016), but will likely be available in a future release.

Also, after checking out Nestopia and Jnes (two of the other seemingly most popular NES emulators), it seems that these emulators do not offer such functionality. As to whether or not there are any other emulators out there that offers this functionality is still an open question, since there are currently many other emulators in existence to check.

0
On

Here's what I use:

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

In 2.2.3 it doesn't seem to work on the old PPU core, but on 2.2.2 it does. Works with new ppu core on both versions.