1 2 3 4 5
8 9
Emulator Coder, Site Developer, Former player
Joined: 11/6/2004
Posts: 833
Problem is this code is meant to be cross-platform. My Linux version must run it successfully. I also draw to an off-screen buffer and blit it to the real screen later due to the one-frame delay (which seems to be consistent with the actual drawing cycle of the snes I think, based on observation with SMetroid.) I plan to keep the current drawing method the same, though some minor changes under the hood. I'm afraid I'm tossing that code in the garbage. On a side note, backing up the old screen image is a good idea. I'm going to do it, because I think you could implement a full GUI overlayed on top of the main control screen by using snes9x.wait() to keep the emulator paused while the user does stuff. I was going to do this to a file/socket so I could enjoy a terminal with game info overlayed, but this seems like fun. As for the cheat search, I just used a copy of gocha's code. I'm pretty sure I didn't modify anything except the File menu and the addition of a new dialog box. I'll see about changing the shortcuts, but I'd dare say the dialogs were screwed up when I found them.
HHS
Active player (286)
Joined: 10/8/2006
Posts: 356
Ah, okay. Well, I can think of one more thing I could use, namely reading and writing the current contents of CPU registers.
Player (89)
Joined: 11/14/2005
Posts: 1058
Location: United States
Can someone help me with writing a code? I was wondering if it is to write a script that would fast forward the game when a certain memory address is not actively increasing, namely an in-game timer. I have realized that almost every game has some sort of internal timer that stops during screen transitions. something like: (pseudo code) If (memory address) != (memory address) on the previous frame then snes9x.speedmode("normal") If (memory address) == (memory address) on the previous frame then snes9x.speedmode("turbo") Though I haven't got a clue how to write it correctly. Any help would be appreciated.
They're off to find the hero of the day...
HHS
Active player (286)
Joined: 10/8/2006
Posts: 356
Here is how: pv=0 while true do cv=memory.readbyte(address) if pv~=cv then snes9x.speedmode("normal") else snes9x.speedmode("turbo") end pv=cv end
Player (89)
Joined: 11/14/2005
Posts: 1058
Location: United States
thanks alot HHS! :D It worked perfectly.
They're off to find the hero of the day...
P.JBoy
Any
Editor
Joined: 3/25/2006
Posts: 850
Location: stuck in Pandora's box HELLPP!!!
What's wrong with this code?
while true do
	for i=0,5
		memory.writeword(0x7E0AFA, i + 285)
		memory.writeword(0x7E0AFC, 0)
		joypad.set(1, down)
		snes9x.frameadvance()
		joypad.set(1,left)
		if memory.readword(0x7E0ADA) = 291 then
			snes9x.message(i works)
			else snes9x.message(i fails)
		end
	end
	snes9x.pause()
end
Active player (356)
Joined: 1/16/2008
Posts: 358
Location: The Netherlands
P.JBoy wrote:
What's wrong with this code? if memory.readword(0x7E0ADA) == 291 then
TASes: [URL=http://tasvideos.org/Movies-298up-Obs.html]Mr. Nutz (SNES), Young Merlin 100% (SNES), Animaniacs 100% (SNES)[/URL]
P.JBoy
Any
Editor
Joined: 3/25/2006
Posts: 850
Location: stuck in Pandora's box HELLPP!!!
That doesn't fix it. I get an error "3: 'do' expected near 'memory'". Try running it yourself EDIT: DeHackEd resolved it :)
P.JBoy
Any
Editor
Joined: 3/25/2006
Posts: 850
Location: stuck in Pandora's box HELLPP!!!
snes9x crashes when using "SuperEagle", "Super2xSul" or "2xSul" as a display filter while a ROM is open or opening a ROM (this is the cause of the freuquent crashing I was having aswell)
Former player
Joined: 2/19/2007
Posts: 424
Location: UK
Here is an improvement to drawbox, to prevent it from crashing when drawing boxes that extend outside the screen:
int x1m = min(max(0,x1),255);
    int y1m = min(max(0,y1),238);
    int x2m = min(max(0,x2),255);
    int y2m = min(max(0,y2),238);

    // top surface
    if(y1 == y1m) for (i=x1m; i <= x2m; i++)
        gui_data[y1*256 + i] = colour;

    // bottom surface
    if(y2 == y2m) for (i=x1m; i <= x2m; i++)
        gui_data[y2*256 + i] = colour;

    // left surface
    if(x1 == x1m) for (i=y1m; i <= y2m; i++)
        gui_data[i*256+x1] = colour;

    // right surface
    if(x2 == x2m) for (i=y1m; i <= y2m; i++)
        gui_data[i*256+x2] = colour;
This replaces the end of drawbox. I have also found this function handy:
// movie.open()
//
//   Opens movie playback/recording
static int movie_open(lua_State *L) {
    const char *filename = lua_tostring(L,1);
    int readonly = TRUE;
    int sync = 0;
    Settings.WrongMovieStateProtection = false;
    S9xMovieOpen(filename, readonly, sync);
    return 0;
}
Using these changes, I have made a ghosting script for Super Metroid. I suspect that it might sometimes be off by 1 frame, and it has not been completely tested how it reacts to savestates, but perhaps it will come in handy. It consists of two parts: recorder.lua:
-- the movie file to use
moviefile = "herooftheday2-smetroid.smv"
-- output file. contains ingame frame, scroll, position and hitbox information
dumpfile = moviefile..".dump"
io.output(dumpfile)
-- index file. contains room transition information
indexfile = io.open(moviefile..".index", "w")
snes9x.speedmode("turbo")
movie.open(moviefile)
prev = {}
prev.roomid1 = memory.readword(0x7ED552)
prev.roomid2 = memory.readword(0x7ED554)
prev.roomid3 = memory.readword(0x7ED556)
while true do
    if movie.mode() then
        igframe = 3600*memory.readbyte(0x7E09DE)+60*memory.readbyte(0x7E09DC)+memory.readbyte(0x7E09DA)
        roomid1 = memory.readword(0x7ED552)
        roomid2 = memory.readword(0x7ED554)
        roomid3 = memory.readword(0x7ED556)
        scrollx, scrolly = memory.readword(0x7E0911), memory.readword(0x7E0915)
        posx, posy = memory.readword(0x7E0AF6), memory.readword(0x7E0AFA)
        hitx, hity = memory.readword(0x7E0AFE), memory.readword(0x7E0B00)
        io.write(igframe, " ", roomid1, " ", roomid2, " ", roomid3, " ", scrollx, " ",
            scrolly, " ", posx, " ", posy, " ", hitx, " ", hity, "\n")
        -- keep track of door transitions. this is used in room ghost mode
        if roomid1 ~= prev.roomid1 or roomid2 ~= prev.roomid2 or roomid3 ~= prev.roomid3 then
            indexfile:write(roomid1, " ", roomid2, " ", roomid3, " ", prev.roomid1, " ", prev.roomid2, " ", prev.roomid3, " ", movie.framecount(), " ", igframe, "\n")
        end
        prev.roomid1 = roomid1
        prev.roomid2 = roomid2
        prev.roomid3 = roomid3
    end
    snes9x.frameadvance()
end
Specify which smv to use at the top of this file, and it will generate a .dump and .index file for that movie. Do this for all the movies you want a ghost for. The main part of the script is in player.lua:
moviefile = "cpadolf2-smetroid.smv"
others = { "herooftheday-smetroid.smv", "herooftheday2-smetroid.smv", "cpadolf-smetroid.smv", "cpadolf2-smetroid.smv" }
colors = { "red", "green", "blue", "white" }
        
-- ingame mode:
-- 0: Show all real-time frames
-- 1: Skip non-ingame frames for main character 
-- 2: Skip non-ingame frames for everybody
ingame = 2

-- room comparison mode:
-- 0: Normal
-- 1: Synchroniced room entry
room = 1

-- set up movie data
data = {}
pattern = "(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)"
for i,name in ipairs(others) do
    print("Loading "..name.." position data")
    local filedata = {}
    io.input(name..".dump")
    for line in io.lines() do
        local entry = {}
        _,_,entry.igframe,entry.roomid1, entry.roomid2, entry.roomid3,
        entry.scrollx, entry.scrolly, entry.posx, entry.posy,
        entry.hitx, entry.hity = string.find(line,pattern)
        table.insert(filedata, entry)
    end
    filedata.progress = 1
    filedata.sync = 0
    filedata.offset = 0
    filedata.init = false
    table.insert(data,filedata)
    snes9x.frameadvance() -- hack to avoid snes9x thinking we are hanging
end
-- set up index data
index = {}
pattern = "(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)"
for i,name in ipairs(others) do
    print("Loading "..name.." room transition data")
    local filedata = {}
    io.input(name..".index")
    for line in io.lines() do
        local entry = {}
        _,_,entry.roomid1, entry.roomid2, entry.roomid3,
        entry.roomid1p, entry.roomid2p, entry.roomid3p,
        entry.frame, entry.igframe = string.find(line,pattern)
        local key = entry.roomid1.." "..entry.roomid2.." "..entry.roomid3.." "..
            entry.roomid1p.." "..entry.roomid2p.." "..entry.roomid3p
        if not filedata[key] then filedata[key] = {} end
        local val = {}
        val.frame = entry.frame
        val.igframe = entry.igframe
        table.insert(filedata[key],val)
    end
    table.insert(index,filedata)
    snes9x.frameadvance() -- hack to avoid snes9x thinking we are hanging
end

-- find the frame corresponding to a given in-game frame.
-- Does a halving search, but is optimized for the case where
-- we are quite close already
seek = function(t, igf)
    local tmp = 4
    local a = t.progress
    local b = t.progress
    -- setup search window
    while igf - t[a].igframe < 0 do
        b = a
        a = a - tmp
        tmp = tmp * 2
        if a <1> 0 do
        a = b
        b = b + tmp
        tmp = tmp * 2
        if b > table.getn(t) then b = table.getn(t) end
    end
    if t[b].igframe == igf then return b end
    -- now do a halving search.
    -- We know that the value we want is between a and b
    -- and that a < b
    while true do
        local mid = (a+b)/2
        if t[mid].igframe - igf == 0 then return mid
        elseif t[mid].igframe - igf <0> 0 then igframe = 0
        else beginning = false end
    end
    -- igframe = memory.readword(0x7E1842)

    -- unless we are in real time mode, we will skip frames where the
    -- incame frame counter does not change
    if ingame > 0 and igframe ~= igframe_old then
        skippedframes = count-lastcount-1
        snes9x.speedmode("normal")
        -- build the full room id. not sure if all these are needed
        roomid1 = memory.readword(0x7ED552)
        roomid2 = memory.readword(0x7ED554)
        roomid3 = memory.readword(0x7ED556)
        roomid = ""..roomid1.." "..roomid2.." "..roomid3
        -- information for drawing the hitboxes
        scrollx, scrolly = memory.readword(0x7E0911), memory.readword(0x7E0915)
        posx, posy = memory.readword(0x7E0AF6), memory.readword(0x7E0AFA)
        hitx, hity = memory.readword(0x7E0AFE), memory.readword(0x7E0B00)

        -- have we had any room transitions?
        if roomid ~= roomid_old then transition = true else transition = false end
        for i = 1, table.getn(data) do
            -- do we need to fix our position in the file?
            if stloaded then
                if ingame > 0 then data[i].progress = seek(data[i],igframe)
                else data[i].progress = count+1 end
            end

            -- however, in room ghost mode, we will find the closest
            -- matching room transition.
            if room == 1 and transition then
                local frame
                if ingame < 2 then frame = count else frame = igframe end
                local trans = index[i][roomid.." "..roomid_old]
                if trans then
                    local diff = 1e55, bj
                    for j = 1, table.getn(trans) do
                        local tframe
                        if ingame < 2 then tframe = trans[j].frame
                        else tframe = trans[j].igframe end
                        local cdiff = math.abs(frame-tframe)
                        if cdiff <diff> 0 do
                        data[i].progress = data[i].progress + 1
                    end
                    data[i].init = true
                end
                -- eat frames where igframe does not change
                while data[i][data[i].progress].igframe -
                    data[i][data[i].progress+1].igframe == 0 do
                    data[i].progress = data[i].progress+1
                end
                data[i].progress = data[i].progress+1
            else data[i].progress = count+1 end
            c = data[i].progress
            if c < table.getn(data[i]) then
                local entry = data[i][c]
                if roomid1 - entry.roomid1 == 0 and
                    roomid2 - entry.roomid2 == 0 and
                    roomid3 - entry.roomid3 == 0 then
                    x = entry.posx-scrollx
                    y = entry.posy-scrolly
                    gui.drawbox(x-entry.hitx,y-entry.hity,x+entry.hitx,y+entry.hity,colors[i])
                end
            end
        end
        roomid_old = roomid
        lastcount = count
    else snes9x.speedmode("maximum") end
    igframe_old = igframe
    frameinc()
    snes9x.frameadvance()
end
The options of interest are moviefile, others, ingame and room, and are documented in the file.
Emulator Coder, Site Developer, Former player
Joined: 11/6/2004
Posts: 833
I kinda did the border drawing thing intentionally. I don't remember my exact reasons, but all the drawing functions will error out if you don't draw within the viewing area. If you really want to change behaviour, do this:
do
  local oldbox = gui.drawbox
  function gui.drawbox(x1,y1,x1,y2,colour)
      [ Your code here, use oldbox in place of gui.drawbox]
  end
end
And it's completely compatible with any existing code. As for your ghost, nice going. My basic strategy was two emulators running different scripts, probably using gd to assist. One would locate $PROTAGONIST, take a screen shot, feed it to gd, crop out the excess, and send it to the first emulator which would draw it transparent over where $PROTAGONIST would have been. It requires the gd library and some IPC (interprocess communication) library, probably sockets.
Player (121)
Joined: 2/11/2007
Posts: 1522
I have a Lua question: I want to run an external program, passing it input from my script and using the output as data. I believe I can do this by saving the input as a file, running the program with os.execute and pointing to the file, sending the output from the external program to another file then reading that file back into Lua. Is there an easier way, like sending the stdout from Lua into the stdin of the external program, and then vice versa?
I make a comic with no image files and you should read it. While there is a lower class, I am in it, and while there is a criminal element I am of it, and while there is a soul in prison, I am not free. -Eugene Debs
Emulator Coder, Site Developer, Former player
Joined: 11/6/2004
Posts: 833
Here's half your solution. I recommend using "r" mode. Bidirectional mode is not supported. Maybe you can use use command-line parameters for the other half? Or you'll be stuck with files.
Former player
Joined: 2/19/2007
Posts: 424
Location: UK
My original attempt used lots of savestates to jump between several movies in the same emulator, trying to grab only the sprite layer from all but one of one of them, and put them on top of the image from the other. I wrote a quicker set of savestates (using no compression and only memory) for this, and added lua functions for adding together several screenshots with transparent parts (and avoiding putting the screenshot in a string), as well as functions for updating the image without frame advancing, turning on and off backgrounds in lua, etc. but it was too slow to be practical, and eded up desyncing anyway. It could have looked nice, though. It sounds, from what you write, like you have implemented ghosting already yourself. If so, it would be nice to see. Using two (or more) emulators was my original plan, but being unfamiliar with interprocess communication, I fell back on trying to do it with one emulator. One thing my script does better than such an approach, is the room ghost mode, where you can compare how different movies traverse each room. Oh, and I can't see how it's possible to implement clipped rectangles by using your rectangle code. One would have to use drawline instead. After all,
+------------------+
|    |        +---+|
| A  |        | B ||
|----+        +---+|
|                  |
|                  |
|  Screen          |
|                  |
+------------------+
A is a clipped rectangle. Some of its edges are off screen, so we only see two of them in this case. B is a shrunk rectangle. All its edges are visible. I think my implementation of drawrect is the most sensible, as it allows drawing of clipped rectangles, and spares the user from having to perform a very common check.
Former player
Joined: 2/19/2007
Posts: 424
Location: UK
Dehacked, is there a way to store some information in savestates? I found a few bugs in my script, and I need this feature to be able to fix it. I need to know both which room I am in and which room I came from, but there is no way to recover the latter if a savestate is loaded. I seem to remember talk of such a feature before, but I can't find it.
Emulator Coder, Site Developer, Former player
Joined: 11/6/2004
Posts: 833
Please read the savestate.registersave() and savestate.registerload() sections of the API docs. I think you'll be happy.
Former player
Joined: 2/19/2007
Posts: 424
Location: UK
Yes, those should do it... but it seems that my version is 0.05. I can find the source code for 0.04 and 0.05, but 0.06 seems to lack it.
Player (121)
Joined: 2/11/2007
Posts: 1522
DeHackEd wrote:
Here's half your solution. I recommend using "r" mode. Bidirectional mode is not supported. Maybe you can use use command-line parameters for the other half? Or you'll be stuck with files.
That's a pretty neat method, too bad no "rw" though :( The input to the external program can't go through command-line parameters unfortunately. But files will work fine, just wanted to see if there was something cleaner. New question: is there any way to take a snapshot of a smaller part of the screen, something between getpixel and snapshot? Or a way to "crop" the gd string? Thanks again.
I make a comic with no image files and you should read it. While there is a lower class, I am in it, and while there is a criminal element I am of it, and while there is a soul in prison, I am not free. -Eugene Debs
Emulator Coder, Site Developer, Former player
Joined: 11/6/2004
Posts: 833
The gdstring thing was intended for use with the gd library. It would probably have a crop or rectangle copy feature. Failing that, no, there's nothing better available.
Player (121)
Joined: 2/11/2007
Posts: 1522
Can I just import the library into my script or something? I was looking at that but wasn't sure how to make use of it.
I make a comic with no image files and you should read it. While there is a lower class, I am in it, and while there is a criminal element I am of it, and while there is a soul in prison, I am not free. -Eugene Debs
Emulator Coder, Site Developer, Former player
Joined: 11/6/2004
Posts: 833
Grab the gd for lua package at luaforge. Unzip the contents into your snes9x directory. The DLL and any .lua scripts should all be dumped there. In your smes9x script, put
require "gd"
on your top line. Assuming it doesn't error out, you should have full gd functions available now.
Player (121)
Joined: 2/11/2007
Posts: 1522
Sweet. Any updates on Lua for other emulators? :)
I make a comic with no image files and you should read it. While there is a lower class, I am in it, and while there is a criminal element I am of it, and while there is a soul in prison, I am not free. -Eugene Debs
Emulator Coder, Site Developer, Former player
Joined: 11/6/2004
Posts: 833
Yeah, VBA won't compile over here. Mupen64 and I have a hate-hate relationship. Nobody seems to care about FCEU. Gens is Upthron's project by his own choice -- he has the source code, and knows where he can ask any questions.
Player (121)
Joined: 2/11/2007
Posts: 1522
I care about FCEU! And maybe you could take a look at dega?
I make a comic with no image files and you should read it. While there is a lower class, I am in it, and while there is a criminal element I am of it, and while there is a soul in prison, I am not free. -Eugene Debs
Joined: 3/17/2007
Posts: 97
Location: Berkeley, CA
Here's a script for Gradius 3, heavily based on smetroid.lua. You get differently colored hitboxes for the player, shields, missiles, projectiles, and enemies (killable or otherwise). Enemies have HP counters. Finally, some address at the bottom gives an indicator of progress through the auto-scrolling level (to estimate lag?) and the vertical size of the level (256 = 1 screenful). I think it looks pretty awesome. :)
local playerX, playerY
local cameraX, cameraY


gui.transparency(0)

while true do

	playerX = memory.readword(0x7e020a)
	playerY = memory.readword(0x7e020e)

	-- Camera abuse: This might be wrong, but it looks right.
	cameraX = 0
	cameraY = 16

	local radiusX, radiusY = memory.readword(0x7e0228), memory.readword(0x7e022a)

	-- Draw player's hitbox
	do
		-- Use of tables wastes some memory, but I don't really care.

		local topleft = {playerX - cameraX - radiusX, playerY - cameraY - radiusY}
		if topleft[1] < 0 then
			topleft[1] = 0
		elseif topleft[1] >= 256 then
			topleft[1] = 255
		end
		if topleft[2] < 0 then
			topleft[2] = 0
		elseif topleft[2] >= 239 then
			topleft[2] = 238
		end

		local bottomright = {playerX - cameraX + radiusX, playerY - cameraY + radiusY}
		if bottomright[1] < 0 then
			bottomright[1] = 0
		elseif bottomright[1] >= 256 then
			bottomright[1] = 255
		end
		if bottomright[2] < 0 then
			bottomright[2] = 0
		elseif bottomright[2] >= 239 then
			bottomright[2] = 238
		end

		-- Render
		gui.drawbox(topleft[1],topleft[2], bottomright[1],bottomright[2], "#808080")

	end


	-- Check out interesting hit-boxes
	for i=0,58 do
	
	-- Much to my annoyance, lua has no "continue" construct. So
	-- I'm wrapping the whole thing in a "loop" which I can break out of
	-- and force the for loop to start its next iteration
	 while true do
		local boxColor
		local monActive = memory.readword(0x7e0350 + 64*i)
		local monHP = memory.readword(0x7e0346 + 64*i)
		local monX, monY = memory.readword(0x7e034a + 64*i), memory.readword(0x7e034e + 64*i)
		local monRadX, monRadY = memory.readword(0x7e0368 + 64*i), memory.readword(0x7e036a + 64*i)

		-- Skip inactive monsters
		if monActive == 0 then
			break
		end

		if i < 2 then -- shield
			boxColor = "#800080" -- purple
		elseif i < 12 then -- projectile
                	if monHP <= 0 then
				monRadX = 3
				monRadY = 3
                        end
			boxColor = "blue"
		elseif i < 22 then -- missile
                	if monHP <= 0 then
				monRadX = 3
				monRadY = 3
                        end
			boxColor = "#800080" -- maroon
		elseif monHP == 0 or monHP >= 32767 then -- not alive
			boxColor = "#808080" -- grey
		elseif AND(memory.readword(0x7e036e + 64*i), 0x0100) ~= 0 then -- killable enemy
			boxColor = "green"
		elseif AND(memory.readword(0x7e036e + 64*i), 0x0040) ~= 0 then -- enemy fire
			boxColor = "red"
		else -- other enemy
			boxColor = "#ffff00" -- yellow
		end

		-- Constrain coordinates to viewable area
		local topleft = {monX - cameraX - monRadX, monY - cameraY - monRadY}
		if topleft[1] < 0 then
			topleft[1] = 0
		elseif topleft[1] >= 256 then
			topleft[1] = 255
		end
		if topleft[2] < 0 then
			topleft[2] = 0
		elseif topleft[2] >= 239 then
			topleft[2] = 238
		end

		local bottomright = {monX - cameraX + monRadX, monY - cameraY + monRadY}
		if bottomright[1] < 0 then
			bottomright[1] = 0
		elseif bottomright[1] >= 256 then
			bottomright[1] = 255
		end
		if bottomright[2] < 0 then
			bottomright[2] = 0
		elseif bottomright[2] >= 239 then
			bottomright[2] = 238
		end

		-- If we determine that it's off the screen, screw it.
		if topleft[1] == bottomright[1] or topleft[2] == bottomright[2] then
			break
		end

		-- Draw the hitbox
		gui.drawbox(topleft[1],topleft[2], bottomright[1],bottomright[2], boxColor)
	
		-- Render the monster's HP right there
		if (boxColor == "green") then
			local textX, textY = topleft[1] + (bottomright[1]-topleft[1])/2, bottomright[2]
			
			if textY > (224-8) then
				textY = 224-8
			end
			gui.text(textX, textY, tostring(monHP))
		end

		
		break -- out of fake while loop
	 end -- Of false fake loop	
	end

	-- Lastly, player's progress
        local progress = memory.readword(0x7e128a)
        local height = memory.readword(0x7e0090) + 1
	gui.text(0,215,string.format("Progress=%05d Height=%05d", progress, height))

	-- Continue emulation
	snes9x.frameadvance()

	-- io.write(collectgarbage("count")  io.write("\r")  io.flush()

end
IRC nick: UncombedCoconut
1 2 3 4 5
8 9