Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- This same script works for both NES and SNES games.
- -- Make sure the following line contains your user name, case sensitive:
- PLAYER_ID = "yourusername"
- -- Base directory of SGL stuff. Start with drive letter, use double backslash as seperator, end with a trailing double backslash. Path MAY NOT contain spaces!
- SGLBASE = "c:\\SGL\\"
- -- **********************************************************************
- -- DON'T CHANGE ANYTHING BELOW THIS LINE
- -- **********************************************************************
- MOVIEDIR = SGLBASE .. "movies\\"
- TOP_SPLITS = 1
- BOTTOM_SPLITS = 213
- cur = 0
- numsplits = 2
- splitsThis = {0, 0, 0, 0}
- splitsPB = {-1, -1, -1, -1}
- splitsY = TOP_SPLITS
- lastSplit = false
- lastRestart = false
- lastBack = false
- lastSave = false
- lastUpdate = false
- lastNumsplit = false
- finishedState = false
- waiting = false
- waitingbegin = 0
- new_pb = false
- RED = "#FF0000"
- GREEN = "#00FF00"
- GRAY = "#CCCCCC"
- WHITE = "#FFFFFF"
- CREAM = "#FFFFDD"
- BGCOLOR = "#000000"
- initialsave = savestate.create()
- endsave = savestate.create()
- isNES = nil
- function worksOnFceux() sound.get(); end
- function worksOnSnes9x() snes9x.emulating(); end
- if pcall(worksOnFceux) then
- isNES = true
- elseif pcall(worksOnSnes9x) then
- isNES = false
- else
- print("Unable to identify emulator")
- return
- end
- if isNES then
- MOVIE_EXTENSION = "fm2"
- else
- MOVIE_EXTENSION = "smv"
- end
- -- ---------------------------------------------------------------------------
- function finalizesmv(fromfilepath, tofilepath)
- function findcontrolleroffset(fromfilepath)
- local inpf = io.open(fromfilepath, "rb")
- local d1 = nil
- local d2 = nil
- local d3 = nil
- local i = 0
- local x
- while 1 do
- x = inpf:read(1)
- if (x == nil) then
- inpf:close()
- return nil, nil
- end
- i = i + 1
- d1 = d2
- d2 = d3
- d3 = x
- if (d1=='\199' and d2=='\017' and d3=='\000') then
- while 1 do
- x = inpf:read(1)
- if (x == nil) then
- inpf:close()
- return nil, nil
- end
- i = i + 1
- d1 = d2
- d2 = d3
- d3 = x
- if (x ~= '\204') then
- local offset = i - 1
- if (offset % 16 == 0) then
- local bytes = 1
- while 1 do
- x = inpf:read(1)
- if (x == nil) then
- break
- end
- bytes = bytes + 1
- end
- local frames = math.floor(bytes / 2)
- frames = frames-1 -- SMV file has 1 extra frame before the first one
- inpf:close()
- return offset, frames
- else
- break
- end
- end
- end
- end
- end
- end
- function copybytes(inpf, outf, x)
- if x < 0 then
- print("ERROR - bytes to be copied: ", x)
- x = 0
- end
- for i=1,x do
- local s = inpf:read(1)
- if s == nil then
- print("ERROR - end of file encountered during byte copy")
- return
- end
- outf:write(s)
- end
- end
- function copyrest(inpf, outf)
- while 1 do
- local s = inpf:read(1)
- if s == nil then
- return
- end
- outf:write(s)
- end
- end
- function write4bytesLE(outf, num)
- local x = num
- outf:write(string.char(x % 256))
- x = math.floor(x / 256)
- outf:write(string.char(x % 256))
- x = math.floor(x / 256)
- outf:write(string.char(x % 256))
- x = math.floor(x / 256)
- outf:write(string.char(x % 256))
- end
- -- --------------------------------------------------------------
- local offsetComputed, frames = findcontrolleroffset(fromfilepath)
- if offsetComputed == nil then
- return nil
- end
- if offsetComputed < 64 then
- return nil
- end
- local inpf = io.open(fromfilepath, "rb")
- local outf = io.open(tofilepath, "wb")
- copybytes(inpf, outf, 16)
- inpf:read(4) -- skip these zeros
- write4bytesLE(outf, frames)
- copybytes(inpf, outf, 8)
- local xx = inpf:read(4) -- skip these zeros
- -- Decide whether to use the controller-data-block offset that was read from the raw SMV file or the offset that was computed by searching for the end-of-savestate signature.
- -- If the one read from the file is 0 then we can't use it (and print an error because I didn't think this was possible now that we jump to the beginning and end of the file to flush it before copying).
- -- If there is a mismatch, this likely due to the computed offset being wrong because of false-positives on the end-of-savestate signature. Basically always use the version that was read from the file unless it's 0.
- -- Note that if the false-positive occurs and computed offset is wrong (i.e. too early) then the value of "frames" is going to be too large since the algorithm thinks the controller data region is starting sooner than it's supposed to.
- local offsetFromFile = 256*(256*(256*string.byte(xx,4) + string.byte(xx,3)) + string.byte(xx,2)) + string.byte(xx,1)
- local offset
- if offsetFromFile == 0 then
- print("ERROR - Zero offset in file:read. Using computed = %d (%08X)", offsetComputed, offsetComputed)
- offset = offsetComputed
- elseif offsetFromFile ~= offsetComputed then
- print("ERROR - Offset mistmatch - file:read = %d (%08X), computed = %d (%08X)", offsetFromFile, offsetFromFile, offsetComputed, offsetComputed)
- offset = offsetFromFile
- else
- offset = offsetFromFile
- end
- write4bytesLE(outf, offset)
- inpf:read(4) -- skip these zeros
- write4bytesLE(outf, frames)
- copybytes(inpf, outf, offset - 36)
- -- If we were copying the original in-use SMV before the person has re-saved over it we'd have to instert 2 bytes of 0s in order to add the extra pre-movie frame that is included in finalized SMV files (but not the first version of the raw SMV).
- copyrest(inpf, outf)
- -- NOTE: Copying from the raw SMV file will copy all controller data that is in the file, including extra stuff after what should be the endpoint, which is leftover from earlier versions of the movie (like if a longer movie were overwritten with a short one). This means that the copied finalized SMV file may appear longer than the run itself and may contain garbage inputs at the end.
- inpf:close()
- outf:close()
- end
- -- ----------------------------------------------------------------------
- function drawtext(x, y, s, color)
- -- x and y are the top-left coordinates of the first character's space
- -- text itself is 7x10 with 1px between characters.
- if isNES then -- The 8-pixel overscan is counted in the coordinate system
- y = y + 8
- end
- for i = 1,string.len(s) do
- local c = string.byte(s,i)
- if c == 48 then -- 0
- gui.line(x+0,y+2, x+0,y+7, color)
- gui.line(x+6,y+2, x+6,y+7, color)
- gui.line(x+1,y+1, x+1,y+8, color)
- gui.line(x+5,y+1, x+5,y+8, color)
- gui.line(x+2,y+0, x+4,y+0, color)
- gui.line(x+2,y+9, x+4,y+9, color)
- gui.line(x+2,y+1, x+4,y+1, color)
- gui.line(x+2,y+8, x+4,y+8, color)
- x = x + 8
- elseif c == 49 then -- 1
- gui.line(x+3,y+0, x+3,y+9, color)
- gui.line(x+4,y+0, x+4,y+9, color)
- gui.line(x+2,y+1, x+2,y+2, color)
- gui.pixel(x+1,y+2, color)
- x = x + 8
- elseif c == 50 then -- 2
- gui.line(x+2,y+0, x+4,y+0, color)
- gui.line(x+1,y+1, x+5,y+1, color)
- gui.line(x+0,y+2, x+1,y+2, color)
- gui.line(x+5,y+2, x+6,y+2, color)
- gui.line(x+5,y+3, x+6,y+3, color)
- gui.line(x+4,y+4, x+5,y+4, color)
- gui.line(x+3,y+5, x+4,y+5, color)
- gui.line(x+2,y+6, x+3,y+6, color)
- gui.line(x+1,y+7, x+2,y+7, color)
- gui.line(x+0,y+8, x+6,y+8, color)
- gui.line(x+0,y+9, x+6,y+9, color)
- x = x + 8
- elseif c == 51 then -- 3
- gui.line(x+2,y+0, x+4,y+0, color)
- gui.line(x+1,y+1, x+5,y+1, color)
- gui.line(x+0,y+2, x+1,y+2, color)
- gui.line(x+5,y+2, x+6,y+2, color)
- gui.line(x+5,y+3, x+6,y+3, color)
- gui.line(x+3,y+4, x+5,y+4, color)
- gui.line(x+3,y+5, x+5,y+5, color)
- gui.line(x+5,y+6, x+6,y+6, color)
- gui.line(x+5,y+7, x+6,y+7, color)
- gui.line(x+0,y+7, x+1,y+7, color)
- gui.line(x+1,y+8, x+5,y+8, color)
- gui.line(x+2,y+9, x+4,y+9, color)
- x = x + 8
- elseif c == 52 then -- 4
- gui.line(x+5,y+0, x+5,y+9, color)
- gui.line(x+6,y+0, x+6,y+9, color)
- gui.line(x+0,y+6, x+4,y+6, color)
- gui.line(x+0,y+5, x+1,y+5, color)
- gui.line(x+1,y+4, x+2,y+4, color)
- gui.line(x+2,y+3, x+3,y+3, color)
- gui.line(x+3,y+2, x+4,y+2, color)
- gui.pixel(x+4,y+1, color)
- x = x + 8
- elseif c == 53 then -- 5
- gui.line(x+0,y+0, x+6,y+0, color)
- gui.line(x+0,y+1, x+6,y+1, color)
- gui.line(x+0,y+2, x+1,y+2, color)
- gui.line(x+0,y+3, x+4,y+3, color)
- gui.line(x+0,y+4, x+5,y+4, color)
- gui.line(x+5,y+5, x+5,y+7, color)
- gui.line(x+6,y+5, x+6,y+7, color)
- gui.line(x+0,y+7, x+1,y+7, color)
- gui.line(x+1,y+8, x+5,y+8, color)
- gui.line(x+2,y+9, x+4,y+9, color)
- x = x + 8
- elseif c == 54 then -- 6
- gui.line(x+0,y+3, x+3,y+0, color)
- gui.line(x+0,y+4, x+4,y+0, color)
- gui.line(x+1,y+4, x+5,y+4, color)
- gui.line(x+0,y+5, x+0,y+7, color)
- gui.line(x+1,y+5, x+1,y+7, color)
- gui.line(x+1,y+8, x+5,y+8, color)
- gui.line(x+2,y+9, x+4,y+9, color)
- gui.line(x+5,y+5, x+5,y+7, color)
- gui.line(x+6,y+5, x+6,y+7, color)
- x = x + 8
- elseif c == 55 then -- 7
- gui.line(x+0,y+0, x+6,y+0, color)
- gui.line(x+0,y+1, x+6,y+1, color)
- gui.line(x+5,y+2, x+6,y+2, color)
- gui.line(x+2,y+7, x+2,y+9, color)
- gui.line(x+3,y+5, x+3,y+9, color)
- gui.line(x+4,y+3, x+4,y+6, color)
- gui.line(x+5,y+3, x+5,y+4, color)
- x = x + 8
- elseif c == 56 then -- 8
- gui.line(x+2,y+0, x+4,y+0, color)
- gui.line(x+1,y+1, x+5,y+1, color)
- gui.line(x+2,y+4, x+4,y+4, color)
- gui.line(x+2,y+5, x+4,y+5, color)
- gui.line(x+1,y+8, x+5,y+8, color)
- gui.line(x+2,y+9, x+4,y+9, color)
- gui.line(x+0,y+2, x+0,y+3, color)
- gui.line(x+0,y+6, x+0,y+7, color)
- gui.line(x+6,y+2, x+6,y+3, color)
- gui.line(x+6,y+6, x+6,y+7, color)
- gui.line(x+1,y+2, x+1,y+7, color)
- gui.line(x+5,y+2, x+5,y+7, color)
- x = x + 8
- elseif c == 57 then -- 9
- gui.line(x+2,y+0, x+4,y+0, color)
- gui.line(x+1,y+1, x+5,y+1, color)
- gui.line(x+0,y+2, x+1,y+2, color)
- gui.line(x+0,y+3, x+1,y+3, color)
- gui.line(x+0,y+4, x+4,y+4, color)
- gui.line(x+1,y+5, x+4,y+5, color)
- gui.line(x+5,y+2, x+5,y+7, color)
- gui.line(x+6,y+2, x+6,y+7, color)
- gui.line(x+4,y+8, x+5,y+8, color)
- gui.line(x+2,y+9, x+4,y+9, color)
- x = x + 8
- elseif c == 32 then -- space
- x = x + 8
- elseif c == 43 then -- +
- gui.line(x+2,y+4, x+6,y+4, color)
- gui.line(x+4,y+2, x+4,y+6, color)
- x = x + 8
- elseif c == 45 then -- -
- gui.line(x+2,y+4, x+6,y+4, color)
- x = x + 8
- elseif c == 58 then -- :
- gui.line(x+0,y+2, x+1,y+2, color)
- gui.line(x+0,y+3, x+1,y+3, color)
- gui.line(x+0,y+6, x+1,y+6, color)
- gui.line(x+0,y+7, x+1,y+7, color)
- x = x + 3
- elseif c == 46 then -- .
- gui.line(x+0,y+8, x+1,y+8, color)
- gui.line(x+0,y+9, x+1,y+9, color)
- x = x + 3
- end
- end
- end
- function copyMovie(recfilename, srcefilepath)
- local tempfilepath = MOVIEDIR .. "__" .. recfilename
- local destfilepath = MOVIEDIR .. recfilename
- -- make the dir
- os.execute("mkdir \"" .. MOVIEDIR .. "\"")
- if isNES then
- -- copy the movie
- local s = string.format("copy \"%s\" \"%s\"", srcefilepath, destfilepath)
- print(s)
- os.execute(s)
- else
- -- copy the movie
- local s = string.format("copy \"%s\" \"%s\"", srcefilepath, tempfilepath)
- print(s)
- os.execute(s)
- -- finalize the movie
- finalizesmv(tempfilepath, destfilepath)
- end
- end
- function restartMovie()
- if movie.active() then
- if initialsave ~= nil then
- savestate.load(initialsave)
- else
- print("initialsave is nil")
- end
- else
- print("No active movie for initialstate")
- end
- end
- function returnMovieToEnd()
- if movie.active() then
- if endsave ~= nil then
- savestate.load(endsave)
- else
- print("endsave is nil")
- end
- else
- print("No active movie for endstate")
- end
- end
- function loadSplits()
- local filepath = SGLBASE .. "splits.txt"
- local f,err = io.open(filepath, "r")
- if f == nil then
- splitsPB = {-1, -1, -1, -1}
- -- numsplits remains unchanged
- return
- end
- numsplits = f:read("*number")
- if numsplits < 1 or numsplits > 8 then
- print("Error in splits.txt")
- keepgoing = false
- return
- end
- if numsplits > 4 then
- numsplits = numsplits - 4
- splitsY = BOTTOM_SPLITS
- else
- splitsY = TOP_SPLITS
- end
- for i = 1,4 do
- splitsPB[i] = f:read("*number")
- end
- f:close()
- end
- function writeSplits()
- os.execute("mkdir \"" .. SGLBASE .. "\"")
- local filepath = SGLBASE .. "splits.txt"
- local f = assert(io.open(filepath, "w"))
- if splitsY == TOP_SPLITS then
- f:write(string.format("%d ", numsplits))
- else
- f:write(string.format("%d ", numsplits + 4))
- end
- for i = 1,4 do
- f:write(string.format("%d ", splitsThis[i]))
- end
- f:write("\n")
- f:close()
- end
- function uploadRun(recfilename)
- local filepath = MOVIEDIR .. recfilename
- local s = string.format("%ssgl-upload.exe \"%s\" \"%s\"", SGLBASE, PLAYER_ID, filepath)
- print(s)
- local result = os.execute(s)
- if result == 0 then
- print(" UPLOAD COMPLETE")
- else
- print(" FAILURE (" .. result .. ")")
- end
- end
- function convertDiffShort(frames)
- local sign
- if frames < 0 then
- frames = frames * -1
- sign = "-"
- else
- sign = "+"
- end
- local seconds = frames / 60.099822938442230224609375
- local ss = math.floor(seconds)
- local xx = math.floor((seconds - ss) * 10)
- return string.format("(%s%d.%01d)", sign, ss, xx)
- end
- function convertDiff(frames)
- local sign
- if frames < 0 then
- frames = frames * -1
- sign = "-"
- else
- sign = "+"
- end
- local seconds = frames / 60.099822938442230224609375
- local mm = math.floor(seconds / 60)
- seconds = seconds - mm * 60
- local ss = math.floor(seconds)
- local xx = math.floor((seconds - ss) * 10)
- return string.format("%s%1d:%02d.%01d", sign, mm, ss, xx)
- end
- function convertTime(frames)
- local seconds = frames / 60.099822938442230224609375
- local mm = math.floor(seconds / 60)
- seconds = seconds - mm * 60
- local ss = math.floor(seconds)
- local xx = math.floor((seconds - ss) * 10)
- mm = mm % 100 -- will never get this high unless emu is running with no movie playing
- return string.format("%2d:%02d.%01d", mm, ss, xx)
- end
- --- -----------------------------------------------------------------------
- --- -----------------------------------------------------------------------
- --- -----------------------------------------------------------------------
- print("Controls: ")
- print(" 'space' -- split")
- print(" 'backspace' -- undo a split")
- print(" 'R' -- restart")
- print(" 'Y' -- submit run")
- print(" 'N' -- change the number of splits")
- print(" 'C' -- clear the splits")
- if movie.active() then
- savestate.save(initialsave)
- else
- while (true) do
- if not isNES then -- Snes9x needs text background
- gui.box(2, 39, 230, 170, BGCOLOR, BGCOLOR)
- end
- gui.text(3, 40, " NO MOVIE FILE IS RECORDING NOW ")
- gui.text(3, 50, " Stop this LUA script. ")
- gui.text(3, 60, " Pause your emulator if it's running ('Pause' key). ")
- gui.text(3, 70, " Load the save state file you were provided. ")
- gui.text(3, 80, " Begin a new movie recording -- ")
- gui.text(3, 88, " Choose 'Record From Now' and hit 'OK'. ")
- gui.text(3, 98, " Run this LUA script again. ")
- gui.text(3,108, " Click on the emulator window so that it has focus. ")
- gui.text(3,118, " Unpause your emulator ('Pause' key). ")
- gui.text(3,128, " Play the game. ")
- gui.text(3,136, " 'space' to split ")
- gui.text(3,144, " 'backspace' to undo a split ")
- gui.text(3,152, " 'R' to restart ")
- gui.text(3,160, " 'N' to change the number of splits ")
- gui.text(3,168, " 'C' to clear the splits ")
- emu.frameadvance()
- end
- end
- loadSplits() --- defaults to {-1, -1, -1} if file is not found
- keepgoing = true
- while (keepgoing) do
- if isNES then -- The 8-pixel overscan is counted in the coordinate system
- gui.box(0, 8+splitsY-1, 255, 8+splitsY+10, "#000000DD", "#000000DD")
- else
- gui.box(0, splitsY-1, 255, splitsY+10, "#000000DD", "#000000DD")
- end
- if not waiting then
- local keypress = input.get()
- local pressSplit = keypress.space and not lastSplit
- local pressBack = keypress.backspace and not lastBack
- local pressRestart = keypress.R and not lastRestart
- local pressSave = keypress.Y and not lastSave
- local pressNumsplit = keypress.N and not lastNumsplit
- local pressClear = keypress.C and not lastClear
- lastSplit = keypress.space
- lastBack = keypress.backspace
- lastRestart = keypress.R
- lastSave = keypress.Y
- lastNumsplit = keypress.N
- lastClear = keypress.C
- if pressSplit then
- if cur < numsplits then
- cur = cur + 1
- splitsThis[cur] = emu.framecount()
- end
- end
- if pressBack then
- if finishedState then -- already saved and uploaded the run
- finishedState = false -- go back to the run-ended-but-not-uploaded state
- else -- haven't yet saved
- if cur > 0 then
- splitsThis[cur] = 0
- cur = cur - 1
- end
- end
- end
- if pressRestart then
- if new_pb then
- new_pb = false
- splitsPB[1] = splitsThis[1]
- splitsPB[2] = splitsThis[2]
- splitsPB[3] = splitsThis[3]
- splitsPB[4] = splitsThis[4]
- end
- restartMovie()
- cur = 0
- splitsThis = {0, 0, 0, 0}
- finishedState = false
- end
- if pressNumsplit then
- numsplits = numsplits + 1
- if numsplits == 5 then
- numsplits = 1
- if splitsY == TOP_SPLITS then
- splitsY = BOTTOM_SPLITS
- else
- splitsY = TOP_SPLITS
- end
- end
- end
- if pressClear then
- splitsPB = {-1, -1, -1, -1}
- end
- if pressSave then
- if cur == numsplits and not finishedState then
- waiting = true
- waitingbegin = emu.framecount()
- new_pb = true
- end
- end
- if cur == numsplits and not finishedState then
- gui.text(33, 22, "Press Y to save run or R to restart.", CREAM, BGCOLOR)
- end
- else -- waiting to flush movie file before uploading
- local elapsed = (emu.framecount() - waitingbegin) / 60.099822938442230224609375
- local waittime = 4
- if (elapsed > waittime) then
- waiting = false
- local tm = os.date("*t")
- local secsofday = tm.hour * 60 * 60 + tm.min * 60 + tm.sec
- local recfilename = string.format("rec_%s_%05d.%s", PLAYER_ID, secsofday, MOVIE_EXTENSION)
- if movie.active() then
- local currentfilepath = movie.name()
- -- These lines only needed for SNES, but they don't hurt in FCEUX
- savestate.save(endsave)
- restartMovie()
- emu.frameadvance()
- returnMovieToEnd()
- emu.frameadvance()
- copyMovie(recfilename, currentfilepath)
- writeSplits()
- uploadRun(recfilename)
- finishedState = true
- else
- print("No active movie")
- end
- else
- gui.text(90, 28, string.format("Please wait... %d", math.ceil(waittime-elapsed)), CREAM, BGCOLOR)
- end
- end
- for i = 1, numsplits do
- x = 18 + 48 * (i + 4 - numsplits)
- if cur > i-1 then
- if splitsPB[i] == -1 then
- drawtext(x, splitsY, convertTime(splitsThis[i]), GRAY)
- else
- local diff, color
- diff = splitsThis[i] - splitsPB[i]
- if diff < 0 then
- color = GREEN
- else
- color = RED
- end
- drawtext(x, splitsY, convertDiff(diff), color)
- end
- else -- haven't reached split "i" yet
- if splitsPB[i] == -1 then
- drawtext(x, splitsY, "--:--.-", GRAY)
- else
- drawtext(x, splitsY, convertTime(splitsPB[i]), GRAY)
- end
- end
- end
- if (cur > 0) and (splitsPB[cur] ~= -1) and (numsplits < 4) then
- if cur == 1 then
- diff = (splitsThis[cur]-0) - (splitsPB[cur]-0)
- else
- diff = (splitsThis[cur]-splitsThis[cur-1]) - (splitsPB[cur]-splitsPB[cur-1])
- end
- if diff < 0 then
- color = GREEN
- else
- color = RED
- end
- drawtext(55, splitsY, convertDiffShort(diff), color)
- end
- if cur >= numsplits then
- drawtext(0, splitsY, convertTime(splitsThis[cur]), WHITE)
- else
- drawtext(0, splitsY, convertTime(emu.framecount()), CREAM)
- end
- emu.frameadvance()
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement