User File #6680317080319470

Upload All User Files

#6680317080319470 - MtEdit for lsnes v2 (PROTOTYPE)

MtEdit_lsnes_v2.lua
System: Super Nintendo Entertainment System
1062 downloads
Uploaded 5/19/2013 7:57 PM by FatRatKnight (see all 245)
Features:
  • Displays a list of input in easy red and green colors on the left side
  • Displays the immediate input to inject to lsnes along the bottom
  • Auto-detects which controllers are plugged in at script start
    • Gamepads and multitaps (and 16 button versions) only, no analog controls ready
  • The hotkeys set up for player 1 and player 2 works (in theory!)
    • Normal controls should press or release as normal; Or the reverse if the button is held.
    • Type controls should toggle the immediate input to inject, as normal
    • Hold controls switch between four different read modes from recorded input, for individual buttons (normal, keep released, keep pressed, invert)
      • Will automatically revert to normal on state load; Mostly because I, FatRatKnight, had some TASing screw-ups when it didn't automatically do this.
Feel free to try it out. I have a few things I want to add to it, though...
Also, for lsnes only. Will not work in other emulators. A shame, since I think it's just plain useful.
--MtEdit lsnes v2 (PROTOTYPE)
--For lsnes rr1 delta17 epsilon3
--FatRatKnight

--[[
Checklist:
[X] - gamepad
[X] - gamepad16
[X] - multitap
[X] - multitap16
[?] - mouse
[ ] - superscope
[ ] - justifier
[ ] - justifiers

[ ] - Allow a way to control analog stuff
[ ] - Record/apply reset
[ ] - Insert/delete frames
[ ] - Subframe control
[ ] - Macros
[ ] - Rewind
[ ] - Sanity versus unusual circumstances (Like new -> movie)
]]--

--#############################################################################
-- Key bindings

local inp_MouseUp= "i"
local inp_MouseDn= "k"
local inp_MouseLf= "j"
local inp_MouseRt= "l"

local cmd_SelectPlayer= "p"


--#############################################################################
-- Anything other than key bindings

local Tc= { -- Tile colors to display button pressed state.
--   Released  Pressed
[0]= 0xFF2000, 0x00FF00, -- Saturated, normal
     0xFF8080, 0xA0FFA0, -- Light, lag indicator
     0xC05040, 0x40C040, -- Desat., oversnoop indicator (no changing inputs)
     0xFFFF00, 0xFFFF00  -- Yellow=changing oversnoop
}


-- Beyond this point is probably a good idea not to change.
-- Unless, of course, you know what you're doing.

--#############################################################################
--#############################################################################
-- General purpose functions and utilities


local function NullFN() end  -- Take a wild guess. If you said nothing, you win


GapVals= {top=0,bottom=0,left=0,right=0}

--*****************************************************************************
function RequestGap(side,value)
--*****************************************************************************
-- For on_paint or on_video only. Messy stuff might happen otherwise.
-- Adds to a number for the next time gaps are to be drawn.
-- Returns an x or y position, based on side:
--   top:    y position of total requested top gaps
--   bottom: y position of total bottom gaps + Y resolution
--   left:   x position of total requested left gaps
--   right:  x position of total right gaps  + X resolution
-- These values returned *EXCLUDE* the latest requested gap.
-- You should be able to add them yourself - You requested that gap!
-- Feel free to call this with a value of zero if you want the stored value.
-- Although, why not just look at GapVals.whatever or GapVals["whatever"]?

-- Defensive sanity
    if not GapVals[side] then
        error("Bad argument #1",2)
    end

    if type(value) ~= "number" then
        error("Argument #2 must be number",2)
    end

    value= math.floor(value) -- Trim off fractional portion.
    if value < 0 then value= value end -- Negative gaps? No.

-- Now do complicated calculations!
    local LastGap= GapVals[side]
    GapVals[side]= GapVals[side]+value

    return LastGap
end

--*****************************************************************************
function ApplyGaps()
--*****************************************************************************
-- on_paint or on_video only.
-- Please call at the very end of an on_paint or on_video callback.
-- Makes use of the overtaking property of gap functions.
-- Will also reset GapVals as they are used.

    gui.top_gap(   GapVals.top)   ;GapVals.top   =0
    gui.bottom_gap(GapVals.bottom);GapVals.bottom=0
    gui.right_gap( GapVals.right) ;GapVals.right =0
    gui.left_gap(  GapVals.left)  ;GapVals.left  =0
end


-- Key press stuff.

local KeyPress,KeyRelease= {},{}

--*****************************************************************************
local function RegisterKeyPress(key,fn)
--*****************************************************************************
    local OldFn= KeyPress[key]
    KeyPress[key]= fn
    input.keyhook(key,type(fn or KeyRelease[key]) == "function")
    return OldFn
end

--*****************************************************************************
local function RegisterKeyRelease(key,fn)
--*****************************************************************************
    local OldFn= KeyRelease[key]
    KeyRelease[key]= fn
    input.keyhook(key,type(fn or KeyPress[key]) == "function")
    return OldFn
end

--*****************************************************************************
local function AltKeyhook(s,t)
--*****************************************************************************
    if     KeyPress[s]   and (t.last_rawval == 1) then
        KeyPress[s](s,t)
    elseif KeyRelease[s] and (t.last_rawval == 0) then
        KeyRelease[s](s,t)
    end
end

--#############################################################################
--#############################################################################
-- Global

local PlSel= 0  -- Which player is the focus? Zero to seven.
local cPort= 1  -- Which port is PlSel on? ... I need this all the time. Eep.
local Ports= {input.port_type(1),input.port_type(2)}

local cf= movie.currentframe()
local ResX, ResY= 512,448       -- May want to dynamically read the resolution.

local MaxPlayers= {
    gamepad   =1,
    gamepad16 =1,
    multitap  =4,
    multitap16=4,
    mouse     =1,
    superscope=1,
    justifier =1,
    justifiers=2,
    none      =0
}

--local ListSize  = math.floor((ResY+1)/8)
--local ListOffset= math.floor(ListSize/2)
--local ListWidth = PortWidths[Ports[1]] or 0

local Immediate   = {[0]=0,0,0,0,0,0,0,0} --[player]; For immediate injection.
local BtnState    = {0,0}
local FrameList   = {} --[player][frame]
local SubframeList= {} --[player][frame][subframe]
local ListRead    = {} --[player] How we'll read stored input
for pl= 0, 7 do
    FrameList[pl]= {}
    SubframeList[pl]= {}
    ListRead[pl]=0x000000FFFFFF
end


--*****************************************************************************
local TranslateIndex= {}
--*****************************************************************************
-- Get the proper bit position.

-------------------------------------------------------------------------------
function TranslateIndex.gamepad(i)    return bit.value(i)     end
function TranslateIndex.mouse(i)      return bit.value(i+16)  end
function TranslateIndex.superscope(i) return 0  end -- Not supported, yet
function TranslateIndex.justifier(i)  return 0  end -- Not supported, yet
function TranslateIndex.none(i)       return 0  end -- This shouldn't happen
TranslateIndex.gamepad16 = TranslateIndex.gamepad
TranslateIndex.multitap  = TranslateIndex.gamepad
TranslateIndex.multitap16= TranslateIndex.gamepad
TranslateIndex.justifiers= TranslateIndex.justifier


--Structure:

--gamepad,gamepad16,multitap,multitap16:
--  0xccccbbbbaaaa
--  a = Pressed or not pressed
--  b = Lag indicator.
--  c = Oversnoop indicator. Are subframes needed?

--mouse:
--  0xcbaYYXX
--  X = X coordinate (8th bit indicates positive/negative: 1 is negative)
--  Y = Y coordinate (Same deal with X coordinate)
--  a = Pressed or not pressed; For X,Y coordinates, record as 1 if non-zero.
--  b = Lag inicator.
--  c = Oversnoop indicator. Are subframes needed?

--superscope,justifier,justifiers:
--  Undefined. To be defined when someone codes it up.

--system:
--  Just one number. Call it [V]
--  If negative, no reset takes place
--  Otherwise, request reset after [V] cycles.
--  Not sure if there's any other data to pick up.

--none:
--  Empty set. Do not record or overwrite.

--[[
--*****************************************************************************
local function GetFrame(port,frame,player,subframe)
--*****************************************************************************
-- Returns a number representing the controller input.
-- These numbers are the base storage for MtEdit.

end

--*****************************************************************************
local function GetFrameFromlsnes(port,frame,player,subframe)
--*****************************************************************************
-- Returns a number, stitched together from lsnes own storage.
-- Returns nil if the frame doesn't exist.

    if (frame <= 0) or (frame > movie.framecount()) then return nil end
end
]]--

--#############################################################################
--#############################################################################
-- Display

--Icons

--*****************************************************************************
local function Dbmp7x7(n,clr)
--*****************************************************************************
-- Converts a number into a monochrome 7x7 BITMAP.
-- Enough space in a double for this to happen. So I use it.
-- However, readability suffers greatly due to this.

   local bitmap= gui.bitmap_new(7,7,true)
   for i= 0, 48 do
      local c= -1
      if bit.extract(n,i) == 1 then c= clr end
      gui.bitmap_pset(bitmap, -- DBITMAP object.
         i%7,                 -- x
         math.floor(i/7),     -- y
         c                    -- color
      )
   end

   return bitmap -- Be sure to hand out what I just made.
end

-------------------------------------------------------------------------------
local Icons7x7= {
[0]=0x0FBFE3C78FFBE, -- 0
    0x1FFF98306CF1C, -- 1
    0x1FF98638C73BE, -- 2
    0x0FB3F079C33BE, -- 3
    0x0C187FFECD9B3, -- 4
    0x0FB1E07E0FFFF, -- 5
    0x0FB1E77E0C1BE, -- 6
    0x0186186183FFF, -- 7
    0x0FB1E37D8F1BE, -- 8    Too similar to B. I'm going to change it soon...
    0x10205EE7879BE, -- 9    ... This one looks stupid
    0x18FFFFC78DF1C, -- A
    0x0FF1E37F8F1BF, -- B
    0x0FBF83060FFBE, -- C
    0x07D9E3C78D99F, -- D
    0x1FC1837E0C1FF, -- E
    0x00C1BF7E0FFFF, -- F

    0x000000FE00000, -- -
    0x020408FE20408, -- +

--B
    0x060C1830F3343, -- Y
    0x198CB4A484486, -- Select
    0x0994A848894F6, -- Start
    0x18DB3E3870408, -- Up
    0x02041C38F9B63, -- Down
    0x10383C3EF3840, -- Left
    0x00439EF878381, -- Right
--A
    0x10F33C30F3343, -- X
    0x1FFF83060C183, -- L
    0x18F1BF7F8F1BF, -- R
}

-------------------------------------------------------------------------------

-- Make white tiles.
for x= 0, #Icons7x7 do
    Icons7x7[x]= Dbmp7x7(Icons7x7[x],0xFFFFFF)
end

local EmptyIcon= gui.bitmap_new(7,7,true)

-------------------------------------------------------------------------------
--gamepad16 (and friends)
--BYsS^v<>AXLR0123

local BlankPad=   gui.bitmap_new(127,7,true)

--                    B  Y  s  S  ^  v  <  >  A  X  L  R  0  1  2  3
local PadIcons= {[0]=11,18,19,20,21,22,23,24,10,25,26,27, 0, 1, 2, 3}

for i= 0, 15 do
    local IconIndex= PadIcons[i]

--Produce the blank frame.
    gui.bitmap_blit(
        BlankPad,           i*8,0,
        Icons7x7[IconIndex],0  ,0, 7,7
    )

--Produce the various colored tiles!
    PadIcons[i]= {}

    for c= 0, #Tc do
        PadIcons[i][c]= gui.bitmap_new(7,7,true,Tc[c])
        gui.bitmap_blit(
            PadIcons[i][c],     0,0,
            Icons7x7[IconIndex],0,0, 7,7 , 0xFFFFFF
        )  -- Use blank tiles like stencil. Should be faster than pset.
    end
end


-------------------------------------------------------------------------------
--mouse
--X+7F Y+7F LR

-- Produce blank mouse frame:
local BlankMouse= gui.bitmap_new(95,7,true)

gui.bitmap_blit(BlankMouse, 0,0 , Icons7x7[25],0,0, 7,7) -- X...........
gui.bitmap_blit(BlankMouse,16,0 , Icons7x7[16],0,0, 7,7) -- ..-.........
gui.bitmap_blit(BlankMouse,24,0 , Icons7x7[16],0,0, 7,7) -- ...-........
gui.bitmap_blit(BlankMouse,40,0 , Icons7x7[18],0,0, 7,7) -- .....Y......
gui.bitmap_blit(BlankMouse,56,0 , Icons7x7[16],0,0, 7,7) -- .......-....
gui.bitmap_blit(BlankMouse,64,0 , Icons7x7[16],0,0, 7,7) -- ........-...
gui.bitmap_blit(BlankMouse,80,0 , Icons7x7[26],0,0, 7,7) -- ..........L.
gui.bitmap_blit(BlankMouse,88,0 , Icons7x7[27],0,0, 7,7) -- ...........R

-- Now lets assign a bunch of icons for the mouse!
local MouseIcons= {}
for i= 0, 0xF do MouseIcons[i]= Icons7x7[i] end  -- White digits.

MouseIcons[16]= PadIcons[ 9] -- X
MouseIcons[17]= PadIcons[ 1] -- Y
MouseIcons[18]= PadIcons[10] -- L
MouseIcons[19]= PadIcons[11] -- R

MouseIcons[20]= gui.bitmap_new(7,7,true,0x00FFFF) -- Cyan plus
gui.bitmap_blit(MouseIcons[20],0,0 , Icons7x7[17],0,0, 7,7 , 0xFFFFFF)
MouseIcons[21]= gui.bitmap_new(7,7,true,0xFFFF00) -- Yellow minus
gui.bitmap_blit(MouseIcons[21],0,0 , Icons7x7[16],0,0, 7,7 , 0xFFFFFF)

--Wonky indexing, but string indecies make the code too messy.


--#############################################################################
-- Display Frame List (left)

local DisplayLength= 56
local DisplayOffset= math.floor(DisplayLength/2)

local DispList=  gui.bitmap_new( 95,447,true)
local DispListEx=gui.bitmap_new(127,447,true)
local TempFrame= gui.bitmap_new(127,  7,true)

local MainList     = nil  -- Should hold the current display
local MainListWidth= 0

local PortWidths= {  -- How wide to make the gap?
    gamepad   = 95,
    gamepad16 =127,
    multitap  = 95,
    multitap16=127,
    mouse     = 95,

    [ 95]= DispList,
    [127]= DispListEx
}

--*****************************************************************************
local function BlitGamepad(IN)
--*****************************************************************************
    for i= 0, 15 do
        local color= bit.extract(IN,i,i+16,i+32)
        gui.bitmap_blit(
            TempFrame,8*i,0,
            PadIcons[i][color],0,0, 7,7
        )
    end
    return TempFrame
end

local MouseTilePos= {[0]=0,40,80,88}
--*****************************************************************************
local function BlitMouse(IN)
--*****************************************************************************
--Blank spaces
    gui.bitmap_blit(TempFrame,32,0, EmptyIcon,0,0, 7,7)
    gui.bitmap_blit(TempFrame,72,0, EmptyIcon,0,0, 7,7)

--X,Y indicators and Left, Right mouse buttons
    for i= 0, 3 do
        local color= bit.bor(
            bit.extract(IN,i+16),       --Pressed/released
            bit.extract(IN,nil,i+20),   --Lag indicator
            bit.extract(IN,nil,nil,i+24)--Oversnoop indicator
        )
        gui.bitmap_blit(
            TempFrame,MouseTilePos[i],0,
            MouseIcons[i+16][color],0,0, 7,7
        )
    end

--Now to blit in change of mouseposition...
    for i= 0, 1 do
        local v= 8*i
        local index= 20 + bit.extract(IN,7+v) --Plus or minus
        gui.bitmap_blit(
            TempFrame, 8+40*i,0,
            MouseIcons[index],0,0, 7,7
        )

        index= bit.extract(IN,4+v,5+v,6+v)    --Upper hex digit
        gui.bitmap_blit(
            TempFrame,16+40*i,0,
            MouseIcons[index],0,0, 7,7
        )

        index= bit.extract(IN,v,1+v,2+v,3+v)  --Lower hex digit
        gui.bitmap_blit(
            TempFrame,24+40*i,0,
            MouseIcons[index],0,0, 7,7
        )
    end
    return TempFrame
end

local BlitFn
local BlankFr
--*****************************************************************************
local function ChooseList(PortType)
--*****************************************************************************
-- Best used on startup and whenever a new player is selected.
-- ... Turn this into a table at some point...

    MainListWidth= PortWidths[PortType]
    if MainListWidth then
        MainList= PortWidths[MainListWidth]
        if PortType == "mouse" then
            BlitFn= BlitMouse
            BlankFr= BlankMouse
        else
            BlitFn= BlitGamepad
            BlankFr= BlankPad
        end
        return
    end

--Either unsupported or this is an empty port. Either way, set up blanks.
    MainListWidth= 0
    MainList= nil
end

--*****************************************************************************
local function MakeListFrame(i,frame)
--*****************************************************************************
-- Assumes MainList exists. Don't call without ensuring it's there!

    local IN= FrameList[PlSel][frame]
    local Frame= BlankFr              --Confusing names. Sorry! "frame" "Frame"
    if IN then Frame= BlitFn(IN) end
    gui.bitmap_blit(
        MainList,0,i*8 ,
        Frame,   0,0   ,  MainListWidth,7
    )
end

--*****************************************************************************
local function MakeListFromScratch()
--*****************************************************************************
--Call on script startup, new player selected, and anytime the frame shifts by
--anything other than +0 or +1.
--Really would love it if ChooseList got the right player first.
--Otherwise, the display 

    if not MainList then return end  -- Don't. Just, don't blit onto nothing!

    for i= 0, DisplayLength-1 do
        MakeListFrame(i,  cf - DisplayOffset + i)
    end
end

--*****************************************************************************
local function AdvanceList()
--*****************************************************************************
--Use inside callback on_frame_emulated
--cf (Current Frame) is still on its old value.
--Moves the display up one step, then gets a single new frame.
    if not MainList then return end  -- No MainList? Then do nothing.

    gui.bitmap_blit(  -- Dangerous use of blit: source == target
        MainList,0,0, -- I'm abusing a quirk for efficiency reasons.
        MainList,0,8, MainListWidth,DisplayLength*8-9
    )
    MakeListFrame(
        DisplayLength - 1,
        cf - DisplayOffset + DisplayLength
    )
    MakeListFrame(
        DisplayOffset - 1,
        cf
    )
end

local ReadListColors= {
[0]= 0xFF0000,  -- Always return released
     0xFFFFFF,  -- Read movie
     0x0000FF,  -- Invert movie
     0x00FF00   -- Always return pressed
}
--*****************************************************************************
local MovieReadDots= {}
--*****************************************************************************

-------------------------------------------------------------------------------
function MovieReadDots.GetColor(i)
-------------------------------------------------------------------------------
-- Need this sequence of code in all my ReadDots...
    return ReadListColors[bit.extract(ListRead[PlSel],i,i+24)]
end

-------------------------------------------------------------------------------
function MovieReadDots.gamepad(X)
-------------------------------------------------------------------------------
    for i= 0, 11 do
        local x= X + i*8 + 4
        gui.circle(x,ResY+4,4,0,0,MovieReadDots.GetColor(i))
    end
end
MovieReadDots.multitap= MovieReadDots.gamepad

-------------------------------------------------------------------------------
function MovieReadDots.gamepad16(X)
-------------------------------------------------------------------------------
    for i= 0, 15 do
        local x= X + i*8 + 4
        gui.circle(x,ResY+4,4,0,0,MovieReadDots.GetColor(i))
    end
end
MovieReadDots.multitap16= MovieReadDots.gamepad16

-------------------------------------------------------------------------------
function MovieReadDots.mouse(X)
-------------------------------------------------------------------------------
    gui.rectangle(X+  1,ResY,31,7,0,0,MovieReadDots.GetColor(16))
    gui.rectangle(X+ 41,ResY,31,7,0,0,MovieReadDots.GetColor(17))
    gui.circle(   X+ 84,ResY+4,4 ,0,0,MovieReadDots.GetColor(18))
    gui.circle(   X+ 92,ResY+4,4 ,0,0,MovieReadDots.GetColor(19))
end


--*****************************************************************************
local function ShowList()
--*****************************************************************************
-- on_paint
-- Just request the gap,
-- and paint our bitmap.
-- Oh, and display some dots indicating movie playback mode.

    if not MainList then return end
    local X= RequestGap("left",MainListWidth+2)
    local Y= DisplayOffset*8-1
    gui.rectangle(X-MainListWidth-2,Y,MainListWidth+2,9, 1,0x00FFFF,0)
    gui.bitmap_draw(- X - MainListWidth - 1,0,MainList)
    MovieReadDots[Ports[cPort]](X - MainListWidth - 2)

    --Eh, dot them later...
end

--*****************************************************************************
local function LGui()
--*****************************************************************************

    ShowList()

--[[
    for i= 0, 23 do
        local x= i*8 + 4
        gui.circle(x,400,4,0,0,
            ReadListColors[bit.extract(ListRead[PlSel],i,i+24)])
    end
]]--
end

--#############################################################################
-- Display Immediate Input (bottom)

--                 B  Y  s  S  ^  v  <  >  A  X  L  R  0  1  2  3
local x_off= {[0]=48,40,24,32, 8, 8, 0,16,56,48, 0,56,16,24,32,40}
local y_off= {[0]=16, 8, 8, 8, 0,16, 8, 8, 8, 0, 0, 0,16,16,16,16}

--*****************************************************************************
local function DrawPad(x,y,IN)
--*****************************************************************************
-- Draws the controller using IN as input at coordinates x,y.
    for i= 0, 11 do
        local color= bit.extract(IN,i)
        gui.bitmap_draw(x+x_off[i],y+y_off[i],PadIcons[i][color])
    end
end

--*****************************************************************************
local function DrawPad16(x,y,IN)
--*****************************************************************************
-- Like DrawPad, but with the four extended buttons.
    for i= 0, 15 do
        local color= bit.extract(IN,i)
        gui.bitmap_draw(x+x_off[i],y+y_off[i],PadIcons[i][color])
    end
end

local m_sign= {[0]="+","-"}
local m_XY=   {[0]="X","Y"}
--*****************************************************************************
local function DrawMouse(x,y,IN)
--*****************************************************************************
-- L and R buttons, as well as X,Y deltas.
    gui.bitmap_draw(x+16,y,MouseIcons[18][bit.extract(IN,18)]) -- L
    gui.bitmap_draw(x+40,y,MouseIcons[19][bit.extract(IN,19)]) -- R

    for i=0,1 do
        local b= 8*i -- Bit index
        gui.text(x+32*i,y+8,string.format("%s%s%2X",
            m_XY[i],
            m_sign[bit.extract(IN,b+7)],
            bit.band(bit.lrshift(IN,b),0x7F)
        ))
    end
end

--*****************************************************************************
local DrawController= {}
--*****************************************************************************
-- Intermediary functions to display the immediate input.

-------------------------------------------------------------------------------
function DrawController.gamepad(x,y,port)
    DrawPad(x,y,Immediate[port*4])
end
-------------------------------------------------------------------------------
function DrawController.gamepad16(x,y,port)
    DrawPad16(x,y,Immediate[port*4])
end
-------------------------------------------------------------------------------
function DrawController.multitap(x,y,port)
    local P= port*4
    for pl= 0,3 do DrawPad(x+pl*64,y,Immediate[pl+P]) end
end
-------------------------------------------------------------------------------
function DrawController.multitap16(x,y,port)
    local P= port*4
    for pl= 0,3 do DrawPad16(x+pl*64,y,Immediate[pl+P]) end
end
-------------------------------------------------------------------------------
function DrawController.mouse(x,y,port)
    DrawMouse(x,y,Immediate[port*4])
end
-------------------------------------------------------------------------------
function DrawController.superscope(x,y,port)
    gui.text(x,y,"Not supported!",0xFF2000)
end
-------------------------------------------------------------------------------
function DrawController.justifier(x,y,port)
    gui.text(x,y,"Not supported!",0xFF2000)
end
-------------------------------------------------------------------------------
function DrawController.justifiers(x,y,port)
    gui.text(x,y,"Not supported!",0xFF2000)
end
-------------------------------------------------------------------------------
function DrawController.none(x,y,port)
    gui.text(x,y,"No Controller",0xFFFF00)
end

--*****************************************************************************
local function BGui()
--*****************************************************************************
-- Currently assuming width of 512.
-- Probably not a good idea if the drawing area can be resized.
    local Y= ResY+RequestGap("bottom",25)
    local x= PlSel*64
    gui.rectangle(x,Y,63,25,0x000080,0x000080)
    DrawController[Ports[1]](  0,Y+1,0)
    DrawController[Ports[2]](256,Y+1,1)
end

--#############################################################################

--*****************************************************************************
local MarkController= {}
--*****************************************************************************

-------------------------------------------------------------------------------
function MarkController.gamepad(pl)
-------------------------------------------------------------------------------
    Immediate[pl]= bit.bor(
        bit.band(Immediate[pl],0x00000000FFFF),0x0000FFFF0000
    )  -- Trim off oversnoop indicator, push in lag indicator
end
MarkController.gamepad16 = MarkController.gamepad
MarkController.multitap  = MarkController.gamepad
MarkController.multitap16= MarkController.gamepad

-------------------------------------------------------------------------------
function MarkController.mouse(pl)
-------------------------------------------------------------------------------
    Immediate[pl]= bit.bor(
        bit.band(Immediate[pl],0x0000000FFFFF),0x000000F00000
    )
end

MarkController.superscope= NullFN
MarkController.justifier = NullFN
MarkController.justifiers= NullFN
MarkController.none      = NullFN

--*****************************************************************************
local function MarkImmediate()
--*****************************************************************************
    for port= 1, 2 do
        local pl= (port-1)*4
        for p= 0, 3 do
            MarkController[Ports[port]](pl+p)
        end
    end
end

--*****************************************************************************
local function SaveImmediate(frame)
--*****************************************************************************
    for port= 1, 2 do
        local n= MaxPlayers[Ports[cPort]]-1    -- Don't store non-players.
        local pl= (port-1)*4
        for p= 0, n do
            FrameList[pl+p][frame]= Immediate[pl+p]
        end
    end
end

--*****************************************************************************
local function LoadImmediate(frame)
--*****************************************************************************
    for pl= 0, 7 do
        local temp= FrameList[pl][frame] or 0
        temp= bit.bor(
            bit.band(         temp ,            ListRead[pl]    ),
            bit.band(bit.bnot(temp),bit.lrshift(ListRead[pl],24))
        )
        Immediate[pl]= temp
    end

--One more thing! Those keys the user has held? Best handle 'em!
    Immediate[PlSel]= bit.bxor(Immediate[PlSel],BtnState[cPort])
end

--#############################################################################
--Set up key commands!

-------------------------------------------------------------------------------
RegisterKeyPress(cmd_SelectPlayer,function(s,t)
-------------------------------------------------------------------------------
-- Select the next player! Increment PlSel and refresh the display!
-- Inconsistently selects an empty port. That wasn't intended. Might fix later.
    PlSel= (PlSel+1)%8
    cPort= math.floor(PlSel/4)+1
    if PlSel%4 >= MaxPlayers[Ports[cPort]] then
        if (cPort == 1) and (Ports[cPort] ~= "none") then
            PlSel= 4; cPort= 2
        else
            PlSel= 0; cPort= 1
        end
    end

    ChooseList(Ports[cPort])
    MakeListFromScratch()
    gui.repaint()
end)

--#############################################################################
--Controller get!

--Just to group them together. So I treat "gamepad" and "multitap16" as equal.
local Ptype= {gamepad=1, gamepad16=1, multitap=1, multitap16=1,
              mouse=2, superscope=3, justifier=4, justifiers=4, none=0}

--*****************************************************************************
function on_button(p,c,i,t)
--*****************************************************************************
-- Do not check system. Won't happen, to my knowledge, but just in case...
    if p == 0 then return end

-- Do not check analog. Might be a good idea in a later version, though.
    if t == "analog" then return end

-- Don't allow hold keys to hold after script ends (and avoid message spam).
-- (Though allow the unhold or untype to clear the state for me)
    if (t == "hold") or (t == "type") then input.veto_button() end

-- Block any controller of a port aside from the first...
    if (c ~= 0) then return end

-- Block control if mismatched control type for PlSel:
    if (cPort ~= p) and (Ptype[Ports[1]] ~= Ptype[Ports[2]]) then return end

-- At this point, the first controller of either port 1 or port 2 goes
-- through. If different controller types are hooked up, the mismatched
-- controller feed is blocked; Only first controller of appropriate port goes.

    local bitval= TranslateIndex[Ports[p]](i)

    if     (t == "hold") or (t == "unhold") then  -- Switch movie read options
--Subtract "movie mode" by 1. Do this using XORs.
        local temp= ListRead[PlSel]
        temp= bit.bxor(temp,bitval)
        bitval= bit.band(bitval,temp)
        ListRead[PlSel]= bit.bxor(temp,bit.lshift(bitval,24))
        gui.repaint()

    elseif (t == "type") or (t == "untype") then  -- Toggle Immediate.
        Immediate[PlSel]= bit.bxor(Immediate[PlSel],bitval)
        gui.repaint()

--Looks odd; I'm looking if my bit matches the pressed state. If not, do stuff!
    elseif (t == "press") == (bit.band(BtnState[cPort],bitval) == 0) then
        Immediate[PlSel]= bit.bxor(Immediate[PlSel],bitval)
        BtnState[cPort]=  bit.bxor(BtnState[cPort] ,bitval)
        gui.repaint()
    end
end


--#############################################################################
-- Time to inject Immediate to lsnes!

--*****************************************************************************
local InjectInput= {}
--*****************************************************************************

-------------------------------------------------------------------------------
function InjectInput.gamepad(port)
    local IN= Immediate[(port-1)*4]
    for i= 0, 11 do
        input.set2(port,0,i,bit.extract(IN,i))
    end
end
-------------------------------------------------------------------------------
function InjectInput.gamepad16(port)
    local IN= Immediate[(port-1)*4]
    for i= 0, 15 do
        input.set2(port,0,i,bit.extract(IN,i))
    end
end

-------------------------------------------------------------------------------
function InjectInput.multitap(port)
    for c= 0, 3 do
        local IN= Immediate[(port-1)*4 + c]
        for i= 0, 11 do
            input.set2(port,c,i,bit.extract(IN,i))
        end
    end
end
-------------------------------------------------------------------------------
function InjectInput.multitap16(port)
    for c= 0, 3 do
        local IN= Immediate[(port-1)*4 + c]
        for i= 0, 15 do
            input.set2(port,c,i,bit.extract(IN,i))
        end
    end
end

-------------------------------------------------------------------------------
function InjectInput.mouse(port)
    local IN= Immediate[(port-1)*4]
    for i= 0, 1 do
        local b= i*8
        local val= bit.band(bit.lrshift(IN,b),0x7F)
        if bit.extract(IN,b+7) ~= 0 then val= -val end
        input.set2(port,0,i,val)
    end
    input.set2(port,0,2,bit.extract(IN,18))
    input.set2(port,0,3,bit.extract(IN,19))
end

-------------------------------------------------------------------------------
InjectInput.superscope= NullFN  -- Unsupported garbage. Sorry.
InjectInput.justifier=  NullFN
InjectInput.justifiers= NullFN
InjectInput.none=       NullFN  -- This one really should be null.


--*****************************************************************************
function on_input(subframe)
--*****************************************************************************
    for port= 1, 2 do
        InjectInput[Ports[port]](port)
    end
    if not subframe then MarkImmediate() end
end

local IndexOffsets= {
    gamepad   = 16,  gamepad16 = 16,  multitap  = 16,  multitap16= 16,
    mouse     =  4,  superscope=  0,  justifier =  0,  justifiers=  0,
    none      =  0
}

--*****************************************************************************
local HandleAnalog= {}
--*****************************************************************************

-------------------------------------------------------------------------------
function HandleAnalog.mouse(pl,i,v)
-------------------------------------------------------------------------------
    v= math.max(-127,math.min(127,v))  -- Enforce limits!
-- I don't care if lsnes gets out of range, I'm not following.

    if v < 0 then v= bit.bor(-v,0x80) end

    local mask= bit.bnot(bit.lshift(0xFF,i*8))

    Immediate[pl]= bit.bor(
        bit.band(Immediate[pl],mask),
        bit.lshift(v,i*8)
    )
end

HandleAnalog.superscope= NullFN  -- Should be handled at some point.
HandleAnalog.justifier = NullFN
HandleAnalog.justifiers= NullFN

HandleAnalog.gamepad   = NullFN  -- These have no analog to handle.
HandleAnalog.gamepad16 = NullFN
HandleAnalog.multitap  = NullFN
HandleAnalog.multitap16= NullFN
HandleAnalog.none      = NullFN

--*****************************************************************************
function on_snoop2(p,c,i,v)
--*****************************************************************************
    if p == 0 then return end  -- Do not handle system. Not this time.

--Run generic bit stuff!
    local PortType= Ports[p]
    local bitval= TranslateIndex[PortType](i)
    local offset= IndexOffsets[PortType]
    local pl= (p-1)*4+c
    local bitset= 0; if (v ~= 0) then bitset= 1 end

--Start by setting oversnoop, then clear lag.
    local temp= Immediate[pl]
    local temp2= bit.lshift(bitval,offset)
    temp= bit.bor(temp,bit.lshift(bit.band(bit.bnot(temp),temp2),offset))
    temp= bit.band(temp,bit.bnot(temp2))
    temp= bit.bor(bit.band(temp,bit.bnot(bitval)),bitset*bitval)
    Immediate[pl]= temp

--Now call analog functions
    HandleAnalog[PortType](pl,i,v)
end

--#############################################################################
--#############################################################################
-- Error protection sanity functions

--*****************************************************************************
local function HandleFrameJump()
--*****************************************************************************
-- Does not update cf - That's up to the calling function.
-- This function can't know what the proper CurrentFrame is anyway.

    LoadImmediate(cf)
    MakeListFromScratch()
    for pl= 0, 7 do ListRead[pl]=0x000000FFFFFF end -- Revert setup.
    gui.repaint()
end

--*****************************************************************************
local function Sanity_cf(non_synth)
--*****************************************************************************
    local CurrentFrame= movie.currentframe()
    if non_synth then CurrentFrame= CurrentFrame+1 end

    if CurrentFrame ~= cf then
        --Bad news: A frame jump occured, bypassing other forms of detection
        --Should only happen under File -> New -> Movie
        --Regardless, this should be handled.
    end
end

--*****************************************************************************
local function Sanity_Resolution()
--*****************************************************************************
-- TODO: Ensure that the display fits!

    ResX,ResY= gui.resolution()
end

--*****************************************************************************
local function Sanity_Controllers()
--*****************************************************************************
-- Ensures the internals are up to date with changes to controller port.
-- Also prints a warning in the message log.
    local sane= true
    for i= 1,2 do
        local temp= input.port_type(i)
        sane= sane and (Ports[i] == temp)
        Ports[i]= temp
    end
    if not sane then
        print("MtEdit: WARNING! Controller types have changed!")
--        print("MtEdit: Loaded the wrong state? Started a new movie?")
    end
end

--#############################################################################
--#############################################################################
-- lsnes stuff and top-level functions

--*****************************************************************************
function on_frame_emulated()
--*****************************************************************************
    cf= movie.currentframe() -- Make certain cf exists!
    SaveImmediate(cf)
    AdvanceList()
    LoadImmediate(cf+1)

    cf= cf+1
end

--*****************************************************************************
function on_post_load()
--*****************************************************************************
-- Well, uh... Best fix the stuff!
    cf= movie.currentframe()
    HandleFrameJump()
end

--*****************************************************************************
function on_rewind()
--*****************************************************************************
    cf= movie.currentframe() + 1  -- Rewind puts us back to zero.
    HandleFrameJump()
end

--*****************************************************************************
function on_paint(non_synth)
--*****************************************************************************
--Mostly just tell the left and bottom sides to paint themselves.
    Sanity_cf(non_synth)
    Sanity_Resolution()

    BGui()
    LGui()
    ApplyGaps()
end


--#############################################################################
--#############################################################################
--Execute now.

ChooseList(Ports[1])
MakeListFromScratch()
gui.repaint()


--[[
on_paint         |Needed to display, well... Anything!
on_frame_emulated|Lets me know when the frame ends.
on_post_load     |Important for detecting a jump.
on_rewind        |Also important for detecting a jump.
on_input         |Required to hijack control. Or else half the fun is gone.
on_snoop2        |For knowing what lsnes did.
on_keyhook       |Important to get at commands for this script.
on_button        |Very useful for various controls.

on_reset         |Commanding and detecting resets would be handy.
on_set_rewind    |Unsafe rewinds? Fits this script really, really well!
on_pre_rewind
on_post_rewind

Then there are these callbacks that MtEdit will likely leave alone.
on_video
on_frame
on_startup
on_pre_load
on_err_load
on_pre_save
on_err_save
on_post_save
on_quit
on_readwrite
on_snoop
on_idle
on_timer
]]--